Merge lp:~stephen-j-boddy/terminator/feature-remember-focus into lp:terminator/trunk

Proposed by Stephen Boddy
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
Reviewer Review Type Date Requested Status
Terminator Pending
Review via email: mp+199654@code.launchpad.net

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.

To post a comment you must log in.
Revision history for this message
Stephen Boddy (stephen-j-boddy) wrote :

Ha, continuing my tradition of spotting errors after I submit for merging, I just noticed the following:
terminatorlib/window.py
@@ -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.

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'terminatorlib/container.py'
--- terminatorlib/container.py 2013-09-25 22:07:01 +0000
+++ terminatorlib/container.py 2014-01-21 22:41:47 +0000
@@ -263,6 +263,7 @@
263 263
264 if hasattr(self, 'isfullscreen'):264 if hasattr(self, 'isfullscreen'):
265 layout['fullscreen'] = self.isfullscreen265 layout['fullscreen'] = self.isfullscreen
266
266 if hasattr(self, 'ratio'):267 if hasattr(self, 'ratio'):
267 layout['ratio'] = self.ratio268 layout['ratio'] = self.ratio
268269
@@ -272,15 +273,26 @@
272 if hasattr(self, 'title'):273 if hasattr(self, 'title'):
273 layout['title'] = self.title.text274 layout['title'] = self.title.text
274275
275 labels = []
276 if mytype == 'Notebook':276 if mytype == 'Notebook':
277 labels = []
278 last_active_term = []
277 for tabnum in xrange(0, self.get_n_pages()):279 for tabnum in xrange(0, self.get_n_pages()):
278 page = self.get_nth_page(tabnum)280 page = self.get_nth_page(tabnum)
279 label = self.get_tab_label(page)281 label = self.get_tab_label(page)
280 labels.append(label.get_custom_label())282 labels.append(label.get_custom_label())
283 last_active_term.append(self.last_active_term[self.get_nth_page(tabnum)])
284 layout['labels'] = labels
285 layout['last_active_term'] = last_active_term
281 layout['active_page'] = self.get_current_page()286 layout['active_page'] = self.get_current_page()
282 if len(labels) > 0:287 else:
283 layout['labels'] = labels288 if hasattr(self, 'last_active_term') and self.last_active_term is not None:
289 layout['last_active_term'] = self.last_active_term
290
291 if mytype == 'Window':
292 if self.uuid == self.terminator.last_active_window:
293 layout['last_active_window'] = True
294 else:
295 layout['last_active_window'] = False
284296
285 name = 'child%d' % count297 name = 'child%d' % count
286 count = count + 1298 count = count + 1
287299
=== modified file 'terminatorlib/notebook.py'
--- terminatorlib/notebook.py 2013-10-25 14:55:26 +0000
+++ terminatorlib/notebook.py 2014-01-21 22:41:47 +0000
@@ -12,11 +12,14 @@
12from container import Container12from container import Container
13from editablelabel import EditableLabel13from editablelabel import EditableLabel
14from translation import _14from translation import _
15from util import err, dbg, enumerate_descendants15from util import err, dbg, enumerate_descendants, make_uuid
1616
17class Notebook(Container, gtk.Notebook):17class Notebook(Container, gtk.Notebook):
18 """Class implementing a gtk.Notebook container"""18 """Class implementing a gtk.Notebook container"""
19 window = None19 window = None
20 last_active_term = None
21 pending_on_tab_switch = None
22 pending_on_tab_switch_args = None
2023
21 def __init__(self, window):24 def __init__(self, window):
22 """Class initialiser"""25 """Class initialiser"""
@@ -30,12 +33,16 @@
30 self.window = window33 self.window = window
31 gobject.type_register(Notebook)34 gobject.type_register(Notebook)
32 self.register_signals(Notebook)35 self.register_signals(Notebook)
36 self.connect('switch-page', self.deferred_on_tab_switch)
33 self.configure()37 self.configure()
3438
35 child = window.get_child()39 child = window.get_child()
36 window.remove(child)40 window.remove(child)
37 window.add(self)41 window.add(self)
38 self.newtab(widget=child)42 self.newtab(widget=child)
43 if window.last_active_term:
44 self.set_last_active_term(window.last_active_term)
45 window.last_active_term = None
3946
40 self.show_all()47 self.show_all()
4148
@@ -63,6 +70,7 @@
63 style.xthickness = 070 style.xthickness = 0
64 style.ythickness = 071 style.ythickness = 0
65 self.modify_style(style)72 self.modify_style(style)
73 self.last_active_term = {}
6674
67 def create_layout(self, layout):75 def create_layout(self, layout):
68 """Apply layout configuration"""76 """Apply layout configuration"""
@@ -117,10 +125,14 @@
117 label = self.get_tab_label(page)125 label = self.get_tab_label(page)
118 label.set_custom_label(labeltext)126 label.set_custom_label(labeltext)
119 page.create_layout(children[child_key])127 page.create_layout(children[child_key])
128
129 if layout.get('last_active_term', None):
130 self.last_active_term[page] = make_uuid(layout['last_active_term'][num])
120 num = num + 1131 num = num + 1
121132
122 if layout.has_key('active_page'):133 if layout.has_key('active_page'):
123 self.set_current_page(int(layout['active_page']))134 # Need to do it later, or layout changes result
135 gobject.idle_add(self.set_current_page, int(layout['active_page']))
124 else:136 else:
125 self.set_current_page(0)137 self.set_current_page(0)
126138
@@ -167,12 +179,13 @@
167 self.set_current_page(page_num)179 self.set_current_page(page_num)
168180
169 self.show_all()181 self.show_all()
170 terminal.grab_focus()
171182
172 while gtk.events_pending():183 while gtk.events_pending():
173 gtk.main_iteration_do(False)184 gtk.main_iteration_do(False)
174 self.get_toplevel().set_pos_by_ratio = False185 self.get_toplevel().set_pos_by_ratio = False
175186
187 gobject.idle_add(terminal.ensure_visible_and_focussed)
188
176 def add(self, widget, metadata=None):189 def add(self, widget, metadata=None):
177 """Add a widget to the container"""190 """Add a widget to the container"""
178 dbg('adding a new tab')191 dbg('adding a new tab')
@@ -268,15 +281,22 @@
268281
269 dbg('inserting page at position: %s' % tabpos)282 dbg('inserting page at position: %s' % tabpos)
270 self.insert_page(widget, None, tabpos)283 self.insert_page(widget, None, tabpos)
271 self.set_tab_label(widget, label)284 child_widgets = [widget]
272 self.set_tab_label_packing(widget, not self.config['scroll_tabbar'],285 child_widgets .extend(enumerate_descendants(widget))
273 not self.config['scroll_tabbar'],286 term_widget = None
274 gtk.PACK_START)287 for term_widget in child_widgets:
288 if maker.isinstance(term_widget, 'Terminal'):
289 self.set_last_active_term(term_widget.uuid)
290 self.set_tab_label(term_widget, label)
291 self.set_tab_label_packing(term_widget, not self.config['scroll_tabbar'],
292 not self.config['scroll_tabbar'],
293 gtk.PACK_START)
294 break
275295
276 self.set_tab_reorderable(widget, True)296 self.set_tab_reorderable(widget, True)
277 self.set_current_page(tabpos)297 self.set_current_page(tabpos)
278 self.show_all()298 self.show_all()
279 if maker.isinstance(widget, 'Terminal'):299 if maker.isinstance(term_widget, 'Terminal'):
280 widget.grab_focus()300 widget.grab_focus()
281301
282 def wrapcloseterm(self, widget):302 def wrapcloseterm(self, widget):
@@ -311,6 +331,7 @@
311331
312 if maker.isinstance(child, 'Terminal'):332 if maker.isinstance(child, 'Terminal'):
313 dbg('Notebook::closetab: child is a single Terminal')333 dbg('Notebook::closetab: child is a single Terminal')
334 del nb.last_active_term[child]
314 child.close()335 child.close()
315 # FIXME: We only do this del and return here to avoid removing the336 # FIXME: We only do this del and return here to avoid removing the
316 # page below, which child.close() implicitly does337 # page below, which child.close() implicitly does
@@ -318,9 +339,7 @@
318 return339 return
319 elif maker.isinstance(child, 'Container'):340 elif maker.isinstance(child, 'Container'):
320 dbg('Notebook::closetab: child is a Container')341 dbg('Notebook::closetab: child is a Container')
321 dialog = self.construct_confirm_close(self.window, _('tab'))342 result = self.construct_confirm_close(self.window, _('tab'))
322 result = dialog.run()
323 dialog.destroy()
324343
325 if result == gtk.RESPONSE_ACCEPT:344 if result == gtk.RESPONSE_ACCEPT:
326 containers = None345 containers = None
@@ -340,9 +359,6 @@
340 err('Notebook::closetab: child is unknown type %s' % child)359 err('Notebook::closetab: child is unknown type %s' % child)
341 return360 return
342361
343 nb.remove_page(tabnum)
344 del(label)
345
346 def resizeterm(self, widget, keyname):362 def resizeterm(self, widget, keyname):
347 """Handle a keyboard event requesting a terminal resize"""363 """Handle a keyboard event requesting a terminal resize"""
348 raise NotImplementedError('resizeterm')364 raise NotImplementedError('resizeterm')
@@ -403,6 +419,65 @@
403 terms = parent.get_visible_terminals()419 terms = parent.get_visible_terminals()
404 terms.keys()[-1].grab_focus()420 terms.keys()[-1].grab_focus()
405421
422 def page_num_descendant(self, widget):
423 """Find the tabnum of the tab containing a widget at any level"""
424 tabnum = self.page_num(widget)
425 dbg("widget is direct child if not equal -1 - tabnum: %d" % tabnum)
426 while tabnum == -1 and widget.get_parent():
427 widget = widget.get_parent()
428 tabnum = self.page_num(widget)
429 dbg("found tabnum containing widget: %d" % tabnum)
430 return tabnum
431
432 def set_last_active_term(self, uuid):
433 """Set the last active term for uuid"""
434 widget = self.terminator.find_terminal_by_uuid(uuid.urn)
435 if not widget:
436 err("Cannot find terminal with uuid: %s, so cannot make it active" % (uuid.urn))
437 return
438 tabnum = self.page_num_descendant(widget)
439 if tabnum == -1:
440 err("No tabnum found for terminal with uuid: %s" % (uuid.urn))
441 return
442 nth_page = self.get_nth_page(tabnum)
443 self.last_active_term[nth_page] = uuid
444
445 def clean_last_active_term(self):
446 """Clean up old entries in last_active_term"""
447 if self.terminator.doing_layout == True:
448 return
449 last_active_term = {}
450 for tabnum in xrange(0, self.get_n_pages()):
451 nth_page = self.get_nth_page(tabnum)
452 if nth_page in self.last_active_term:
453 last_active_term[nth_page] = self.last_active_term[nth_page]
454 self.last_active_term = last_active_term
455
456 def deferred_on_tab_switch(self, notebook, page, page_num, data=None):
457 """Prime a single idle tab switch signal, using the most recent set of params"""
458 tabs_last_active_term = self.last_active_term.get(self.get_nth_page(page_num), None)
459 data = {'tabs_last_active_term':tabs_last_active_term}
460
461 self.pending_on_tab_switch_args = (notebook, page, page_num, data)
462 if self.pending_on_tab_switch == True:
463 return
464 gobject.idle_add(self.do_deferred_on_tab_switch)
465 self.pending_on_tab_switch = True
466
467 def do_deferred_on_tab_switch(self):
468 """Perform the latest tab switch signal, and resetting the pending flag"""
469 self.on_tab_switch(*self.pending_on_tab_switch_args)
470 self.pending_on_tab_switch = False
471 self.pending_on_tab_switch_args = None
472
473 def on_tab_switch(self, notebook, page, page_num, data=None):
474 """Do the real work for a tab switch"""
475 tabs_last_active_term = data['tabs_last_active_term']
476 if tabs_last_active_term:
477 term = self.terminator.find_terminal_by_uuid(tabs_last_active_term.urn)
478 gobject.idle_add(term.ensure_visible_and_focussed)
479 return True
480
406class TabLabel(gtk.HBox):481class TabLabel(gtk.HBox):
407 """Class implementing a label widget for Notebook tabs"""482 """Class implementing a label widget for Notebook tabs"""
408 notebook = None483 notebook = None
409484
=== modified file 'terminatorlib/terminal.py'
--- terminatorlib/terminal.py 2013-10-25 14:57:14 +0000
+++ terminatorlib/terminal.py 2014-01-21 22:41:47 +0000
@@ -15,7 +15,7 @@
15import subprocess15import subprocess
16import urllib16import urllib
1717
18from util import dbg, err, gerr, spawn_new_terminator18from util import dbg, err, gerr, spawn_new_terminator, make_uuid
19import util19import util
20from config import Config20from config import Config
21from cwd import get_default_cwd21from cwd import get_default_cwd
@@ -1069,13 +1069,12 @@
1069 maker = Factory()1069 maker = Factory()
10701070
1071 if maker.isinstance(topchild, 'Notebook'):1071 if maker.isinstance(topchild, 'Notebook'):
1072 prevtmp = None1072 # Find which page number this term is on
1073 tmp = self.get_parent()1073 tabnum = topchild.page_num_descendant(self)
1074 while tmp != topchild:1074 # If terms page number is not the current one, switch to it
1075 prevtmp = tmp1075 current_page = topchild.get_current_page()
1076 tmp = tmp.get_parent() 1076 if tabnum != current_page:
1077 page = topchild.page_num(prevtmp)1077 topchild.set_current_page(tabnum)
1078 topchild.set_current_page(page)
10791078
1080 self.grab_focus()1079 self.grab_focus()
10811080
@@ -1088,7 +1087,15 @@
1088 self.vte.set_colors(self.fgcolor_active, self.bgcolor,1087 self.vte.set_colors(self.fgcolor_active, self.bgcolor,
1089 self.palette_active)1088 self.palette_active)
1090 self.set_cursor_color()1089 self.set_cursor_color()
1091 self.terminator.last_focused_term = self1090 if not self.terminator.doing_layout:
1091 self.terminator.last_focused_term = self
1092 if self.get_toplevel().is_child_notebook():
1093 notebook = self.get_toplevel().get_children()[0]
1094 notebook.set_last_active_term(self.uuid)
1095 notebook.clean_last_active_term()
1096 self.get_toplevel().last_active_term = None
1097 else:
1098 self.get_toplevel().last_active_term = self.uuid
1092 self.emit('focus-in')1099 self.emit('focus-in')
10931100
1094 def on_vte_focus_out(self, _widget, _event):1101 def on_vte_focus_out(self, _widget, _event):
@@ -1490,6 +1497,7 @@
1490 title = self.titlebar.get_custom_string()1497 title = self.titlebar.get_custom_string()
1491 if title:1498 if title:
1492 layout['title'] = title1499 layout['title'] = title
1500 layout['uuid'] = self.uuid
1493 name = 'terminal%d' % count1501 name = 'terminal%d' % count
1494 count = count + 11502 count = count + 1
1495 global_layout[name] = layout1503 global_layout[name] = layout
@@ -1511,6 +1519,8 @@
1511 self.titlebar.set_custom_string(layout['title'])1519 self.titlebar.set_custom_string(layout['title'])
1512 if layout.has_key('directory') and layout['directory'] != '':1520 if layout.has_key('directory') and layout['directory'] != '':
1513 self.directory = layout['directory']1521 self.directory = layout['directory']
1522 if layout.has_key('uuid') and layout['uuid'] != '':
1523 self.uuid = make_uuid(layout['uuid'])
15141524
1515 def scroll_by_page(self, pages):1525 def scroll_by_page(self, pages):
1516 """Scroll up or down in pages"""1526 """Scroll up or down in pages"""
15171527
=== modified file 'terminatorlib/terminator.py'
--- terminatorlib/terminator.py 2013-09-04 20:59:27 +0000
+++ terminatorlib/terminator.py 2014-01-21 22:41:47 +0000
@@ -34,6 +34,7 @@
34 debug_address = None34 debug_address = None
3535
36 doing_layout = None36 doing_layout = None
37 last_active_window = None
3738
38 groupsend = None39 groupsend = None
39 groupsend_type = {'all':0, 'group':1, 'off':2}40 groupsend_type = {'all':0, 'group':1, 'off':2}
@@ -291,11 +292,59 @@
291 def layout_done(self):292 def layout_done(self):
292 """Layout operations have finished, record that fact"""293 """Layout operations have finished, record that fact"""
293 self.doing_layout = False294 self.doing_layout = False
295 maker = Factory()
296
297 window_last_active_term_mapping = {}
298 for window in self.windows:
299 if window.is_child_notebook():
300 source = window.get_toplevel().get_children()[0]
301 else:
302 source = window
303 window_last_active_term_mapping[window] = copy.copy(source.last_active_term)
294304
295 for terminal in self.terminals:305 for terminal in self.terminals:
296 if not terminal.pid:306 if not terminal.pid:
297 terminal.spawn_child()307 terminal.spawn_child()
298308
309 for window in self.windows:
310 if window.is_child_notebook():
311 # For windows with a notebook
312 notebook = window.get_toplevel().get_children()[0]
313 # Cycle through pages by number
314 for page in xrange(0, notebook.get_n_pages()):
315 # Try and get the entry in the previously saved mapping
316 mapping = window_last_active_term_mapping[window]
317 page_last_active_term = mapping.get(notebook.get_nth_page(page), None)
318 if page_last_active_term is None:
319 # Couldn't find entry, so we find the first child of type Terminal
320 children = notebook.get_nth_page(page).get_children()
321 for page_last_active_term in children:
322 if maker.isinstance(page_last_active_term, 'Terminal'):
323 page_last_active_term = page_last_active_term.uuid
324 break
325 else:
326 err('Should never reach here!')
327 page_last_active_term = None
328 if page_last_active_term is None:
329 # Bail on this tab as we're having no luck here, continue with the next
330 continue
331 # Set the notebook entry, then ensure Terminal is visible and focussed
332 urn = page_last_active_term.urn
333 notebook.last_active_term[notebook.get_nth_page(page)] = page_last_active_term
334 if urn:
335 term = self.find_terminal_by_uuid(urn)
336 if term:
337 term.ensure_visible_and_focussed()
338 else:
339 # For windows without a notebook ensure Terminal is visible and focussed
340 if window_last_active_term_mapping[window]:
341 term = self.find_terminal_by_uuid(window_last_active_term_mapping[window].urn)
342 term.ensure_visible_and_focussed()
343
344 for window in self.windows:
345 if window.uuid == self.last_active_window:
346 window.show()
347
299 def reconfigure(self):348 def reconfigure(self):
300 """Update configuration for the whole application"""349 """Update configuration for the whole application"""
301350
302351
=== modified file 'terminatorlib/util.py'
--- terminatorlib/util.py 2013-08-28 21:09:17 +0000
+++ terminatorlib/util.py 2014-01-21 22:41:47 +0000
@@ -277,8 +277,10 @@
277 len(terminals), parent))277 len(terminals), parent))
278 return(containers, terminals)278 return(containers, terminals)
279279
280def make_uuid():280def make_uuid(str_uuid=None):
281 """Generate a UUID for an object"""281 """Generate a UUID for an object"""
282 if str_uuid:
283 return uuid.UUID(str_uuid)
282 return uuid.uuid4()284 return uuid.uuid4()
283285
284def inject_uuid(target):286def inject_uuid(target):
285287
=== modified file 'terminatorlib/window.py'
--- terminatorlib/window.py 2013-10-25 14:57:14 +0000
+++ terminatorlib/window.py 2014-01-21 22:41:47 +0000
@@ -5,12 +5,13 @@
55
6import copy6import copy
7import time7import time
8import uuid
8import pygtk9import pygtk
9pygtk.require('2.0')10pygtk.require('2.0')
10import gobject11import gobject
11import gtk12import gtk
1213
13from util import dbg, err14from util import dbg, err, make_uuid
14import util15import util
15from translation import _16from translation import _
16from version import APP_NAME17from version import APP_NAME
@@ -38,6 +39,7 @@
38 position = None39 position = None
39 ignore_startup_show = None40 ignore_startup_show = None
40 set_pos_by_ratio = None41 set_pos_by_ratio = None
42 last_active_term = None
4143
42 zoom_data = None44 zoom_data = None
4345
@@ -239,6 +241,8 @@
239 def on_focus_in(self, window, event):241 def on_focus_in(self, window, event):
240 """Focus has entered the window"""242 """Focus has entered the window"""
241 self.set_urgency_hint(False)243 self.set_urgency_hint(False)
244 if not self.terminator.doing_layout:
245 self.terminator.last_active_window = self.uuid
242 # FIXME: Cause the terminal titlebars to update here246 # FIXME: Cause the terminal titlebars to update here
243247
244 def is_child_notebook(self):248 def is_child_notebook(self):
@@ -865,6 +869,12 @@
865869
866 self.get_children()[0].create_layout(child)870 self.get_children()[0].create_layout(child)
867871
872 if layout.has_key('last_active_term') and layout['last_active_term'] not in ['', None]:
873 self.last_active_term = make_uuid(layout['last_active_term'])
874
875 if layout.has_key('last_active_window') and layout['last_active_window'] == 'True':
876 self.terminator.last_active_window = self.uuid
877
868class WindowTitle(object):878class WindowTitle(object):
869 """Class to handle the setting of the window title"""879 """Class to handle the setting of the window title"""
870880