Skip to content

Commit

Permalink
Merge pull request #175 from compomics/feature/gui-streamlining
Browse files Browse the repository at this point in the history
Improve GUI user experience
  • Loading branch information
RalfG authored Sep 5, 2024
2 parents e66bc86 + a016ce3 commit c43a1be
Show file tree
Hide file tree
Showing 10 changed files with 364 additions and 365 deletions.
409 changes: 277 additions & 132 deletions ms2rescore/gui/app.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ms2rescore/gui/function2ctk.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def __init__(

# 2x3 grid, only logging column expands with window
self.grid_columnconfigure(0, weight=0) # Left: Sidebar
self.grid_columnconfigure(1, weight=2) # Middle: Configuration
self.grid_columnconfigure(1, weight=0) # Middle: Configuration
self.grid_columnconfigure(2, weight=1) # Right: Logging
self.grid_rowconfigure(0, weight=1)

Expand Down
163 changes: 86 additions & 77 deletions ms2rescore/gui/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,61 @@
import customtkinter as ctk


class Heading(ctk.CTkLabel):
class _Heading(ctk.CTkLabel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.configure(
font=ctk.CTkFont(weight="bold"),
fg_color=("gray80", "gray30"),
text_color=("black", "white"),
corner_radius=6,
)


class LabeledEntry(ctk.CTkFrame):
class _LabeledWidget(ctk.CTkFrame):
def __init__(self, *args, label="Label", description=None, wraplength=0, **kwargs):
super().__init__(*args, **kwargs)
self.grid_columnconfigure(0, weight=1)

self.row_n = 0

self._label_frame = ctk.CTkFrame(self)
self._label_frame.grid_columnconfigure(0, weight=1)
self._label_frame.configure(fg_color="transparent")
self._label_frame.grid(row=self.row_n, column=0, padx=0, pady=(0, 5), sticky="nsew")
self.row_n += 1

self._label = ctk.CTkLabel(
self._label_frame,
text=label,
font=ctk.CTkFont(weight="bold"),
wraplength=wraplength,
justify="left",
anchor="w",
)
self._label.grid(row=0, column=0, padx=0, pady=0, sticky="w")

if description:
self._description = ctk.CTkLabel(
self._label_frame,
text=description,
wraplength=wraplength,
justify="left",
anchor="w",
)
self._description.grid(row=1, column=0, padx=0, pady=0, sticky="ew")


class LabeledEntry(_LabeledWidget):
def __init__(
self,
*args,
label="Enter text",
placeholder_text="Enter text...",
default_value="",
**kwargs,
):
super().__init__(*args, **kwargs)
self.grid_columnconfigure(0, weight=1)

self._variable = ctk.StringVar(value=default_value)

self._label = ctk.CTkLabel(self, text=label)
self._label.grid(row=0, column=0, padx=0, pady=5, sticky="w")

self._entry = ctk.CTkEntry(
self, placeholder_text=placeholder_text, textvariable=self._variable
)
Expand All @@ -43,11 +71,10 @@ def get(self):
return self._variable.get()


class LabeledEntryTextbox(ctk.CTkFrame):
class LabeledEntryTextbox(_LabeledWidget):
def __init__(
self,
*args,
label="Enter text",
initial_contents="Enter text here...",
box_height=100,
**kwargs,
Expand All @@ -56,49 +83,40 @@ def __init__(
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(1, weight=1)

self._label = ctk.CTkLabel(self, text=label, anchor="w")
self._label.grid(row=0, column=0, padx=0, pady=5, sticky="ew")

self.text_box = ctk.CTkTextbox(self, height=box_height)
self.text_box.insert("1.0", initial_contents)
self.text_box.grid(row=1, column=0, padx=0, pady=5, sticky="nsew")
self.text_box.grid(row=self.row_n, column=0, padx=0, pady=5, sticky="nsew")
self.row_n += 1

def get(self):
return self.text_box.get("0.0", tk.END)


class LabeledRadioButtons(ctk.CTkFrame):
def __init__(self, *args, label="Select option", options=[], default_value=None, **kwargs):
class LabeledRadioButtons(_LabeledWidget):
def __init__(
self,
*args,
options=[],
default_value=None,
**kwargs,
):
super().__init__(*args, **kwargs)
self.grid_columnconfigure(0, weight=1)

self.value = ctk.StringVar(value=default_value or options[0])

self._label = ctk.CTkLabel(self, text=label)
self._label.grid(row=0, column=0, padx=0, pady=(0, 5), sticky="w")

self._radio_buttons = []
for i, option in enumerate(options):
radio_button = ctk.CTkRadioButton(self, text=option, variable=self.value, value=option)
radio_button.grid(row=i + 1, column=0, padx=0, pady=(0, 5), sticky="w")
radio_button.grid(row=self.row_n, column=0, padx=0, pady=(0, 5), sticky="w")
self._radio_buttons.append(radio_button)
self.row_n += 1

def get(self):
return self.value.get()


class LabeledOptionMenu(ctk.CTkFrame):
def __init__(
self, *args, vertical=False, label="Select option", values=[], default_value=None, **kwargs
):
class LabeledOptionMenu(_LabeledWidget):
def __init__(self, *args, vertical=False, values=[], default_value=None, **kwargs):
super().__init__(*args, **kwargs)
self.grid_columnconfigure(0, weight=1)

self.value = ctk.StringVar(value=default_value or values[0])

self._label = ctk.CTkLabel(self, text=label)
self._label.grid(row=0, column=0, padx=0, pady=0 if vertical else 5, sticky="w")

self._option_menu = ctk.CTkOptionMenu(self, variable=self.value, values=values)
self._option_menu.grid(
row=1 if vertical else 0,
Expand All @@ -112,16 +130,10 @@ def get(self):
return self.value.get()


class LabeledSwitch(ctk.CTkFrame):
def __init__(self, *args, label="Enable/disable", default=False, **kwargs):
class LabeledSwitch(_LabeledWidget):
def __init__(self, *args, default=False, **kwargs):
super().__init__(*args, **kwargs)
self.grid_columnconfigure(0, weight=1)

self.value = ctk.StringVar(value="0")

self._label = ctk.CTkLabel(self, text=label)
self._label.grid(row=0, column=0, padx=0, pady=5, sticky="w")

self._switch = ctk.CTkSwitch(self, variable=self.value, text="", onvalue="1", offvalue="0")
self._switch.grid(row=0, column=1, padx=0, pady=5, sticky="e")
if default:
Expand Down Expand Up @@ -202,21 +214,17 @@ def set(self, value: float):
self.entry.insert(0, format(value, self.str_format))


class LabeledFloatSpinbox(ctk.CTkFrame):
class LabeledFloatSpinbox(_LabeledWidget):
def __init__(
self,
*args,
label="Enter value",
initial_value=0.0,
step_size=1,
**kwargs,
):
super().__init__(*args, **kwargs)
self.grid_columnconfigure(0, weight=1)

self._label = ctk.CTkLabel(self, text=label)
self._label.grid(row=0, column=0, padx=0, pady=5, sticky="w")

self._spinbox = FloatSpinbox(
self,
initial_value=initial_value,
Expand All @@ -228,15 +236,20 @@ def get(self):
return self._spinbox.get()


class LabeledFileSelect(ctk.CTkFrame):
def __init__(self, *args, label="Select file", file_option="openfile", **kwargs):
class LabeledFileSelect(_LabeledWidget):
def __init__(
self,
*args,
file_option="openfile",
**kwargs,
):
"""
Advanced file selection widget with entry and file, directory, or save button.
Parameters
----------
label : str
Label text.
wraplength : int
Maximum line length before wrapping.
file_option : str
One of "openfile", "directory", "file/dir", or "savefile. Determines the type of file
selection dialog that is shown when the button is pressed.
Expand All @@ -253,14 +266,12 @@ def __init__(self, *args, label="Select file", file_option="openfile", **kwargs)
self._button_1 = None
self._button_2 = None

self.grid_columnconfigure(0, weight=1)

# Subwidgets
self._label = ctk.CTkLabel(self, text=label)
self._label.grid(row=0, column=0, columnspan=2, padx=0, pady=(5, 0), sticky="w")
self._label_frame.grid(
row=self.row_n - 1, column=0, columnspan=3, padx=0, pady=(0, 5), sticky="nsew"
) # Override grid placement of _LabeledWidget label frame to span all columns

self._entry = ctk.CTkEntry(self, placeholder_text="Select a file or directory")
self._entry.grid(row=1, column=0, padx=0, pady=0, sticky="ew")
self._entry.grid(row=self.row_n, column=0, padx=0, pady=5, sticky="ew")

if file_option == "directory":
self._button_1 = ctk.CTkButton(self, text="Browse directories", command=self._pick_dir)
Expand All @@ -279,9 +290,10 @@ def __init__(self, *args, label="Select file", file_option="openfile", **kwargs)
self, text="Path to save file(s)", command=self._save_file
)

self._button_1.grid(row=1, column=1, padx=(5, 0), pady=0, sticky="e")
self._button_1.grid(row=self.row_n, column=1, padx=(5, 0), pady=5, sticky="e")
if self._button_2:
self._button_2.grid(row=1, column=2, padx=(5, 0), pady=0, sticky="e")
self._button_2.grid(row=self.row_n, column=2, padx=(5, 0), pady=5, sticky="e")
self.row_n += 1

def get(self):
"""Returns the selected file or directory."""
Expand Down Expand Up @@ -312,41 +324,36 @@ def _save_file(self):
self._update_entry()


class TableInput(ctk.CTkFrame):
def __init__(self, *args, label=None, columns=2, header_labels=["A", "B"], **kwargs):
class TableInput(_LabeledWidget):
def __init__(
self,
*args,
columns=2,
header_labels=["A", "B"],
**kwargs,
):
"""
Table input widget with user-configurable number of rows.
Parameters
----------
label : str
Label text.
columns : int
Number of columns in the table.
header_labels : list of str
Labels for the header row.
"""
super().__init__(*args, **kwargs)
self.label = label
self.columns = columns
self.header_labels = header_labels

self.uniform_hash = str(random.getrandbits(128))

self.grid_columnconfigure(0, weight=1)

# Label
if label:
self.label = ctk.CTkLabel(self, text=label)
self.label.grid(row=0, column=0, pady=(5, 0), sticky="w")
label_row = 1
else:
label_row = 0

# Header row
header_row = ctk.CTkFrame(self)
header_row.grid(row=0 + label_row, column=0, padx=(33, 0), pady=(0, 5), sticky="ew")
header_row.grid(row=self.row_n, column=0, padx=(33, 0), pady=(5, 5), sticky="ew")
self.row_n += 1

for i, header in enumerate(self.header_labels):
header_row.grid_columnconfigure(i, weight=1, uniform=self.uniform_hash)
padx = (0, 5) if i < len(self.header_labels) - 1 else (0, 0)
Expand All @@ -364,15 +371,17 @@ def __init__(self, *args, label=None, columns=2, header_labels=["A", "B"], **kwa
self.input_frame = ctk.CTkFrame(self)
self.input_frame.uniform_hash = self.uniform_hash
self.input_frame.grid_columnconfigure(0, weight=1)
self.input_frame.grid(row=1 + label_row, column=0, sticky="new")
self.input_frame.grid(row=self.row_n, column=0, sticky="new")
self.row_n += 1

# Add first row that cannot be removed
self.add_row()
self.rows[0].remove_button.configure(state="disabled")

# Button to add more rows
self.add_button = ctk.CTkButton(self, text="+", width=28, command=self.add_row)
self.add_button.grid(row=2 + label_row, column=0, pady=(0, 5), sticky="w")
self.add_button.grid(row=self.row_n, column=0, pady=(0, 5), sticky="w")
self.row_n += 1

def add_row(self):
row = _TableInputRow(self.input_frame, columns=self.columns)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ms2rescore/package_data/img/docs_icon_black.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ms2rescore/package_data/img/docs_icon_white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit c43a1be

Please sign in to comment.