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

PR: Improve UI/UX of the Run plugin configuration widgets #22141

Merged
merged 57 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
e73518d
Run: Hide file combobox in RunDialog
ccordoba12 Oct 29, 2023
5e0182f
Run: Remove checkbox to always show RunDialog
ccordoba12 Oct 29, 2023
2c33b79
Run: Simplify layout of executor and parameters comboboxes in RunDialog
ccordoba12 Oct 29, 2023
91f2ddb
Run: Automatically save config if customized by users in RunDialog
ccordoba12 Oct 30, 2023
e52c13f
Run: Initial UX improvements for ExecutionParametersDialog and conf page
ccordoba12 Oct 31, 2023
bdbef37
Run: Fix deleting parameters from its config page
ccordoba12 Nov 3, 2023
1dcf7d5
Widgets: Make HoverRowsTableView highlight a hovered row on its own
ccordoba12 Nov 3, 2023
448cc85
Run: Make RunParametersTableView highlight its rows on hover
ccordoba12 Nov 3, 2023
db398f7
Run: Fix saving config parameters when the user has saved some before
ccordoba12 Nov 5, 2023
f57ba7b
Widgets: Add a new CollapsibleWidget to hide and show child widgets
ccordoba12 Nov 8, 2023
51063c8
Run: Group widgets to customize config into a collapsible one in RunD…
ccordoba12 Nov 8, 2023
530bc9a
Minor fixes to the run config group of several plugins
ccordoba12 Nov 8, 2023
3a48a87
Run: Center dialog after custom_config widget is expanded/collapsed
ccordoba12 Nov 8, 2023
8be23b4
Merge branch 'master' into improve-run-config-ux
ccordoba12 May 16, 2024
f5cc47c
Widgets: Change cursor shape when hovering toggle button of Collapsib…
ccordoba12 May 16, 2024
ebf949f
Run: Several fixes to its widgets
ccordoba12 May 17, 2024
543ad31
IPython console: Rename file that contains run conf options to run_conf
ccordoba12 May 17, 2024
5c00548
Set min width for command line options of run conf widgets
ccordoba12 May 17, 2024
34366eb
Run: Additional UI improvements for ExecutionParametersDialog
ccordoba12 May 18, 2024
03adbaf
Run: Correctly set bottom margins for QGroupBoxes shown in its dialogs
ccordoba12 May 18, 2024
f0a0aba
Run: Allow to delete and save globally config parameters in RunDialog
ccordoba12 May 18, 2024
2706ea3
Editor: Remove unnecessary run extensions
ccordoba12 May 20, 2024
4e07ae5
Simplify code related to Run options in plugins to make it easier to …
ccordoba12 May 20, 2024
a070cf6
Run: Save default executor configurations to our config system
ccordoba12 May 20, 2024
e22e6c9
Run: Don't allow to remove default executor parameters in its confpage
ccordoba12 May 21, 2024
736721b
Run: More UI improvements to its confpage
ccordoba12 May 21, 2024
fac3f3b
Run: Improve UI of ExecutorRunParametersTableModel
ccordoba12 May 21, 2024
3b5b773
Run: Add "Edit selected" button to its confpage
ccordoba12 May 22, 2024
ddb8541
Run: Improve ExecutionParametersDialog UI when displaying default params
ccordoba12 May 22, 2024
74d45fb
Run: Improve code and UX related to reset changes in its confpage
ccordoba12 May 22, 2024
02c753b
Stylesheet: Move style declaration to SidebarDialog
ccordoba12 May 23, 2024
daed316
Run: Fix saving and executing custom configs in RunDialog
ccordoba12 May 24, 2024
3375eb4
Run: Improve RunDialog layout and style
ccordoba12 May 24, 2024
5e1862d
Run: Prevent saving global configs for different conditions in RunDialog
ccordoba12 May 29, 2024
a1a7d7b
Run: Add tip widgets in several places of RunDialog
ccordoba12 May 29, 2024
df272ec
Run: Fix content's right margin of custom_config widget in RunDialog
ccordoba12 May 29, 2024
16b070c
App: Filter warnings shown when uncollapsing CollapsibleWidget
ccordoba12 May 29, 2024
2d14675
Run: Fix a couple of errors with its dialogs
ccordoba12 May 31, 2024
ebab9c6
External terminal: Improve UI of ExternalTerminalShConfiguration widget
ccordoba12 Jun 1, 2024
e5fa369
Merge branch 'master' into improve-run-config-ux
ccordoba12 Jun 2, 2024
9910e1b
Testing: Fix failing main windows tests
ccordoba12 Jun 2, 2024
ce66902
Run: UI fixes for Mac and Windows
ccordoba12 Jun 3, 2024
36dea53
API: Add kwarg to force rendering a SpyderMenu
ccordoba12 Jun 4, 2024
4c660c2
Run: Fix style of buttons used to select a directory
ccordoba12 Jun 4, 2024
ffc08a5
Run: Remove "Save globally" button and move "Delete" one to button box
ccordoba12 Jun 8, 2024
9e87ea5
Run: Don't allow to save file parameters with a repeated name
ccordoba12 Jun 8, 2024
a6eb582
Run: Improve validation of global configuration names
ccordoba12 Jun 8, 2024
06e35bd
Run: Fix disabling edit/delete/clone buttons after some operations
ccordoba12 Jun 8, 2024
dcc838a
Run: Simplify layout of buttons in its confpage
ccordoba12 Jun 8, 2024
5598cf1
Run: Fix Reset button in ExecutionParametersDialog
ccordoba12 Jun 9, 2024
3d85d44
Run: Remove "Runner settings" groupbox to simplify its dialogs
ccordoba12 Jun 9, 2024
b741f4e
Run: Add menu entry to go to its Preferences page
ccordoba12 Jun 10, 2024
554ed05
Run: Use table_model current executor params when deleting confs
ccordoba12 Jun 11, 2024
353e4bd
Run: Make customizing global configs for files easier in RunDialog
ccordoba12 Jun 11, 2024
ef830c7
Apply suggestions from code review
ccordoba12 Jun 17, 2024
b1e6280
Utils: Add accessors to SpyderApplication for main window properties
ccordoba12 Jun 18, 2024
5e801e3
Testing: Fix failing tests due to the Numpy 2.0 release
ccordoba12 Jun 18, 2024
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
Prev Previous commit
Next Next commit
Run: Automatically save config if customized by users in RunDialog
- The name of that config is the "Custom" string localized.
- If the config is not named differently, additional customizations will
also be saved in "Custom".
- Also, only show global configs (i.e. those created in Preferences) or
the ones that correspond to the file currently displayed in RunDialog.
  • Loading branch information
ccordoba12 committed Dec 10, 2023
commit 91f2ddb542ddc42cdb752b0bc3bdc31f959a7082
4 changes: 4 additions & 0 deletions spyder/plugins/run/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@ class ExtendedRunExecutionParameters(TypedDict):
# The run execution parameters.
params: RunExecutionParameters

# The unique identifier for the file to which these parameters correspond
# to, if any.
file_uuid: Optional[str]


class StoredRunExecutorParameters(TypedDict):
"""Per run executor configuration parameters."""
Expand Down
32 changes: 22 additions & 10 deletions spyder/plugins/run/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,13 +284,10 @@ def __init__(self, parent):
def data(self, index: QModelIndex, role: int = Qt.DisplayRole):
pos = index.row()
total_saved_params = len(self.executor_conf_params)

if pos == total_saved_params:
if role == Qt.DisplayRole:
return _("Default/Transient")
elif role == Qt.ToolTipRole:
return _(
"This configuration will not be saved after execution"
)
return _("Default")
else:
params_id = self.params_index[pos]
params = self.executor_conf_params[params_id]
Expand Down Expand Up @@ -341,13 +338,28 @@ def set_parameters(
self.beginResetModel()
self.executor_conf_params = parameters
self.params_index = dict(enumerate(self.executor_conf_params))
self.inverse_index = {self.params_index[k]: k
for k in self.params_index}
self.inverse_index = {
self.params_index[k]: k for k in self.params_index
}
self.endResetModel()

def get_parameters_index(self, parameters_name: Optional[str]) -> int:
index = self.inverse_index.get(parameters_name,
len(self.executor_conf_params))
def get_parameters_index_by_uuid(
self,
parameters_uuid: Optional[str]
) -> int:
index = self.inverse_index.get(
parameters_uuid, len(self.executor_conf_params)
)

return index

def get_parameters_index_by_name(self, parameters_name: str) -> int:
index = -1
for id_, idx in self.inverse_index.items():
if self.executor_conf_params[id_]['name'] == parameters_name:
index = idx
break

return index

def __len__(self) -> int:
Expand Down
48 changes: 30 additions & 18 deletions spyder/plugins/run/tests/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,9 +597,9 @@ def test_run_plugin(qtbot, run_mock):
assert test_executor_name == executor_name
assert handler == 'both'

# Ensure that the executor run configuration is transient
assert exec_conf['uuid'] is None
assert exec_conf['name'] is None
# Ensure that the executor run configuration was saved
assert exec_conf['uuid'] is not None
assert exec_conf['name'] == "Custom"

# Check that the configuration parameters are the ones defined by the
# dialog
Expand All @@ -612,23 +612,37 @@ def test_run_plugin(qtbot, run_mock):
# Assert that the run_exec dispatcher works for the specific combination
assert handler_name == f'{ext}_{context["identifier"]}'

# Assert that the run configuration gets registered without executor params
# Assert that the run configuration gets registered
stored_run_params = run.get_last_used_executor_parameters(run_conf_uuid)
current_exec_uuid = exec_conf['uuid']
assert stored_run_params['executor'] == test_executor_name
assert stored_run_params['selected'] is None
assert stored_run_params['selected'] == current_exec_uuid

# The configuration gets run again
with qtbot.waitSignal(executor_1.sig_run_invocation) as sig:
run_act.trigger()
# Spawn the configuration dialog
run_act = run.get_action(RunActions.Run)
run_act.trigger()

dialog = container.dialog
with qtbot.waitSignal(dialog.finished, timeout=200000):
# Select the first configuration again
conf_combo.setCurrentIndex(0)

# Change some options
conf_widget = dialog.current_widget
conf_widget.widgets['opt1'].setChecked(False)

# Assert that the transient run executor parameters reverted to the
# default ones
# Execute the configuration
buttons = dialog.bbox.buttons()
run_btn = buttons[2]
with qtbot.waitSignal(executor_1.sig_run_invocation) as sig:
qtbot.mouseClick(run_btn, Qt.LeftButton)

# Assert that changes took effect and that the run executor parameters were
# saved in the Custom config
_, _, _, exec_conf = sig.args[0]
params = exec_conf['params']
working_dir = params['working_dir']
assert working_dir['source'] == WorkingDirSource.ConfigurationDirectory
assert working_dir['path'] == ''
assert params['executor_params'] == default_conf
assert params['executor_params']['opt1'] == False
assert exec_conf['uuid'] == current_exec_uuid

# Focus into another configuration
exec_provider_2.switch_focus('ext3', 'AnotherSuperContext')
Expand Down Expand Up @@ -737,17 +751,15 @@ def test_run_plugin(qtbot, run_mock):
with qtbot.waitSignal(dialog.finished, timeout=200000):
conf_combo = dialog.configuration_combo
exec_combo = dialog.executor_combo
store_params_cb = dialog.store_params_cb
store_params_text = dialog.store_params_text
name_params_text = dialog.name_params_text

# Modify some options
conf_widget = dialog.current_widget
conf_widget.widgets['name_1'].setChecked(True)
conf_widget.widgets['name_2'].setChecked(False)

# Make sure that the custom configuration is stored
store_params_cb.setChecked(True)
store_params_text.setText('CustomParams')
name_params_text.setText('CustomParams')

# Execute the configuration
buttons = dialog.bbox.buttons()
Expand Down
106 changes: 63 additions & 43 deletions spyder/plugins/run/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@
from qtpy.QtCore import QSize, Qt, Signal
from qtpy.QtGui import QFontMetrics
from qtpy.QtWidgets import (
QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QGroupBox, QHBoxLayout,
QLabel, QLineEdit, QLayout, QRadioButton, QStackedWidget, QVBoxLayout,
QWidget)
QDialog, QDialogButtonBox, QGridLayout, QGroupBox, QHBoxLayout, QLabel,
QLineEdit, QLayout, QRadioButton, QStackedWidget, QVBoxLayout, QWidget)
import qstylizer.style

# Local imports
Expand Down Expand Up @@ -63,8 +62,6 @@
CW_DIR = _("The current working directory")
FIXED_DIR = _("The following directory:")

STORE_PARAMS = _('Store current configuration as:')


class RunDialogStatus:
Close = 0
Expand Down Expand Up @@ -447,7 +444,7 @@ def setup(self):

executor_label = QLabel(_("Run this file in:"))
self.executor_combo = SpyderComboBox(self)
parameters_label = QLabel(_("Preset run parameters:"))
parameters_label = QLabel(_("Preset configuration:"))
self.parameters_combo = SpyderComboBox(self)

self.executor_combo.setMinimumWidth(250)
Expand Down Expand Up @@ -499,16 +496,12 @@ def setup(self):
wdir_layout.addLayout(fixed_dir_layout)

# --- Store new custom configuration
self.store_params_cb = QCheckBox(STORE_PARAMS)
self.store_params_text = QLineEdit(self)
store_params_layout = QHBoxLayout()
store_params_layout.addWidget(self.store_params_cb)
store_params_layout.addWidget(self.store_params_text)
parameters_layout.addLayout(store_params_layout)

self.store_params_cb.toggled.connect(self.store_params_text.setEnabled)
self.store_params_text.setPlaceholderText(_('My configuration name'))
self.store_params_text.setEnabled(False)
name_params_label = QLabel(_("Save current configuration as:"))
self.name_params_text = QLineEdit(self)
name_params_layout = QHBoxLayout()
name_params_layout.addWidget(name_params_label)
name_params_layout.addWidget(self.name_params_text)
parameters_layout.addLayout(name_params_layout)

layout = self.add_widgets(
self.header_label,
Expand Down Expand Up @@ -638,14 +631,22 @@ def display_executor_configuration(self, index: int):
if uuid not in self.run_conf_model:
return

stored_param = self.run_conf_model.get_run_configuration_parameters(
uuid, executor_name)
stored_params = self.run_conf_model.get_run_configuration_parameters(
uuid, executor_name)['params']

self.parameter_model.set_parameters(stored_param['params'])
# Only show global parameters (i.e. those with file_uuid = None) or
# those that correspond to the current file.
stored_params = {
k:v for (k, v) in stored_params.items()
if v.get("file_uuid") in [None, uuid]
}

self.parameter_model.set_parameters(stored_params)
selected_params = self.run_conf_model.get_last_used_execution_params(
uuid, executor_name)
index = self.parameter_model.get_parameters_index(selected_params)
index = self.parameter_model.get_parameters_index_by_uuid(
selected_params
)

if self.parameters_combo.count() == 0:
self.index_to_select = index
Expand All @@ -661,18 +662,50 @@ def reset_btn_clicked(self):
self.parameters_combo.setCurrentIndex(-1)
index = self.executor_combo.currentIndex()
self.display_executor_configuration(index)
self.store_params_text.setText('')
self.store_params_cb.setChecked(False)
self.name_params_text.setText('')

def run_btn_clicked(self):
self.status |= RunDialogStatus.Run
self.accept()

def get_configuration(
self
) -> Tuple[str, str, ExtendedRunExecutionParameters, bool]:

return self.saved_conf

# ---- Qt methods
def accept(self) -> None:
self.status |= RunDialogStatus.Save

default_conf = self.current_widget.get_default_configuration()
widget_conf = self.current_widget.get_configuration()

# Check if config is named
given_name = self.name_params_text.text()
if not given_name and widget_conf != default_conf:
# If parameters are not named and are different from the default
# ones, we always save them in a config named "Custom". This avoids
# the hassle of asking users to provide a name when they want to
# customize the config.
given_name = _("Custom")

# Get index associated with config
if given_name:
idx = self.parameter_model.get_parameters_index_by_name(given_name)
else:
idx = self.parameters_combo.currentIndex()

# Get uuid and name from index
if idx == -1:
# This means that there are no saved parameters for given_name, so
# we need to generate a new uuid for them.
uuid = str(uuid4())
name = given_name
else:
# Retrieve uuid and name from our config system
uuid, name = self.parameter_model.get_parameters_uuid_name(idx)

path = None
source = None
if self.file_dir_radio.isChecked():
Expand All @@ -688,39 +721,26 @@ def accept(self) -> None:
exec_params = RunExecutionParameters(
working_dir=cwd_opts, executor_params=widget_conf)

uuid, name = self.parameter_model.get_parameters_uuid_name(
self.parameters_combo.currentIndex()
metadata_info = self.run_conf_model.get_metadata(
self.configuration_combo.currentIndex()
)

if self.store_params_cb.isChecked():
uuid = str(uuid4())
name = self.store_params_text.text()
if name == '':
date_str = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
name = f'Configuration-{date_str}'

ext_exec_params = ExtendedRunExecutionParameters(
uuid=uuid, name=name, params=exec_params
uuid=uuid,
name=name,
params=exec_params,
file_uuid=metadata_info['uuid']
)
executor_name, _ = self.executors_model.get_selected_run_executor(

executor_name, __ = self.executors_model.get_selected_run_executor(
self.executor_combo.currentIndex()
)
metadata_info = self.run_conf_model.get_metadata(
self.configuration_combo.currentIndex()
)

self.saved_conf = (metadata_info['uuid'], executor_name,
ext_exec_params)

return super().accept()

def get_configuration(
self
) -> Tuple[str, str, ExtendedRunExecutionParameters, bool]:

return self.saved_conf

# ---- Qt methods
def showEvent(self, event):
"""Adjustments when the widget is shown."""
if not self._is_shown:
Expand Down