Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
Release 0.8.0
- disable mouse wheel scrolling for main window's viewport
- always use full C locale (instead of using just the number options)
- when re-opening the currently open config file, the gui state is now restored properly (issue 38)
- add a reload button for reloading the configuration's python modules (issue 9)
- improved copy&paste handling of file browser widget (issue 24)
- GenericReader now providses a property for the selection of the default step stream (issue 39)
  • Loading branch information
cwiede committed Jan 21, 2022
2 parents 7ad9492 + 98402b3 commit 3416523
Show file tree
Hide file tree
Showing 13 changed files with 360 additions and 31 deletions.
4 changes: 1 addition & 3 deletions nexxT/core/AppConsole.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,7 @@ def startNexT(cfgfile, active, execScripts, execCode, withGui):
"""
logger.debug("Starting nexxT...")
config = Configuration()
lcl = QLocale.system()
lcl.setNumberOptions(QLocale.c().numberOptions())
QLocale.setDefault(lcl)
QLocale.setDefault(QLocale.c())
if withGui:
app = QApplication() if QApplication.instance() is None else QApplication.instance()
app.setWindowIcon(QIcon(":icons/nexxT.svg"))
Expand Down
7 changes: 7 additions & 0 deletions nexxT/core/ConfigFiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def load(config, file):
:param config: Configuration instance to be populated
:return: dictionary with configuration contents (default values from schema are already applied)
"""
config.close()
validator, validatorGuiState = ConfigFileLoader._getValidator()
if not isinstance(file, Path):
file = Path(file)
Expand All @@ -42,6 +43,8 @@ def load(config, file):
try:
with guistateFile.open("r", encoding="utf-8") as fp:
guistate = json.load(fp)
logger.internal("loaded gui state from %s -> %s", guistateFile,
json.dumps(guistate, indent=2, ensure_ascii=False))
validatorGuiState.validate(guistate)
except Exception as e: # pylint: disable=broad-except
# catching a broad exception is exactly wanted here.
Expand Down Expand Up @@ -104,6 +107,8 @@ def save(config, file=None, forceGuiState=False):
guistateFile = file.parent / (file.name + ".guistate")
with guistateFile.open("w", encoding="utf-8") as fp:
json.dump(guistate, fp, indent=2, ensure_ascii=False)
logger.internal("saved gui state to %s -> %s", guistateFile,
json.dumps(guistate, indent=2, ensure_ascii=False))

@staticmethod
def saveGuiState(config):
Expand Down Expand Up @@ -136,6 +141,8 @@ def saveGuiState(config):
guistateFile = file.parent / (file.name + ".guistate")
with guistateFile.open("w", encoding="utf-8") as fp:
json.dump(guistate, fp, indent=2, ensure_ascii=False)
logger.internal("saving gui state to %s -> %s", guistateFile,
json.dumps(guistate, indent=2, ensure_ascii=False))

@staticmethod
def _extendWithDefault(validatorClass):
Expand Down
8 changes: 5 additions & 3 deletions nexxT/core/Configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,11 @@ def load(self, cfg):
"""
self.close()
try:
self._propertyCollection.defineProperty("CFGFILE", cfg["CFGFILE"],
"The absolute path to the configuration file.",
options=dict(enum=[cfg["CFGFILE"]]))
if cfg["CFGFILE"] is not None:
# might happen during reload
self._propertyCollection.defineProperty("CFGFILE", cfg["CFGFILE"],
"The absolute path to the configuration file.",
options=dict(enum=[cfg["CFGFILE"]]))
try:
self._propertyCollection.deleteChild("_guiState")
except PropertyCollectionChildNotFound:
Expand Down
18 changes: 16 additions & 2 deletions nexxT/filters/GenericReader.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
import time
import logging
import math
from PySide2.QtCore import Signal, QTimer
from PySide2.QtCore import Signal, QTimer, Qt
from PySide2.QtWidgets import QFileDialog
from nexxT.interface import Filter, Services, DataSample
from nexxT.core.Utils import handleException, isMainThread
from nexxT.core.Utils import handleException, isMainThread, MethodInvoker

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -266,6 +266,9 @@ def __init__(self, env):
self._dir = 1
self._ports = None
self._timeFactor = 1
pc = self.propertyCollection()
pc.defineProperty("defaultStepStream", "<all>",
"define the default step stream (the user can override it via menu)")

def onOpen(self):
"""
Expand Down Expand Up @@ -322,6 +325,17 @@ def onStart(self):
self.sequenceOpened.emit(self._name, span[0], span[1], sorted(self._portToIdx.keys()))
self.timeRatioChanged.emit(self._timeFactor)
self.playbackPaused.emit()
try:
srv = Services.getService("PlaybackControl")
except: # pylint: disable=bare-except
srv = None
pc = self.propertyCollection()
stepStream = pc.getProperty("defaultStepStream")
if stepStream not in self._portToIdx:
stepStream = None
if srv is not None and hasattr(srv, "setSelectedStream"):
MethodInvoker(srv.setSelectedStream, Qt.QueuedConnection, stepStream)


def onStop(self):
"""
Expand Down
1 change: 1 addition & 0 deletions nexxT/filters/hdf5.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ def onPortDataChanged(self, port):
# status update once each second
if (rcvTimestamp // 1000000) != (lastRcvTimestamp // 1000000):
if hasattr(os, "posix_fadvise") and self.propertyCollection().getProperty("use_posix_fadvise_if_available"):
# pylint: disable=no-member
os.posix_fadvise(self._currentFile.id.get_vfd_handle(), 0, self._currentFile.id.get_filesize(),
os.POSIX_FADV_DONTNEED)
self.statusUpdate.emit(self._name, rcvTimestamp*1e-6, self._currentFile.id.get_filesize())
Expand Down
54 changes: 54 additions & 0 deletions nexxT/services/SrvConfiguration.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from nexxT.core.Application import Application
from nexxT.core.CompositeFilter import CompositeFilter
from nexxT.core.Utils import assertMainThread, handleException, mainThread, MethodInvoker
from nexxT.interface.Filters import FilterState
from nexxT.interface import Services

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -620,6 +622,7 @@ def __init__(self, configuration):
self._configuration = configuration

self.cfgfile = None
self._reloadToState = None

self.model = ConfigurationModel(configuration, self)
configuration.appActivated.connect(self.appActivated)
Expand Down Expand Up @@ -670,12 +673,63 @@ def loadConfig(self, cfgFileName):
:param cfgFileName: the filename of the configuration
:return:
"""
if (Application.activeApplication is not None and
Application.activeApplication.getState() != FilterState.CONSTRUCTED):
# need to de-initialize application first
Application.deInitialize()
MethodInvoker(dict(object=self, method="loadConfig", thread=mainThread()), Qt.QueuedConnection, cfgFileName)
return
@handleException
def execute():
assertMainThread()
ConfigFileLoader.load(self._configuration, cfgFileName)
execute()

def reload(self):
"""
Reloads all python modules of the current configuration.
Similar to close(), open() and loading the currently actuve sequence.
"""
if Application.activeApplication is not None:
try:
pbc = Services.getService("PlaybackControl")
except: # pylint: disable=bare-except
pbc = None
if pbc is not None:
seq = pbc.getSequence()
else:
seq = None
self._reloadToState = dict(name=Application.activeApplication.getApplication().getName(),
state=Application.activeApplication.getState(),
seq=seq)
else:
self._reloadToState = None
self._reload()

def _reload(self):
if (Application.activeApplication is not None and
Application.activeApplication.getState() != FilterState.CONSTRUCTED):
# need to de-initialize application first
Application.deInitialize()
MethodInvoker(dict(object=self, method="_reload", thread=mainThread()), Qt.QueuedConnection)
return
oldDirty = self._configuration.dirty()
state = self._configuration.save()
self._configuration.close(avoidSave=True)
self._configuration.load(state)
self._configuration.setDirty(oldDirty)
if self._reloadToState is not None:
self._configuration.activate(self._reloadToState["name"])
assert Application.activeApplication.getState() == FilterState.CONSTRUCTED
if self._reloadToState["state"] != FilterState.CONSTRUCTED:
Application.initialize()
seq = self._reloadToState["seq"]
if seq is not None:
pbc = Services.getService("PlaybackControl")
pbc.setSequence(seq)
self._reloadToState = None

@Slot()
def saveConfig(self):
"""
Expand Down
8 changes: 8 additions & 0 deletions nexxT/services/SrvPlaybackControl.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ def __init__(self, config): # pylint: disable=unused-argument
super().__init__()
self._playing = False
self._appConn = None
self._currentSequence = None

def startPlayback(self):
"""
Expand Down Expand Up @@ -458,6 +459,12 @@ def setSequence(self, file):
"""
self._setSequence.emit(file)

def getSequence(self):
"""
Returns the currently active sequence.
"""
return self._currentSequence

def setTimeFactor(self, factor):
"""
Set the time factor to be used.
Expand Down Expand Up @@ -489,6 +496,7 @@ def _sequenceOpened(self, filename, begin, end, streams):
:return: None
"""
self.sequenceOpened.emit(filename, begin, end, streams)
self._currentSequence = filename

def _currentTimestampChanged(self, currentTime):
"""
Expand Down
22 changes: 19 additions & 3 deletions nexxT/services/gui/BrowserWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import platform
import string
from PySide2.QtCore import (QAbstractTableModel, Qt, Signal, QModelIndex, QDateTime, QFileInfo, QDir, QEvent)
from PySide2.QtGui import QKeyEvent
from PySide2.QtGui import QKeyEvent, QKeySequence
from PySide2.QtWidgets import (QWidget, QVBoxLayout, QTreeView, QFileIconProvider, QCompleter, QLineEdit, QHeaderView)

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -201,6 +201,8 @@ class TabCompletionLineEdit(QLineEdit):
"""
This class provides a line edit which changes the tab-key semantics to interact with a completer.
"""
pasted = Signal()

def __init__(self, completer, parent=None):
super().__init__(parent)
self._compl = completer
Expand Down Expand Up @@ -246,6 +248,12 @@ def event(self, event):
event = QKeyEvent(event.type(), event.key(), event.modifiers(), event.text())
return super().event(event)

def keyPressEvent(self, event):
ret = super().keyPressEvent(event)
if event.matches(QKeySequence.Paste):
self.pasted.emit()
return ret

class BrowserWidget(QWidget):
"""
This class puts together a TabCompletionLineEdit and a list view of teh FolderListModel in one single widget.
Expand Down Expand Up @@ -276,6 +284,7 @@ def __init__(self, parent=None):
self._view.activated.connect(self._activated)
self.activated.connect(self._lineedit.setText)
self._lineedit.returnPressed.connect(self._leActivated, Qt.QueuedConnection)
self._lineedit.pasted.connect(self._syncBrowserToLE)
self._lineedit.textEdited.connect(self._leTextEdited)

def setActive(self, activeFile):
Expand Down Expand Up @@ -356,6 +365,14 @@ def _leTextEdited(self, text):
if p.is_dir() and len(text) > 0 and text[-1] in ["/", "\\"]:
self.setFolder(p)

def _syncBrowserToLE(self):
p = Path(self._lineedit.text())
if p.is_dir():
self.setFolder(p)
elif p.is_file() and p.parent.is_dir():
self.setFolder(p.parent)
self.setActive(p)

def _activated(self, idx):
c = self._model.data(idx, Qt.UserRole)
if c is None:
Expand All @@ -373,9 +390,8 @@ def main():
:return:
"""
# pylint: disable-import-outside-toplevel
# this is just the test function part
from PySide2.QtWidgets import QApplication
from PySide2.QtWidgets import QApplication # pylint: disable=import-outside-toplevel

app = QApplication()
bw = BrowserWidget()
Expand Down
44 changes: 28 additions & 16 deletions nexxT/services/gui/Configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ def __init__(self, configuration):
"New config", self)
self.actNew.triggered.connect(self._execNew)

self.actReload = QAction(QIcon.fromTheme("browser-reload", style.standardIcon(QStyle.SP_BrowserReload)),
"Reload python", self)
self.actReload.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_P))
self.actReload.triggered.connect(self._execReload)

self.actActivate = QAction(QIcon.fromTheme("arrow-up", style.standardIcon(QStyle.SP_ArrowUp)),
"Initialize", self)
self.actActivate.triggered.connect(self.activate)
Expand All @@ -67,11 +72,13 @@ def __init__(self, configuration):
confMenu.addAction(self.actLoad)
confMenu.addAction(self.actSave)
confMenu.addAction(self.actSaveWithGuiState)
confMenu.addAction(self.actReload)
confMenu.addAction(self.actNew)
confMenu.addAction(self.actActivate)
confMenu.addAction(self.actDeactivate)
toolBar.addAction(self.actLoad)
toolBar.addAction(self.actSave)
toolBar.addAction(self.actReload)
toolBar.addAction(self.actNew)
toolBar.addAction(self.actActivate)
toolBar.addAction(self.actDeactivate)
Expand Down Expand Up @@ -172,6 +179,9 @@ def _execNew(self):
logger.debug("Creating config file %s", fn)
self.newConfig(fn)

def _execReload(self):
self.reload()

def _execSaveConfig(self):
if self.configuration().filename() is None:
self._execSaveConfigAs()
Expand Down Expand Up @@ -224,9 +234,10 @@ def _addGraphView(self, subConfig):
def _subConfigRemoved(self, subConfigName, configType):
g = self._configuration.subConfigByNameAndTye(subConfigName, configType).getGraph()
for gv in self._graphViews:
if gv.widget().scene().graph == g:
logger.debug("deleting graph view for subconfig %s", subConfigName)
gv.deleteLater()
if shiboken2.isValid(gv): # pylint: disable=no-member
if gv.widget().scene().graph == g:
logger.debug("deleting graph view for subconfig %s", subConfigName)
gv.deleteLater()

def _removeGraphViewFromList(self, visible):
if visible:
Expand Down Expand Up @@ -297,19 +308,20 @@ def _configNameChanged(self, cfgfile):
assertMainThread()
self.cfgfile = cfgfile
self._dirtyChanged(self._configuration.dirty())
foundIdx = None
for i, a in enumerate(self.recentConfigs):
if a.data() == cfgfile:
foundIdx = i
if foundIdx is None:
foundIdx = len(self.recentConfigs)-1
for i in range(foundIdx, 0, -1):
self.recentConfigs[i].setText(self.recentConfigs[i-1].text())
self.recentConfigs[i].setData(self.recentConfigs[i-1].data())
self.recentConfigs[i].setVisible(self.recentConfigs[i-1].data() is not None)
self.recentConfigs[0].setText(cfgfile)
self.recentConfigs[0].setData(cfgfile)
self.recentConfigs[0].setVisible(True)
if cfgfile is not None:
foundIdx = None
for i, a in enumerate(self.recentConfigs):
if a.data() == cfgfile:
foundIdx = i
if foundIdx is None:
foundIdx = len(self.recentConfigs)-1
for i in range(foundIdx, 0, -1):
self.recentConfigs[i].setText(self.recentConfigs[i-1].text())
self.recentConfigs[i].setData(self.recentConfigs[i-1].data())
self.recentConfigs[i].setVisible(self.recentConfigs[i-1].data() is not None)
self.recentConfigs[0].setText(cfgfile)
self.recentConfigs[0].setData(cfgfile)
self.recentConfigs[0].setVisible(True)

def _dirtyChanged(self, dirty):
srv = Services.getService("MainWindow")
Expand Down
9 changes: 8 additions & 1 deletion nexxT/services/gui/MainWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from PySide2.QtWidgets import (QMainWindow, QMdiArea, QMdiSubWindow, QDockWidget, QAction, QWidget, QGridLayout,
QMenuBar, QMessageBox, QScrollArea, QLabel)
from PySide2.QtCore import (QObject, Signal, Slot, Qt, QByteArray, QDataStream, QIODevice, QRect, QPoint, QSettings,
QTimer, QUrl)
QTimer, QUrl, QEvent)
from PySide2.QtGui import QDesktopServices
import nexxT
from nexxT.interface import Filter
Expand Down Expand Up @@ -171,6 +171,7 @@ def __init__(self, config):
self.mdi = QMdiArea(self)
self.mdi.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.mdi.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.mdi.viewport().installEventFilter(self)
self.setCentralWidget(self.mdi)
self.menu = self.menuBar().addMenu("&Windows")
self.aboutMenu = QMenuBar(self.menuBar())
Expand Down Expand Up @@ -200,6 +201,12 @@ def __init__(self, config):
self.activeApp = None
self._ignoreCloseEvent = False

def eventFilter(self, obj, event):
if obj is self.mdi.viewport() and event.type() == QEvent.Wheel:
# disable mouse wheel scrolling on the viewport widget it's pretty annoying...
return True
return False

def closeEvent(self, closeEvent):
"""
Override from QMainWindow, saves the state.
Expand Down
Loading

0 comments on commit 3416523

Please sign in to comment.