Merge lp:~stephen-j-boddy/terminator/feature-remember-focus into lp:terminator/trunk
- feature-remember-focus
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 1490 |
Proposed branch: | lp:~stephen-j-boddy/terminator/feature-remember-focus |
Merge into: | lp:terminator/trunk |
Diff against target: |
443 lines (+186/-28) 6 files modified
terminatorlib/container.py (+15/-3) terminatorlib/notebook.py (+89/-14) terminatorlib/terminal.py (+19/-9) terminatorlib/terminator.py (+49/-0) terminatorlib/util.py (+3/-1) terminatorlib/window.py (+11/-1) |
To merge this branch: | bzr merge lp:~stephen-j-boddy/terminator/feature-remember-focus |
Related bugs: | |
Related blueprints: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Terminator | Pending | ||
Review via email: mp+199654@code.launchpad.net |
Commit message
Description of the change
OK, This took longer and was more tricky than I first anticipated. When loading/saving layouts, Terminator now remembers which:
1) was the active window,
2) terminal was active in non-tabbed windows,
3) tab was current in tabbed windows,
4) terminal was active in each tab (also remembered when switching between tabs at runtime),
5) UUID was assigned to a terminal, and restores it.
(5) means you can rely on the shell environment variable TERMINATOR_UUID to always be the same on relaunching a layout.
I tend to dogfood everything I code, but I'd appreciate it if some others could download and give this a workout before I go and apply to trunk.
Stephen Boddy (stephen-j-boddy) wrote : | # |
- 1484. By Stephen Boddy
-
Remove the unnecessary cruft left over in the windows on_focus_in related to terminal focus
- 1485. By Stephen Boddy
-
Missed a line in the cruft removal
Preview Diff
1 | === modified file 'terminatorlib/container.py' |
2 | --- terminatorlib/container.py 2013-09-25 22:07:01 +0000 |
3 | +++ terminatorlib/container.py 2014-01-21 22:41:47 +0000 |
4 | @@ -263,6 +263,7 @@ |
5 | |
6 | if hasattr(self, 'isfullscreen'): |
7 | layout['fullscreen'] = self.isfullscreen |
8 | + |
9 | if hasattr(self, 'ratio'): |
10 | layout['ratio'] = self.ratio |
11 | |
12 | @@ -272,15 +273,26 @@ |
13 | if hasattr(self, 'title'): |
14 | layout['title'] = self.title.text |
15 | |
16 | - labels = [] |
17 | if mytype == 'Notebook': |
18 | + labels = [] |
19 | + last_active_term = [] |
20 | for tabnum in xrange(0, self.get_n_pages()): |
21 | page = self.get_nth_page(tabnum) |
22 | label = self.get_tab_label(page) |
23 | labels.append(label.get_custom_label()) |
24 | + last_active_term.append(self.last_active_term[self.get_nth_page(tabnum)]) |
25 | + layout['labels'] = labels |
26 | + layout['last_active_term'] = last_active_term |
27 | layout['active_page'] = self.get_current_page() |
28 | - if len(labels) > 0: |
29 | - layout['labels'] = labels |
30 | + else: |
31 | + if hasattr(self, 'last_active_term') and self.last_active_term is not None: |
32 | + layout['last_active_term'] = self.last_active_term |
33 | + |
34 | + if mytype == 'Window': |
35 | + if self.uuid == self.terminator.last_active_window: |
36 | + layout['last_active_window'] = True |
37 | + else: |
38 | + layout['last_active_window'] = False |
39 | |
40 | name = 'child%d' % count |
41 | count = count + 1 |
42 | |
43 | === modified file 'terminatorlib/notebook.py' |
44 | --- terminatorlib/notebook.py 2013-10-25 14:55:26 +0000 |
45 | +++ terminatorlib/notebook.py 2014-01-21 22:41:47 +0000 |
46 | @@ -12,11 +12,14 @@ |
47 | from container import Container |
48 | from editablelabel import EditableLabel |
49 | from translation import _ |
50 | -from util import err, dbg, enumerate_descendants |
51 | +from util import err, dbg, enumerate_descendants, make_uuid |
52 | |
53 | class Notebook(Container, gtk.Notebook): |
54 | """Class implementing a gtk.Notebook container""" |
55 | window = None |
56 | + last_active_term = None |
57 | + pending_on_tab_switch = None |
58 | + pending_on_tab_switch_args = None |
59 | |
60 | def __init__(self, window): |
61 | """Class initialiser""" |
62 | @@ -30,12 +33,16 @@ |
63 | self.window = window |
64 | gobject.type_register(Notebook) |
65 | self.register_signals(Notebook) |
66 | + self.connect('switch-page', self.deferred_on_tab_switch) |
67 | self.configure() |
68 | |
69 | child = window.get_child() |
70 | window.remove(child) |
71 | window.add(self) |
72 | self.newtab(widget=child) |
73 | + if window.last_active_term: |
74 | + self.set_last_active_term(window.last_active_term) |
75 | + window.last_active_term = None |
76 | |
77 | self.show_all() |
78 | |
79 | @@ -63,6 +70,7 @@ |
80 | style.xthickness = 0 |
81 | style.ythickness = 0 |
82 | self.modify_style(style) |
83 | + self.last_active_term = {} |
84 | |
85 | def create_layout(self, layout): |
86 | """Apply layout configuration""" |
87 | @@ -117,10 +125,14 @@ |
88 | label = self.get_tab_label(page) |
89 | label.set_custom_label(labeltext) |
90 | page.create_layout(children[child_key]) |
91 | + |
92 | + if layout.get('last_active_term', None): |
93 | + self.last_active_term[page] = make_uuid(layout['last_active_term'][num]) |
94 | num = num + 1 |
95 | |
96 | if layout.has_key('active_page'): |
97 | - self.set_current_page(int(layout['active_page'])) |
98 | + # Need to do it later, or layout changes result |
99 | + gobject.idle_add(self.set_current_page, int(layout['active_page'])) |
100 | else: |
101 | self.set_current_page(0) |
102 | |
103 | @@ -167,12 +179,13 @@ |
104 | self.set_current_page(page_num) |
105 | |
106 | self.show_all() |
107 | - terminal.grab_focus() |
108 | |
109 | while gtk.events_pending(): |
110 | gtk.main_iteration_do(False) |
111 | self.get_toplevel().set_pos_by_ratio = False |
112 | |
113 | + gobject.idle_add(terminal.ensure_visible_and_focussed) |
114 | + |
115 | def add(self, widget, metadata=None): |
116 | """Add a widget to the container""" |
117 | dbg('adding a new tab') |
118 | @@ -268,15 +281,22 @@ |
119 | |
120 | dbg('inserting page at position: %s' % tabpos) |
121 | self.insert_page(widget, None, tabpos) |
122 | - self.set_tab_label(widget, label) |
123 | - self.set_tab_label_packing(widget, not self.config['scroll_tabbar'], |
124 | - not self.config['scroll_tabbar'], |
125 | - gtk.PACK_START) |
126 | + child_widgets = [widget] |
127 | + child_widgets .extend(enumerate_descendants(widget)) |
128 | + term_widget = None |
129 | + for term_widget in child_widgets: |
130 | + if maker.isinstance(term_widget, 'Terminal'): |
131 | + self.set_last_active_term(term_widget.uuid) |
132 | + self.set_tab_label(term_widget, label) |
133 | + self.set_tab_label_packing(term_widget, not self.config['scroll_tabbar'], |
134 | + not self.config['scroll_tabbar'], |
135 | + gtk.PACK_START) |
136 | + break |
137 | |
138 | self.set_tab_reorderable(widget, True) |
139 | self.set_current_page(tabpos) |
140 | self.show_all() |
141 | - if maker.isinstance(widget, 'Terminal'): |
142 | + if maker.isinstance(term_widget, 'Terminal'): |
143 | widget.grab_focus() |
144 | |
145 | def wrapcloseterm(self, widget): |
146 | @@ -311,6 +331,7 @@ |
147 | |
148 | if maker.isinstance(child, 'Terminal'): |
149 | dbg('Notebook::closetab: child is a single Terminal') |
150 | + del nb.last_active_term[child] |
151 | child.close() |
152 | # FIXME: We only do this del and return here to avoid removing the |
153 | # page below, which child.close() implicitly does |
154 | @@ -318,9 +339,7 @@ |
155 | return |
156 | elif maker.isinstance(child, 'Container'): |
157 | dbg('Notebook::closetab: child is a Container') |
158 | - dialog = self.construct_confirm_close(self.window, _('tab')) |
159 | - result = dialog.run() |
160 | - dialog.destroy() |
161 | + result = self.construct_confirm_close(self.window, _('tab')) |
162 | |
163 | if result == gtk.RESPONSE_ACCEPT: |
164 | containers = None |
165 | @@ -340,9 +359,6 @@ |
166 | err('Notebook::closetab: child is unknown type %s' % child) |
167 | return |
168 | |
169 | - nb.remove_page(tabnum) |
170 | - del(label) |
171 | - |
172 | def resizeterm(self, widget, keyname): |
173 | """Handle a keyboard event requesting a terminal resize""" |
174 | raise NotImplementedError('resizeterm') |
175 | @@ -403,6 +419,65 @@ |
176 | terms = parent.get_visible_terminals() |
177 | terms.keys()[-1].grab_focus() |
178 | |
179 | + def page_num_descendant(self, widget): |
180 | + """Find the tabnum of the tab containing a widget at any level""" |
181 | + tabnum = self.page_num(widget) |
182 | + dbg("widget is direct child if not equal -1 - tabnum: %d" % tabnum) |
183 | + while tabnum == -1 and widget.get_parent(): |
184 | + widget = widget.get_parent() |
185 | + tabnum = self.page_num(widget) |
186 | + dbg("found tabnum containing widget: %d" % tabnum) |
187 | + return tabnum |
188 | + |
189 | + def set_last_active_term(self, uuid): |
190 | + """Set the last active term for uuid""" |
191 | + widget = self.terminator.find_terminal_by_uuid(uuid.urn) |
192 | + if not widget: |
193 | + err("Cannot find terminal with uuid: %s, so cannot make it active" % (uuid.urn)) |
194 | + return |
195 | + tabnum = self.page_num_descendant(widget) |
196 | + if tabnum == -1: |
197 | + err("No tabnum found for terminal with uuid: %s" % (uuid.urn)) |
198 | + return |
199 | + nth_page = self.get_nth_page(tabnum) |
200 | + self.last_active_term[nth_page] = uuid |
201 | + |
202 | + def clean_last_active_term(self): |
203 | + """Clean up old entries in last_active_term""" |
204 | + if self.terminator.doing_layout == True: |
205 | + return |
206 | + last_active_term = {} |
207 | + for tabnum in xrange(0, self.get_n_pages()): |
208 | + nth_page = self.get_nth_page(tabnum) |
209 | + if nth_page in self.last_active_term: |
210 | + last_active_term[nth_page] = self.last_active_term[nth_page] |
211 | + self.last_active_term = last_active_term |
212 | + |
213 | + def deferred_on_tab_switch(self, notebook, page, page_num, data=None): |
214 | + """Prime a single idle tab switch signal, using the most recent set of params""" |
215 | + tabs_last_active_term = self.last_active_term.get(self.get_nth_page(page_num), None) |
216 | + data = {'tabs_last_active_term':tabs_last_active_term} |
217 | + |
218 | + self.pending_on_tab_switch_args = (notebook, page, page_num, data) |
219 | + if self.pending_on_tab_switch == True: |
220 | + return |
221 | + gobject.idle_add(self.do_deferred_on_tab_switch) |
222 | + self.pending_on_tab_switch = True |
223 | + |
224 | + def do_deferred_on_tab_switch(self): |
225 | + """Perform the latest tab switch signal, and resetting the pending flag""" |
226 | + self.on_tab_switch(*self.pending_on_tab_switch_args) |
227 | + self.pending_on_tab_switch = False |
228 | + self.pending_on_tab_switch_args = None |
229 | + |
230 | + def on_tab_switch(self, notebook, page, page_num, data=None): |
231 | + """Do the real work for a tab switch""" |
232 | + tabs_last_active_term = data['tabs_last_active_term'] |
233 | + if tabs_last_active_term: |
234 | + term = self.terminator.find_terminal_by_uuid(tabs_last_active_term.urn) |
235 | + gobject.idle_add(term.ensure_visible_and_focussed) |
236 | + return True |
237 | + |
238 | class TabLabel(gtk.HBox): |
239 | """Class implementing a label widget for Notebook tabs""" |
240 | notebook = None |
241 | |
242 | === modified file 'terminatorlib/terminal.py' |
243 | --- terminatorlib/terminal.py 2013-10-25 14:57:14 +0000 |
244 | +++ terminatorlib/terminal.py 2014-01-21 22:41:47 +0000 |
245 | @@ -15,7 +15,7 @@ |
246 | import subprocess |
247 | import urllib |
248 | |
249 | -from util import dbg, err, gerr, spawn_new_terminator |
250 | +from util import dbg, err, gerr, spawn_new_terminator, make_uuid |
251 | import util |
252 | from config import Config |
253 | from cwd import get_default_cwd |
254 | @@ -1069,13 +1069,12 @@ |
255 | maker = Factory() |
256 | |
257 | if maker.isinstance(topchild, 'Notebook'): |
258 | - prevtmp = None |
259 | - tmp = self.get_parent() |
260 | - while tmp != topchild: |
261 | - prevtmp = tmp |
262 | - tmp = tmp.get_parent() |
263 | - page = topchild.page_num(prevtmp) |
264 | - topchild.set_current_page(page) |
265 | + # Find which page number this term is on |
266 | + tabnum = topchild.page_num_descendant(self) |
267 | + # If terms page number is not the current one, switch to it |
268 | + current_page = topchild.get_current_page() |
269 | + if tabnum != current_page: |
270 | + topchild.set_current_page(tabnum) |
271 | |
272 | self.grab_focus() |
273 | |
274 | @@ -1088,7 +1087,15 @@ |
275 | self.vte.set_colors(self.fgcolor_active, self.bgcolor, |
276 | self.palette_active) |
277 | self.set_cursor_color() |
278 | - self.terminator.last_focused_term = self |
279 | + if not self.terminator.doing_layout: |
280 | + self.terminator.last_focused_term = self |
281 | + if self.get_toplevel().is_child_notebook(): |
282 | + notebook = self.get_toplevel().get_children()[0] |
283 | + notebook.set_last_active_term(self.uuid) |
284 | + notebook.clean_last_active_term() |
285 | + self.get_toplevel().last_active_term = None |
286 | + else: |
287 | + self.get_toplevel().last_active_term = self.uuid |
288 | self.emit('focus-in') |
289 | |
290 | def on_vte_focus_out(self, _widget, _event): |
291 | @@ -1490,6 +1497,7 @@ |
292 | title = self.titlebar.get_custom_string() |
293 | if title: |
294 | layout['title'] = title |
295 | + layout['uuid'] = self.uuid |
296 | name = 'terminal%d' % count |
297 | count = count + 1 |
298 | global_layout[name] = layout |
299 | @@ -1511,6 +1519,8 @@ |
300 | self.titlebar.set_custom_string(layout['title']) |
301 | if layout.has_key('directory') and layout['directory'] != '': |
302 | self.directory = layout['directory'] |
303 | + if layout.has_key('uuid') and layout['uuid'] != '': |
304 | + self.uuid = make_uuid(layout['uuid']) |
305 | |
306 | def scroll_by_page(self, pages): |
307 | """Scroll up or down in pages""" |
308 | |
309 | === modified file 'terminatorlib/terminator.py' |
310 | --- terminatorlib/terminator.py 2013-09-04 20:59:27 +0000 |
311 | +++ terminatorlib/terminator.py 2014-01-21 22:41:47 +0000 |
312 | @@ -34,6 +34,7 @@ |
313 | debug_address = None |
314 | |
315 | doing_layout = None |
316 | + last_active_window = None |
317 | |
318 | groupsend = None |
319 | groupsend_type = {'all':0, 'group':1, 'off':2} |
320 | @@ -291,11 +292,59 @@ |
321 | def layout_done(self): |
322 | """Layout operations have finished, record that fact""" |
323 | self.doing_layout = False |
324 | + maker = Factory() |
325 | + |
326 | + window_last_active_term_mapping = {} |
327 | + for window in self.windows: |
328 | + if window.is_child_notebook(): |
329 | + source = window.get_toplevel().get_children()[0] |
330 | + else: |
331 | + source = window |
332 | + window_last_active_term_mapping[window] = copy.copy(source.last_active_term) |
333 | |
334 | for terminal in self.terminals: |
335 | if not terminal.pid: |
336 | terminal.spawn_child() |
337 | |
338 | + for window in self.windows: |
339 | + if window.is_child_notebook(): |
340 | + # For windows with a notebook |
341 | + notebook = window.get_toplevel().get_children()[0] |
342 | + # Cycle through pages by number |
343 | + for page in xrange(0, notebook.get_n_pages()): |
344 | + # Try and get the entry in the previously saved mapping |
345 | + mapping = window_last_active_term_mapping[window] |
346 | + page_last_active_term = mapping.get(notebook.get_nth_page(page), None) |
347 | + if page_last_active_term is None: |
348 | + # Couldn't find entry, so we find the first child of type Terminal |
349 | + children = notebook.get_nth_page(page).get_children() |
350 | + for page_last_active_term in children: |
351 | + if maker.isinstance(page_last_active_term, 'Terminal'): |
352 | + page_last_active_term = page_last_active_term.uuid |
353 | + break |
354 | + else: |
355 | + err('Should never reach here!') |
356 | + page_last_active_term = None |
357 | + if page_last_active_term is None: |
358 | + # Bail on this tab as we're having no luck here, continue with the next |
359 | + continue |
360 | + # Set the notebook entry, then ensure Terminal is visible and focussed |
361 | + urn = page_last_active_term.urn |
362 | + notebook.last_active_term[notebook.get_nth_page(page)] = page_last_active_term |
363 | + if urn: |
364 | + term = self.find_terminal_by_uuid(urn) |
365 | + if term: |
366 | + term.ensure_visible_and_focussed() |
367 | + else: |
368 | + # For windows without a notebook ensure Terminal is visible and focussed |
369 | + if window_last_active_term_mapping[window]: |
370 | + term = self.find_terminal_by_uuid(window_last_active_term_mapping[window].urn) |
371 | + term.ensure_visible_and_focussed() |
372 | + |
373 | + for window in self.windows: |
374 | + if window.uuid == self.last_active_window: |
375 | + window.show() |
376 | + |
377 | def reconfigure(self): |
378 | """Update configuration for the whole application""" |
379 | |
380 | |
381 | === modified file 'terminatorlib/util.py' |
382 | --- terminatorlib/util.py 2013-08-28 21:09:17 +0000 |
383 | +++ terminatorlib/util.py 2014-01-21 22:41:47 +0000 |
384 | @@ -277,8 +277,10 @@ |
385 | len(terminals), parent)) |
386 | return(containers, terminals) |
387 | |
388 | -def make_uuid(): |
389 | +def make_uuid(str_uuid=None): |
390 | """Generate a UUID for an object""" |
391 | + if str_uuid: |
392 | + return uuid.UUID(str_uuid) |
393 | return uuid.uuid4() |
394 | |
395 | def inject_uuid(target): |
396 | |
397 | === modified file 'terminatorlib/window.py' |
398 | --- terminatorlib/window.py 2013-10-25 14:57:14 +0000 |
399 | +++ terminatorlib/window.py 2014-01-21 22:41:47 +0000 |
400 | @@ -5,12 +5,13 @@ |
401 | |
402 | import copy |
403 | import time |
404 | +import uuid |
405 | import pygtk |
406 | pygtk.require('2.0') |
407 | import gobject |
408 | import gtk |
409 | |
410 | -from util import dbg, err |
411 | +from util import dbg, err, make_uuid |
412 | import util |
413 | from translation import _ |
414 | from version import APP_NAME |
415 | @@ -38,6 +39,7 @@ |
416 | position = None |
417 | ignore_startup_show = None |
418 | set_pos_by_ratio = None |
419 | + last_active_term = None |
420 | |
421 | zoom_data = None |
422 | |
423 | @@ -239,6 +241,8 @@ |
424 | def on_focus_in(self, window, event): |
425 | """Focus has entered the window""" |
426 | self.set_urgency_hint(False) |
427 | + if not self.terminator.doing_layout: |
428 | + self.terminator.last_active_window = self.uuid |
429 | # FIXME: Cause the terminal titlebars to update here |
430 | |
431 | def is_child_notebook(self): |
432 | @@ -865,6 +869,12 @@ |
433 | |
434 | self.get_children()[0].create_layout(child) |
435 | |
436 | + if layout.has_key('last_active_term') and layout['last_active_term'] not in ['', None]: |
437 | + self.last_active_term = make_uuid(layout['last_active_term']) |
438 | + |
439 | + if layout.has_key('last_active_window') and layout['last_active_window'] == 'True': |
440 | + self.terminator.last_active_window = self.uuid |
441 | + |
442 | class WindowTitle(object): |
443 | """Class to handle the setting of the window title""" |
444 |
Ha, continuing my tradition of spotting errors after I submit for merging, I just noticed the following: window. py
terminatorlib/
@@ -239,6 +241,17 @@
Of that entire block of added lines, only the last two are needed. It doesn't do any harm being there, so I'll change it later. Must be something I tried while fighting to get all these other lines to do what *I* wanted.