Skip to content

Commit 8a48b34

Browse files
Remember tab order
Now when you dock the popped out tab, it goes to the right position. Also, the tab you are hovering over when you right-click is the one that pops out.
1 parent 75c0b4e commit 8a48b34

File tree

6 files changed

+104
-70
lines changed

6 files changed

+104
-70
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ navigate
1212
[![codecov](https://codecov.io/gh/TheDeanLab/navigate/branch/develop/graph/badge.svg?token=270RFSZGG5)](https://codecov.io/gh/TheDeanLab/navigate)
1313

1414
**navigate** is an open source Python package for control of light-sheet microscopes.
15-
It allows for easily reconfigurable hardware setups and automated acquisition rotuines.
15+
It allows for easily reconfigurable hardware setups and automated acquisition routines.
1616

1717
### Quick install
1818

src/navigate/model/devices/stages/synthetic.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
import time
3535
from typing import Any, Dict
3636

37-
from typing_extensions import Optional
37+
from typing import Optional
3838

3939
# Third Party Imports
4040

src/navigate/tools/common_functions.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
import importlib
3434
from threading import Lock
3535

36-
from typing_extensions import Optional
36+
from typing import Optional
3737

3838

3939
def combine_funcs(*funclist):

src/navigate/view/custom_widgets/DockableNotebook.py

+98-61
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747

4848
class DockableNotebook(ttk.Notebook):
4949
"""Dockable Notebook that allows for tabs to be popped out into a separate
50-
windows by right clicking on the tab. The tab must be selected before
50+
windows by right-clicking on the tab. The tab must be selected before
5151
right-clicking.
5252
"""
5353

@@ -66,17 +66,17 @@ def __init__(self, parent, root, *args, **kwargs):
6666
**kwargs:
6767
Keyword options for the ttk.Notebook class
6868
"""
69-
ttk.Notebook.__init__(self, parent, *args, **kwargs)
69+
super().__init__(parent, *args, **kwargs)
70+
7071
#: tk.Tk: Tkinter root
7172
self.root = root
73+
74+
#: int: Selected tab id where user right-clicked.
75+
self.selected_tab_id = None
76+
7277
#: list: List of tab variables
7378
self.tab_list = []
7479

75-
# Formatting
76-
tk.Grid.columnconfigure(self, "all", weight=1)
77-
tk.Grid.rowconfigure(self, "all", weight=1)
78-
79-
# Popup setup
8080
#: tk.Menu: Tkinter menu
8181
self.menu = tk.Menu(self, tearoff=0)
8282
self.menu.add_command(label="Popout Tab", command=self.popout)
@@ -87,20 +87,23 @@ def __init__(self, parent, root, *args, **kwargs):
8787
else:
8888
self.bind("<ButtonPress-3>", self.find)
8989

90-
def set_tablist(self, tab_list):
90+
def set_tablist(
91+
self,
92+
tab_list: list
93+
) -> None:
9194
"""Setter for tab list
9295
9396
Parameters
9497
----------
9598
tab_list: list
96-
List of tab variables
99+
The list of tab variables
97100
"""
98101
self.tab_list = tab_list
99102

100-
def get_absolute_position(self):
103+
def get_absolute_position(self) -> tuple:
101104
"""Get absolute position of mouse.
102105
103-
This helps the popup menu appear where the mouse is right clicked.
106+
This helps the popup menu appear where the mouse is right-clicked.
104107
105108
Returns
106109
-------
@@ -111,7 +114,7 @@ def get_absolute_position(self):
111114
y = self.root.winfo_pointery()
112115
return x, y
113116

114-
def find(self, event):
117+
def find(self, event: tk.Event) -> None:
115118
"""Find the widget that was clicked on.
116119
117120
Will check if the proper widget element in the event is what we expect.
@@ -121,69 +124,103 @@ def find(self, event):
121124
122125
Parameters
123126
----------
124-
event: Tkinter event
125-
Holds information about the event that was triggered and caught by Tkinters
126-
event system
127+
event: tk.Event
128+
Tkinter event object
127129
"""
128130
element = event.widget.identify(event.x, event.y)
131+
self.selected_tab_id = self.index(f"@{event.x},{event.y}") # Add this line
132+
print(self.selected_tab_id)
129133
if "label" in element:
130134
try:
131135
x, y = self.get_absolute_position()
132136
self.menu.tk_popup(x, y)
133137
finally:
134138
self.menu.grab_release()
135139

136-
def popout(self):
140+
def popout(self) -> None:
137141
"""Popout the currently selected tab.
138142
139143
Gets the currently selected tab, the tabs name and checks if the tab name is
140-
in the tab list. If the tab is in the list, its removed from the list,
144+
in the tab list. If the tab is in the list, it's removed from the list,
141145
hidden, and then passed to a new Top Level window.
142146
"""
143147
# Get ref to correct tab to popout
144-
tab = self.select()
145-
tab_text = self.tab(tab)["text"]
146-
for tab_name in self.tab_list:
147-
if tab_text == self.tab(tab_name)["text"]:
148-
tab = tab_name
149-
self.tab_list.remove(tab_name)
150-
self.hide(tab)
151-
self.root.wm_manage(tab)
152-
153-
# self.root.wm_title(tab, tab_text)
154-
tk.Wm.title(tab, tab_text)
155-
tk.Wm.protocol(tab, "WM_DELETE_WINDOW", lambda: self.dismiss(tab, tab_text))
156-
if tab_text == "Camera View":
157-
tk.Wm.minsize(tab, 663, 597)
158-
tab.is_docked = False
159-
elif tab_text == "Waveform Settings":
160-
tab.is_docked = False
161-
162-
def dismiss(self, tab, tab_text):
163-
"""Dismisses the popup menu
164-
165-
This function is called when the top level that the tab was originally passed to
166-
has been closed. The window manager releases control and then the tab is
167-
added back to its original ttk.Notebook.
168-
169-
Parameters
170-
----------
171-
tab: Tkinter tab (path to widget represented as a str)
172-
The tab that was popped out, this reference to the dismiss function is
173-
associated with this tab
174-
tab_text: string
175-
Name of the tab as it appears in the GUI
148+
if self.selected_tab_id is None:
149+
return
150+
151+
selected_text = self.tab(self.selected_tab_id, "text")
152+
tab_widget = None
153+
for t in self.tab_list:
154+
if self.tab(t, "text") == selected_text:
155+
tab_widget = t
156+
break
157+
158+
if not tab_widget:
159+
tab_widget = self.selected_tab_id
160+
161+
# Save the original index and the tab's text
162+
tab_widget._original_index = self.index(tab_widget)
163+
tab_widget._saved_text = selected_text
164+
165+
if tab_widget in self.tab_list:
166+
self.tab_list.remove(tab_widget)
167+
self.hide(tab_widget)
168+
self.root.wm_manage(tab_widget)
169+
170+
tk.Wm.title(tab_widget, selected_text)
171+
tk.Wm.protocol(
172+
tab_widget,
173+
"WM_DELETE_WINDOW",
174+
lambda: self.dismiss(tab_widget)
175+
)
176+
177+
if selected_text == "Camera View":
178+
tk.Wm.minsize(tab_widget, 663, 597)
179+
tab_widget.is_docked = False
180+
elif selected_text == "Waveform Settings":
181+
tab_widget.is_docked = False
182+
183+
def dismiss(self, tab_widget):
184+
"""
185+
Called when the popout window is closed.
186+
We 'forget' the window manager, re‐insert the tab into the notebook,
187+
and restore its label text and position.
176188
"""
177-
self.root.wm_forget(tab)
178-
tab.grid(row=0, column=0)
179-
if self.index("end") - 1 > tab.index:
180-
self.insert(tab.index, tab)
189+
popout = getattr(tab_widget, "_popout_window", None)
190+
if popout:
191+
popout.destroy()
192+
del tab_widget._popout_window
181193
else:
182-
self.insert("end", tab)
183-
self.tab(tab, text=tab_text)
184-
self.tab_list.append(tab)
185-
if tab_text == "Camera View":
186-
tab.canvas.configure(width=512, height=512)
187-
tab.is_docked = True
188-
elif tab_text == "Waveform Settings":
189-
tab.is_docked = True
194+
# If we used wm_manage successfully, then do wm_forget
195+
self.root.wm_forget(tab_widget)
196+
197+
# Then remove it from any geometry manager just in case
198+
tab_widget.grid_forget()
199+
tab_widget.pack_forget()
200+
201+
# Retrieve the original index and the saved text
202+
original_index = getattr(tab_widget, "_original_index", None)
203+
saved_text = getattr(tab_widget, "_saved_text", "Untitled")
204+
205+
if original_index is not None:
206+
current_count = self.index("end")
207+
if original_index >= current_count:
208+
original_index = "end"
209+
self.insert(original_index, tab_widget)
210+
211+
else:
212+
self.add(tab_widget)
213+
214+
self.tab(tab_widget, text=saved_text)
215+
self.tab_list.append(tab_widget)
216+
217+
# Restore the tab's docked state
218+
if saved_text == "Camera View":
219+
if hasattr(tab_widget, "canvas"):
220+
tab_widget.canvas.configure(width=512, height=512)
221+
tab_widget.is_docked = True
222+
elif saved_text == "Waveform Settings":
223+
tab_widget.is_docked = True
224+
225+
# Select tab in main window
226+
self.select(tab_widget)

src/navigate/view/main_window_content/camera_tab.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
logger = logging.getLogger(p)
4848

4949

50-
class CameraSettingsTab(ttk.Frame):
50+
class CameraSettingsTab(tk.Frame):
5151
"""
5252
This class holds and controls the layout of the major label frames for the
5353
camera settings tab in the settings notebook. Any imported classes are children
@@ -73,14 +73,11 @@ def __init__(
7373
Keyword arguments for ttk.Frame
7474
"""
7575
# Init Frame
76-
ttk.Frame.__init__(self, setntbk, *args, **kwargs)
76+
super().__init__(setntbk, *args, **kwargs)
7777

7878
#: The index of the tab in the notebook
7979
self.index = 1
8080

81-
tk.Grid.columnconfigure(self, "all", weight=1)
82-
tk.Grid.rowconfigure(self, "all", weight=1)
83-
8481
#: tk.Frame: The camera mode frame
8582
self.camera_mode = CameraMode(self)
8683
self.camera_mode.grid(row=0, column=0, sticky=tk.NSEW, padx=10, pady=10)

src/navigate/view/main_window_content/settings_notebook.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def __init__(self,
8181
"""
8282

8383
# Init notebook
84-
DockableNotebook.__init__(self, frame_left, root, *args, **kwargs)
84+
super().__init__(frame_left, root, *args, **kwargs)
8585

8686
# Putting notebook 1 into left frame
8787
self.grid(row=0, column=0)

0 commit comments

Comments
 (0)