Skip to content

Commit cb48307

Browse files
committed
Add custom tile layout and adjust plot padding
1 parent 99510be commit cb48307

7 files changed

Lines changed: 172 additions & 72 deletions

File tree

pyproject.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,23 @@ extend-ignore-names = ['allKeys',
5151
'closeEvent',
5252
'columnCount',
5353
'createEditor',
54+
'expandingDirections',
5455
'eventFilter',
56+
'hasHeightForWidth',
5557
'headerData',
58+
'heightForWidth',
5659
'isClean',
60+
'itemAt',
5761
'mergeWith',
62+
'minimumSize',
5863
'mouseDoubleClickEvent',
5964
'paintEvent',
6065
'resizeEvent',
6166
'rowCount',
6267
'setClean',
6368
'setData',
6469
'setEditorData',
70+
'setGeometry',
6571
'setModel',
6672
'setModelData',
6773
'setText',
@@ -70,6 +76,7 @@ extend-ignore-names = ['allKeys',
7076
'sizeHint',
7177
'stepBy',
7278
'supportedDropActions',
79+
'takeAt',
7380
'textFromValue',
7481
'timerEvent',
7582
'valueFromText',]

rascal2/ui/view.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def __init__(self):
4747
self.create_toolbar()
4848
self.create_status_bar()
4949

50-
self.setMinimumSize(1024, 800)
50+
self.setMinimumSize(1360, 800)
5151
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
5252

5353
self.settings = Settings()
@@ -182,7 +182,7 @@ def create_actions(self):
182182
self.tile_windows_action = QtGui.QAction("Tile Windows", self)
183183
self.tile_windows_action.setStatusTip("Arrange windows in the default grid.")
184184
self.tile_windows_action.setIcon(QtGui.QIcon(path_for("tile.png")))
185-
self.tile_windows_action.triggered.connect(self.mdi.tileSubWindows)
185+
self.tile_windows_action.triggered.connect(self.custom_tile_layout)
186186
self.tile_windows_action.setEnabled(False)
187187
self.disabled_elements.append(self.tile_windows_action)
188188

@@ -338,7 +338,7 @@ def reset_mdi_layout(self):
338338
if mdi_defaults is None:
339339
for window in self.mdi.subWindowList():
340340
window.showNormal()
341-
self.mdi.tileSubWindows()
341+
self.custom_tile_layout()
342342
else:
343343
for window in self.mdi.subWindowList():
344344
# get corresponding MDIGeometries entry for the widget
@@ -364,6 +364,24 @@ def save_mdi_layout(self):
364364
global_setting.setValue("window_geometry", self.saveGeometry())
365365
global_setting.sync()
366366

367+
def custom_tile_layout(self):
368+
"""Tile the MDI windows using user recommended sizes."""
369+
# The percentages are estimated from provided screenshot in
370+
# https://github.com/RascalSoftware/RasCAL-2/issues/188
371+
rect = self.centralWidget().contentsRect()
372+
plot_width = round(0.6 * rect.width())
373+
plot_height = round(0.65 * rect.height())
374+
project_width = rect.width() - plot_width
375+
project_height = round(0.5 * rect.height())
376+
377+
plot_geom = (0, 0, plot_width, plot_height)
378+
project_geom = (plot_width, 0, project_width, project_height)
379+
terminal_geom = (0, plot_height, plot_width, rect.height() - plot_height)
380+
controls_geom = (plot_width, project_height, project_width, rect.height() - project_height)
381+
geoms = [controls_geom, terminal_geom, project_geom, plot_geom] # windows in reverse order of creation
382+
for geom, windows in zip(geoms, self.mdi.subWindowList(), strict=False):
383+
windows.setGeometry(*geom)
384+
367385
def enable_elements(self):
368386
"""Enable the elements that are disabled on startup."""
369387
for element in self.disabled_elements:

rascal2/widgets/controls.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ def __init__(self, parent):
7676

7777
widget_layout = QtWidgets.QHBoxLayout()
7878
widget_layout.addStretch(1)
79-
widget_layout.addLayout(procedure_box, 4)
79+
widget_layout.addLayout(procedure_box, 8)
8080
widget_layout.addSpacing(20)
81-
widget_layout.addWidget(self.fit_settings, 4)
81+
widget_layout.addWidget(self.fit_settings, 12)
8282
widget_layout.addStretch(1)
8383
self.setLayout(widget_layout)
8484

rascal2/widgets/plot.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ def __init__(self, parent):
215215
sub_layout.addWidget(slider)
216216
sub_layout.addStretch(1)
217217
plot_toolbar.addLayout(sub_layout)
218-
plot_toolbar.addSpacing(15)
218+
plot_toolbar.addSpacing(5)
219219

220220
sidebar = QtWidgets.QHBoxLayout()
221221
sidebar.addWidget(self.plot_controls)
@@ -237,6 +237,7 @@ def __init__(self, parent):
237237
scroll_area.setWidgetResizable(True)
238238

239239
central_layout = QtWidgets.QVBoxLayout()
240+
central_layout.setSpacing(0)
240241
central_layout.setContentsMargins(0, 0, 0, 0)
241242
self.interaction_layout = self.make_interaction_layout()
242243
if self.interaction_layout is not None:
@@ -399,6 +400,7 @@ def make_figure(self) -> matplotlib.figure.Figure:
399400
self.resize_timer = 0
400401
figure = matplotlib.figure.Figure()
401402
figure.subplots(1, 2)
403+
figure.set_tight_layout(True)
402404

403405
return figure
404406

@@ -465,7 +467,6 @@ def plot_event(self, data: ratapi.events.PlotEventData | None = None):
465467
show_legend = self.show_legend.isChecked() if self.current_plot_data.contrastNames else False
466468
self.figure.clear()
467469
self.update_figure_size()
468-
self.figure.tight_layout()
469470
ratapi.plotting.plot_ref_sld_helper(
470471
self.current_plot_data,
471472
self.figure,

rascal2/widgets/project/project.py

Lines changed: 49 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
ProjectFieldWidget,
2222
ResolutionsFieldWidget,
2323
)
24+
from rascal2.widgets.utils import FlowLayout
2425

2526

2627
class ProjectWidget(QtWidgets.QWidget):
@@ -77,16 +78,43 @@ def __init__(self, parent):
7778
layout.addWidget(self.stacked_widget)
7879
self.setLayout(layout)
7980

81+
@staticmethod
82+
def make_labelled_widget(label_text, form_widget):
83+
"""Create widget containing a label and the given widget.
84+
85+
Parameters
86+
----------
87+
label_text: str
88+
The label text for the form widget.
89+
form_widget
90+
The widget to add.
91+
92+
Returns
93+
-------
94+
label_form_widget:
95+
A widget with label and the given widget.
96+
"""
97+
layout = QtWidgets.QHBoxLayout()
98+
layout.setSpacing(0)
99+
layout.setContentsMargins(2, 5, 2, 5)
100+
layout.addWidget(QtWidgets.QLabel(f"{label_text}: ", objectName="BoldLabel"))
101+
layout.addWidget(form_widget)
102+
103+
widget = QtWidgets.QWidget()
104+
widget.setLayout(layout)
105+
widget.setSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed)
106+
return widget
107+
80108
def create_project_view(self) -> QtWidgets.QWidget:
81109
"""Create the project (non-edit) view."""
82110
project_widget = QtWidgets.QWidget()
83111
main_layout = QtWidgets.QVBoxLayout()
84112
main_layout.setSpacing(20)
85113

86-
show_sliders_button = QtWidgets.QPushButton("Show sliders", self)
114+
show_sliders_button = QtWidgets.QPushButton("Show sliders")
87115
show_sliders_button.clicked.connect(self.parent.toggle_sliders)
88116

89-
self.edit_project_button = QtWidgets.QPushButton("Edit Project", self, icon=QtGui.QIcon(path_for("edit.png")))
117+
self.edit_project_button = QtWidgets.QPushButton("Edit Project", icon=QtGui.QIcon(path_for("edit.png")))
90118
self.edit_project_button.clicked.connect(self.show_edit_view)
91119
button_layout = QtWidgets.QHBoxLayout()
92120
button_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight)
@@ -95,42 +123,27 @@ def create_project_view(self) -> QtWidgets.QWidget:
95123

96124
main_layout.addLayout(button_layout)
97125

98-
settings_layout = QtWidgets.QHBoxLayout()
99-
settings_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter)
126+
settings_layout = FlowLayout(spacing=2)
127+
settings_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight)
100128

101-
absorption_label = QtWidgets.QLabel("Absorption:", self, objectName="BoldLabel")
102129
self.absorption_checkbox = QtWidgets.QCheckBox()
103130
self.absorption_checkbox.setDisabled(True)
131+
settings_layout.addWidget(self.make_labelled_widget("Absorption", self.absorption_checkbox))
104132

105-
settings_layout.addWidget(absorption_label)
106-
settings_layout.addWidget(self.absorption_checkbox)
107-
108-
self.calculation_label = QtWidgets.QLabel("Calculation:", self, objectName="BoldLabel")
109-
110-
self.calculation_type = QtWidgets.QLineEdit(self)
133+
self.calculation_type = QtWidgets.QLineEdit()
111134
self.calculation_type.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
112135
self.calculation_type.setReadOnly(True)
136+
settings_layout.addWidget(self.make_labelled_widget("Calculation", self.calculation_type))
113137

114-
settings_layout.addWidget(self.calculation_label)
115-
settings_layout.addWidget(self.calculation_type)
116-
117-
self.model_type_label = QtWidgets.QLabel("Model Type:", self, objectName="BoldLabel")
118-
119-
self.model_type = QtWidgets.QLineEdit(self)
138+
self.model_type = QtWidgets.QLineEdit()
120139
self.model_type.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
121140
self.model_type.setReadOnly(True)
141+
settings_layout.addWidget(self.make_labelled_widget("Model Type", self.model_type))
122142

123-
settings_layout.addWidget(self.model_type_label)
124-
settings_layout.addWidget(self.model_type)
125-
126-
self.geometry_label = QtWidgets.QLabel("Geometry:", self, objectName="BoldLabel")
127-
128-
self.geometry_type = QtWidgets.QLineEdit(self)
143+
self.geometry_type = QtWidgets.QLineEdit()
129144
self.geometry_type.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
130145
self.geometry_type.setReadOnly(True)
131-
132-
settings_layout.addWidget(self.geometry_label)
133-
settings_layout.addWidget(self.geometry_type)
146+
settings_layout.addWidget(self.make_labelled_widget("Geometry", self.geometry_type))
134147

135148
main_layout.addLayout(settings_layout)
136149

@@ -152,11 +165,11 @@ def create_edit_view(self) -> QtWidgets.QWidget:
152165
main_layout.setSpacing(20)
153166

154167
self.save_project_button = QtWidgets.QPushButton(
155-
"Accept Changes", self, icon=QtGui.QIcon(path_for("save-project.png"))
168+
"Accept Changes", icon=QtGui.QIcon(path_for("save-project.png"))
156169
)
157170
self.save_project_button.clicked.connect(self.save_changes)
158171

159-
self.cancel_button = QtWidgets.QPushButton("Cancel", self, icon=QtGui.QIcon(path_for("cancel-dark.png")))
172+
self.cancel_button = QtWidgets.QPushButton("Cancel", icon=QtGui.QIcon(path_for("cancel-dark.png")))
160173
self.cancel_button.clicked.connect(self.show_project_view)
161174

162175
buttons_layout = QtWidgets.QHBoxLayout()
@@ -165,47 +178,24 @@ def create_edit_view(self) -> QtWidgets.QWidget:
165178
buttons_layout.addWidget(self.cancel_button)
166179
main_layout.addLayout(buttons_layout)
167180

168-
settings_layout = QtWidgets.QHBoxLayout()
181+
settings_layout = FlowLayout(spacing=2)
169182
settings_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter)
170183

171-
absorption_label = QtWidgets.QLabel("Absorption:", self, objectName="BoldLabel")
172184
self.edit_absorption_checkbox = QtWidgets.QCheckBox()
185+
settings_layout.addWidget(self.make_labelled_widget("Absorption", self.edit_absorption_checkbox))
173186

174-
settings_layout.addWidget(absorption_label)
175-
settings_layout.addWidget(self.edit_absorption_checkbox)
176-
177-
self.edit_calculation_label = QtWidgets.QLabel("Calculation:", self, objectName="BoldLabel")
178-
179-
self.calculation_combobox = QtWidgets.QComboBox(self)
180-
self.calculation_combobox.setSizePolicy(
181-
QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed
182-
)
187+
self.calculation_combobox = QtWidgets.QComboBox()
183188
self.calculation_combobox.addItems([calc for calc in Calculations])
189+
settings_layout.addWidget(self.make_labelled_widget("Calculation", self.calculation_combobox))
184190

185-
settings_layout.addWidget(self.edit_calculation_label)
186-
settings_layout.addWidget(self.calculation_combobox)
187-
188-
self.edit_model_type_label = QtWidgets.QLabel("Model Type:", self, objectName="BoldLabel")
189-
190-
self.model_combobox = QtWidgets.QComboBox(self)
191-
self.model_combobox.setSizePolicy(
192-
QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed
193-
)
191+
self.model_combobox = QtWidgets.QComboBox()
194192
self.model_combobox.addItems([model for model in LayerModels])
193+
settings_layout.addWidget(self.make_labelled_widget("Model Type", self.model_combobox))
195194

196-
settings_layout.addWidget(self.edit_model_type_label)
197-
settings_layout.addWidget(self.model_combobox)
198-
199-
self.edit_geometry_label = QtWidgets.QLabel("Geometry:", self, objectName="BoldLabel")
200-
201-
self.geometry_combobox = QtWidgets.QComboBox(self)
202-
self.geometry_combobox.setSizePolicy(
203-
QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed
204-
)
195+
self.geometry_combobox = QtWidgets.QComboBox()
205196
self.geometry_combobox.addItems([geo for geo in Geometries])
197+
settings_layout.addWidget(self.make_labelled_widget("Geometry", self.geometry_combobox))
206198

207-
settings_layout.addWidget(self.edit_geometry_label)
208-
settings_layout.addWidget(self.geometry_combobox)
209199
main_layout.addLayout(settings_layout)
210200

211201
self.edit_absorption_checkbox.checkStateChanged.connect(

rascal2/widgets/utils.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
from PyQt6 import QtCore, QtWidgets
2+
3+
4+
class FlowLayout(QtWidgets.QLayout):
5+
"""A layout that rearranges contents when resized.
6+
7+
Adapted from PyQt port of QFlowLayout example https://doc.qt.io/qt-6/qtwidgets-layouts-flowlayout-example.html
8+
"""
9+
10+
def __init__(self, parent=None, margin=0, spacing=5):
11+
super().__init__(parent)
12+
13+
if parent is not None:
14+
self.setContentsMargins(margin, margin, margin, margin)
15+
16+
self.setSpacing(spacing)
17+
self.margin = margin
18+
19+
self.item_list = []
20+
21+
def __del__(self):
22+
item = self.takeAt(0)
23+
while item:
24+
item = self.takeAt(0)
25+
26+
def addItem(self, item):
27+
self.item_list.append(item)
28+
29+
def count(self):
30+
return len(self.item_list)
31+
32+
def itemAt(self, index):
33+
if 0 <= index < len(self.item_list):
34+
return self.item_list[index]
35+
36+
return None
37+
38+
def takeAt(self, index):
39+
if 0 <= index < len(self.item_list):
40+
return self.item_list.pop(index)
41+
42+
return None
43+
44+
def expandingDirections(self):
45+
return QtCore.Qt.Orientation.Horizontal
46+
47+
def hasHeightForWidth(self):
48+
return True
49+
50+
def heightForWidth(self, width):
51+
height = self.do_layout(QtCore.QRect(0, 0, width, 0), True)
52+
return height
53+
54+
def setGeometry(self, rect):
55+
super().setGeometry(rect)
56+
self.do_layout(rect, False)
57+
58+
def sizeHint(self):
59+
return self.minimumSize()
60+
61+
def minimumSize(self):
62+
size = QtCore.QSize()
63+
64+
for item in self.item_list:
65+
size = size.expandedTo(item.minimumSize())
66+
67+
size += QtCore.QSize(2 * self.margin, 2 * self.margin)
68+
return size
69+
70+
def do_layout(self, rect, test_only):
71+
x = rect.x()
72+
y = rect.y()
73+
space_x = space_y = self.spacing()
74+
line_height = 0
75+
76+
for item in self.item_list:
77+
next_x = x + item.sizeHint().width() + space_x
78+
if next_x - space_x > rect.right() and line_height > 0:
79+
x = rect.x()
80+
y = y + line_height + space_y
81+
next_x = x + item.sizeHint().width() + space_x
82+
line_height = 0
83+
84+
if not test_only:
85+
item.setGeometry(QtCore.QRect(QtCore.QPoint(x, y), item.sizeHint()))
86+
87+
x = next_x
88+
line_height = max(line_height, item.sizeHint().height())
89+
90+
return y + line_height - rect.y()

0 commit comments

Comments
 (0)