PySide6でキーボードとマウスの入力内容を表示するアプリを作ってみました
2023/09/23 categories:Python| tags:Python|PySide6|pynput|
PySide6にpynputで取得したキーボードとマウスの入力内容を表示するアプリを作ってみました。
動作の様子
pynputでキーボードやマウスの入力を監視して、PySide6に入力内容を表示できるようにしました。表示設定内度は右クリックで表示されるコンテキストメニューの設定から設定できます。
ソースコード
keyboard_mouse_monitor.py
import sys
import pynput
import json
from pathlib import Path
from PySide6 import QtWidgets, QtCore, QtGui
from settings_dialog import SettingsDialog
class Mainwindow(QtWidgets.QMainWindow):
def __init__(self, app) -> None:
super().__init__()
self.app = app
self.font_size = 12
self.max_row = 3
self.is_stays_on_top = False
self.is_frameless = False
self.foreground_color = '#000000'
self.background_color = '#FFFFFF'
self.keymap = {}
self.mousemap = {}
self.settings_path = Path(__file__).parent / 'settings.json'
self.model = QtGui.QStandardItemModel()
self.view = View(self.model)
self.view.dragged.connect(self.move_window)
self.view.customContextMenuRequested.connect(self.show_context_menu)
self.setCentralWidget(self.view)
self.setWindowTitle('入力表示')
self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowType.WindowMinimizeButtonHint)
self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowType.WindowMaximizeButtonHint)
pixmap = QtGui.QPixmap(1, 1)
pixmap.fill(QtCore.Qt.GlobalColor.transparent)
self.setWindowIcon(QtGui.QIcon(pixmap))
self.load_settings_from_file()
self.mouse_and_key_monitor = MouseAndKeyMonitor()
self.mouse_and_key_monitor.key_changed.connect(self.key_changed)
self.mouse_and_key_monitor.mouse_changed.connect(self.mouse_changed)
self.mouse_and_key_monitor.start()
def load_settings(self, settings):
self.is_stays_on_top = settings['stays_on_top']
self.is_frameless = settings['frameless']
self.font_size = settings['font_size']
self.max_row = settings['max_row']
self.mousemap = settings['mousemap']
self.keymap = settings['keymap']
self.resize(settings['window_width'], settings['window_height'])
self.foreground_color = settings['foreground_color']
self.background_color = settings['background_color']
self.change_window_flag(self.is_stays_on_top, QtCore.Qt.WindowType.WindowStaysOnTopHint)
self.change_window_flag(self.is_frameless, QtCore.Qt.WindowType.FramelessWindowHint)
self.model.removeRows(0, self.model.rowCount())
def load_settings_from_file(self):
try:
with open(self.settings_path, mode='r', encoding='utf-8') as f:
settings = json.load(f)
self.load_settings(settings)
except:
settings = DEFAULT_SETTINGS
self.load_settings(settings)
self.change_window_flag(self.is_stays_on_top, QtCore.Qt.WindowType.WindowStaysOnTopHint)
self.change_window_flag(self.is_frameless, QtCore.Qt.WindowType.FramelessWindowHint)
def settings(self):
settings = {}
settings['stays_on_top'] = self.is_stays_on_top
settings['frameless'] = self.is_frameless
settings['font_size'] = self.font_size
settings['max_row'] = self.max_row
settings['window_width'] = self.width()
settings['window_height'] = self.height()
settings['foreground_color'] = self.foreground_color
settings['background_color'] = self.background_color
settings['mousemap'] = self.mousemap
settings['keymap'] = self.keymap
return settings
def show_settings(self):
dialog = SettingsDialog(self.app, self.settings())
if not dialog.exec():
return
settings = dialog.to_dict()
try:
self.load_settings(settings)
except:
settings = DEFAULT_SETTINGS
self.load_settings(settings)
def closeEvent(self, event: QtGui.QCloseEvent) -> None:
settings = self.settings()
with open(self.settings_path, mode='w', encoding='utf-8') as f:
json.dump(settings, f, indent=4, ensure_ascii=False)
return super().closeEvent(event)
def change_window_flag(self, is_set: bool, window_type: QtCore.Qt.WindowType):
if is_set:
self.setWindowFlags(self.windowFlags() | window_type)
else:
self.setWindowFlags(self.windowFlags() & ~window_type)
self.show()
def show_context_menu(self, pos):
def toggle_stays_on_top():
self.is_stays_on_top = not self.is_stays_on_top
self.change_window_flag(self.is_stays_on_top, QtCore.Qt.WindowType.WindowStaysOnTopHint)
def toggle_frameless():
self.is_frameless = not self.is_frameless
self.change_window_flag(self.is_frameless, QtCore.Qt.WindowType.FramelessWindowHint)
stays_on_top = QtGui.QAction('常に最前面に表示')
stays_on_top.setCheckable(True)
stays_on_top.setChecked(self.is_stays_on_top)
stays_on_top.triggered.connect(toggle_stays_on_top)
frameless = QtGui.QAction('ウィンドウフレームを非表示')
frameless.setCheckable(True)
frameless.setChecked(self.is_frameless)
frameless.triggered.connect(toggle_frameless)
menu = QtWidgets.QMenu()
menu.addAction(stays_on_top)
menu.addAction(frameless)
menu.addSeparator()
menu.addAction('設定', self.show_settings)
menu.addSeparator()
menu.addAction('終了', self.close)
menu.exec(self.mapToGlobal(pos))
def move_window(self):
self.windowHandle().startSystemMove()
def key_changed(self, key_codes: list[int]):
key_names = [ self.keymap[str(i)][0] for i in key_codes ]
self.append_text(' + '.join(key_names))
def mouse_changed(self, mouse: dict):
texts = []
for button, action in mouse.items():
texts.append(self.mousemap['buttons'][button] + self.mousemap['actions'][action])
self.append_text(' + '.join(texts))
def append_text(self, text):
item = self.item(text)
self.model.insertRow(0, item)
if self.model.rowCount() > self.max_row:
self.model.removeRow(self.model.rowCount() - 1)
self.view.setCurrentIndex(self.model.index(0, 0))
def item(self, text):
item = QtGui.QStandardItem()
item.setEnabled(False)
item.setEditable(False)
item.setSelectable(False)
item.setText(text)
item.setData(QtGui.QColor(self.foreground_color), QtCore.Qt.ItemDataRole.ForegroundRole)
item.setData(QtGui.QColor(self.background_color), QtCore.Qt.ItemDataRole.BackgroundRole)
font = item.font()
font.setPointSize(self.font_size)
item.setFont(font)
return item
class MouseAndKeyMonitor(QtCore.QObject):
key_changed = QtCore.Signal(list)
mouse_changed = QtCore.Signal(dict)
def __init__(self) -> None:
super().__init__()
self.key_pressing = []
self.mouse = {}
def start(self):
self.mouser_listener = pynput.mouse.Listener(on_move=self.on_move, on_click=self.on_click, on_scroll=self.on_scroll)
self.mouser_listener.start()
self.keyboard_listener = pynput.keyboard.Listener(on_press=self.on_press,on_release=self.on_release)
self.keyboard_listener.start()
def stop(self):
self.mouser_listener.stop()
self.keyboard_listener.stop()
def on_move(self, x, y):
changed = False
for key in self.mouse:
if self.mouse[key] != 'drag':
self.mouse[key] = 'drag'
changed = True
if changed:
self.mouse_changed.emit(self.mouse)
def on_click(self, x, y, button: pynput.mouse.Button, pressed):
button_name = button.name
if pressed:
if not button_name in self.mouse:
self.mouse[button_name] = 'press'
self.mouse_changed.emit(self.mouse)
else:
if button_name in self.mouse:
del self.mouse[button_name]
#self.mouse_changed.emit(self.mouse)
def on_scroll(self, x, y, dx, dy):
action = 'up' if dy > 0 else 'down'
self.mouse['scroll'] = action
self.mouse_changed.emit(self.mouse)
del self.mouse['scroll']
def on_press(self, key):
key_code = key.vk if type(key) is pynput.keyboard._win32.KeyCode else key.value.vk
if not key_code in self.key_pressing:
self.key_pressing.append(key_code)
self.key_changed.emit(self.key_pressing)
def on_release(self, key):
key_code = key.vk if type(key) is pynput.keyboard._win32.KeyCode else key.value.vk
del self.key_pressing[self.key_pressing.index(key_code)]
#self.key_changed.emit(self.key_pressing)
class View(QtWidgets.QListView):
dragged = QtCore.Signal()
def __init__(self, model) -> None:
super().__init__()
self.setModel(model)
self.setMinimumHeight(1)
self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> None:
if event.buttons() == QtCore.Qt.MouseButton.LeftButton:
self.dragged.emit()
return super().mouseMoveEvent(event)
DEFAULT_SETTINGS = {
'stays_on_top':False,
'frameless':False,
'font_size':12,
'max_row':3,
'window_width':300,
'window_height':100,
'foreground_color': '#000000',
'background_color': '#FFFFFF',
'mousemap':{
'buttons':{'left':'左', 'right':'右', 'middle':'中', 'scroll':'スクロール', 'x1':'x1', 'x2':'x2'},
'actions':{'press' : 'クリック', 'drag' : 'ドラッグ', 'up' : '上', 'down' : '下'}
},
'keymap':{
'0' : ['', ''],
'1' : ['', 'マウスの左ボタン'],
'2' : ['', 'マウスの右ボタン'],
'3' : ['', 'コントロール・ブレイク処理'],
'4' : ['', 'マウスの中央ボタン(3ボタンマウス)'],
'5' : ['', 'マウスの第1拡張ボタン'],
'6' : ['', 'マウスの第2拡張ボタン'],
'7' : ['', '未定義'],
'8' : ['Backspace', 'Backspaceキー'],
'9' : ['Tab', 'Tabキー'],
'10' : ['', '予約済'],
'11' : ['', '予約済'],
'12' : ['Clear', 'Clearキー'],
'13' : ['Enter', 'Enterキー'],
'14' : ['', '未定義'],
'15' : ['', '未定義'],
'16' : ['Shift', 'Shiftキー'],
'17' : ['Ctrl', 'Ctrlキー'],
'18' : ['Alt', 'Altキー'],
'19' : ['Pause', 'Pauseキー'],
'20' : ['CapsLock', 'CapsLockキー'],
'21' : ['', 'IME かなモード'],
'22' : ['', '未定義'],
'23' : ['', 'IME Junjaモード'],
'24' : ['', 'IME ファイナルモード'],
'25' : ['', 'IME 漢字モード'],
'26' : ['', '未定義'],
'27' : ['Esc', 'Escキー'],
'28' : ['', 'IME 変換'],
'29' : ['', 'IME 無変換'],
'30' : ['', 'IME 使用可能'],
'31' : ['', 'IME モード変更要求'],
'32' : ['スペース', 'スペースキー'],
'33' : ['PageUp', 'PageUpキー'],
'34' : ['PageDown', 'PageDownキー'],
'35' : ['End', 'Endキー'],
'36' : ['Home', 'Homeキー'],
'37' : ['←', '←キー'],
'38' : ['↑', '↑キー'],
'39' : ['→', '→キー'],
'40' : ['↓', '↓キー'],
'41' : ['Select', 'Selectキー'],
'42' : ['Print', 'Printキー'],
'43' : ['Execute', 'Executeキー'],
'44' : ['PrintScreen', 'PrintScreenキー'],
'45' : ['Insert', 'Insertキー'],
'46' : ['Delete', 'Deleteキー'],
'47' : ['Help', 'Helpキー'],
'48' : ['0', '0キー'],
'49' : ['1', '1キー'],
'50' : ['2', '2キー'],
'51' : ['3', '3キー'],
'52' : ['4', '4キー'],
'53' : ['5', '5キー'],
'54' : ['6', '6キー'],
'55' : ['7', '7キー'],
'56' : ['8', '8キー'],
'57' : ['9', '9キー'],
'58' : ['', '未定義'],
'59' : ['', '未定義'],
'60' : ['', '未定義'],
'61' : ['', '未定義'],
'62' : ['', '未定義'],
'63' : ['', '未定義'],
'64' : ['', '未定義'],
'65' : ['A', 'Aキー'],
'66' : ['B', 'Bキー'],
'67' : ['C', 'Cキー'],
'68' : ['D', 'Dキー'],
'69' : ['E', 'Eキー'],
'70' : ['F', 'Fキー'],
'71' : ['G', 'Gキー'],
'72' : ['H', 'Hキー'],
'73' : ['I', 'Iキー'],
'74' : ['J', 'Jキー'],
'75' : ['K', 'Kキー'],
'76' : ['L', 'Lキー'],
'77' : ['M', 'Mキー'],
'78' : ['N', 'Nキー'],
'79' : ['O', 'Oキー'],
'80' : ['P', 'Pキー'],
'81' : ['Q', 'Qキー'],
'82' : ['R', 'Rキー'],
'83' : ['S', 'Sキー'],
'84' : ['T', 'Tキー'],
'85' : ['U', 'Uキー'],
'86' : ['V', 'Vキー'],
'87' : ['W', 'Wキー'],
'88' : ['X', 'Xキー'],
'89' : ['Y', 'Yキー'],
'90' : ['Z', 'Zキー'],
'91' : ['左Windows', '左Windowsキー'],
'92' : ['右Windows', '右Windowsキー'],
'93' : ['アプリケーション', 'アプリケーションキー'],
'94' : ['', '予約済'],
'95' : ['コンピュータスリープ', 'コンピュータスリープキー'],
'96' : ['0', 'テンキーの0キー'],
'97' : ['1', 'テンキーの1キー'],
'98' : ['2', 'テンキーの2キー'],
'99' : ['3', 'テンキーの3キー'],
'100' : ['4', 'テンキーの4キー'],
'101' : ['5', 'テンキーの5キー'],
'102' : ['6', 'テンキーの6キー'],
'103' : ['7', 'テンキーの7キー'],
'104' : ['8', 'テンキーの8キー'],
'105' : ['9', 'テンキーの9キー'],
'106' : ['*', 'テンキーの*キー'],
'107' : ['+', 'テンキーの+キー'],
'108' : ['区切り記号', '区切り記号キー'],
'109' : ['減算記号', '減算記号キー'],
'110' : ['小数点', '小数点キー'],
'111' : ['除算記号', '除算記号キー'],
'112' : ['F1', 'F1キー'],
'113' : ['F2', 'F2キー'],
'114' : ['F3', 'F3キー'],
'115' : ['F4', 'F4キー'],
'116' : ['F5', 'F5キー'],
'117' : ['F6', 'F6キー'],
'118' : ['F7', 'F7キー'],
'119' : ['F8', 'F8キー'],
'120' : ['F9', 'F9キー'],
'121' : ['F10', 'F10キー'],
'122' : ['F11', 'F11キー'],
'123' : ['F12', 'F12キー'],
'124' : ['F13', 'F13キー'],
'125' : ['F14', 'F14キー'],
'126' : ['F15', 'F15キー'],
'127' : ['F16', 'F16キー'],
'128' : ['F17', 'F17キー'],
'129' : ['F18', 'F18キー'],
'130' : ['F19', 'F19キー'],
'131' : ['F20', 'F20キー'],
'132' : ['F21', 'F21キー'],
'133' : ['F22', 'F22キー'],
'134' : ['F23', 'F23キー'],
'135' : ['F24', 'F24キー'],
'136' : ['', '割当無し'],
'137' : ['', '割当無し'],
'138' : ['', '割当無し'],
'139' : ['', '割当無し'],
'140' : ['', '割当無し'],
'141' : ['', '割当無し'],
'142' : ['', '割当無し'],
'143' : ['', '割当無し'],
'144' : ['NumLock', 'NumLockキー'],
'145' : ['ScrollLock', 'ScrollLockキー'],
'146' : ['', 'OEM固有'],
'147' : ['', 'OEM固有'],
'148' : ['', 'OEM固有'],
'149' : ['', 'OEM固有'],
'150' : ['', 'OEM固有'],
'151' : ['', '割当無し'],
'152' : ['', '割当無し'],
'153' : ['', '割当無し'],
'154' : ['', '割当無し'],
'155' : ['', '割当無し'],
'156' : ['', '割当無し'],
'157' : ['', '割当無し'],
'158' : ['', '割当無し'],
'159' : ['', '割当無し'],
'160' : ['左Shift', '左Shiftキー'],
'161' : ['右Shift', '右Shiftキー'],
'162' : ['左Ctrl', '左Ctrlキー'],
'163' : ['右Ctrl', '右Ctrlキー'],
'164' : ['左Alt', '左Altキー'],
'165' : ['右Alt', '右Altキー'],
'166' : ['ブラウザーの戻る', 'ブラウザーの戻るキー'],
'167' : ['ブラウザーの進む', 'ブラウザーの進むキー'],
'168' : ['ブラウザーの更新の', 'ブラウザーの更新のキー'],
'169' : ['ブラウザーの停止', 'ブラウザーの停止キー'],
'170' : ['ブラウザーの検索', 'ブラウザーの検索キー'],
'171' : ['ブラウザーのお気に入り', 'ブラウザーのお気に入りキー'],
'172' : ['ブラウザーの開始およびホーム', 'ブラウザーの開始およびホームキー'],
'173' : ['音量ミュート', '音量ミュートキー'],
'174' : ['音量ダウン', '音量ダウンキー'],
'175' : ['音量アップ', '音量アップキー'],
'176' : ['次のトラック', '次のトラックキー'],
'177' : ['前のトラック', '前のトラックキー'],
'178' : ['メディア停止', 'メディア停止キー'],
'179' : ['メディア再生/一時停止', 'メディア再生/一時停止キー'],
'180' : ['メール起動', 'メール起動キー'],
'181' : ['メディア選択', 'メディア選択キー'],
'182' : ['アプリケーション1起動', 'アプリケーション1起動キー'],
'183' : ['アプリケーション2起動', 'アプリケーション2起動キー'],
'184' : ['', '予約済'],
'185' : ['', '予約済'],
'186' : ['OEM1', 'OEM1キー'],
'187' : ['+', '+キー'],
'188' : [',', ',キー'],
'189' : ['-', '-キー'],
'190' : ['.', '.キー'],
'191' : ['OEM2', 'OEM2キー'],
'192' : ['OEM3', 'OEM3キー'],
'193' : ['', '予約済'],
'194' : ['', '予約済'],
'195' : ['', '予約済'],
'196' : ['', '予約済'],
'197' : ['', '予約済'],
'198' : ['', '予約済'],
'199' : ['', '予約済'],
'200' : ['', '予約済'],
'201' : ['', '予約済'],
'202' : ['', '予約済'],
'203' : ['', '予約済'],
'204' : ['', '予約済'],
'205' : ['', '予約済'],
'206' : ['', '予約済'],
'207' : ['', '予約済'],
'208' : ['', '予約済'],
'209' : ['', '予約済'],
'210' : ['', '予約済'],
'211' : ['', '予約済'],
'212' : ['', '予約済'],
'213' : ['', '予約済'],
'214' : ['', '予約済'],
'215' : ['', '予約済'],
'216' : ['', '割当無し'],
'217' : ['', '割当無し'],
'218' : ['', '割当無し'],
'219' : ['OEM4', 'OEM4キー'],
'220' : ['OEM5', 'OEM5キー'],
'221' : ['OEM6', 'OEM6キー'],
'222' : ['OEM7', 'OEM7キー'],
'223' : ['OEM8', 'OEM8キー'],
'224' : ['', '予約済'],
'225' : ['', 'OEM固有'],
'226' : ['OEM102', 'OEM102キー'],
'227' : ['', 'OEM固有'],
'228' : ['', 'OEM固有'],
'229' : ['IME PROCESS', 'IME PROCESSキー'],
'230' : ['', 'OEM固有'],
'231' : ['', 'キーボード以外の入力手段に使用される32 ビット仮想キー値の下位ワード'],
'232' : ['', '割当無し'],
'233' : ['', 'OEM固有'],
'234' : ['', 'OEM固有'],
'235' : ['', 'OEM固有'],
'236' : ['', 'OEM固有'],
'237' : ['', 'OEM固有'],
'238' : ['', 'OEM固有'],
'239' : ['', 'OEM固有'],
'240' : ['', 'OEM固有'],
'241' : ['', 'OEM固有'],
'242' : ['', 'OEM固有'],
'243' : ['', 'OEM固有'],
'244' : ['', 'OEM固有'],
'245' : ['', 'OEM固有'],
'246' : ['Attn', 'Attnキー'],
'247' : ['CrSel', 'CrSelキー'],
'248' : ['ExSel', 'ExSelキー'],
'249' : ['Erase EOF', 'Erase EOFキー'],
'250' : ['Play', 'Playキー'],
'251' : ['Zoom', 'Zoomキー'],
'252' : ['', '予約済'],
'253' : ['PA1', 'PA1キー'],
'254' : ['Clear', 'Clearキー']
}
}
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Mainwindow(app)
w.show()
sys.exit(app.exec())
settings_dialog.py
from PySide6 import QtWidgets, QtCore, QtGui
class SettingsDialog(QtWidgets.QDialog):
def __init__(self, app, settings) -> None:
super().__init__()
self.setWindowTitle('設定')
self.is_stays_on_top = QtWidgets.QCheckBox('常に最前面に表示')
self.is_stays_on_top.setChecked(settings['stays_on_top'])
self.is_frameless = QtWidgets.QCheckBox('ウィンドウフレームを非表示')
self.is_frameless.setChecked(settings['frameless'])
self.font_size = QtWidgets.QSpinBox()
self.font_size.setMaximum(9999)
self.font_size.setValue(settings['font_size'])
self.max_row = QtWidgets.QSpinBox()
self.max_row.setMaximum(9999)
self.max_row.setValue(settings['max_row'])
self.window_width = QtWidgets.QSpinBox()
self.window_width.setMaximum(9999)
self.window_width.setValue(settings['window_width'])
self.window_height = QtWidgets.QSpinBox()
self.window_height.setMaximum(9999)
self.window_height.setValue(settings['window_height'])
preview_item = QtGui.QStandardItem('Preview text')
preview_item.setEditable(False)
preview_item.setSelectable(False)
self.preview_model = QtGui.QStandardItemModel()
self.preview_model.appendRow(preview_item)
self.preview = QtWidgets.QListView()
self.preview.setMaximumHeight(60)
self.preview.setModel(self.preview_model)
self.foreground_color = QtWidgets.QPushButton('文字色:' + settings['foreground_color'])
self.background_color = QtWidgets.QPushButton('背景色:' + settings['background_color'])
self.keymap_model = QtGui.QStandardItemModel()
self.keymap_model.setHorizontalHeaderLabels(['キーコード', '表示名', '説明'])
self.keymap = MyTableView(app)
self.keymap.setModel(self.keymap_model)
self.keymap.verticalHeader().hide()
self.keymap.horizontalHeader().setStretchLastSection(True)
for key_code, (name, discription) in settings['keymap'].items():
item0 = QtGui.QStandardItem()
item0.setData(int(key_code), QtCore.Qt.ItemDataRole.DisplayRole)
item1 = QtGui.QStandardItem(name)
item2 = QtGui.QStandardItem(discription)
self.keymap_model.appendRow([item0, item1, item2])
self.mousemap_model = QtGui.QStandardItemModel()
self.mousemap_model.setHorizontalHeaderLabels(['項目', '表示名'])
self.mousemap = QtWidgets.QTreeView()
self.mousemap.setModel(self.mousemap_model)
buttons = QtGui.QStandardItem('buttons')
for button, name in settings['mousemap']['buttons'].items():
buttons.appendRow([QtGui.QStandardItem(button), QtGui.QStandardItem(name)])
actions = QtGui.QStandardItem('actions')
for action, name in settings['mousemap']['actions'].items():
actions.appendRow([QtGui.QStandardItem(action), QtGui.QStandardItem(name)])
root = self.mousemap_model.invisibleRootItem()
root.appendRow([buttons, QtGui.QStandardItem()])
root.appendRow([actions, QtGui.QStandardItem()])
self.mousemap.expandAll()
button_box = QtWidgets.QDialogButtonBox()
button_box.addButton('OK', button_box.ButtonRole.AcceptRole).clicked.connect(self.accept)
button_box.addButton('Cancel', button_box.ButtonRole.RejectRole).clicked.connect(self.reject)
label0 = QtWidgets.QLabel('フォントサイズ')
label1 = QtWidgets.QLabel('表示行数')
label2 = QtWidgets.QLabel('ウィンドウ幅')
label3 = QtWidgets.QLabel('ウィンドウ高さ')
label4 = QtWidgets.QLabel('プレビュー')
self.setLayout(QtWidgets.QGridLayout())
self.layout().addWidget(self.is_stays_on_top, 0, 0, 1, 2)
self.layout().addWidget(self.is_frameless, 1, 0, 1, 2)
self.layout().addWidget(label1, 2, 0, 1, 1)
self.layout().addWidget(self.max_row, 2, 1, 1, 1)
self.layout().addWidget(label2, 3, 0, 1, 1)
self.layout().addWidget(self.window_height, 3, 1, 1, 1)
self.layout().addWidget(label3, 4, 0, 1, 1)
self.layout().addWidget(self.window_width, 4, 1, 1, 1)
self.layout().addWidget(label0, 5, 0, 1, 1)
self.layout().addWidget(self.font_size, 5, 1, 1, 1)
self.layout().addWidget(self.foreground_color, 6, 0, 1, 1)
self.layout().addWidget(self.background_color, 6, 1, 1, 1)
self.layout().addWidget(label4, 7, 0, 1, 1)
self.layout().addWidget(self.preview, 8, 0, 1, 2)
self.layout().addWidget(self.mousemap, 9, 0, 1, 2)
self.layout().addWidget(self.keymap, 0, 2, 10, 1)
self.layout().addWidget(button_box, 10, 0, 1, 3)
self.preview_update()
self.font_size.valueChanged.connect(self.preview_update)
self.foreground_color.clicked.connect(self.foreground_change_color)
self.background_color.clicked.connect(self.background_change_color)
def foreground_change_color(self):
def color_changed(color: QtGui.QColor):
text = self.foreground_color.text()
self.foreground_color.setText( '{}:{}'.format(text.split(':')[0], color.name()) )
self.preview_update()
dialog = QtWidgets.QColorDialog(self)
dialog.setCurrentColor( self.foreground_color.text().split(':')[1] )
dialog.currentColorChanged.connect(color_changed)
dialog.show()
def background_change_color(self):
def color_changed(color: QtGui.QColor):
text = self.background_color.text()
self.background_color.setText( '{}:{}'.format(text.split(':')[0], color.name()) )
self.preview_update()
dialog = QtWidgets.QColorDialog(self)
dialog.setCurrentColor( self.background_color.text().split(':')[1] )
dialog.currentColorChanged.connect(color_changed)
dialog.show()
def preview_update(self):
foreground_color = self.foreground_color.text().split(':')[1]
background_color = self.background_color.text().split(':')[1]
item = self.preview_model.item(0, 0)
font = item.font()
font.setPointSize(self.font_size.value())
item.setFont(font)
item.setData(QtGui.QColor(foreground_color), QtCore.Qt.ItemDataRole.ForegroundRole)
item.setData(QtGui.QColor(background_color), QtCore.Qt.ItemDataRole.BackgroundRole)
self.preview_model.setItem(0, 0, item)
def to_dict(self):
mousemap = { 'buttons' : {}, 'actions' : {} }
item = self.mousemap_model.invisibleRootItem().child(0, 0)
for row in range( item.rowCount() ):
mousemap['buttons'][item.child(row, 0).text()] = item.child(row, 1).text()
item = self.mousemap_model.invisibleRootItem().child(1, 0)
for row in range( item.rowCount() ):
mousemap['actions'][item.child(row, 0).text()] = item.child(row, 1).text()
keymap = {}
item = self.keymap_model.invisibleRootItem()
for row in range( item.rowCount() ):
keymap[item.child(row, 0).text()] = [ item.child(row, 1).text(), item.child(row, 2).text() ]
settings = {}
settings['stays_on_top'] = self.is_stays_on_top.isChecked()
settings['frameless'] = self.is_frameless.isChecked()
settings['font_size'] = self.font_size.value()
settings['max_row'] = self.max_row.value()
settings['window_width'] = self.window_width.value()
settings['window_height'] = self.window_height.value()
settings['foreground_color'] = self.foreground_color.text().split(':')[1]
settings['background_color'] = self.background_color.text().split(':')[1]
settings['mousemap'] = mousemap
settings['keymap'] = keymap
return settings
class MyTableView(QtWidgets.QTableView):
ctrl_c_pressed = QtCore.Signal()
ctrl_v_pressed = QtCore.Signal()
delete_pressed = QtCore.Signal()
def __init__(self, app) -> None:
super().__init__()
self.app: QtWidgets.QApplication = app
def event(self, event: QtGui.QKeyEvent) -> bool:
if event.type() == QtCore.QEvent.Type.KeyPress:
if event.modifiers() == QtCore.Qt.KeyboardModifier.ControlModifier:
if event.key() == QtCore.Qt.Key.Key_C:
self.table_copy()
self.ctrl_c_pressed.emit()
return True
if event.key() == QtCore.Qt.Key.Key_V:
self.table_paste()
self.ctrl_v_pressed.emit()
return True
else:
if event.key() == QtCore.Qt.Key.Key_Delete:
self.table_clear()
self.delete_pressed.emit()
return True
return super().event(event)
def table_copy(self):
indexes = self.selectedIndexes()
if len(indexes) < 1:
return
data, r = [[]], indexes[0].row()
for index in indexes:
if index.row() - r == 1:
data.append([])
data[-1].append( index.data() )
r = index.row()
text = '\n'.join(['\t'.join([ str(d) for d in row_data ]) for row_data in data])
clipboard = self.app.clipboard()
clipboard.setText(text)
def table_paste(self):
indexes = self.selectedIndexes()
if len(indexes) < 1:
return
model, r0, c0 = indexes[0].model(), indexes[0].row(), indexes[0].column()
data = [ row_data.split('\t') for row_data in self.app.clipboard().text().split('\n') ]
if len(data) == 1 and len(data[0]) == 1:
for index in indexes:
if not index.isValid():
continue
model.setData(index, data[0][0])
return
for r, row_data in enumerate( self.app.clipboard().text().split('\n') ):
for c, data in enumerate( row_data.split('\t') ):
index = model.index(r0 + r, c0 + c)
if not index.isValid():
continue
model.setData(index, data)
def table_clear(self):
indexes = self.selectedIndexes()
if len(indexes) < 1:
return
model = indexes[0].model()
for index in indexes:
if not index.isValid():
continue
model.setData(index, '')
if __name__ == '__main__':
import sys
import json
with open('settings.json', mode='r', encoding='utf-8') as f:
settings = json.load(f)
app = QtWidgets.QApplication(sys.argv)
w = SettingsDialog(app, settings)
w.show()
sys.exit(app.exec())