Skip to content

Commit 9de9068

Browse files
authored
Adds overlay system for the non modal frameless dialogs (#173)
1 parent 4d98408 commit 9de9068

File tree

11 files changed

+176
-57
lines changed

11 files changed

+176
-57
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
conda activate sscanss
3434
conda install -c conda-forge libstdcxx-ng
3535
sudo apt-get update
36-
sudo apt-get install xvfb libqt5x11extras5 libgl1-mesa-glx '^libxcb.*-dev'
36+
sudo apt-get install xvfb libqt5x11extras5 libgl1 libglx-mesa0 '^libxcb.*-dev'
3737
xvfb-run --auto-servernum python make.py --build-all
3838
- name: Run unit-tests (Windows)
3939
if: runner.os == 'Windows'

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ NLopt==2.7.1
88
numpy==1.23.5
99
gimpact==1.0.1
1010
Pillow==9.2.0
11-
PyInstaller==6.7.0
11+
PyInstaller==6.11.1
1212
PyOpenGL==3.1.6
1313
PyQt6==6.3.1
1414
PyQt6-Qt6==6.5.2

sscanss/app/dialogs/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from .misc import (ProgressDialog, ProjectDialog, AlignmentErrorDialog, SimulationDialog, ScriptExportDialog,
2-
PathLengthPlotter, AboutDialog, CalibrationErrorDialog, CurveEditor, InstrumentCoordinatesDialog)
1+
from .misc import (ProgressDialog, ProjectWidget, AlignmentErrorDialog, SimulationDialog, ScriptExportDialog,
2+
PathLengthPlotter, AboutWidget, CalibrationErrorDialog, CurveEditor, InstrumentCoordinatesDialog)
33
from .preferences import Preferences
44
from .insert import (InsertPrimitiveDialog, InsertPointDialog, InsertVectorDialog, PickPointDialog, AlignSample,
55
VolumeLoader)

sscanss/app/dialogs/misc.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from sscanss.app.widgets import AlignmentErrorModel, ErrorDetailModel, CenteredBoxProxy
1717

1818

19-
class AboutDialog(QtWidgets.QDialog):
19+
class AboutWidget(QtWidgets.QWidget):
2020
"""Creates a UI that displays information about the software
2121
2222
:param parent: main window instance
@@ -25,14 +25,14 @@ class AboutDialog(QtWidgets.QDialog):
2525
def __init__(self, parent):
2626
super().__init__(parent)
2727

28+
self.setObjectName('FramelessDialog')
2829
self.main_layout = QtWidgets.QVBoxLayout()
2930
self.setLayout(self.main_layout)
30-
self.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint | QtCore.Qt.WindowType.Dialog)
3131
self.setMinimumSize(640, 560)
3232
self.main_layout.setContentsMargins(1, 1, 1, 1)
3333

3434
header = ImageHeader()
35-
header.close_button.clicked.connect(self.reject)
35+
header.close_button.clicked.connect(self.close)
3636
self.main_layout.addWidget(header)
3737

3838
layout = QtWidgets.QVBoxLayout()
@@ -72,8 +72,16 @@ def __init__(self, parent):
7272
label.setWordWrap(True)
7373
layout.addWidget(label)
7474

75+
def paintEvent(self, event):
76+
opt = QtWidgets.QStyleOption()
77+
opt.initFrom(self)
78+
p = QtGui.QPainter(self)
79+
self.style().drawPrimitive(QtWidgets.QStyle.PrimitiveElement.PE_Widget, opt, p, self)
80+
81+
super().paintEvent(event)
82+
7583

76-
class ProjectDialog(QtWidgets.QDialog):
84+
class ProjectWidget(QtWidgets.QWidget):
7785
"""Creates a UI for creating and loading projects
7886
7987
:param parent: main window instance
@@ -86,6 +94,7 @@ class ProjectDialog(QtWidgets.QDialog):
8694
def __init__(self, parent):
8795
super().__init__(parent)
8896

97+
self.setObjectName('FramelessDialog')
8998
self._busy = False
9099
self.parent = parent
91100
self.instruments = sorted(parent.presenter.model.instruments.keys())
@@ -94,13 +103,12 @@ def __init__(self, parent):
94103

95104
self.main_layout = QtWidgets.QVBoxLayout()
96105
self.setLayout(self.main_layout)
97-
self.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint | QtCore.Qt.WindowType.Dialog)
98106
self.setMinimumSize(640, 500)
99107
self.main_layout.setContentsMargins(1, 1, 1, 1)
100108

101-
header = ImageHeader()
102-
header.close_button.clicked.connect(self.reject)
103-
self.main_layout.addWidget(header)
109+
self.header = ImageHeader()
110+
self.header.close_button.clicked.connect(self.reject)
111+
self.main_layout.addWidget(self.header)
104112

105113
self.createTabWidgets()
106114
self.createNewProjectWidgets()
@@ -117,7 +125,10 @@ def is_busy(self):
117125
@is_busy.setter
118126
def is_busy(self, value):
119127
self._busy = value
120-
self.setModal(value)
128+
self.parent.updateMenus(not value and self.parent.presenter.model.project_data is not None)
129+
self.parent.docks.upper_dock.setDisabled(value)
130+
self.parent.docks.bottom_dock.setDisabled(value)
131+
self.header.close_button.setVisible(not value)
121132
self.loading_bar.setMaximum(0 if value else 1)
122133

123134
def createTabWidgets(self):
@@ -244,20 +255,24 @@ def onFailure(self, exception, args):
244255
self.parent.presenter.projectCreationError(exception, args)
245256
self.is_busy = False
246257

247-
def keyPressEvent(self, event):
248-
"""This ensures the user cannot close the dialog box with the Esc key"""
249-
if not self.is_busy:
250-
super().keyPressEvent(event)
258+
def paintEvent(self, event):
259+
opt = QtWidgets.QStyleOption()
260+
opt.initFrom(self)
261+
p = QtGui.QPainter(self)
262+
self.style().drawPrimitive(QtWidgets.QStyle.PrimitiveElement.PE_Widget, opt, p, self)
263+
264+
super().paintEvent(event)
251265

252266
def accept(self):
253267
self.project_name_textbox.clear()
254268
self.is_busy = False
255-
super().accept()
269+
self.close()
256270

257271
def reject(self):
258272
if self.parent.presenter.model.project_data is None:
259273
self.parent.instrument_label.setText("Most menu options are disabled until you create/open a project.")
260-
super().reject()
274+
self.is_busy = False
275+
self.close()
261276

262277

263278
class ProgressDialog(QtWidgets.QDialog):

sscanss/app/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def ui_execute():
3030
msg = f'{filename} could not be opened because it has an unknown file type'
3131
QtCore.QTimer.singleShot(wait_time, lambda: window.showMessage(msg))
3232
else:
33-
QtCore.QTimer.singleShot(wait_time, window.showNewProjectDialog)
33+
QtCore.QTimer.singleShot(wait_time, window.showNewProjectWidget)
3434

3535
window.show()
3636
window.updater.check(True)

sscanss/app/window/presenter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def updateView(self):
9696

9797
self.view.docks.closeAll()
9898
self.view.closeNonModalDialog()
99-
self.view.updateMenus()
99+
self.view.updateMenus(True)
100100

101101
def projectCreationError(self, exception, args):
102102
"""Handles errors from project creation or instrument change
@@ -109,7 +109,7 @@ def projectCreationError(self, exception, args):
109109
self.view.docks.closeAll()
110110
if self.model.project_data is None or self.model.instrument is None:
111111
self.model.project_data = None
112-
self.view.updateMenus()
112+
self.view.updateMenus(False)
113113
self.view.clearUndoStack()
114114
else:
115115
toggle_action_in_group(self.model.instrument.name, self.view.change_instrument_action_group)

sscanss/app/window/view.py

Lines changed: 85 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
from sscanss.__version import __version__, Version
1111
from sscanss.config import settings, DOCS_URL, UPDATE_URL, RELEASES_URL
1212
from sscanss.themes import ThemeManager, path_for, IconEngine
13-
from sscanss.app.dialogs import (ProgressDialog, ProjectDialog, Preferences, AlignmentErrorDialog, ScriptExportDialog,
14-
PathLengthPlotter, AboutDialog, CalibrationErrorDialog, InstrumentCoordinatesDialog,
13+
from sscanss.app.dialogs import (ProgressDialog, ProjectWidget, Preferences, AlignmentErrorDialog, ScriptExportDialog,
14+
PathLengthPlotter, AboutWidget, CalibrationErrorDialog, InstrumentCoordinatesDialog,
1515
CurveEditor, VolumeLoader)
1616
from sscanss.core.scene import Node, OpenGLRenderer, SceneManager
1717
from sscanss.core.util import (Primitives, Directions, TransformType, PointType, MessageType, Attributes,
@@ -37,6 +37,69 @@ def __call__(self):
3737
return self.view.presenter.openProject(self.filename)
3838

3939

40+
class OverlayContainer(QtWidgets.QWidget):
41+
"""A container that allow a widget to be overlaid over a background widget
42+
43+
:param background: background widget
44+
:type background: QWidget
45+
:param parent: instance of main window
46+
:type parent: MainWindow
47+
"""
48+
def __init__(self, background, parent):
49+
super().__init__(parent)
50+
51+
self.widget = None
52+
self.main_layout = QtWidgets.QStackedLayout()
53+
self.main_layout.setStackingMode(QtWidgets.QStackedLayout.StackingMode.StackAll)
54+
self.setLayout(self.main_layout)
55+
56+
self.main_layout.addWidget(background)
57+
overlay_widget = QtWidgets.QWidget()
58+
overlay_widget.setObjectName('overlay')
59+
overlay_widget.setStyleSheet('#overlay {background: transparent;}')
60+
61+
layout = QtWidgets.QHBoxLayout()
62+
self.overlay_layout = QtWidgets.QVBoxLayout()
63+
layout.addStretch(1)
64+
layout.addLayout(self.overlay_layout)
65+
layout.addStretch(1)
66+
overlay_widget.setLayout(layout)
67+
self.main_layout.addWidget(overlay_widget)
68+
69+
def setOverlayWidget(self, widget):
70+
"""Sets and shows the overlay widget
71+
72+
:param widget: widget to overlay over background
73+
:type widget: QWidget
74+
"""
75+
self.closeOverlayWidget()
76+
self.widget = widget
77+
self.overlay_layout.addStretch(1)
78+
self.overlay_layout.addWidget(widget)
79+
self.overlay_layout.addStretch(1)
80+
self.main_layout.setCurrentIndex(1)
81+
82+
widget.installEventFilter(self)
83+
84+
def eventFilter(self, _obj, event):
85+
"""Catch close event for overlay widget"""
86+
if event.type() == QtCore.QEvent.Type.Close:
87+
self.closeOverlayWidget()
88+
return True
89+
return False
90+
91+
def closeOverlayWidget(self):
92+
"""Closes the overlay widget and show background"""
93+
if self.widget is None:
94+
return
95+
96+
self.main_layout.setCurrentIndex(0)
97+
for i in reversed(range(self.overlay_layout.count())):
98+
self.overlay_layout.takeAt(i)
99+
self.widget.hide()
100+
self.widget = None
101+
102+
40103
class MainWindow(QtWidgets.QMainWindow):
41104
"""Creates the main view for the sscanss app"""
42105
def __init__(self):
@@ -54,9 +117,11 @@ def __init__(self):
54117
self.themes = ThemeManager(self)
55118
self.themes.theme_changed.connect(self.setStyleSheet)
56119
self.themes.theme_changed.connect(self.updateImages)
120+
57121
self.gl_widget = OpenGLRenderer(self)
58122
self.gl_widget.custom_error_handler = self.sceneSizeErrorHandler
59-
self.setCentralWidget(self.gl_widget)
123+
self.overlay = OverlayContainer(self.gl_widget, self)
124+
self.setCentralWidget(self.overlay)
60125

61126
self.docks = DockManager(self)
62127
self.scenes = SceneManager(self.presenter.model, self.gl_widget)
@@ -76,21 +141,21 @@ def __init__(self):
76141
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
77142

78143
self.readSettings()
79-
self.updateMenus()
144+
self.updateMenus(False)
80145

81146
def createActions(self):
82147
"""Creates the menu and toolbar actions """
83148
self.new_project_action = QtGui.QAction('&New Project', self)
84149
self.new_project_action.setStatusTip('Create a new project')
85150
self.new_project_action.setIcon(QtGui.QIcon(IconEngine('file.png')))
86151
self.new_project_action.setShortcut(QtGui.QKeySequence.StandardKey.New)
87-
self.new_project_action.triggered.connect(lambda: self.presenter.confirmSave(self.showNewProjectDialog))
152+
self.new_project_action.triggered.connect(lambda: self.presenter.confirmSave(self.showNewProjectWidget))
88153

89154
self.open_project_action = QtGui.QAction('&Open Project', self)
90155
self.open_project_action.setStatusTip('Open an existing project')
91156
self.open_project_action.setIcon(QtGui.QIcon(IconEngine('folder-open.png')))
92157
self.open_project_action.setShortcut(QtGui.QKeySequence.StandardKey.Open)
93-
self.open_project_action.triggered.connect(lambda: self.presenter.confirmSave(self.presenter.openProject()))
158+
self.open_project_action.triggered.connect(lambda: self.presenter.confirmSave(self.presenter.openProject))
94159

95160
self.save_project_action = QtGui.QAction('&Save Project', self)
96161
self.save_project_action.setStatusTip('Save project')
@@ -378,7 +443,7 @@ def createActions(self):
378443

379444
self.show_about_action = QtGui.QAction(f'&About {MAIN_WINDOW_TITLE}', self)
380445
self.show_about_action.setStatusTip(f'About {MAIN_WINDOW_TITLE}')
381-
self.show_about_action.triggered.connect(self.showAboutDialog)
446+
self.show_about_action.triggered.connect(self.showAboutWidget)
382447

383448
# ToolBar Actions
384449
self.rotate_sample_action = QtGui.QAction('Rotate Sample', self)
@@ -422,10 +487,11 @@ def createActions(self):
422487
self.show_curve_editor_action.setIcon(QtGui.QIcon(IconEngine('curve.png')))
423488
self.show_curve_editor_action.triggered.connect(self.showCurveEditor)
424489

425-
def showAboutDialog(self):
490+
def showAboutWidget(self):
426491
"""Display the About Dialog"""
427-
self.createNonModalDialog(AboutDialog)
428-
self.non_modal_dialog.show()
492+
self.closeNonModalDialog()
493+
about_dialog = AboutWidget(self)
494+
self.overlay.setOverlayWidget(about_dialog)
429495

430496
def updateImages(self):
431497
"""Updates the images of the actions on the menu"""
@@ -579,10 +645,8 @@ def createMenus(self):
579645
help_menu.addAction(self.check_update_action)
580646
help_menu.addAction(self.show_about_action)
581647

582-
def updateMenus(self):
648+
def updateMenus(self, enable):
583649
"""Disables the menus when a project is not created and enables menus when a project is created"""
584-
enable = self.presenter.model.project_data is not None
585-
586650
self.save_project_action.setEnabled(enable)
587651
self.save_as_action.setEnabled(enable)
588652
for action in self.export_menu.actions():
@@ -706,6 +770,7 @@ def createStatusBar(self):
706770
sb.addPermanentWidget(self.size_label)
707771

708772
def createNonModalDialog(self, dialog_type):
773+
self.overlay.closeOverlayWidget()
709774
if not isinstance(self.non_modal_dialog, dialog_type):
710775
self.closeNonModalDialog()
711776
dialog = dialog_type(self)
@@ -716,6 +781,7 @@ def createNonModalDialog(self, dialog_type):
716781
def closeNonModalDialog(self):
717782
if self.non_modal_dialog is not None:
718783
self.non_modal_dialog.close()
784+
self.non_modal_dialog = None
719785

720786
def clearUndoStack(self):
721787
"""Clears the undo stack and ensures stack is cleaned even when stack is empty"""
@@ -859,11 +925,12 @@ def __createChangeCollimatorAction(self, name, active, detector):
859925

860926
return change_collimator_action
861927

862-
def showNewProjectDialog(self):
863-
"""Opens the new project dialog"""
864-
self.createNonModalDialog(ProjectDialog)
865-
self.non_modal_dialog.updateRecentProjects(self.recent_projects)
866-
self.non_modal_dialog.show()
928+
def showNewProjectWidget(self):
929+
"""Opens the new project widget"""
930+
self.closeNonModalDialog()
931+
project_dialog = ProjectWidget(self)
932+
project_dialog.updateRecentProjects(self.recent_projects)
933+
self.overlay.setOverlayWidget(project_dialog)
867934

868935
def showPreferences(self, group=None):
869936
"""Opens the preferences dialog"""

sscanss/static/dark_theme.css

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ QDialog
2929
background-color: rgb(52, 49, 49);
3030
}
3131

32+
#FramelessDialog{
33+
border: 1px solid rgb(70, 70, 70);
34+
color: rgb(255, 255, 255);
35+
background-color: rgb(52, 49, 49);
36+
}
3237

3338
QListWidget
3439
{
@@ -601,7 +606,7 @@ QProgressBar::chunk
601606
width: 3px;
602607
margin: 1px;
603608
}
604-
ProjectDialog QProgressBar
609+
ProjectWidget QProgressBar
605610
{
606611
border: transparent;
607612
border-radius: 0px;

sscanss/static/mac_style.css

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@
2020
QDialog{
2121
border: 1px solid #ddd;
2222
}
23-
23+
24+
#FramelessDialog{
25+
border: 1px solid #ddd;
26+
background-color: #eee;
27+
}
2428
QToolBar {
2529
spacing: 10px; /* spacing between items in the tool bar */
2630
padding: 5px;
@@ -258,7 +262,7 @@ QComboBox {
258262
image: url(@Path/splitter.png);
259263
}
260264

261-
ProjectDialog QProgressBar {
265+
ProjectWidget QProgressBar {
262266
border: transparent;
263267
border-radius: 0px;
264268
background-color: transparent;

0 commit comments

Comments
 (0)