Skip to content

Commit f26f013

Browse files
author
Shengjie Xu
committed
[GUI/PySide6] port basic execution support from old UI
1 parent 0799fc8 commit f26f013

15 files changed

+1068
-19
lines changed

.github/actions/build/action.yml

+5-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ runs:
2121
git describe --tags
2222
shell: bash
2323
- name: install dependencies
24-
run: pip install --upgrade build twine
24+
run: pip install --upgrade build twine pyside6
25+
shell: bash
26+
- name: compile Qt UI files
27+
run: |
28+
cd src/preppipe_gui_pyside6/forms ; make ; cd ../../..
2529
shell: bash
2630
- name: build
2731
run: python3 -X utf8 -m build

preppipe_gui_entry.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
2-
import preppipe_gui
3-
import preppipe_gui.main
2+
import preppipe_gui_pyside6
3+
import preppipe_gui_pyside6.main
44

55
if __name__ == '__main__':
6-
preppipe_gui.main.gui_main(settings_path=os.path.dirname(__file__))
6+
preppipe_gui_pyside6.main.gui_main(settings_path=os.path.dirname(__file__))

setup.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ install_requires =
4848
# https://setuptools.pypa.io/en/latest/userguide/declarative_config.html#configuring-setup-using-setup-cfg-files
4949
[options.extras_require]
5050
gui =
51-
tkinterdnd2
51+
pyside6
5252

5353
[options.packages.find]
5454
where = src
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import os
2+
import typing
3+
from PySide6.QtWidgets import *
4+
from PySide6.QtCore import *
5+
from PySide6.QtGui import *
6+
from ..forms.generated.ui_fileselectionwidget import Ui_FileSelectionWidget
7+
from preppipe.language import *
8+
from ..translatablewidgetinterface import *
9+
10+
class FileSelectionWidget(QWidget, TranslatableWidgetInterface):
11+
# Signal to indicate that the file path has been updated.
12+
filePathUpdated = Signal(str)
13+
14+
TR_gui_fileselectionwidget = TranslationDomain("gui_fileselectionwidget")
15+
16+
tr_pleaseselect = TR_gui_fileselectionwidget.tr("pleaseselect",
17+
en="Please select {fieldname}",
18+
zh_cn="请选择{fieldname}",
19+
zh_hk="請選擇{fieldname}",
20+
)
21+
tr_notselected = TR_gui_fileselectionwidget.tr("notselected",
22+
en="({fieldname}: Not selected)",
23+
zh_cn="({fieldname}: 未选择)",
24+
zh_hk="({fieldname}: 未選擇)",
25+
)
26+
tr_select = TR_gui_fileselectionwidget.tr("select",
27+
en="Select",
28+
zh_cn="选择",
29+
zh_hk="選擇",
30+
)
31+
32+
@staticmethod
33+
def default_file_checker(path: str) -> bool:
34+
return os.path.exists(path) and os.path.isfile(path)
35+
36+
@staticmethod
37+
def default_directory_checker(path: str) -> bool:
38+
return os.path.exists(path) and os.path.isdir(path)
39+
40+
ui : Ui_FileSelectionWidget
41+
isDirectoryMode : bool
42+
isOutputInsteadofInput : bool
43+
isExistingOnly : bool
44+
verifyCB : typing.Callable[[str], bool] | None
45+
currentPath : str
46+
fieldName : Translatable | str
47+
filter : Translatable | str
48+
defaultName : Translatable | str
49+
50+
def __init__(self, parent=None):
51+
super().__init__(parent)
52+
# Instantiate the UI from the compiled .ui file.
53+
self.ui = Ui_FileSelectionWidget()
54+
self.ui.setupUi(self)
55+
56+
# Initial state
57+
self.isDirectoryMode = False
58+
self.isOutputInsteadofInput = False
59+
self.isExistingOnly = False
60+
self.verifyCB = None
61+
self.currentPath = ""
62+
self.fieldName = ""
63+
self.filter = ""
64+
self.defaultName = ""
65+
66+
# Connect the push button's clicked signal to open the dialog.
67+
self.ui.pushButton.clicked.connect(self.requestOpenDialog)
68+
self.bind_text(self.ui.pushButton.setText, self.tr_select)
69+
70+
# Enable drag and drop.
71+
self.setAcceptDrops(True)
72+
# Initialize the verifier based on the directory mode.
73+
self.setDirectoryMode(self.isDirectoryMode)
74+
75+
def setDirectoryMode(self, v: bool):
76+
self.isDirectoryMode = v
77+
self.verifyCB = FileSelectionWidget.getDefaultVerifier(self.isDirectoryMode)
78+
79+
def getIsDirectoryMode(self) -> bool:
80+
return self.isDirectoryMode
81+
82+
@staticmethod
83+
def getDefaultVerifier(isDirectoryMode: bool):
84+
if isDirectoryMode:
85+
return FileSelectionWidget.default_directory_checker
86+
else:
87+
return FileSelectionWidget.default_file_checker
88+
89+
def setVerifyCallBack(self, cb):
90+
self.verifyCB = cb
91+
92+
def setIsOutputInsteadofInput(self, v: bool):
93+
self.isOutputInsteadofInput = v
94+
95+
def getIsOutputInsteadofInput(self) -> bool:
96+
return self.isOutputInsteadofInput
97+
98+
def setExistingOnly(self, v: bool):
99+
self.isExistingOnly = v
100+
101+
def setFieldName(self, name: Translatable | str):
102+
self.fieldName = name
103+
self.updateLabelText()
104+
105+
def getFieldName(self) -> Translatable | str:
106+
return self.fieldName
107+
108+
def setFilter(self, filter_str: Translatable | str):
109+
self.filter = filter_str
110+
111+
def getFilter(self) -> Translatable | str:
112+
return self.filter
113+
114+
def getCurrentPath(self) -> str:
115+
return self.currentPath
116+
117+
def setDefaultName(self, name: Translatable | str):
118+
self.defaultName = name
119+
120+
def getDefaultName(self) -> Translatable | str:
121+
return self.defaultName
122+
123+
def setCurrentPath(self, newpath: str):
124+
"""Slot to update the current path and refresh the label."""
125+
self.currentPath = newpath
126+
self.updateLabelText()
127+
self.filePathUpdated.emit(self.currentPath)
128+
129+
def requestOpenDialog(self):
130+
dialogTitle = self.tr_pleaseselect.format(fieldname=str(self.fieldName))
131+
dialog = QFileDialog(self, dialogTitle, self.currentPath, str(self.filter))
132+
if self.isDirectoryMode:
133+
dialog.setFileMode(QFileDialog.Directory)
134+
dialog.setOption(QFileDialog.ShowDirsOnly, True)
135+
else:
136+
dialog.setFileMode(QFileDialog.ExistingFile if self.isExistingOnly else QFileDialog.AnyFile)
137+
if self.isOutputInsteadofInput:
138+
dialog.setAcceptMode(QFileDialog.AcceptSave)
139+
else:
140+
dialog.setAcceptMode(QFileDialog.AcceptOpen)
141+
dialog.fileSelected.connect(self.setCurrentPath)
142+
dialog.finished.connect(dialog.deleteLater)
143+
dialog.show()
144+
145+
def updateLabelText(self):
146+
if not self.currentPath:
147+
self.ui.pathLabel.setText(self.tr_notselected.format(fieldname=str(self.fieldName)))
148+
else:
149+
self.ui.pathLabel.setText(self.currentPath)
150+
151+
def update_text(self):
152+
super().update_text()
153+
self.updateLabelText()
154+
155+
def dragEnterEvent(self, e: QDragEnterEvent):
156+
if e.mimeData().hasUrls():
157+
path = e.mimeData().urls()[0].toLocalFile()
158+
if not self.verifyCB or self.verifyCB(path):
159+
e.acceptProposedAction()
160+
else:
161+
super().dragEnterEvent(e)
162+
163+
def dropEvent(self, event: QDropEvent):
164+
for url in event.mimeData().urls():
165+
path = url.toLocalFile()
166+
if not self.verifyCB or self.verifyCB(path):
167+
self.setCurrentPath(path)
168+
return
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import sys
2+
import subprocess
3+
from PySide6.QtWidgets import *
4+
from PySide6.QtCore import *
5+
from PySide6.QtGui import QDesktopServices
6+
from preppipe.language import *
7+
from ..forms.generated.ui_outputentrywidget import Ui_OutputEntryWidget
8+
from ..translatablewidgetinterface import *
9+
10+
class OutputEntryWidget(QWidget, TranslatableWidgetInterface):
11+
TR_gui_outputentrywidget = TranslationDomain("gui_outputentrywidget")
12+
_tr_open_containing_directory = TR_gui_outputentrywidget.tr("open_containing_directory",
13+
en="Open Containing Directory",
14+
zh_cn="打开所在目录",
15+
zh_hk="打開所在目錄",
16+
)
17+
_tr_open = TR_gui_outputentrywidget.tr("open",
18+
en="Open",
19+
zh_cn="打开",
20+
zh_hk="打開",
21+
)
22+
_tr_filestate_initial = TR_gui_outputentrywidget.tr("filestate_initial",
23+
en="Init",
24+
zh_cn="初始",
25+
zh_hk="初始",
26+
)
27+
_tr_filestate_generated = TR_gui_outputentrywidget.tr("filestate_generated",
28+
en="Generated",
29+
zh_cn="已生成",
30+
zh_hk="已生成",
31+
)
32+
_tr_filestate_not_updated = TR_gui_outputentrywidget.tr("filestate_not_updated",
33+
en="Not Updated",
34+
zh_cn="未更新",
35+
zh_hk="未更新",
36+
)
37+
_tr_filestate_not_generated_yet = TR_gui_outputentrywidget.tr("filestate_not_generated_yet",
38+
en="Not Generated Yet",
39+
zh_cn="尚未生成",
40+
zh_hk="尚未生成",
41+
)
42+
_tr_not_supported = TR_gui_outputentrywidget.tr("not_supported",
43+
en="Not Supported",
44+
zh_cn="暂不支持",
45+
zh_hk="暫不支持",
46+
)
47+
_tr_not_supporting_open_directory = TR_gui_outputentrywidget.tr("not_supporting_open_directory",
48+
en="Sorry, we do not support opening directories in the current system yet.",
49+
zh_cn="抱歉,我们暂不支持在当前系统下打开目录。",
50+
zh_hk="抱歉,我們暫不支持在當前系統下打開目錄。",
51+
)
52+
53+
ui : Ui_OutputEntryWidget
54+
fieldName : Translatable | str
55+
path : str
56+
lastModified : QDateTime
57+
58+
def __init__(self, parent=None):
59+
super().__init__(parent)
60+
self.ui = Ui_OutputEntryWidget()
61+
self.ui.setupUi(self)
62+
63+
# Connect UI signals
64+
self.ui.openInExplorerPushButton.clicked.connect(self.requestOpenContainingDirectory)
65+
self.ui.openPushButton.clicked.connect(self.requestOpen)
66+
67+
self.fieldName = ""
68+
self.path = ""
69+
self.lastModified = QDateTime()
70+
71+
self.bind_text(self.ui.openPushButton.setText, self._tr_open)
72+
self.bind_text(self.ui.openInExplorerPushButton.setText, self._tr_open_containing_directory)
73+
74+
@staticmethod
75+
def getLatestModificationInDir(dirpath: str) -> QDateTime:
76+
"""Recursively iterates through the directory and returns the latest modification time."""
77+
result = QDateTime()
78+
iterator = QDirIterator(dirpath, QDir.Files | QDir.Dirs, QDirIterator.Subdirectories)
79+
while iterator.hasNext():
80+
file_info = QFileInfo(iterator.next())
81+
if not result.isValid() or result < file_info.lastModified():
82+
result = file_info.lastModified()
83+
return result
84+
85+
def setData(self, fieldName: Translatable | str, path: str):
86+
"""Set the field name and path, update labels and store the initial modification time."""
87+
self.fieldName = fieldName
88+
self.path = path
89+
if isinstance(fieldName, Translatable):
90+
self.bind_text(self.ui.fieldNameLabel.setText, fieldName)
91+
else:
92+
self.ui.fieldNameLabel.setText(fieldName)
93+
self.ui.pathLabel.setText(path)
94+
self.bind_text(self.ui.statusLabel.setText, self._tr_filestate_initial)
95+
96+
info = QFileInfo(path)
97+
if info.exists():
98+
if info.isDir():
99+
# 如果是目录的话,我们使用当前时间
100+
# 其实应该遍历目录下所有文件、取最新的时间的,这里就先不这样做了
101+
self.lastModified = QDateTime.currentDateTime()
102+
else:
103+
self.lastModified = info.lastModified()
104+
else:
105+
self.lastModified = QDateTime()
106+
107+
def updateStatus(self):
108+
"""Updates the status label based on the modification time of the target file/directory."""
109+
info = QFileInfo(self.path)
110+
if info.exists():
111+
curtime = info.lastModified()
112+
if info.isDir():
113+
curtime = self.getLatestModificationInDir(self.path)
114+
if not self.lastModified.isValid() or self.lastModified < curtime:
115+
self.bind_text(self.ui.statusLabel.setText, self._tr_filestate_generated)
116+
return
117+
self.bind_text(self.ui.statusLabel.setText, self._tr_filestate_not_updated)
118+
else:
119+
self.bind_text(self.ui.statusLabel.setText, self._tr_filestate_not_generated_yet)
120+
121+
def requestOpenContainingDirectory(self):
122+
if not os.path.exists(self.path):
123+
return
124+
125+
if sys.platform.startswith('win'):
126+
# Windows
127+
explorer = 'explorer'
128+
path = os.path.normpath(self.path)
129+
subprocess.Popen([explorer, '/select,', path])
130+
elif sys.platform.startswith('darwin'):
131+
# macOS
132+
subprocess.Popen(['open', '-R', self.path])
133+
elif sys.platform.startswith('linux'):
134+
# Linux
135+
try:
136+
subprocess.Popen(['xdg-open', os.path.dirname(self.path)])
137+
except Exception:
138+
QMessageBox.warning(self, self._tr_not_supported.get(), self._tr_not_supporting_open_directory.get())
139+
else:
140+
QMessageBox.warning(self, self._tr_not_supported.get(), self._tr_not_supporting_open_directory.get())
141+
142+
def requestOpen(self):
143+
"""Opens the target file or directory using the default system handler."""
144+
info = QFileInfo(self.path)
145+
if info.exists():
146+
QDesktopServices.openUrl(QUrl.fromLocalFile(self.path))

0 commit comments

Comments
 (0)