Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into stephan-meta
Browse files Browse the repository at this point in the history
  • Loading branch information
jsheunis committed Oct 14, 2022
2 parents 6777ba1 + 27e0a04 commit c4f996d
Show file tree
Hide file tree
Showing 10 changed files with 423 additions and 130 deletions.
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,20 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<table>
<tbody>
<tr>
<td align="center"><a href="http://psychoinformatics.de"><img src="https://avatars.githubusercontent.com/u/136479?v=4?s=100" width="100px;" alt="Michael Hanke"/><br /><sub><b>Michael Hanke</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=mih" title="Code">💻</a> <a href="#ideas-mih" title="Ideas, Planning, & Feedback">🤔</a> <a href="#projectManagement-mih" title="Project Management">📆</a> <a href="#mentoring-mih" title="Mentoring">🧑‍🏫</a></td>
<td align="center"><a href="www.onerussian.com"><img src="https://avatars.githubusercontent.com/u/39889?v=4?s=100" width="100px;" alt="Yaroslav Halchenko"/><br /><sub><b>Yaroslav Halchenko</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=yarikoptic" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/effigies"><img src="https://avatars.githubusercontent.com/u/83442?v=4?s=100" width="100px;" alt="Chris Markiewicz"/><br /><sub><b>Chris Markiewicz</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=effigies" title="Code">💻</a></td>
<td align="center"><a href="www.adina-wagner.com"><img src="https://avatars.githubusercontent.com/u/29738718?v=4?s=100" width="100px;" alt="Adina Wagner"/><br /><sub><b>Adina Wagner</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=adswa" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/jwodder"><img src="https://avatars.githubusercontent.com/u/98207?v=4?s=100" width="100px;" alt="John T. Wodder II"/><br /><sub><b>John T. Wodder II</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=jwodder" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/bpoldrack"><img src="https://avatars.githubusercontent.com/u/10498301?v=4?s=100" width="100px;" alt="Benjamin Poldrack"/><br /><sub><b>Benjamin Poldrack</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=bpoldrack" title="Code">💻</a></td>
<td align="center"><a href="https://jsheunis.github.io/"><img src="https://avatars.githubusercontent.com/u/10141237?v=4?s=100" width="100px;" alt="Stephan Heunis"/><br /><sub><b>Stephan Heunis</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=jsheunis" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://psychoinformatics.de"><img src="https://avatars.githubusercontent.com/u/136479?v=4?s=100" width="100px;" alt="Michael Hanke"/><br /><sub><b>Michael Hanke</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=mih" title="Code">💻</a> <a href="#ideas-mih" title="Ideas, Planning, & Feedback">🤔</a> <a href="#projectManagement-mih" title="Project Management">📆</a> <a href="#mentoring-mih" title="Mentoring">🧑‍🏫</a></td>
<td align="center" valign="top" width="14.28%"><a href="www.onerussian.com"><img src="https://avatars.githubusercontent.com/u/39889?v=4?s=100" width="100px;" alt="Yaroslav Halchenko"/><br /><sub><b>Yaroslav Halchenko</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=yarikoptic" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/effigies"><img src="https://avatars.githubusercontent.com/u/83442?v=4?s=100" width="100px;" alt="Chris Markiewicz"/><br /><sub><b>Chris Markiewicz</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=effigies" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="www.adina-wagner.com"><img src="https://avatars.githubusercontent.com/u/29738718?v=4?s=100" width="100px;" alt="Adina Wagner"/><br /><sub><b>Adina Wagner</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=adswa" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jwodder"><img src="https://avatars.githubusercontent.com/u/98207?v=4?s=100" width="100px;" alt="John T. Wodder II"/><br /><sub><b>John T. Wodder II</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=jwodder" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bpoldrack"><img src="https://avatars.githubusercontent.com/u/10498301?v=4?s=100" width="100px;" alt="Benjamin Poldrack"/><br /><sub><b>Benjamin Poldrack</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=bpoldrack" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://jsheunis.github.io/"><img src="https://avatars.githubusercontent.com/u/10141237?v=4?s=100" width="100px;" alt="Stephan Heunis"/><br /><sub><b>Stephan Heunis</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=jsheunis" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="mslw.github.io"><img src="https://avatars.githubusercontent.com/u/11985212?v=4?s=100" width="100px;" alt="Michał Szczepanik"/><br /><sub><b>Michał Szczepanik</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=mslw" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/aqw"><img src="https://avatars.githubusercontent.com/u/765557?v=4?s=100" width="100px;" alt="Alex Waite"/><br /><sub><b>Alex Waite</b></sub></a><br /><a href="#userTesting-aqw" title="User Testing">📓</a> <a href="#ideas-aqw" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="manukapp.itch.io"><img src="https://avatars.githubusercontent.com/u/86295664?v=4?s=100" width="100px;" alt="Leonardo Muller-Rodriguez"/><br /><sub><b>Leonardo Muller-Rodriguez</b></sub></a><br /><a href="#userTesting-Manukapp" title="User Testing">📓</a> <a href="https://github.com/datalad/datalad-gooey/commits?author=Manukapp" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/loj"><img src="https://avatars.githubusercontent.com/u/15157717?v=4?s=100" width="100px;" alt="Laura Waite"/><br /><sub><b>Laura Waite</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=loj" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/christian-monch"><img src="https://avatars.githubusercontent.com/u/17925232?v=4?s=100" width="100px;" alt="Christian Mönch"/><br /><sub><b>Christian Mönch</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=christian-monch" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="mslw.github.io"><img src="https://avatars.githubusercontent.com/u/11985212?v=4?s=100" width="100px;" alt="Michał Szczepanik"/><br /><sub><b>Michał Szczepanik</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=mslw" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/aqw"><img src="https://avatars.githubusercontent.com/u/765557?v=4?s=100" width="100px;" alt="Alex Waite"/><br /><sub><b>Alex Waite</b></sub></a><br /><a href="#userTesting-aqw" title="User Testing">📓</a> <a href="#ideas-aqw" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="manukapp.itch.io"><img src="https://avatars.githubusercontent.com/u/86295664?v=4?s=100" width="100px;" alt="Leonardo Muller-Rodriguez"/><br /><sub><b>Leonardo Muller-Rodriguez</b></sub></a><br /><a href="#userTesting-Manukapp" title="User Testing">📓</a> <a href="https://github.com/datalad/datalad-gooey/commits?author=Manukapp" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/loj"><img src="https://avatars.githubusercontent.com/u/15157717?v=4?s=100" width="100px;" alt="Laura Waite"/><br /><sub><b>Laura Waite</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=loj" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/christian-monch"><img src="https://avatars.githubusercontent.com/u/17925232?v=4?s=100" width="100px;" alt="Christian Mönch"/><br /><sub><b>Christian Mönch</b></sub></a><br /><a href="https://github.com/datalad/datalad-gooey/commits?author=christian-monch" title="Code">💻</a></td>
</tr>
</tbody>
</table>
Expand Down
158 changes: 96 additions & 62 deletions datalad_gooey/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from pathlib import Path
from PySide6.QtWidgets import (
QApplication,
QMainWindow,
QMenu,
QPlainTextEdit,
QStatusBar,
Expand All @@ -23,10 +22,10 @@
Qt,
Signal,
Slot,
QEvent,
)
from PySide6.QtGui import (
QAction,
QCloseEvent,
QCursor,
QGuiApplication,
)
Expand All @@ -53,12 +52,17 @@
from .resource_provider import gooey_resources
from . import utility_actions as ua
from . import credentials as cred
from .history_widget import HistoryWidget
from .metadata_widget import MetadataWidget

lgr = logging.getLogger('datalad.ext.gooey.app')


class GooeyQMainWindow(QMainWindow):
class GooeyApp(QObject):

execute_dataladcmd = Signal(str, MappingProxyType, MappingProxyType)
configure_dataladcmd = Signal(str, MappingProxyType)

# Mapping of key widget names used in the main window to their widget
# classes. This mapping is used (and needs to be kept up-to-date) to look
# up widget (e.g. to connect their signals/slots)
Expand All @@ -69,6 +73,7 @@ class GooeyQMainWindow(QMainWindow):
'commandLogTab': QWidget,
'metadataTab': QWidget,
'metadataTabWidget': MetadataWidget,
'historyWidget': HistoryWidget,
'helpTab': QWidget,
'helpBrowser': QTextBrowser,
'propertyBrowser': QTextBrowser,
Expand All @@ -88,48 +93,6 @@ class GooeyQMainWindow(QMainWindow):
'actionDiagnostic_infos': QAction,
}

def closeEvent(self, event: QCloseEvent) -> None:
self._store_configuration()
super().closeEvent(event)

def get_widget(self, name: str) -> QWidget:
wgt_cls = self._widgets.get(name)
if not wgt_cls:
raise ValueError(f"Unknown widget {name}")
wgt = cast(QWidget, self.findChild(wgt_cls, name=name))
if not wgt:
# if this happens, our internal _widgets is out of sync
# with the UI declaration
raise RuntimeError(
f"Could not locate widget {name} ({wgt_cls.__name__})")
return wgt

def _restore_configuration(self) -> None:
# Restore prior configuration
self._qt_settings = QSettings("datalad", self.__class__.__name__)
self.restoreGeometry(self._qt_settings.value('geometry'))
self.restoreState(self._qt_settings.value('state'))

fs_browser: QTreeWidget = cast(QTreeWidget, self.get_widget('fsBrowser'))
fs_browser.restoreGeometry(self._qt_settings.value('geometry/fsBrowser'))
fs_browser.header().restoreState(
self._qt_settings.value('state/fsBrowser/header'))

def _store_configuration(self) -> None:
# Store configuration of main elements we care storing
self._qt_settings.setValue('geometry', self.saveGeometry())
self._qt_settings.setValue('state', self.saveState())

fs_browser: QTreeWidget = cast(QTreeWidget, self.get_widget('fsBrowser'))
self._qt_settings.setValue('geometry/fsBrowser', fs_browser.saveGeometry())
self._qt_settings.setValue('state/fsBrowser/header', fs_browser.header().saveState())


class GooeyApp(QObject):

execute_dataladcmd = Signal(str, MappingProxyType, MappingProxyType)
configure_dataladcmd = Signal(str, MappingProxyType)

def __init__(self, path: Path = None):
super().__init__()
# bend datalad to our needs
Expand Down Expand Up @@ -161,8 +124,8 @@ def __init__(self, path: Path = None):
self._setup_looknfeel()

self._dlapi = None
self._main_window : GooeyQMainWindow = \
load_ui('main_window', custom_widgets=[GooeyQMainWindow, MetadataWidget])
self.__main_window = None
self.__app_close_requested = False
self._cmdexec = GooeyDataladCmdExec()
self._cmdui = GooeyDataladCmdUI(self, self.get_widget('cmdTab'))

Expand Down Expand Up @@ -214,7 +177,7 @@ def __init__(self, path: Path = None):
if dlcfg.get('user.name') is None or dlcfg.get('user.email') is None:
ua.set_git_identity(self.main_window)

self._main_window._restore_configuration()
self._restore_configuration()

def _setup_menus(self):
# arrange for the dataset menu to populate itself lazily once
Expand Down Expand Up @@ -242,10 +205,6 @@ def _setup_menus(self):
self._connect_menu_view(self.get_widget('menuView'))

def _setup_ongoing_cmdexec(self, thread_id, cmdname, cmdargs, exec_params):
# bring console tab to the front
self.get_widget('consoleTabs').setCurrentWidget(
self.get_widget('commandLogTab'))

self.get_widget('statusbar').showMessage(f'Started `{cmdname}`')
self.main_window.setCursor(QCursor(Qt.BusyCursor))
# and give a persistent visual indication of what exactly is happening
Expand All @@ -254,6 +213,10 @@ def _setup_ongoing_cmdexec(self, thread_id, cmdname, cmdargs, exec_params):
# but not for internal calls
# https://github.com/datalad/datalad-gooey/issues/182
return
# bring console tab to the front
self.get_widget('consoleTabs').setCurrentWidget(
self.get_widget('commandLogTab'))

self.get_widget('commandLog').appendHtml(
f"<hr>{render_cmd_call(cmdname, cmdargs, 'Running')}"
)
Expand Down Expand Up @@ -297,21 +260,38 @@ def _setup_stopped_cmdexec(
if not self._cmdexec.n_running:
self.main_window.setCursor(QCursor(Qt.ArrowCursor))

def deinit(self):
dlui.ui.set_backend(self._prev_ui_backend)
# restore any possible term prompt setup
for var, val in self._restore_env.items():
if val is not None:
environ[var] = val
# act on any pending close request
if self.__app_close_requested:
self.__app_close_requested = False
self.main_window.close()

#@cached_property not available for PY3.7
@property
def main_window(self):
return self._main_window
if self.__main_window is None:
self.__main_window = load_ui(
'main_window',
custom_widgets=[
HistoryWidget,
MetadataWidget,
]
)
# hook into all events that the main window receives
# e.g. to catch close events and store window configuration
self.__main_window.installEventFilter(self)
return self.__main_window

def get_widget(self, name) -> QWidget:
# convenience proxy
return self._main_window.get_widget(name)
def get_widget(self, name: str) -> QWidget:
wgt_cls = self._widgets.get(name)
if not wgt_cls:
raise ValueError(f"Unknown widget {name}")
wgt = cast(QWidget, self.main_window.findChild(wgt_cls, name=name))
if not wgt:
# if this happens, our internal _widgets is out of sync
# with the UI declaration
raise RuntimeError(
f"Could not locate widget {name} ({wgt_cls.__name__})")
return wgt

def _set_root_path(self, path: Path = None):
"""Store the application root path and change PWD to it
Expand Down Expand Up @@ -479,6 +459,60 @@ def _setup_suites(self):
action.setToolTip(description)
suite_menu.addAction(action)

def _restore_configuration(self) -> None:
mw = self.main_window
# Restore prior configuration
self._qt_settings = QSettings("datalad", self.__class__.__name__)
mw.restoreGeometry(self._qt_settings.value('geometry'))
mw.restoreState(self._qt_settings.value('state'))

fs_browser: QWidget = self.get_widget('fsBrowser')
fs_browser.restoreGeometry(
self._qt_settings.value('geometry/fsBrowser'))
fs_browser.header().restoreState(
self._qt_settings.value('state/fsBrowser/header'))

def _store_configuration(self) -> None:
mw = self.main_window
# Store configuration of main elements we care storing
self._qt_settings.setValue('geometry', mw.saveGeometry())
self._qt_settings.setValue('state', mw.saveState())

fs_browser: QWidget = self.get_widget('fsBrowser')
self._qt_settings.setValue(
'geometry/fsBrowser', fs_browser.saveGeometry())
self._qt_settings.setValue(
'state/fsBrowser/header', fs_browser.header().saveState())

def eventFilter(self, watched, event):
if event.type() == QEvent.Close and watched is self.main_window:
if self._cmdexec.n_running:
# we ignore close events while exec threads are still running
# instead we set a flag to trigger another close
# event when a command exits
self.__app_close_requested = True
# prevent further commands from starting, even if already
# queued
self._cmdexec.shutdown()
self.get_widget('statusbar').showMessage(
'Shutting down, waiting for pending commands to finish...')
event.ignore()
return True
self._store_configuration()
# undo UI backend
dlui.ui.set_backend(self._prev_ui_backend)
# restore any possible term prompt setup
for var, val in self._restore_env.items():
if val is not None:
environ[var] = val
return super().eventFilter(watched, event)
elif event.type() in (QEvent.Destroy, QEvent.ChildRemoved):
# must catch this one or the access of `watched` in the `else`
# will crash the app, because it is already gone
return False
else:
return super().eventFilter(watched, event)


def main():
# must set this flag to make Qt WebEngine initialize properly
Expand Down
10 changes: 2 additions & 8 deletions datalad_gooey/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,5 @@ def get_headless_qtapp():

@pytest.fixture(scope="function")
def gooey_app(tmp_path):
try:
gooey = GooeyApp(tmp_path)
# maybe leave that to a caller?
gooey.main_window.show()
yield gooey
finally:
if gooey is not None:
gooey.deinit()
gooey = GooeyApp(tmp_path)
yield gooey
7 changes: 5 additions & 2 deletions datalad_gooey/datalad_ui.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import threading
from textwrap import wrap
from types import MappingProxyType
Expand Down Expand Up @@ -226,8 +227,10 @@ def question(self, text,
# to make sure that our signal is the only one putting an answer in
# the queue
self._uibridge.question_asked.emit(MappingProxyType(dict(
title=title,
text=text,
title="Input required",
# Note, that ui.question's `title` is meant for the prompting
# text:
text=title + os.linesep + text,
choices=choices,
default=default,
hidden=hidden,
Expand Down
Loading

0 comments on commit c4f996d

Please sign in to comment.