Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Save entries by default in the Form Dialog #97

Merged
merged 32 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
906dd76
Add functionality of default saving in a new example
DanicaSTFC Oct 19, 2023
c143250
Save states and restore states as a default in FormDialog
DanicaSTFC Oct 20, 2023
071aa0c
Add all widgets to example 3 and set state method
DanicaSTFC Oct 20, 2023
9473391
Add unit test for state changes in form dialog --- not finished
DanicaSTFC Oct 20, 2023
2e05d97
Unit tests working
DanicaSTFC Oct 26, 2023
0538ef5
Modify and tidy example
DanicaSTFC Oct 26, 2023
46231b5
Add minor changes
DanicaSTFC Oct 26, 2023
d2cfb9a
Add changes from pre-commit
DanicaSTFC Oct 26, 2023
ffe74e4
Fix first cancel call bug
DanicaSTFC Oct 30, 2023
00dbb7a
Save default values one by one in `_addWidget`
DanicaSTFC Oct 30, 2023
6be5a9c
Close dialog when Ok is clicked and add example file for methods
DanicaSTFC Oct 31, 2023
4c5ca41
Add example state to tests, add docstring to set_state
DanicaSTFC Oct 31, 2023
540f27e
ii changed to i in test set_state
DanicaSTFC Nov 17, 2023
006e4a9
Improve applyDefaultWidgetValuesToState with docstring and new name
DanicaSTFC Nov 17, 2023
54235ec
Change docstring to restoreAllSavedWidgetStates
DanicaSTFC Nov 17, 2023
424d6b4
Pre-commit run
DanicaSTFC Nov 17, 2023
92d1332
Fix issues with unit tests
lauramurgatroyd Nov 21, 2023
94f172e
Fix issues with session label
lauramurgatroyd Nov 21, 2023
d6d2cc6
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 21, 2023
73f419c
Fix formatting
lauramurgatroyd Nov 21, 2023
ccdf0fa
Remove unused imports
lauramurgatroyd Nov 21, 2023
05ff037
remove prints
lauramurgatroyd Nov 21, 2023
d3bf171
Add default widget state as a larger dictionary
DanicaSTFC Nov 23, 2023
90ea8e8
Polish test_dialog_buttons_default_behaviour
DanicaSTFC Nov 23, 2023
1ecc16a
pre-commit run
DanicaSTFC Nov 23, 2023
69701cd
improve docstring and other minor things
DanicaSTFC Nov 23, 2023
1afac70
merge origin main (remove widgets)
DanicaSTFC Nov 23, 2023
6643167
Attempt to make remove widget and save default states compatible, not…
DanicaSTFC Nov 24, 2023
970be18
Polish code and run pre-commit
DanicaSTFC Dec 4, 2023
53104e8
Merge branch 'main' of github.com:TomographicImaging/eqt
DanicaSTFC Dec 4, 2023
e99a648
Merge fix_unit_tests
DanicaSTFC Dec 4, 2023
f9a342b
Change method name and remove qlabel from default state dictionary
DanicaSTFC Dec 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions eqt/ui/FormDialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def __init__(self, parent=None, title=None):
self.setWindowTitle(title)
# add button box to the UI
self.formWidget.uiElements['verticalLayout'].addWidget(bb)
bb.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self._onOk)
bb.button(QtWidgets.QDialogButtonBox.Cancel).clicked.connect(self._onCancel)

@property
Expand All @@ -35,14 +36,27 @@ def Cancel(self):
'''returns a reference to the Dialog Cancel button to connect its signals'''
return self.buttonBox.button(QtWidgets.QDialogButtonBox.Cancel)

def _onOk(self):
'''saves the widget states and calls `onOk`'''
self.saveAllWidgetStates()
self.onOk()
self.close()

def _onCancel(self):
'''calls onCancel and closes the FormDialog'''
'''calls `onCancel`, closes the FormDialog and restores the previously saved states
or the default states.'''
self.onCancel()
self.close()
self.restoreAllSavedWidgetStates()

def onOk(self):
'''Called when the dialog's "Ok" button is clicked.
Can be redefined to add additional functionality on "Ok"'''
pass

def onCancel(self):
'''Called when the dialog's "Cancel" button is clicked.
Can be redefined to add additional functionality on "Cancel"'''
Can be redefined to add additional functionality on "Cancel"'''
pass

@property
Expand Down
35 changes: 32 additions & 3 deletions eqt/ui/UIFormWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def createForm(self):
'verticalLayout': verticalLayout, 'groupBox': groupBox,
'groupBoxFormLayout': groupBoxFormLayout}
self.widgets = {}
self.default_widgets = {}

@property
def groupBox(self):
Expand Down Expand Up @@ -140,6 +141,7 @@ def _addWidget(self, name, qwidget, qlabel=None):
# add the field
field = f'{name}_field'
self.widgets[field] = qwidget
self.default_widgets[field] = qwidget

if qlabel is not None:
# add the label
Expand All @@ -152,6 +154,7 @@ def _addWidget(self, name, qwidget, qlabel=None):

# save a reference to label widgets in the dictionary
self.widgets[label] = qlabel
self.default_widgets[label] = qlabel

field_form_role = QtWidgets.QFormLayout.FieldRole

Expand All @@ -161,6 +164,28 @@ def _addWidget(self, name, qwidget, qlabel=None):

formLayout.setWidget(widgetno, field_form_role, qwidget)
self.num_widgets += 1
self.populate_default_widget_states_dictionary(name)

def populate_default_widget_states_dictionary(self, name):
'''
Creates an attribute dictionary of default widget states. The entries are in the
format: {'value': str | bool | int, 'enabled': bool, 'visible': bool}.
This can be used to restore the default states of the widgets invoking `applyWidgetStates`.
'''
if not hasattr(self, 'default_widget_states'):
self.default_widget_states = {}
# add the default state of the qwidget
self.default_widget_states[f'{name}_field'] = self.getWidgetState(name, 'field')
# add the default state of the qlabel
if f'{name}_label' in self.widgets.keys():
self.default_widget_states[f'{name}_label'] = self.getWidgetState(name, 'label')

def set_default_widget_states_visible_true(self):
'''
Sets all of the entries 'visible' in the `default_widget_states` dictionary to be `True`.
'''
for key in self.default_widget_states.keys():
self.default_widget_states[key]['visible'] = True

def getAllWidgetStates(self):
'''
Expand Down Expand Up @@ -320,10 +345,14 @@ def saveAllWidgetStates(self):

def restoreAllSavedWidgetStates(self):
'''
Restore all widgets in the form to the state saved by `saveAllWidgetStates()`.
If `saveAllWidgetStates()` method was not previously invoked, do nothing.
All widgets in the form are restored to the saved states. There are saved states only if
`saveAllWidgetStates` was previously invoked. If there are no previously saved states,
`default_widget_states` are used instead, after being made visible.
'''
if hasattr(self, 'widget_states'):
if not hasattr(self, 'widget_states'):
self.set_default_widget_states_visible_true()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we do this? What if the default state of a widget is that it is invisible and we want to re-apply that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it because the default states are added when the widgets are added which could be when the form is closed?

If so, should it be instead that the default state is saved upon first opening the form?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this is a later problem and we may need to open an issue about rethinking how the visibility is saved.

Copy link
Collaborator Author

@DanicaSTFC DanicaSTFC Dec 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am happy with the way I saved the visibility: they are "not visible" in the default states because when we created the widgets they were, indeed, non visible.

If I want to apply default states which are not visible to a visible form, the restoration step should inherit the visibility of the form. I tried to change the visibility after the cancel button was clicked and the form was closed hoping the widgets would inherit the visibility from the opened form but I was not successful. I do agree with you and this was not done optimally here.

I cannot think of an example where we would want to restore non visible default states to a visible form.

I have addressed some problems with saving default states in my next PR. Hopefully once this and the next PR are merged, we could optimise these procedures before the milestone?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, I think when we click the cancel button the form is open so it is not wrong to set the visibility true in that step.

Copy link
Member

@lauramurgatroyd lauramurgatroyd Dec 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example I was thinking of would be a case where the default case has some advanced widgets that are by default hidden and could be shown using a checkbox or similar.

But I can see the docstring explains what is happening with the visibility being restored as True so I am happy to approve

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok thank you, I will open an issue with this. #113

self.applyWidgetStates(self.default_widget_states)
else:
self.applyWidgetStates(self.widget_states)


Expand Down
50 changes: 50 additions & 0 deletions examples/dialog_example_3_save_default.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import sys

import utilitiesForExamples as utex
from PySide2 import QtWidgets

from eqt.ui import FormDialog


class MainUI(QtWidgets.QMainWindow):
def __init__(self, parent=None):
QtWidgets.QMainWindow.__init__(self, parent)

pb = QtWidgets.QPushButton(self)
pb.setText("Open Dialog with form layout")
pb.clicked.connect(lambda: self.executeDialog())

layout = QtWidgets.QHBoxLayout()
layout.addWidget(pb)
widg = QtWidgets.QWidget()
widg.setLayout(layout)

self.setCentralWidget(widg)
self.dialog = FormDialog(parent=self, title='Example')
self.openFormDialog()

self.show()

def openFormDialog(self):
utex.addWidgetsToExample(self.dialog)
# redefine the onOk and onCancel functions
self.dialog.onOk = self.accepted
self.dialog.onCancel = self.rejected

def accepted(self):
print("States saved")

def rejected(self):
print("States rejected")

# open dialog function when the parent button is clicked
def executeDialog(self):
self.dialog.open()


if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)

window = MainUI()

sys.exit(app.exec_())
8 changes: 7 additions & 1 deletion examples/remove_widgets_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,18 @@ def openFormDialog(self):
# store a reference
self.dialog = dialog
self.dialog.onCancel = self.rejected
# redefine `onOk`` so it does not close the dialog.
self.dialog._onOk = self._onOkRedefined

# print dictionary of all widgets in dialog
print("Dictionary of widgets in Form Dialog:\n" + str(self.dialog.getWidgets()))
print("\nDictionary of widgets in Form Dialog:\n" + str(self.dialog.getWidgets()))

dialog.open()

def _onOkRedefined(self):
'''Saves the widget states.'''
self.dialog.saveAllWidgetStates()

def addWidgetsToExampleForm(self, form):

# add widget 1 as QLineEdit
Expand Down
26 changes: 26 additions & 0 deletions examples/utilitiesForExamples.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from PySide2 import QtWidgets

from eqt.ui.UISliderWidget import UISliderWidget


def addWidgetsToExample(form):
'''
Adds a spanning widget and every type of widget to a form
'''
# add a spanning widget
form.addSpanningWidget(QtWidgets.QLabel("Input Values: "), 'input_title')
# add all widgets
form.addWidget(QtWidgets.QLabel('Label'), 'Label: ', 'label')
form.addWidget(QtWidgets.QCheckBox('check me'), 'CheckBox: ', 'checkBox')
combobox_list = ['choice 1', 'choice 2']
form.addWidget(QtWidgets.QComboBox(), 'ComboBox: ', 'comboBox')
form.getWidget('comboBox').addItems(combobox_list)
form.addWidget(QtWidgets.QDoubleSpinBox(), 'DoubleSpinBox: ', 'doubleSpinBox')
form.addWidget(QtWidgets.QSpinBox(), 'SpinBox: ', 'spinBox')
form.addWidget(QtWidgets.QSlider(), 'Slider: ', 'slider')
form.addWidget(UISliderWidget(QtWidgets.QLabel()), 'UISlider: ', 'uiSliderWidget')
form.addWidget(QtWidgets.QRadioButton('select me'), 'RadioButton: ', 'radioButton')
form.addWidget(QtWidgets.QTextEdit('write text here'), 'TextEdit: ', 'textEdit')
form.addWidget(QtWidgets.QPlainTextEdit('write text here'), 'PlainTextEdit: ', 'plainTextEdit')
form.addWidget(QtWidgets.QLineEdit('write text here'), 'LineEdit: ', 'lineEdit')
form.addWidget(QtWidgets.QPushButton('Click me'), 'Button: ', 'button')
88 changes: 88 additions & 0 deletions test/test__formUI_status_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from unittest import mock

from PySide2 import QtWidgets
from PySide2.QtCore import Qt
from PySide2.QtTest import QTest

from eqt.ui.FormDialog import FormDialog
from eqt.ui.UIFormWidget import FormDockWidget, FormWidget
Expand All @@ -17,6 +19,22 @@ class FormsCommonTests(metaclass=abc.ABCMeta):
def setUp(self):
raise NotImplementedError

@property
def exampleState(self):
# define two states for every widget
state = [{
'label_value': 'Test label state 0', 'checkbox_value': False, 'combobox_value': 0,
'doubleSpinBox_value': 10.0, 'spinBox_value': 10, 'slider_value': 10,
'uislider_value': 10, 'radio_value': False, 'textEdit_value': 'test edit 0',
'plainTextEdit_value': 'test plain 0', 'lineEdit_value': 'test line 0',
'pushButton_value': False}, {
'label_value': 'Test label state 1', 'checkbox_value': True, 'combobox_value': 1,
'doubleSpinBox_value': 1.0, 'spinBox_value': 1, 'slider_value': 1,
'uislider_value': 1, 'radio_value': True, 'textEdit_value': 'test edit 1',
'plainTextEdit_value': 'test plain 1', 'lineEdit_value': 'test line 1',
'pushButton_value': True}]
return state

def add_every_widget(self):
"""Generate every widget and add it to `self.form`"""
form = self.form
Expand All @@ -39,6 +57,44 @@ def add_two_widgets(self):
form.addWidget(QtWidgets.QLabel('test label'), 'Label: ', 'label')
form.addWidget(QtWidgets.QCheckBox('test checkbox'), 'CheckBox: ', 'checkBox')

def set_state(self, i):
"""
Applies the values saved in `self.exampleState` at position `i` to the widgets in the form.

Parameters
----------------
i: int
"""
state = self.exampleState
# set the states
# QLabel
self.form.getWidget('label').setText(state[i]['label_value'])
# QCheckBox
self.form.getWidget('checkBox').setChecked(state[i]['checkbox_value'])
# QComboBox
combobox_list = ['test', 'test2']
self.form.getWidget('comboBox').addItems(combobox_list)
self.form.getWidget('comboBox').setCurrentIndex(state[i]['combobox_value'])
# QDoubleSpinBox
self.form.getWidget('doubleSpinBox').setValue(state[i]['doubleSpinBox_value'])
# QSpinBox
self.form.getWidget('spinBox').setValue(state[i]['spinBox_value'])
# QSlider
self.form.getWidget('slider').setValue(state[i]['slider_value'])
# UISlider
self.form.getWidget('uiSliderWidget').setValue(state[i]['uislider_value'])
# QRadioButton
self.form.getWidget('radioButton').setChecked(state[i]['radio_value'])
# QTextEdit
self.form.getWidget('textEdit').setText(state[i]['textEdit_value'])
# QPlainTextEdit
self.form.getWidget('plainTextEdit').setPlainText(state[i]['plainTextEdit_value'])
# QLineEdit
self.form.getWidget('lineEdit').setText(state[i]['lineEdit_value'])
# QPushButton
self.form.getWidget('button').setCheckable(True)
self.form.getWidget('button').setChecked(state[i]['pushButton_value'])

def _test_remove_one_widget(self, name):
"""
Remove one widget.
Expand Down Expand Up @@ -328,6 +384,38 @@ def setUp(self):
self.add_two_widgets()
self.layout = self.form.formWidget.uiElements['groupBoxFormLayout']

def click_Ok(self):
QTest.mouseClick(self.form.Ok, Qt.LeftButton)

def click_Cancel(self):
QTest.mouseClick(self.form.Cancel, Qt.LeftButton)

def test_dialog_buttons_default_behaviour(self):
# create the states dictionary
self.set_state(1)
states1 = self.form.getAllWidgetStates()
self.set_state(0)
states0 = self.form.getAllWidgetStates()
# check state 0 and 1 are not saved when Cancel is pressed
self.click_Cancel()
self.assertNotEqual(states0, self.form.getAllWidgetStates())
self.assertNotEqual(states1, self.form.getAllWidgetStates())
# save state 0
self.set_state(0)
self.assertEqual(states0, self.form.getAllWidgetStates())
self.click_Ok()
self.assertEqual(states0, self.form.getAllWidgetStates())
# save state 1
self.set_state(1)
self.assertEqual(states1, self.form.getAllWidgetStates())
self.click_Ok()
self.assertEqual(states1, self.form.getAllWidgetStates())
# change to state 0 without saving
self.set_state(0)
self.assertEqual(states0, self.form.getAllWidgetStates())
self.click_Cancel()
self.assertEqual(states1, self.form.getAllWidgetStates())

def test_form_init_title(self):
"""Tests if the FormDialog is created correctly with or without the title argument."""
FormDialog()
Expand Down