Skip to content

Commit 23e84ca

Browse files
Merge pull request #1108 from annie-xd-wang/resizable-window
Resizable window
2 parents 3f205db + 7f32fd2 commit 23e84ca

File tree

8 files changed

+127
-67
lines changed

8 files changed

+127
-67
lines changed

src/navigate/controller/controller.py

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ def __init__(
152152

153153
#: Tk top-level widget: Tk.tk GUI instance.
154154
self.root = root
155+
#: bool: Flag to indicate if the GUI is ready for resizing.
156+
self.resize_ready_flag = False
155157

156158
#: Tk top-level widget: Tk.tk GUI instance.
157159
self.splash_screen = splash_screen
@@ -346,8 +348,10 @@ def __init__(
346348

347349
#: int: ID for the resize event.Only works on Windows OS.
348350
self.resize_event_id = None
349-
if platform.system() == "Windows":
350-
self.view.root.bind("<Configure>", self.resize)
351+
self.window_width = 0
352+
self.window_height = 0
353+
self.view.root.after(5000, self.enable_resize)
354+
self.view.root.bind("<Configure>", self.resize)
351355

352356
def update_buffer(self):
353357
"""Update the buffer size according to the camera
@@ -566,6 +570,12 @@ def update_experiment_setting(self):
566570
return warning_message
567571
return ""
568572

573+
def enable_resize(self):
574+
"""Enable window resizing.
575+
576+
"""
577+
self.resize_ready_flag = True
578+
569579
def resize(self, event):
570580
"""Resize the GUI.
571581
@@ -584,22 +594,27 @@ def refresh(width, height):
584594
Width of the GUI.
585595
height : int
586596
Height of the GUI.
587-
"""
588-
if width < 1300 or height < 800:
589-
return
590-
self.view.camera_waveform["width"] = (
591-
width - self.view.left_frame.winfo_width() - 35
592-
) #
593-
self.view.camera_waveform["height"] = height - 117
594-
595-
print("camera_waveform height", self.view.camera_waveform["height"])
597+
"""
598+
self.view.scroll_frame.resize(width, height)
599+
self.view.right_frame.config(
600+
width=width-self.view.left_frame.winfo_width()-3,
601+
height=height-self.view.left_frame.winfo_height()
602+
)
603+
596604

597-
if event.widget != self.view.scroll_frame:
605+
if not self.resize_ready_flag:
606+
return
607+
if event.widget != self.root:
598608
return
609+
if event.width == self.window_width and event.height == self.window_height:
610+
return
611+
599612
if self.resize_event_id:
600613
self.view.after_cancel(self.resize_event_id)
614+
self.window_width = event.width
615+
self.window_height = event.height
601616
self.resize_event_id = self.view.after(
602-
1000, lambda: refresh(event.width, event.height)
617+
300, lambda: refresh(event.width, event.height)
603618
)
604619

605620
def prepare_acquire_data(self):

src/navigate/controller/sub_controllers/camera_view.py

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -964,15 +964,28 @@ def resize(self, event: tk.Event) -> None:
964964
event : tk.Event
965965
Tkinter event.
966966
"""
967-
if self.view.is_popup is False and event.widget != self.view:
967+
if not self.parent_controller.resize_ready_flag:
968968
return
969-
if self.view.is_popup is True and event.widget.widgetName != "toplevel":
969+
if event.width < 512 or event.height < 512:
970970
return
971+
if event.widget != self.view:
972+
return
973+
if self.view.is_docked:
974+
left_width = self.parent_controller.view.left_frame.winfo_width()
975+
top_height = self.parent_controller.view.top_frame.winfo_height()
976+
w_width = self.parent_controller.view.winfo_width()
977+
w_height = self.parent_controller.view.winfo_height()
978+
width = max(
979+
w_width - left_width - 16, 560 + self.view.lut.winfo_width()
980+
)
981+
height = max(w_height - top_height - 50, 670)
982+
else:
983+
width = event.width
984+
height = event.height - 24
985+
971986
if self.resize_event_id:
972987
self.view.after_cancel(self.resize_event_id)
973-
self.resize_event_id = self.view.after(
974-
1000, lambda: self.refresh(event.width, event.height)
975-
)
988+
self.resize_event_id = self.view.after(300, lambda: self.refresh(width, height))
976989

977990
def refresh(self, width: int, height: int) -> None:
978991
"""Refresh the window.
@@ -984,17 +997,29 @@ def refresh(self, width: int, height: int) -> None:
984997
height : int
985998
Height of the window.
986999
"""
987-
if width == self.width and height == self.height:
1000+
if (
1001+
self.width
1002+
and self.height
1003+
and abs(width - self.width) < 10
1004+
and abs(height - self.height) < 10
1005+
):
9881006
return
9891007
self.canvas_width = width - self.view.lut.winfo_width() - 24
990-
self.canvas_height = height - 153
1008+
widget_height = 0
1009+
for widget in self.view.cam_image.winfo_children():
1010+
if widget != self.view.canvas:
1011+
if self.view.is_docked or widget.winfo_ismapped():
1012+
widget_height += widget.winfo_height() + 5
1013+
if widget.winfo_height() < 30:
1014+
widget_height += 30
1015+
1016+
self.canvas_height = (
1017+
height - widget_height - (50 if self.view.is_docked else -5)
1018+
)
9911019
self.view.canvas.config(width=self.canvas_width, height=self.canvas_height)
9921020
self.view.update_idletasks()
9931021

994-
if self.view.is_popup:
995-
self.width, self.height = self.view.winfo_width(), self.view.winfo_height()
996-
else:
997-
self.width, self.height = width, height
1022+
self.width, self.height = width, height
9981023

9991024
# if resize the window during acquisition, the image showing should be updated
10001025
self.update_canvas_size()
@@ -1091,8 +1116,7 @@ def __init__(self, view, parent_controller=None) -> None:
10911116
# Slider Binding
10921117
self.view.slider.bind("<Motion>", self.slider_update)
10931118

1094-
if platform.system() == "Windows":
1095-
self.resize_event_id = self.view.bind("<Configure>", self.resize)
1119+
self.resize_event_id = self.view.bind("<Configure>", self.resize)
10961120

10971121
#: str: The display state.
10981122
self.display_state = "Live"
@@ -1450,8 +1474,7 @@ def __init__(self, view, parent_controller=None) -> None:
14501474
#: dict: The render widgets.
14511475
self.render_widgets = self.view.render.get_widgets()
14521476

1453-
if platform.system() == "Windows":
1454-
self.resize_event_id = self.view.bind("<Configure>", self.resize)
1477+
self.resize_event_id = self.view.bind("<Configure>", self.resize)
14551478

14561479
#: bool: The display enabled flag.
14571480
self.display_enabled = tk.BooleanVar()

src/navigate/view/custom_widgets/DockableNotebook.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ def popout(self) -> None:
161161
# Save the original index and the tab's text
162162
tab_widget._original_index = self.selected_tab_id
163163
tab_widget._saved_text: str = selected_text
164+
tab_widget.is_docked = False
164165

165166
# Remove the tab from the notebook
166167
if tab_widget in self.tab_list:
@@ -173,9 +174,6 @@ def popout(self) -> None:
173174

174175
if selected_text == "Camera View":
175176
tk.Wm.minsize(tab_widget, 663, 597)
176-
tab_widget.is_docked = False
177-
elif selected_text == "Waveform Settings":
178-
tab_widget.is_docked = False
179177

180178
def dismiss(self, tab_widget):
181179
"""Dismiss the popout window.
@@ -203,6 +201,7 @@ def dismiss(self, tab_widget):
203201
# Retrieve the original index and the saved text
204202
original_index = getattr(tab_widget, "_original_index", None)
205203
saved_text = getattr(tab_widget, "_saved_text", "Untitled")
204+
tab_widget.is_docked = True
206205

207206
if original_index is not None:
208207
current_count = self.index("end")

src/navigate/view/custom_widgets/scrollbars.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,28 +58,28 @@ def __init__(self, parent, *args, **kw):
5858
ttk.Frame.__init__(self, parent, *args, **kw)
5959

6060
# Create a canvas object and a vertical scrollbar for scrolling it.
61-
vscrollbar = ttk.Scrollbar(self, orient=tk.constants.VERTICAL)
62-
vscrollbar.pack(
63-
fill=tk.constants.Y, side=tk.constants.RIGHT, expand=tk.constants.FALSE
61+
self.vscrollbar = ttk.Scrollbar(self, orient=tk.constants.VERTICAL)
62+
self.vscrollbar.pack(
63+
fill=tk.constants.Y, side=tk.constants.RIGHT, expand=tk.constants.TRUE
6464
)
65-
hscrollbar = ttk.Scrollbar(self, orient=tk.constants.HORIZONTAL)
66-
hscrollbar.pack(
67-
fill=tk.constants.X, side=tk.constants.BOTTOM, expand=tk.constants.FALSE
65+
self.hscrollbar = ttk.Scrollbar(self, orient=tk.constants.HORIZONTAL)
66+
self.hscrollbar.pack(
67+
fill=tk.constants.X, side=tk.constants.BOTTOM, expand=tk.constants.TRUE
6868
)
6969
#: tk.Canvas: The canvas object for the ScrolledFrame.
7070
self.canvas = tk.Canvas(
7171
self,
7272
bd=0,
7373
highlightthickness=0,
74-
yscrollcommand=vscrollbar.set,
75-
xscrollcommand=hscrollbar.set,
74+
yscrollcommand=self.vscrollbar.set,
75+
xscrollcommand=self.hscrollbar.set,
7676
scrollregion=(0, 0, 100, 100),
7777
)
7878
self.canvas.pack(
7979
side=tk.constants.LEFT, fill=tk.constants.BOTH, expand=tk.constants.TRUE
8080
)
81-
vscrollbar.config(command=self.canvas.yview)
82-
hscrollbar.config(command=self.canvas.xview)
81+
self.vscrollbar.config(command=self.canvas.yview)
82+
self.hscrollbar.config(command=self.canvas.xview)
8383

8484
# Reset the view
8585
self.canvas.xview_moveto(0)
@@ -88,7 +88,7 @@ def __init__(self, parent, *args, **kw):
8888
# Create a frame inside the canvas which will be scrolled with it.
8989
#: ttk.Frame: The interior frame of the ScrolledFrame.
9090
self.interior = interior = ttk.Frame(self.canvas)
91-
_ = self.canvas.create_window(0, 0, window=interior, anchor=tk.constants.NW)
91+
self.interior_window = self.canvas.create_window(0, 0, window=interior, anchor=tk.constants.NW)
9292

9393
# Track changes to the canvas and frame width and sync them,
9494
# also updating the scrollbar.
@@ -110,7 +110,20 @@ def _configure_interior(event):
110110
if interior.winfo_reqheight() != self.canvas.winfo_reqheight():
111111
self.canvas.config(height=interior.winfo_reqheight())
112112

113-
interior.bind("<Configure>", _configure_interior)
113+
self.bind_id = interior.bind("<Configure>", _configure_interior)
114+
115+
def unbind_autosize(self):
116+
if self.bind_id:
117+
self.interior.unbind("<Configure>", self.bind_id)
118+
self.bind_id = 0
119+
120+
def resize(self, width, height):
121+
self.unbind_autosize()
122+
width = width-self.vscrollbar.winfo_width()
123+
height = height-self.hscrollbar.winfo_height()
124+
self.canvas.config(width=width)
125+
self.canvas.config(height=height)
126+
self.canvas.itemconfig(self.interior_window, width=width, height=height)
114127

115128
def mouse_wheel(self, event):
116129
"""Handle the mouse wheel event for scrolling.

src/navigate/view/main_application_window.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
from navigate.view.main_window_content.acquire_notebook import AcquireBar
4545
from navigate.view.main_window_content.menus import Menubar
4646
from navigate.view.custom_widgets.scrollbars import ScrolledFrame
47+
from navigate.view.custom_widgets.common import uniform_grid
4748

4849
# Logger Setup
4950
p = __name__.split(".")[1]
@@ -114,6 +115,8 @@ def __init__(self, root: tk.Tk, *args: Iterable, **kwargs: Dict[str, Any]) -> No
114115
#: tk.Tk: The main window of the application
115116
self.root = root
116117
self.root.title("navigate")
118+
self.root.grid_rowconfigure(0, weight=1)
119+
self.root.grid_columnconfigure(0, weight=1)
117120

118121
# keep icons relative to view directory structure
119122
view_directory = Path(__file__).resolve().parent
@@ -124,6 +127,7 @@ def __init__(self, root: tk.Tk, *args: Iterable, **kwargs: Dict[str, Any]) -> No
124127
pass
125128

126129
self.root.resizable(True, True)
130+
self.root.minsize(1350, 750)
127131
self.root.geometry("")
128132

129133
#: Menubar: The menu bar for the application
@@ -144,7 +148,7 @@ def __init__(self, root: tk.Tk, *args: Iterable, **kwargs: Dict[str, Any]) -> No
144148
row=0, column=0, columnspan=2, sticky=tk.NSEW, padx=3, pady=3
145149
)
146150
self.left_frame.grid(row=1, column=0, rowspan=2, sticky=tk.NSEW, padx=3, pady=3)
147-
self.right_frame.grid(row=1, column=1, sticky=tk.NSEW, padx=3, pady=3)
151+
self.right_frame.grid(row=1, column=1, sticky=tk.NW, padx=3, pady=3)
148152

149153
#: SettingsNotebook: The settings notebook for the application
150154
self.settings = SettingsNotebook(self.left_frame, self.root)
@@ -154,3 +158,10 @@ def __init__(self, root: tk.Tk, *args: Iterable, **kwargs: Dict[str, Any]) -> No
154158

155159
#: AcquireBar: The acquire bar for the application
156160
self.acquire_bar = AcquireBar(self.top_frame, self.root)
161+
162+
uniform_grid(self.scroll_frame.interior)
163+
self.grid_rowconfigure(0, weight=0)
164+
self.grid_columnconfigure(0, weight=0)
165+
self.grid_rowconfigure(1, weight=1)
166+
self.grid_columnconfigure(1, weight=1)
167+
uniform_grid(self.right_frame)

src/navigate/view/main_window_content/display_notebook.py

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ def __init__(
9090
self.add(self.mip_tab, text="MIP", sticky=tk.NSEW)
9191
self.add(self.waveform_tab, text="Waveforms", sticky=tk.NSEW)
9292

93+
uniform_grid(self)
94+
9395

9496
class MIPTab(tk.Frame):
9597
"""MipTab class."""
@@ -114,19 +116,13 @@ def __init__(
114116
#: int: The index of the tab.
115117
self.index = 1
116118

117-
#: Bool: The popup flag.
118-
self.is_popup = False
119-
120119
#: Bool: The docked flag.
121120
self.is_docked = True
122121

123122
#: ttk.Frame: The frame that will hold the camera image.
124123
self.cam_image = ttk.Frame(self)
125124
self.cam_image.grid(row=0, column=0, rowspan=3, sticky=tk.NSEW)
126125

127-
#: bool: The popup flag.
128-
self.is_popup = False
129-
130126
#: bool: The docked flag.
131127
self.is_docked = True
132128

@@ -182,16 +178,14 @@ def __init__(
182178
#: int: The index of the tab.
183179
self.index = 0
184180

185-
# Formatting
186-
tk.Grid.columnconfigure(self, "all", weight=1)
187-
tk.Grid.rowconfigure(self, "all", weight=1)
188-
189181
#: ttk.Frame: The frame that will hold the camera image.
190182
self.cam_image = ttk.Frame(self)
191-
self.cam_image.grid(row=0, column=0, rowspan=3, sticky=tk.NSEW)
183+
self.cam_image.grid(row=0, column=0, sticky=tk.NSEW)
184+
self.display_setting = ttk.Frame(self)
185+
self.display_setting.grid(row=0, column=1, sticky=tk.NSEW)
192186

193-
#: bool: The popup flag.
194-
self.is_popup = False
187+
self.grid_rowconfigure(0, weight=1)
188+
self.grid_columnconfigure(0, weight=1)
195189

196190
#: bool: The docked flag.
197191
self.is_docked = True
@@ -214,13 +208,9 @@ def __init__(
214208
#: FigureCanvasTkAgg: The canvas that will hold the camera image.
215209
self.matplotlib_canvas = FigureCanvasTkAgg(self.matplotlib_figure, self.canvas)
216210

217-
#: IntensityFrame: The frame that will hold the scale settings/palette color.
218-
self.lut = IntensityFrame(self)
219-
self.lut.grid(row=0, column=1, sticky=tk.NSEW, padx=5, pady=5)
220-
221211
#: tk.Scale: The slider that will hold the slice index.
222212
self.slider = tk.Scale(
223-
self,
213+
self.cam_image,
224214
from_=0,
225215
to=200,
226216
tickinterval=20,
@@ -229,19 +219,23 @@ def __init__(
229219
label="Slice",
230220
)
231221
self.slider.configure(state="disabled")
232-
self.slider.grid(row=3, column=0, sticky=tk.NSEW, padx=5, pady=5)
222+
self.slider.grid(row=1, column=0, sticky=tk.NSEW, padx=5, pady=5)
233223
self.slider.grid_remove()
234224

235225
#: HistogramFrame: The frame that will hold the histogram.
236-
self.histogram = HistogramFrame(self)
237-
self.histogram.grid(row=4, column=0, sticky=tk.NSEW, padx=5, pady=5)
226+
self.histogram = HistogramFrame(self.cam_image)
227+
self.histogram.grid(row=2, column=0, sticky=tk.NSEW, padx=5, pady=5)
228+
229+
#: IntensityFrame: The frame that will hold the scale settings/palette color.
230+
self.lut = IntensityFrame(self.display_setting)
231+
self.lut.grid(row=0, column=1, sticky=tk.NSEW, padx=5, pady=5)
238232

239233
#: MetricsFrame: The frame that will hold the camera selection and counts.
240-
self.image_metrics = MetricsFrame(self)
234+
self.image_metrics = MetricsFrame(self.display_setting)
241235
self.image_metrics.grid(row=1, column=1, sticky=tk.NSEW, padx=5, pady=5)
242236

243237
#: RenderFrame: The frame that will hold the live display functionality.
244-
self.live_frame = RenderFrame(self)
238+
self.live_frame = RenderFrame(self.display_setting)
245239
self.live_frame.grid(row=2, column=1, sticky=tk.NSEW, padx=5, pady=5)
246240

247241

0 commit comments

Comments
 (0)