Last active
April 23, 2023 15:04
-
-
Save wakarase/609cd330e3e32e9b86882fc006fc4f92 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# バイナリエディタ🙂 | |
import ctypes, hashlib, math, os, random, sys | |
import PySide6.QtCore as C | |
import PySide6.QtGui as G | |
import PySide6.QtWidgets as W | |
MONOSPACE_FONT = 'MS ゴシック' | |
class Application(W.QApplication): | |
"""アプリ全体を表す抽象的なクラスです。""" | |
def __init__(self): | |
super().__init__(sys.argv) | |
self.window = MainWindow() | |
AUTOMATIC_LOAD_PATH = 'example.bin' | |
class MainWindow(W.QMainWindow): | |
"""アプリのウィンドウです。""" | |
def __init__(self): | |
super().__init__() | |
self.resize(640, 480) | |
self.setAcceptDrops(True) | |
self.setWindowIcon(self.style().standardIcon(W.QStyle.SP_DriveFDIcon)) | |
self.actionFileNew = ActionFileNew(self) | |
self.actionFileOpen = ActionFileOpen(self) | |
self.actionFileSave = ActionFileSave(self) | |
self.addAction(ActionMoveDocumentEnd(self)) | |
self.addAction(ActionMoveDocumentStart(self)) | |
self.addAction(ActionMoveDown(self)) | |
self.addAction(ActionMoveLeft(self)) | |
self.addAction(ActionMoveLineEnd(self)) | |
self.addAction(ActionMoveLineStart(self)) | |
self.addAction(ActionMoveRight(self)) | |
self.addAction(ActionMoveUp(self)) | |
self.tabWidget = TabWidget(self) | |
self.setMenuBar(MenuBar(self)) | |
self.addToolBar(ToolBar(self)) | |
self.setCentralWidget(CentralWidget(self)) | |
self.setStatusBar(StatusBar(self)) | |
self.show() | |
self.open(AUTOMATIC_LOAD_PATH) # 開発用です。 | |
self.updateTitle() | |
def closeEvent(self, event): | |
print('Bye.') | |
def dragEnterEvent(self, event): | |
"""他のアプリからウィンドウ内に何かドラッグされると呼ばれます。""" | |
if event.mimeData().hasUrls(): | |
event.accept() # dropEvent()が呼ばれるのに必要です。 | |
else: | |
event.ignore() | |
def dropEvent(self, event): | |
"""ドラッグ&ドロップされたファイルらを開きます。""" | |
for url in event.mimeData().urls(): | |
self.open(url.toLocalFile()) | |
def keyPressEvent(self, event): | |
numberKeys = [C.Qt.Key_0, C.Qt.Key_1, C.Qt.Key_2, C.Qt.Key_3, C.Qt.Key_4, C.Qt.Key_5, C.Qt.Key_6, C.Qt.Key_7, C.Qt.Key_8, C.Qt.Key_9, C.Qt.Key_A, C.Qt.Key_B, C.Qt.Key_C, C.Qt.Key_D, C.Qt.Key_E, C.Qt.Key_F] | |
def isNumber(event): | |
""""押されたのが数字のキーなら真です。""" | |
return event.key() in numberKeys | |
def getNumber(event): | |
"""押されたキーの数字を返します。""" | |
return numberKeys.index(event.key()) | |
if isNumber(event): | |
tab = self.tabWidget.currentWidget() | |
if not tab: | |
return | |
if not tab.editModeGet(): | |
tab.editModeSet(getNumber(event)) | |
else: | |
firstHex = tab.editModeGet() | |
secondHex = getNumber(event) | |
newByte = firstHex * 0x10 + secondHex | |
offset = tab.cursorStartGet() | |
if offset == len(tab.bytes): | |
tab.bytes.append(newByte) | |
tab.scrollBar.onNBytesUpdated() | |
else: | |
tab.bytes[offset] = newByte | |
tab.editModeSet(None) | |
tab.cursorStartSet(offset + 1) | |
tab.cursorEndSet(offset + 1) | |
tab.scrollBar.updateVisual() | |
def open(self, path): | |
"""与えられたpathのファイルを開きます。""" | |
self.tabWidget.open(path) | |
def updateTitle(self): | |
def createTitle(): | |
s = 'ばいなりえでぃた' | |
tab = self.tabWidget.currentWidget() | |
if not tab: | |
return s | |
start, end = tab.cursorStartGet(), tab.cursorEndGet() | |
if start == end: | |
s += ' 0x{:X}'.format(start) | |
else: | |
s += ' 0x{:X}-0x{:X}'.format(start, end) | |
nBytes = len(tab.bytes) | |
s += ' / 0x{:X} ({:,} bytes)'.format(nBytes, nBytes) | |
return s | |
self.setWindowTitle(createTitle()) | |
class StatusBar(W.QStatusBar): | |
"""下端に表示するステータスバーです。""" | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow) | |
class MenuBar(W.QMenuBar): | |
"""メニューバーです。""" | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow) | |
self.fileMenu = FileMenu(mainWindow) | |
self.addMenu(self.fileMenu) | |
class FileMenu(W.QMenu): | |
"""ファイルメニューです。""" | |
def __init__(self, mainWindow): | |
super().__init__('File', mainWindow) | |
self.addAction(mainWindow.actionFileNew) | |
self.addAction(mainWindow.actionFileOpen) | |
self.addAction(ActionFileReload(mainWindow)) | |
self.addAction(mainWindow.actionFileSave) | |
self.addAction(ActionExit(mainWindow)) | |
class ToolBar(W.QToolBar): | |
"""メニューバーの下に表示されるツールバーです。""" | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow) | |
self.addAction(mainWindow.actionFileNew) | |
self.addAction(mainWindow.actionFileOpen) | |
self.addAction(mainWindow.actionFileSave) | |
class CentralWidget(W.QWidget): | |
"""ウィンドウの中央に表示されるウィジェットです。""" | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow) | |
hbox = W.QHBoxLayout() | |
hbox.addWidget(mainWindow.tabWidget) | |
self.setLayout(hbox) | |
class TabWidget(W.QTabWidget): | |
"""複数のタブを表します。""" | |
def __init__(self, mainWindow): | |
self.mainWindow = mainWindow | |
super().__init__(mainWindow) | |
self.n_untitiled = 0 | |
self.setMovable(True) # タブの順序をドラッグで移動できます。 | |
self.setTabsClosable(True) # タブにクローズボタンをつけます。 | |
self.currentChanged.connect(self.onCurrentChanged) | |
self.tabCloseRequested.connect(self.onTabCloseRequested) | |
def addTab(self, path): | |
"""新しくタブを追加します。""" | |
if path: | |
tab = Tab(self.mainWindow, path) | |
basename = os.path.basename(path) | |
i = super().addTab(tab, basename) | |
else: | |
self.n_untitiled += 1 | |
tab = Tab(self.mainWindow, None) | |
i = super().addTab(tab, 'Untitled-{}'.format(self.n_untitiled)) | |
self.setCurrentIndex(i) | |
def moveCursor(self, dx, dy): | |
"""カーソルを左右にdx、上下にdyだけ移動します。""" | |
tab = self.mainWindow.tabWidget.currentWidget() | |
if not tab: | |
return | |
if dx == -math.inf: | |
next = tab.cursorStartGet() // tab.nColumns * tab.nColumns | |
elif dx == math.inf: | |
next = (tab.cursorStartGet() // tab.nColumns + 1) * tab.nColumns - 1 | |
next = min(next, len(tab.bytes)) | |
elif dy == -math.inf: | |
next = 0 | |
elif dy == math.inf: | |
next = len(tab.bytes) | |
else: | |
delta = dy * tab.nColumns + dx | |
if delta < 0: | |
next = tab.cursorStartGet() + delta | |
else: | |
next = tab.cursorEndGet() + delta | |
if next < 0 or len(tab.bytes) < next: | |
return | |
tab.cursorStartSet(next) | |
tab.cursorEndSet(next) | |
tab.scrollBar.showOffset(next) | |
tab.scrollBar.updateVisual() | |
def onCurrentChanged(self, i): | |
print('onCurrentChanged', i) | |
def onTabCloseRequested(self, i): | |
self.removeTab(i) | |
def open(self, path): | |
# すでに開かれているファイルなら、そのタブをアクティブにするだけです。 | |
path = os.path.realpath(path) # パスの表現を一意にします。 | |
for tab in self.tabs(): | |
if tab.path == path: | |
self.setCurrentWidget(tab) | |
return | |
self.addTab(path) | |
def tabs(self): | |
tabs = [] | |
for i in range(self.count()): | |
tabs.append(self.widget(i)) | |
return tabs | |
class Tab(W.QWidget): | |
"""一つのタブの内容全体を表します。""" | |
def __init__(self, mainWindow, path): | |
self.mainWindow = mainWindow | |
super().__init__(mainWindow) | |
self.bytes = bytearray(b'') | |
self._cursorStart = 0 | |
self._cursorEnd = 0 | |
self.edited = False | |
self._editMode = None | |
self.nColumns = 0x10 | |
self.nRows = 0x10 | |
self.path = path | |
if path: | |
self.path = os.path.realpath(path) | |
self.read() | |
else: | |
self.path = None | |
hbox = W.QHBoxLayout() | |
self.setLayout(hbox) | |
self.grid = W.QGridLayout() | |
self.grid.setSpacing(0) | |
hbox.addLayout(self.grid) | |
def addLabel(y, x, s, color=None): | |
label = W.QLabel(s, self) | |
label.setContentsMargins(2, 2, 2, 2) | |
font = G.QFont(MONOSPACE_FONT) | |
font.setPointSizeF(font.pointSizeF()) | |
label.setFont(font) | |
if color: | |
label.setStyleSheet('color: {};\n'.format(color)) | |
self.grid.addWidget(label, y, x) | |
addLabel(0, 0, 'Address', color='blue') # 左上 | |
addLabel(0, self.nColumns + 2, 'UTF-8 Decoded', color='blue') # 右上 | |
for i in range(self.nColumns): # 上のアドレス表示 | |
addLabel(0, i + 1, '{:02X}'.format(i), color='blue') | |
for i in range(self.nRows): # 左のアドレス表示 | |
addLabel(i + 1, 0, '{:08X}'.format(self.nColumns * i), color='blue') | |
for y in range(self.nRows): | |
for x in range(self.nColumns): | |
# 各バイト値を表示する部分です。 | |
n = random.randint(0x00, 0xff) | |
addLabel(y + 1, x + 1, '{:02X}'.format(n)) | |
for y in range(self.nRows + 1): # レイアウトを調整するためのダミー列です。 | |
addLabel(y, self.nColumns + 1, '') | |
for y in range(self.nRows): | |
addLabel(y + 1, self.nColumns + 2, 'あいうえおかきく', color='gray') | |
hbox.addStretch() | |
vbox = W.QVBoxLayout() | |
hbox.addLayout(vbox) | |
vbox.setSpacing(0) | |
vbox.setAlignment(C.Qt.AlignHCenter) | |
def addToolButton(layout, action): | |
button = W.QToolButton() | |
layout.setAlignment(button, C.Qt.AlignHCenter) | |
button.setAutoRepeat(True) | |
button.setDefaultAction(action) | |
button.setStyleSheet('border: none') | |
layout.addWidget(button) | |
addToolButton(vbox, ActionPageUp(mainWindow)) | |
addToolButton(vbox, ActionLineUp(mainWindow)) | |
self.scrollBar = ScrollBar(self) | |
vbox.addWidget(self.scrollBar) | |
vbox.setAlignment(self.scrollBar, C.Qt.AlignHCenter) | |
addToolButton(vbox, ActionLineDown(mainWindow)) | |
addToolButton(vbox, ActionPageDown(mainWindow)) | |
def cursorEndGet(self): | |
return self._cursorEnd | |
def cursorEndSet(self, x): | |
self._cursorEnd = x | |
self.mainWindow.updateTitle() | |
def cursorStartGet(self): | |
return self._cursorStart | |
def cursorStartSet(self, x): | |
self._cursorStart = x | |
self.mainWindow.updateTitle() | |
def editModeGet(self): | |
return self._editMode | |
def editModeSet(self, x): | |
self._editMode = x | |
start, end = self.cursorStartGet(), self.cursorEndGet() | |
if start != end: | |
self.cursorEndSet(start) | |
self.scrollBar.updateVisual() | |
def getOffset(self, y, x): | |
"""QGridLayout上の位置からバイトオフセットを得ます。""" | |
offset = self.nColumns * self.scrollBar.value() | |
return offset + (y - 1) * self.nColumns + (x - 1) | |
def mouseMoveEvent(self, event): | |
if not self.mousePressItemPosition: | |
return | |
child = self.childAt(event.position().toPoint()) | |
if isinstance(child, W.QLabel): | |
index = self.grid.indexOf(child) | |
position = self.grid.getItemPosition(index) | |
y, x, _, _ = position | |
if 1 <= x <= self.nColumns and 1 <= y <= self.nRows: | |
mouseMoveItemPosition = y, x | |
a = self.getOffset(*self.mousePressItemPosition) | |
b = self.getOffset(*mouseMoveItemPosition) | |
self.cursorStartSet(min(a, b)) | |
self.cursorEndSet(max(a, b)) | |
self.scrollBar.updateVisual() | |
def mousePressEvent(self, event): | |
self.mousePressItemPosition = None | |
child = self.childAt(event.position().toPoint()) | |
if isinstance(child, W.QLabel): | |
index = self.grid.indexOf(child) | |
position = self.grid.getItemPosition(index) | |
y, x, _, _ = position | |
if 1 <= x <= self.nColumns and 1 <= y <= self.nRows: | |
self.mousePressItemPosition = y, x | |
event.accept() | |
def mouseReleaseEvent(self, event): | |
if not self.mousePressItemPosition: | |
return | |
mousePressItemPosition = self.mousePressItemPosition | |
self.mousePressItemPosition = None | |
child = self.childAt(event.position().toPoint()) | |
if isinstance(child, W.QLabel): | |
index = self.grid.indexOf(child) | |
position = self.grid.getItemPosition(index) | |
y, x, _, _ = position | |
if 1 <= x <= self.nColumns and 1 <= y <= self.nRows: | |
mouseReleaseItemPosition = y, x | |
a = self.getOffset(*mousePressItemPosition) | |
b = self.getOffset(*mouseReleaseItemPosition) | |
self.cursorStartSet(min(a, b)) | |
self.cursorEndSet(max(a, b)) | |
self.scrollBar.updateVisual() | |
def read(self): | |
"""バイナリファイルを読み込みます。""" | |
if not self.path: | |
return | |
s = 'Opening a file as binary. path="{}" size(bytes)={:,}' | |
s = s.format(self.path, os.path.getsize(self.path)) | |
print(s) | |
with open(self.path, 'rb') as file: | |
self.bytes = bytearray(file.read()) | |
s = 'Successfully loaded {:,} bytes into memory.' | |
s = s.format(len(self.bytes)) | |
print(s) | |
self.sha1 = hashlib.sha1(self.bytes).hexdigest() | |
s = 'SHA-1: {}'.format(self.sha1) | |
print(s) | |
def write(self): | |
"""バイナリファイルに書き込みます。""" | |
if not self.path: | |
return | |
if os.path.exists(self.path): | |
with open(self.path, 'rb') as file: | |
fileBytes = file.read() | |
sha1 = hashlib.sha1(fileBytes).hexdigest() | |
if sha1 != self.sha1: | |
print('Warning: SHA-1 of the file changed.') | |
s = 'Writing binary to the file. path="{}" size(bytes)={:,}' | |
s = s.format(self.path, len(self.bytes)) | |
print(s) | |
with open(self.path, 'wb') as file: | |
file.write(self.bytes) | |
s = 'Successfully wrote {:,} bytes into the file.' | |
s = s.format(os.path.getsize(self.path)) | |
print(s) | |
class ScrollBar(W.QScrollBar): | |
"""タブ内の右に表示される縦長のスクロールバーです。""" | |
def __init__(self, tab): | |
self.tab = tab | |
super().__init__(tab) | |
self.valueChanged.connect(self.onValueChanged) | |
self.onNBytesUpdated() | |
def getVisibleOffset(self): | |
"""第startバイトから第end - 1バイトまで表示されます。""" | |
start = self.value() * self.tab.nColumns | |
end = (self.value() + self.pageStep()) * self.tab.nColumns | |
return start, end | |
def isOffsetVisible(self, i): | |
"""第iバイトが見えていれば真です。""" | |
start, end = self.getVisibleOffset() | |
return start <= i < end | |
def moveLine(self, delta): | |
self.setValue(self.value() + delta) | |
def movePage(self, delta): | |
self.setValue(self.value() + delta * self.pageStep()) | |
def onNBytesUpdated(self): | |
tab = self.tab | |
nBytes = len(tab.bytes) | |
nColumns = tab.nColumns | |
nRows = (nBytes + nColumns - 1) // nColumns | |
maximum = max(0, nRows - tab.nRows) | |
self.setPageStep(tab.nRows) | |
self.setMaximum(maximum) | |
self.updateVisual() | |
def onValueChanged(self, value): | |
self.updateVisual() | |
def showOffset(self, i): | |
"""第iバイトが見えるようにスクロールを修正します。""" | |
start, end = self.getVisibleOffset() | |
if i < start: # 表示したい位置が表示されている位置より前にあります。 | |
self.setValue(i // self.tab.nColumns) | |
elif i >= end: # 表示したい位置が表示されている位置より後にあります。 | |
self.setValue(i // self.tab.nColumns - self.pageStep() + 1) | |
def updateVisual(self): | |
"""タブ内の表示内容を更新します。""" | |
tab = self.tab | |
length = len(tab.bytes) | |
start, _ = self.getVisibleOffset() | |
for y in range(tab.nRows): # 左端列のアドレス表示です。 | |
label = tab.grid.itemAtPosition(y + 1, 0).widget() | |
i = start + y * tab.nColumns | |
if i > length: | |
# データがある行についてだけアドレスを表示します。 | |
# ただし、行末までデータがある場合は、次行でも表示します。 | |
label.setText(' ') | |
else: | |
label.setText('{:08X}'.format(i)) | |
for y in range(tab.nRows): | |
for x in range(tab.nColumns): | |
label = tab.grid.itemAtPosition(y + 1, x + 1).widget() | |
i = start + y * tab.nColumns + x | |
if i >= length: | |
label.setText(' ') | |
else: | |
label.setText('{:02X}'.format(tab.bytes[i])) | |
if tab.cursorStartGet() <= i <= tab.cursorEndGet(): | |
label.setStyleSheet('background-color: yellow') | |
else: | |
label.setStyleSheet('') | |
if tab.editModeGet(): # 編集中のバイトを表示します。 | |
offset = tab.cursorStartGet() | |
if self.isOffsetVisible(offset): | |
offset -= start | |
y = offset // tab.nColumns | |
x = offset % tab.nColumns | |
label = tab.grid.itemAtPosition(y + 1, x + 1).widget() | |
label.setText('{:X}_'.format(tab.editModeGet())) | |
label.setStyleSheet('background-color: pink') | |
for y in range(tab.nRows): # 右側の文字デコード表示です。 | |
label = tab.grid.itemAtPosition(y + 1, tab.nColumns + 2).widget() | |
s = '' | |
i = start + y * tab.nColumns | |
nextLineHead = start + (y + 1) * tab.nColumns | |
while i < nextLineHead: | |
isDecodeSuccess = False | |
nDecodedBytes = 0 | |
for nBytes in [1, 2, 3, 4]: | |
if i + nBytes > length: | |
continue | |
bytes = tab.bytes[i:i + nBytes] | |
try: | |
character = bytes.decode('utf-8') | |
except UnicodeError: | |
continue | |
if '\u0000' <= character <= '\u001f': | |
# https://en.wikipedia.org/wiki/Unicode_control_characters | |
# control pictureというものが0x2400番台に定義されています。 | |
n = ord(character) | |
n += 0x2400 # 制御文字を対応する記号に置き換えます。 | |
character = chr(n) | |
s += character | |
isDecodeSuccess = True | |
nDecodedBytes = nBytes | |
break | |
if isDecodeSuccess: | |
i += nDecodedBytes | |
else: | |
i += 1 | |
label.setText(s) | |
class Action(G.QAction): | |
"""継承して利用するための共通部分です。""" | |
def __init__(self, mainWindow, text, statusTip, shortcut=None, standardIcon=None): | |
self.mainWindow = mainWindow | |
if standardIcon: | |
icon = mainWindow.style().standardIcon(standardIcon) | |
super().__init__(icon, text, mainWindow) | |
else: | |
super().__init__(text, mainWindow) | |
self.setStatusTip(statusTip) | |
if shortcut: | |
self.setShortcut(G.QKeySequence(shortcut)) | |
self.triggered.connect(self.run) | |
def run(self): | |
raise NotImplementedError() | |
class ActionExit(Action): | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow, 'Exit Program', 'プログラムを終了します。', 'Escape') | |
def run(self): | |
self.mainWindow.close() | |
class ActionFileNew(Action): | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow, 'New', '新規作成します。', None, W.QStyle.SP_FileIcon) | |
def run(self): | |
self.mainWindow.tabWidget.addTab(None) | |
class ActionFileOpen(Action): | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow, 'Open File...', 'ファイルを開きます。', None, W.QStyle.SP_DialogOpenButton) | |
def run(self): | |
fileName, _ = W.QFileDialog.getOpenFileName(self.mainWindow) | |
if fileName != '': | |
self.mainWindow.open(fileName) | |
class ActionFileReload(Action): | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow, 'Reload File', 'ファイルを再読み込みします。', 'F5') | |
def run(self): | |
tab = self.mainWindow.tabWidget.currentWidget() | |
tab.read() | |
class ActionFileSave(Action): | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow, 'Save File...', 'ファイルに保存します。', None, W.QStyle.SP_DialogSaveButton) | |
def run(self): | |
tab = self.mainWindow.tabWidget.currentWidget() | |
if not tab.path: | |
fileName, _ = W.QFileDialog.getSaveFileName(self.mainWindow, dir='untitled.bin') | |
if fileName == '': | |
return | |
tab.path = os.path.realpath(fileName) | |
tab.write() | |
class ActionLineDown(Action): | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow, 'Line Down', '1行下にスクロールします。', None, W.QStyle.SP_TitleBarUnshadeButton) | |
def run(self): | |
tab = self.mainWindow.tabWidget.currentWidget() | |
value = tab.scrollBar.value() | |
tab.scrollBar.setValue(value + 1) | |
class ActionLineUp(Action): | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow, 'Line Up', '1行上にスクロールします。', None, W.QStyle.SP_TitleBarShadeButton) | |
def run(self): | |
tab = self.mainWindow.tabWidget.currentWidget() | |
value = tab.scrollBar.value() | |
tab.scrollBar.setValue(value - 1) | |
class ActionMoveDocumentEnd(Action): | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow, 'Move to Document End', '下端に移動します。', 'Ctrl+End') | |
def run(self): | |
self.mainWindow.tabWidget.moveCursor(0, math.inf) | |
class ActionMoveDocumentStart(Action): | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow, 'Move to Document Start', '上端に移動します。', 'Ctrl+Home') | |
def run(self): | |
self.mainWindow.tabWidget.moveCursor(0, -math.inf) | |
class ActionMoveDown(Action): | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow, 'Move Up', '下に1文字移動します。', 'Down') | |
def run(self): | |
self.mainWindow.tabWidget.moveCursor(0, 1) | |
class ActionMoveLeft(Action): | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow, 'Move Left', '左に1文字移動します。', 'Left') | |
def run(self): | |
self.mainWindow.tabWidget.moveCursor(-1, 0) | |
class ActionMoveLineEnd(Action): | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow, 'Move to Line End', '行末に移動します。', 'End') | |
def run(self): | |
self.mainWindow.tabWidget.moveCursor(math.inf, 0) | |
class ActionMoveLineStart(Action): | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow, 'Move to Line Start', '行頭に移動します。', 'Home') | |
def run(self): | |
self.mainWindow.tabWidget.moveCursor(-math.inf, 0) | |
class ActionMoveRight(Action): | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow, 'Move Right', '右に1文字移動します。', 'Right') | |
def run(self): | |
self.mainWindow.tabWidget.moveCursor(1, 0) | |
class ActionMoveUp(Action): | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow, 'Move Up', '上に1文字移動します。', 'Up') | |
def run(self): | |
self.mainWindow.tabWidget.moveCursor(0, -1) | |
class ActionPageDown(Action): | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow, 'Page Down', '1ページ下にスクロールします。', 'PgDown', W.QStyle.SP_ArrowDown) | |
def run(self): | |
tab = self.mainWindow.tabWidget.currentWidget() | |
tab.scrollBar.movePage(1) | |
class ActionPageUp(Action): | |
def __init__(self, mainWindow): | |
super().__init__(mainWindow, 'Page Up', '1ページ上にスクロールします。', 'PgUp', W.QStyle.SP_ArrowUp) | |
def run(self): | |
tab = self.mainWindow.tabWidget.currentWidget() | |
tab.scrollBar.movePage(-1) | |
if __name__ == '__main__': | |
# https://stackoverflow.com/questions/1551605 | |
# How to set application's taskbar icon in Windows 7 | |
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID('myhexeditor') | |
application = Application() | |
sys.exit(application.exec()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment