47
47
48
48
class DockableNotebook (ttk .Notebook ):
49
49
"""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
51
51
right-clicking.
52
52
"""
53
53
@@ -66,17 +66,17 @@ def __init__(self, parent, root, *args, **kwargs):
66
66
**kwargs:
67
67
Keyword options for the ttk.Notebook class
68
68
"""
69
- ttk .Notebook .__init__ (self , parent , * args , ** kwargs )
69
+ super ().__init__ (parent , * args , ** kwargs )
70
+
70
71
#: tk.Tk: Tkinter root
71
72
self .root = root
73
+
74
+ #: int: Selected tab id where user right-clicked.
75
+ self .selected_tab_id = None
76
+
72
77
#: list: List of tab variables
73
78
self .tab_list = []
74
79
75
- # Formatting
76
- tk .Grid .columnconfigure (self , "all" , weight = 1 )
77
- tk .Grid .rowconfigure (self , "all" , weight = 1 )
78
-
79
- # Popup setup
80
80
#: tk.Menu: Tkinter menu
81
81
self .menu = tk .Menu (self , tearoff = 0 )
82
82
self .menu .add_command (label = "Popout Tab" , command = self .popout )
@@ -87,20 +87,23 @@ def __init__(self, parent, root, *args, **kwargs):
87
87
else :
88
88
self .bind ("<ButtonPress-3>" , self .find )
89
89
90
- def set_tablist (self , tab_list ):
90
+ def set_tablist (
91
+ self ,
92
+ tab_list : list
93
+ ) -> None :
91
94
"""Setter for tab list
92
95
93
96
Parameters
94
97
----------
95
98
tab_list: list
96
- List of tab variables
99
+ The list of tab variables
97
100
"""
98
101
self .tab_list = tab_list
99
102
100
- def get_absolute_position (self ):
103
+ def get_absolute_position (self ) -> tuple :
101
104
"""Get absolute position of mouse.
102
105
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.
104
107
105
108
Returns
106
109
-------
@@ -111,7 +114,7 @@ def get_absolute_position(self):
111
114
y = self .root .winfo_pointery ()
112
115
return x , y
113
116
114
- def find (self , event ) :
117
+ def find (self , event : tk . Event ) -> None :
115
118
"""Find the widget that was clicked on.
116
119
117
120
Will check if the proper widget element in the event is what we expect.
@@ -121,69 +124,103 @@ def find(self, event):
121
124
122
125
Parameters
123
126
----------
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
127
129
"""
128
130
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 )
129
133
if "label" in element :
130
134
try :
131
135
x , y = self .get_absolute_position ()
132
136
self .menu .tk_popup (x , y )
133
137
finally :
134
138
self .menu .grab_release ()
135
139
136
- def popout (self ):
140
+ def popout (self ) -> None :
137
141
"""Popout the currently selected tab.
138
142
139
143
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,
141
145
hidden, and then passed to a new Top Level window.
142
146
"""
143
147
# 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.
176
188
"""
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
181
193
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 )
0 commit comments