feat(gui): open log line via configurable editor command (template {file}/{line})
refactor(settings): defaults carried by SettingsItem, getters/setters via _pref refactor(pref-win): declarative Field table + _FIELD bridge + merged file pickers Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -323,6 +323,7 @@ The `testium_assist` editor extension is a thin LSP client that spawns `testium
|
|||||||
Both Flatpak and AppImage export `TESTIUM_VERSION` from a launcher (Flatpak: launcher script in `org.testium.Testium.yaml`; AppImage: `runtime.env` in `AppImageBuilder.yml`). `get_testium_version()` checks `/.flatpak-info` / `APPIMAGE` and reads `TESTIUM_VERSION` rather than relying on package metadata or repo introspection.
|
Both Flatpak and AppImage export `TESTIUM_VERSION` from a launcher (Flatpak: launcher script in `org.testium.Testium.yaml`; AppImage: `runtime.env` in `AppImageBuilder.yml`). `get_testium_version()` checks `/.flatpak-info` / `APPIMAGE` and reads `TESTIUM_VERSION` rather than relying on package metadata or repo introspection.
|
||||||
|
|
||||||
## Recent fixes / notable changes
|
## Recent fixes / notable changes
|
||||||
|
- Open-log-at-line (GUI): double-click on a tree item opened the log via a hardcoded `code -g {file}:{line}` (broke in Flatpak where `code` is absent). Now driven by a configurable `editor_cmd` preference (placeholders `{file}`/`{line}`, default `code -g {file}:{line}`); the argv is built by `shlex.split` then per-token `.format` (paths with spaces stay one token), wrapped by `bins.host_console_command()` for Flatpak host-spawn, with a `host_open_path`/`openUrl` fallback (no line) when empty or failing. Settings/pref refactor alongside: `SettingsItem` carries its default (single source of truth), trivial getters/setters collapse to `_pref(item)` bindings, the pref window's `elements` dict becomes a `Field(key, type, widget)` table with a per-type `_FIELD` read/write bridge, and the four file-picker slots fold into `_pick_dir`/`_pick_file`. (Also fixed a latent default mismatch: `report_path` defaulted to `$(home)` in the property but `$(test_directory)` in the pref window; unified to `$(test_directory)`.)
|
||||||
- Show Results (GUI): the toolbar action stays enabled during a run (the log grows live, so it is useful mid-test), not just after. In Flatpak `QDesktopServices.openUrl` routes through the OpenURI portal and often opens no editor for a `.log`; `bins.host_open_path()` now spawns `xdg-open` on the host via `flatpak-spawn --host` (returns False outside Flatpak so the caller falls back to `openUrl`).
|
- Show Results (GUI): the toolbar action stays enabled during a run (the log grows live, so it is useful mid-test), not just after. In Flatpak `QDesktopServices.openUrl` routes through the OpenURI portal and often opens no editor for a `.log`; `bins.host_open_path()` now spawns `xdg-open` on the host via `flatpak-spawn --host` (returns False outside Flatpak so the caller falls back to `openUrl`).
|
||||||
- Test-tree search (GUI): a Ctrl+F find bar highlights + navigates matching items, with Name/Type/Doc field checkboxes. Search modifications run under `blockSignals` (else `setBackground`→`itemChanged`→`on_testChecked` storms the controller), and the search/run highlights share one flag-driven `_refresh_highlight()` (run > search > default) so overlapping layers never leave a stale colour. See "## Test-tree search (GUI)".
|
- Test-tree search (GUI): a Ctrl+F find bar highlights + navigates matching items, with Name/Type/Doc field checkboxes. Search modifications run under `blockSignals` (else `setBackground`→`itemChanged`→`on_testChecked` storms the controller), and the search/run highlights share one flag-driven `_refresh_highlight()` (run > search > default) so overlapping layers never leave a stale colour. See "## Test-tree search (GUI)".
|
||||||
- `pytest` item: pytest analogue of `unittest`, but runs on the **host interpreter in a subprocess** (`bins.python_bin()`, like `py_func`) so it works across every packaging channel. A stdlib-only pytest plugin streams collected node-ids + per-test results back over stdout via sentinels; each test becomes a child item with its own PASS/FAIL/SKIP, duration and failure message. Params: `test_file`, `test_method`. Validation item: `test/validation/items/pytest/` (the validation venv now pip-installs `pytest`). See "### `pytest` item".
|
- `pytest` item: pytest analogue of `unittest`, but runs on the **host interpreter in a subprocess** (`bins.python_bin()`, like `py_func`) so it works across every packaging channel. A stdlib-only pytest plugin streams collected node-ids + per-test results back over stdout via sentinels; each test becomes a child item with its own PASS/FAIL/SKIP, duration and failure message. Params: `test_file`, `test_method`. Validation item: `test/validation/items/pytest/` (the validation venv now pip-installs `pytest`). See "### `pytest` item".
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ version 0.3.2
|
|||||||
even when testium is in the GUI.
|
even when testium is in the GUI.
|
||||||
- The captured output of a ``run`` step can be saved with ``store_result`` and
|
- The captured output of a ``run`` step can be saved with ``store_result`` and
|
||||||
inspected afterwards (for example with ``expected_result`` or a ``py_func``).
|
inspected afterwards (for example with ``expected_result`` or a ``py_func``).
|
||||||
|
- "Show Results" now opens the log on Flatpak (it used to do nothing) and can
|
||||||
|
be used while a test is running, not only after it finishes.
|
||||||
|
- Double-clicking a test item to open the log now uses an editor of your choice:
|
||||||
|
a new preference holds the command (default ``code -g {file}:{line}``); set it
|
||||||
|
to your editor (for example ``kate -l {line} {file}``). Works on Flatpak too.
|
||||||
|
|
||||||
version 0.3.1
|
version 0.3.1
|
||||||
==============
|
==============
|
||||||
|
|||||||
@@ -13,30 +13,59 @@ def init():
|
|||||||
settings = TestiumSettings()
|
settings = TestiumSettings()
|
||||||
|
|
||||||
|
|
||||||
|
_UNSET = object()
|
||||||
|
|
||||||
|
|
||||||
class SettingsItem():
|
class SettingsItem():
|
||||||
def __init__(self, name: str, item_type: type) -> None:
|
def __init__(self, name: str, item_type: type, default=None) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.t = item_type
|
self.t = item_type
|
||||||
|
self.default = default
|
||||||
|
|
||||||
|
|
||||||
|
def _pref(item):
|
||||||
|
"""Build a get/set property reading/writing *item* (default carried by the item)."""
|
||||||
|
return property(lambda self: self.value(item),
|
||||||
|
lambda self, value: self.set_value(item, value))
|
||||||
|
|
||||||
|
|
||||||
class TestiumSettings():
|
class TestiumSettings():
|
||||||
SettingsRecentFiles = SettingsItem('recentFileList', list)
|
SettingsRecentFiles = SettingsItem('recentFileList', list, [])
|
||||||
SettingsLastLogFile = SettingsItem('lastLogFile', str)
|
SettingsLastLogFile = SettingsItem('lastLogFile', str, '')
|
||||||
SettingsLogFileSaved = SettingsItem('logFileSaved', bool)
|
SettingsLogFileSaved = SettingsItem('logFileSaved', bool, False)
|
||||||
SettingsHideDocPane = SettingsItem('docPaneHidden', bool)
|
SettingsHideDocPane = SettingsItem('docPaneHidden', bool, False)
|
||||||
SettingsHideLogPane = SettingsItem('logPaneHidden', bool)
|
SettingsHideLogPane = SettingsItem('logPaneHidden', bool, False)
|
||||||
SettingsShowCheckboxes = SettingsItem('checkBoxesShow', bool)
|
SettingsShowCheckboxes = SettingsItem('checkBoxesShow', bool, False)
|
||||||
SettingsLogPath = SettingsItem('defaultLogPath', str)
|
SettingsLogPath = SettingsItem('defaultLogPath', str, '$(test_directory)')
|
||||||
SettingsReportPath = SettingsItem('defaultReportPath', str)
|
SettingsReportPath = SettingsItem('defaultReportPath', str, '$(test_directory)')
|
||||||
SettingsShowTimeColumn = SettingsItem('showTimeColumn', bool)
|
SettingsShowTimeColumn = SettingsItem('showTimeColumn', bool, False)
|
||||||
SettingsColumnsSize = SettingsItem('columnsSize', dict)
|
SettingsColumnsSize = SettingsItem('columnsSize', dict, {})
|
||||||
SettingsDblClickEnabled = SettingsItem('dblClickEnabled', bool)
|
SettingsDblClickEnabled = SettingsItem('dblClickEnabled', bool, False)
|
||||||
SettingsIconsTheme = SettingsItem('iconsTheme', int)
|
SettingsEditorCmd = SettingsItem('editorCmd', str, 'code -g {file}:{line}')
|
||||||
SettingsLogFont = SettingsItem('logFont', str)
|
SettingsIconsTheme = SettingsItem('iconsTheme', int, 0)
|
||||||
SettingsLogFontSize = SettingsItem('logFontSize', int)
|
SettingsLogFont = SettingsItem('logFont', str, 'Monospace')
|
||||||
SettingsGitSupported = SettingsItem('logGitSupported', bool)
|
SettingsLogFontSize = SettingsItem('logFontSize', int, 8)
|
||||||
SettingsPythonPath = SettingsItem('pythonPath', str)
|
SettingsGitSupported = SettingsItem('logGitSupported', bool, True)
|
||||||
SettingsLuaPath = SettingsItem('luaPath', str)
|
SettingsPythonPath = SettingsItem('pythonPath', str, '')
|
||||||
|
SettingsLuaPath = SettingsItem('luaPath', str, '')
|
||||||
|
|
||||||
|
recent_files = _pref(SettingsRecentFiles)
|
||||||
|
log_file = _pref(SettingsLastLogFile)
|
||||||
|
log_file_saved = _pref(SettingsLogFileSaved)
|
||||||
|
hide_doc_pane = _pref(SettingsHideDocPane)
|
||||||
|
hide_log_pane = _pref(SettingsHideLogPane)
|
||||||
|
show_checkboxes = _pref(SettingsShowCheckboxes)
|
||||||
|
log_path = _pref(SettingsLogPath)
|
||||||
|
report_path = _pref(SettingsReportPath)
|
||||||
|
show_time_column = _pref(SettingsShowTimeColumn)
|
||||||
|
columns_size = _pref(SettingsColumnsSize)
|
||||||
|
dbl_click_enabled = _pref(SettingsDblClickEnabled)
|
||||||
|
editor_cmd = _pref(SettingsEditorCmd)
|
||||||
|
icons_theme = _pref(SettingsIconsTheme)
|
||||||
|
log_font = _pref(SettingsLogFont)
|
||||||
|
git_supported = _pref(SettingsGitSupported)
|
||||||
|
python_bin = _pref(SettingsPythonPath)
|
||||||
|
lua_bin = _pref(SettingsLuaPath)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if 'windows' in platform.system().lower():
|
if 'windows' in platform.system().lower():
|
||||||
@@ -71,9 +100,11 @@ class TestiumSettings():
|
|||||||
self.conf['Default'] = {}
|
self.conf['Default'] = {}
|
||||||
self.sync()
|
self.sync()
|
||||||
|
|
||||||
def value(self, key: SettingsItem, default=''):
|
def value(self, key: SettingsItem, default=_UNSET):
|
||||||
if not isinstance(key, SettingsItem):
|
if not isinstance(key, SettingsItem):
|
||||||
raise ETUMRuntimeError('Not a proper Settings item.')
|
raise ETUMRuntimeError('Not a proper Settings item.')
|
||||||
|
if default is _UNSET:
|
||||||
|
default = key.default
|
||||||
if type(default) != key.t:
|
if type(default) != key.t:
|
||||||
raise ETUMRuntimeError(
|
raise ETUMRuntimeError(
|
||||||
'Types mismatch in config file. You could try to erase "{}" to solve the issue'.format(self.settings_fname))
|
'Types mismatch in config file. You could try to erase "{}" to solve the issue'.format(self.settings_fname))
|
||||||
@@ -120,161 +151,14 @@ class TestiumSettings():
|
|||||||
if configfile.writable():
|
if configfile.writable():
|
||||||
self.conf.write(configfile)
|
self.conf.write(configfile)
|
||||||
|
|
||||||
# SettingsRecentFiles = 'recentFileList'
|
# log_font_size keeps a custom getter: clamp non-positive sizes to 8.
|
||||||
@property
|
|
||||||
def recent_files(self):
|
|
||||||
return self.value(self.SettingsRecentFiles, [])
|
|
||||||
|
|
||||||
@recent_files.setter
|
|
||||||
def recent_files(self, value):
|
|
||||||
self.set_value(self.SettingsRecentFiles, value)
|
|
||||||
|
|
||||||
# SettingsLastLogFile = 'lastLogFile'
|
|
||||||
@property
|
|
||||||
def log_file(self):
|
|
||||||
return self.value(self.SettingsLastLogFile)
|
|
||||||
|
|
||||||
@log_file.setter
|
|
||||||
def log_file(self, value):
|
|
||||||
self.set_value(self.SettingsLastLogFile, value)
|
|
||||||
|
|
||||||
# SettingsLogFileSaved = 'logFileSaved'
|
|
||||||
@property
|
|
||||||
def log_file_saved(self):
|
|
||||||
return self.value(self.SettingsLogFileSaved, False)
|
|
||||||
|
|
||||||
@log_file_saved.setter
|
|
||||||
def log_file_saved(self, value):
|
|
||||||
self.set_value(self.SettingsLogFileSaved, value)
|
|
||||||
|
|
||||||
# SettingsHideDocPane = 'docPaneHidden'
|
|
||||||
@property
|
|
||||||
def hide_doc_pane(self):
|
|
||||||
return self.value(self.SettingsHideDocPane, False)
|
|
||||||
|
|
||||||
@hide_doc_pane.setter
|
|
||||||
def hide_doc_pane(self, value):
|
|
||||||
self.set_value(self.SettingsHideDocPane, value)
|
|
||||||
|
|
||||||
# SettingsHideLogPane = 'logPaneHidden'
|
|
||||||
@property
|
|
||||||
def hide_log_pane(self):
|
|
||||||
return self.value(self.SettingsHideLogPane, False)
|
|
||||||
|
|
||||||
@hide_log_pane.setter
|
|
||||||
def hide_log_pane(self, value):
|
|
||||||
self.set_value(self.SettingsHideLogPane, value)
|
|
||||||
|
|
||||||
# SettingsShowCheckboxes = 'checkBoxesShow'
|
|
||||||
@property
|
|
||||||
def show_checkboxes(self):
|
|
||||||
return self.value(self.SettingsShowCheckboxes, False)
|
|
||||||
|
|
||||||
@show_checkboxes.setter
|
|
||||||
def show_checkboxes(self, value):
|
|
||||||
self.set_value(self.SettingsShowCheckboxes, value)
|
|
||||||
|
|
||||||
# SettingsLogPath = 'defaultLogPath'
|
|
||||||
@property
|
|
||||||
def log_path(self):
|
|
||||||
return self.value(self.SettingsLogPath, '$(test_directory)')
|
|
||||||
|
|
||||||
@log_path.setter
|
|
||||||
def log_path(self, value):
|
|
||||||
self.set_value(self.SettingsLogPath, value)
|
|
||||||
|
|
||||||
# SettingsReportPath = 'defaultReportPath'
|
|
||||||
@property
|
|
||||||
def report_path(self):
|
|
||||||
return self.value(self.SettingsReportPath, '$(home)')
|
|
||||||
|
|
||||||
@report_path.setter
|
|
||||||
def report_path(self, value):
|
|
||||||
self.set_value(self.SettingsReportPath, value)
|
|
||||||
|
|
||||||
# SettingsShowTimeColumn = 'showTimeColumn'
|
|
||||||
@property
|
|
||||||
def show_time_column(self):
|
|
||||||
return self.value(self.SettingsShowTimeColumn, False)
|
|
||||||
|
|
||||||
@show_time_column.setter
|
|
||||||
def show_time_column(self, value):
|
|
||||||
self.set_value(self.SettingsShowTimeColumn, value)
|
|
||||||
|
|
||||||
# SettingsColumnsSize = 'columnsSize'
|
|
||||||
@property
|
|
||||||
def columns_size(self):
|
|
||||||
return self.value(self.SettingsColumnsSize, {})
|
|
||||||
|
|
||||||
@columns_size.setter
|
|
||||||
def columns_size(self, value):
|
|
||||||
self.set_value(self.SettingsColumnsSize, value)
|
|
||||||
|
|
||||||
# SettingsDblClickEnabled = 'dblClickEnabled'
|
|
||||||
@property
|
|
||||||
def dbl_click_enabled(self):
|
|
||||||
return self.value(self.SettingsDblClickEnabled, False)
|
|
||||||
|
|
||||||
@dbl_click_enabled.setter
|
|
||||||
def dbl_click_enabled(self, value):
|
|
||||||
self.set_value(self.SettingsDblClickEnabled, value)
|
|
||||||
|
|
||||||
# SettingsIconsTheme = 'iconsTheme'
|
|
||||||
@property
|
|
||||||
def icons_theme(self):
|
|
||||||
return self.value(self.SettingsIconsTheme, 0)
|
|
||||||
|
|
||||||
@icons_theme.setter
|
|
||||||
def icons_theme(self, value):
|
|
||||||
self.set_value(self.SettingsIconsTheme, value)
|
|
||||||
|
|
||||||
# SettingsLogFont = 'logFont'
|
|
||||||
@property
|
|
||||||
def log_font(self):
|
|
||||||
return self.value(self.SettingsLogFont, 'Monospace')
|
|
||||||
|
|
||||||
@log_font.setter
|
|
||||||
def log_font(self, value):
|
|
||||||
self.set_value(self.SettingsLogFont, value)
|
|
||||||
|
|
||||||
# SettingsLogFontSize = 'logFontSize'
|
|
||||||
@property
|
@property
|
||||||
def log_font_size(self):
|
def log_font_size(self):
|
||||||
v = self.value(self.SettingsLogFontSize, 8)
|
v = self.value(self.SettingsLogFontSize)
|
||||||
if v <= 0:
|
if v <= 0:
|
||||||
v = 8
|
v = 8
|
||||||
return v
|
return v
|
||||||
|
|
||||||
@log_font_size.setter
|
@log_font_size.setter
|
||||||
def log_font_size(self, value):
|
def log_font_size(self, value):
|
||||||
self.set_value(self.SettingsLogFontSize, value)
|
self.set_value(self.SettingsLogFontSize, value)
|
||||||
|
|
||||||
# SettingsGitSupported = 'gitSupported'
|
|
||||||
@property
|
|
||||||
def git_supported(self):
|
|
||||||
r = self.value(self.SettingsGitSupported, True)
|
|
||||||
return r
|
|
||||||
|
|
||||||
@git_supported.setter
|
|
||||||
def git_supported(self, value):
|
|
||||||
self.set_value(self.SettingsGitSupported, value)
|
|
||||||
|
|
||||||
# SettingsPythonPath = 'python_bin'
|
|
||||||
@property
|
|
||||||
def python_bin(self):
|
|
||||||
r = self.value(self.SettingsPythonPath, "")
|
|
||||||
return r
|
|
||||||
|
|
||||||
@python_bin.setter
|
|
||||||
def python_bin(self, value):
|
|
||||||
self.set_value(self.SettingsPythonPath, value)
|
|
||||||
|
|
||||||
# SettingsLuaPath = 'luaPath'
|
|
||||||
@property
|
|
||||||
def lua_bin(self):
|
|
||||||
r = self.value(self.SettingsLuaPath, "")
|
|
||||||
return r
|
|
||||||
|
|
||||||
@lua_bin.setter
|
|
||||||
def lua_bin(self, value):
|
|
||||||
self.set_value(self.SettingsLuaPath, value)
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
from PySide6.QtCore import Slot, Qt
|
from collections import namedtuple
|
||||||
from PySide6.QtWidgets import QDialog, QFileDialog
|
|
||||||
|
from PySide6.QtCore import Slot
|
||||||
|
from PySide6.QtWidgets import QDialog, QFileDialog, QLabel, QLineEdit
|
||||||
from PySide6.QtGui import QFont
|
from PySide6.QtGui import QFont
|
||||||
|
|
||||||
from main_win.preference_win.preference_core_win import Ui_preferenceWindow
|
from main_win.preference_win.preference_core_win import Ui_preferenceWindow
|
||||||
@@ -8,6 +10,24 @@ from main_win import file_dialog
|
|||||||
import interpreter.utils.settings as prefs
|
import interpreter.utils.settings as prefs
|
||||||
|
|
||||||
|
|
||||||
|
def _set_font(w, v):
|
||||||
|
f = QFont()
|
||||||
|
f.fromString(v)
|
||||||
|
w.setCurrentFont(f)
|
||||||
|
|
||||||
|
|
||||||
|
# Per-type widget <-> value bridge: (read from widget, write to widget).
|
||||||
|
_FIELD = {
|
||||||
|
"bool": (lambda w: w.isChecked(), lambda w, v: w.setChecked(v)),
|
||||||
|
"text": (lambda w: w.text(), lambda w, v: w.setText(v)),
|
||||||
|
"int": (lambda w: int(w.value()), lambda w, v: w.setValue(v)),
|
||||||
|
"combo": (lambda w: int(w.currentIndex()), lambda w, v: w.setCurrentIndex(v)),
|
||||||
|
"font": (lambda w: w.currentFont().toString(), _set_font),
|
||||||
|
}
|
||||||
|
|
||||||
|
Field = namedtuple("Field", "key type widget")
|
||||||
|
|
||||||
|
|
||||||
class PrefWindow(QDialog):
|
class PrefWindow(QDialog):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@@ -17,162 +37,57 @@ class PrefWindow(QDialog):
|
|||||||
self.ui.buttonBox.accepted.connect(self.on_buttOKPressed)
|
self.ui.buttonBox.accepted.connect(self.on_buttOKPressed)
|
||||||
self.ui.buttonBox.rejected.connect(self.on_buttCancelPressed)
|
self.ui.buttonBox.rejected.connect(self.on_buttCancelPressed)
|
||||||
self.finished.connect(self.on_finishedPressed)
|
self.finished.connect(self.on_finishedPressed)
|
||||||
self.ui.butLogPath.triggered.connect(self.on_butLogPath_pressed)
|
|
||||||
self.ui.butReportPath.triggered.connect(self.on_butReportPath_pressed)
|
|
||||||
self.ui.butPythonPath.triggered.connect(self.on_butPythonPath_pressed)
|
|
||||||
self.ui.butLuaPath.triggered.connect(self.on_butLuaPath_pressed)
|
|
||||||
self.elements = {
|
|
||||||
prefs.settings.SettingsHideDocPane: {
|
|
||||||
"type": "bool",
|
|
||||||
"widget": self.ui.checkDocPane,
|
|
||||||
"value": prefs.settings.hide_doc_pane,
|
|
||||||
"default": False,
|
|
||||||
"changed": False,
|
|
||||||
},
|
|
||||||
prefs.settings.SettingsHideLogPane: {
|
|
||||||
"type": "bool",
|
|
||||||
"widget": self.ui.checkLogPane,
|
|
||||||
"value": prefs.settings.hide_log_pane,
|
|
||||||
"default": False,
|
|
||||||
"changed": False,
|
|
||||||
},
|
|
||||||
prefs.settings.SettingsShowCheckboxes: {
|
|
||||||
"type": "bool",
|
|
||||||
"widget": self.ui.checkBoxTest,
|
|
||||||
"value": prefs.settings.show_checkboxes,
|
|
||||||
"default": False,
|
|
||||||
"changed": False,
|
|
||||||
},
|
|
||||||
prefs.settings.SettingsShowTimeColumn: {
|
|
||||||
"type": "bool",
|
|
||||||
"widget": self.ui.checkShowTime,
|
|
||||||
"value": prefs.settings.show_time_column,
|
|
||||||
"default": False,
|
|
||||||
"changed": False,
|
|
||||||
},
|
|
||||||
prefs.settings.SettingsLogPath: {
|
|
||||||
"type": "text",
|
|
||||||
"widget": self.ui.editDefaultLogPath,
|
|
||||||
"value": prefs.settings.log_path,
|
|
||||||
"default": "$(test_directory)",
|
|
||||||
"changed": False,
|
|
||||||
},
|
|
||||||
prefs.settings.SettingsReportPath: {
|
|
||||||
"type": "text",
|
|
||||||
"widget": self.ui.editDefaultReportPath,
|
|
||||||
"value": prefs.settings.report_path,
|
|
||||||
"default": "$(test_directory)",
|
|
||||||
"changed": False,
|
|
||||||
},
|
|
||||||
prefs.settings.SettingsDblClickEnabled: {
|
|
||||||
"type": "bool",
|
|
||||||
"widget": self.ui.checkDblClick,
|
|
||||||
"value": prefs.settings.dbl_click_enabled,
|
|
||||||
"default": False,
|
|
||||||
"changed": False,
|
|
||||||
},
|
|
||||||
prefs.settings.SettingsIconsTheme: {
|
|
||||||
"type": "combo",
|
|
||||||
"widget": self.ui.choiceIconsTheme,
|
|
||||||
"value": prefs.settings.icons_theme,
|
|
||||||
"default": 0,
|
|
||||||
"changed": False,
|
|
||||||
},
|
|
||||||
prefs.settings.SettingsLogFont: {
|
|
||||||
"type": "font",
|
|
||||||
"widget": self.ui.font_choice,
|
|
||||||
"value": prefs.settings.log_font,
|
|
||||||
"default": "Monospace",
|
|
||||||
"changed": False,
|
|
||||||
},
|
|
||||||
prefs.settings.SettingsLogFontSize: {
|
|
||||||
"type": "int",
|
|
||||||
"widget": self.ui.font_size,
|
|
||||||
"value": prefs.settings.log_font_size,
|
|
||||||
"default": 8,
|
|
||||||
"changed": False,
|
|
||||||
},
|
|
||||||
prefs.settings.SettingsGitSupported: {
|
|
||||||
"type": "bool",
|
|
||||||
"widget": self.ui.checkGitSupported,
|
|
||||||
"value": prefs.settings.git_supported,
|
|
||||||
"default": True,
|
|
||||||
"changed": False,
|
|
||||||
},
|
|
||||||
prefs.settings.SettingsPythonPath: {
|
|
||||||
"type": "text",
|
|
||||||
"widget": self.ui.editPythonPath,
|
|
||||||
"value": prefs.settings.python_bin,
|
|
||||||
"default": "",
|
|
||||||
"changed": False,
|
|
||||||
},
|
|
||||||
prefs.settings.SettingsLuaPath: {
|
|
||||||
"type": "text",
|
|
||||||
"widget": self.ui.editLuaPath,
|
|
||||||
"value": prefs.settings.lua_bin,
|
|
||||||
"default": "",
|
|
||||||
"changed": False,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
self.ui.butLogPath.triggered.connect(
|
||||||
|
lambda: self._pick_dir(self.ui.editDefaultLogPath, "Select the default log directory"))
|
||||||
|
self.ui.butReportPath.triggered.connect(
|
||||||
|
lambda: self._pick_dir(self.ui.editDefaultReportPath, "Select the default report directory"))
|
||||||
|
self.ui.butPythonPath.triggered.connect(
|
||||||
|
lambda: self._pick_file(self.ui.editPythonPath, "Select the python interpreter"))
|
||||||
|
self.ui.butLuaPath.triggered.connect(
|
||||||
|
lambda: self._pick_file(self.ui.editLuaPath, "Select the lua interpreter"))
|
||||||
|
|
||||||
|
# Editor command field, added in code (mirrors the F1 filter approach) so the
|
||||||
|
# generated UI stays untouched. Sits with the double-click toggle it feeds.
|
||||||
|
self.editEditorCmd = QLineEdit(self.ui.scrollAreaWidgetContents)
|
||||||
|
self.editEditorCmd.setPlaceholderText("ex: code -g {file}:{line}")
|
||||||
|
self.ui.formLayout.addRow(QLabel("Open log line in editor"), self.editEditorCmd)
|
||||||
|
|
||||||
|
s = prefs.settings
|
||||||
|
self.fields = [
|
||||||
|
Field(s.SettingsHideDocPane, "bool", self.ui.checkDocPane),
|
||||||
|
Field(s.SettingsHideLogPane, "bool", self.ui.checkLogPane),
|
||||||
|
Field(s.SettingsShowCheckboxes, "bool", self.ui.checkBoxTest),
|
||||||
|
Field(s.SettingsShowTimeColumn, "bool", self.ui.checkShowTime),
|
||||||
|
Field(s.SettingsLogPath, "text", self.ui.editDefaultLogPath),
|
||||||
|
Field(s.SettingsReportPath, "text", self.ui.editDefaultReportPath),
|
||||||
|
Field(s.SettingsDblClickEnabled, "bool", self.ui.checkDblClick),
|
||||||
|
Field(s.SettingsEditorCmd, "text", self.editEditorCmd),
|
||||||
|
Field(s.SettingsIconsTheme, "combo", self.ui.choiceIconsTheme),
|
||||||
|
Field(s.SettingsLogFont, "font", self.ui.font_choice),
|
||||||
|
Field(s.SettingsLogFontSize, "int", self.ui.font_size),
|
||||||
|
Field(s.SettingsGitSupported, "bool", self.ui.checkGitSupported),
|
||||||
|
Field(s.SettingsPythonPath, "text", self.ui.editPythonPath),
|
||||||
|
Field(s.SettingsLuaPath, "text", self.ui.editLuaPath),
|
||||||
|
]
|
||||||
|
self._changed = set()
|
||||||
self.restore_prefs()
|
self.restore_prefs()
|
||||||
|
|
||||||
def store_prefs(self):
|
def store_prefs(self):
|
||||||
for k, v in self.elements.items():
|
self._changed = set()
|
||||||
self.elements[k]["changed"] = False
|
for f in self.fields:
|
||||||
if v["type"] == "bool":
|
val = _FIELD[f.type][0](f.widget)
|
||||||
val = v["widget"].isChecked()
|
if val != prefs.settings.value(f.key):
|
||||||
if self.elements[k]["value"] != val:
|
prefs.settings.set_value(f.key, val)
|
||||||
self.elements[k]["value"] = val
|
self._changed.add(f.key.name)
|
||||||
self.elements[k]["changed"] = True
|
|
||||||
|
|
||||||
if v["type"] == "text":
|
|
||||||
val = v["widget"].text()
|
|
||||||
if self.elements[k]["value"] != val:
|
|
||||||
self.elements[k]["value"] = val
|
|
||||||
self.elements[k]["changed"] = True
|
|
||||||
|
|
||||||
if v["type"] == "font":
|
|
||||||
val = v["widget"].currentFont().toString()
|
|
||||||
if self.elements[k]["value"] != val:
|
|
||||||
self.elements[k]["value"] = val
|
|
||||||
self.elements[k]["changed"] = True
|
|
||||||
|
|
||||||
if v["type"] == "int":
|
|
||||||
val = int(v["widget"].value())
|
|
||||||
if self.elements[k]["value"] != val:
|
|
||||||
self.elements[k]["value"] = val
|
|
||||||
self.elements[k]["changed"] = True
|
|
||||||
|
|
||||||
if v["type"] == "combo":
|
|
||||||
val = int(v["widget"].currentIndex())
|
|
||||||
if self.elements[k]["value"] != val:
|
|
||||||
self.elements[k]["value"] = val
|
|
||||||
self.elements[k]["changed"] = True
|
|
||||||
|
|
||||||
if self.elements[k]["changed"]:
|
|
||||||
prefs.settings.set_value(k, v["value"])
|
|
||||||
|
|
||||||
prefs.settings.sync()
|
prefs.settings.sync()
|
||||||
|
|
||||||
def restore_prefs(self):
|
def restore_prefs(self):
|
||||||
for k, v in self.elements.items():
|
for f in self.fields:
|
||||||
v["value"] = prefs.settings.value(k, v["default"])
|
_FIELD[f.type][1](f.widget, prefs.settings.value(f.key))
|
||||||
if v["type"] == "bool":
|
|
||||||
v["widget"].setChecked(v["value"])
|
|
||||||
elif v["type"] == "text":
|
|
||||||
v["widget"].setText(self.elements[k]["value"])
|
|
||||||
elif v["type"] == "font":
|
|
||||||
f = QFont()
|
|
||||||
f.fromString(self.elements[k]["value"])
|
|
||||||
v["widget"].setCurrentFont(f)
|
|
||||||
elif v["type"] == "int":
|
|
||||||
v["widget"].setValue(self.elements[k]["value"])
|
|
||||||
elif v["type"] == "combo":
|
|
||||||
v["widget"].setCurrentIndex(self.elements[k]["value"])
|
|
||||||
|
|
||||||
def isChanged(self, setting):
|
def isChanged(self, setting):
|
||||||
return self.elements[setting]["changed"]
|
return setting.name in self._changed
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def on_buttOKPressed(self):
|
def on_buttOKPressed(self):
|
||||||
@@ -188,46 +103,14 @@ class PrefWindow(QDialog):
|
|||||||
def on_finishedPressed(self):
|
def on_finishedPressed(self):
|
||||||
self.restore_prefs()
|
self.restore_prefs()
|
||||||
|
|
||||||
@Slot()
|
def _pick_dir(self, edit, caption):
|
||||||
def on_butReportPath_pressed(self):
|
|
||||||
path = QFileDialog.getExistingDirectory(
|
path = QFileDialog.getExistingDirectory(
|
||||||
self,
|
self, caption=caption, dir=edit.text(), options=file_dialog.options())
|
||||||
caption="Select the default report directory",
|
|
||||||
dir=self.ui.editDefaultReportPath.text(),
|
|
||||||
options=file_dialog.options(),
|
|
||||||
)
|
|
||||||
if path:
|
if path:
|
||||||
self.ui.editDefaultReportPath.setText(path)
|
edit.setText(path)
|
||||||
|
|
||||||
@Slot()
|
def _pick_file(self, edit, caption):
|
||||||
def on_butLogPath_pressed(self):
|
|
||||||
path = QFileDialog.getExistingDirectory(
|
|
||||||
self,
|
|
||||||
caption="Select the default log directory",
|
|
||||||
dir=self.ui.editDefaultLogPath.text(),
|
|
||||||
options=file_dialog.options(),
|
|
||||||
)
|
|
||||||
if path:
|
|
||||||
self.ui.editDefaultLogPath.setText(path)
|
|
||||||
|
|
||||||
@Slot()
|
|
||||||
def on_butPythonPath_pressed(self):
|
|
||||||
path, _ = QFileDialog.getOpenFileName(
|
path, _ = QFileDialog.getOpenFileName(
|
||||||
self,
|
self, caption=caption, dir=edit.text(), options=file_dialog.options())
|
||||||
caption="Select the python interpreter",
|
|
||||||
dir=self.ui.editPythonPath.text(),
|
|
||||||
options=file_dialog.options(),
|
|
||||||
)
|
|
||||||
if path:
|
if path:
|
||||||
self.ui.editPythonPath.setText(path)
|
edit.setText(path)
|
||||||
|
|
||||||
@Slot()
|
|
||||||
def on_butLuaPath_pressed(self):
|
|
||||||
path, _ = QFileDialog.getOpenFileName(
|
|
||||||
self,
|
|
||||||
caption="Select the lua interpreter",
|
|
||||||
dir=self.ui.editLuaPath.text(),
|
|
||||||
options=file_dialog.options(),
|
|
||||||
)
|
|
||||||
if path:
|
|
||||||
self.ui.editLuaPath.setText(path)
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from multiprocessing import Queue
|
from multiprocessing import Queue
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
@@ -745,7 +747,21 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||||||
if (self.logFileName is not None) and os.access(self.logFileName, os.R_OK):
|
if (self.logFileName is not None) and os.access(self.logFileName, os.R_OK):
|
||||||
ln = tm.line_number("@@{}@@".format(item.timestamp()), self.logFileName)
|
ln = tm.line_number("@@{}@@".format(item.timestamp()), self.logFileName)
|
||||||
if ln > 0:
|
if ln > 0:
|
||||||
os.system("{} -g {}:{} &".format("code", self.logFileName, ln + 1))
|
self._open_in_editor(self.logFileName, ln + 1)
|
||||||
|
|
||||||
|
def _open_in_editor(self, path, line):
|
||||||
|
"""Open path at line via the configured editor template ({file}/{line}).
|
||||||
|
Empty template or failure falls back to opening the file without line."""
|
||||||
|
tmpl = prefs.settings.editor_cmd
|
||||||
|
if tmpl:
|
||||||
|
try:
|
||||||
|
argv = [p.format(file=path, line=line) for p in shlex.split(tmpl)]
|
||||||
|
subprocess.Popen(bins.host_console_command(argv, os.path.dirname(path) or "."))
|
||||||
|
return
|
||||||
|
except (KeyError, ValueError, IndexError, OSError):
|
||||||
|
pass
|
||||||
|
if not bins.host_open_path(path):
|
||||||
|
QDesktopServices.openUrl(QUrl.fromLocalFile(path))
|
||||||
|
|
||||||
def on_spacePressed(self):
|
def on_spacePressed(self):
|
||||||
item = self.treeTests.currentItem()
|
item = self.treeTests.currentItem()
|
||||||
|
|||||||
Reference in New Issue
Block a user