1#!/usr/bin/env python
2"""Preferences Editor for Terminator.
3
4Load a UIBuilder config file, display it,
5populate it with our current config, then optionally read that back out and
6write it to a config file
7
8"""
9
10import os
11from gi.repository import GObject, Gtk, Gdk
12
13from .util import dbg, err
14from . import config
15from .keybindings import Keybindings, KeymapError
16from .translation import _
17from .encoding import TerminatorEncoding
18from .terminator import Terminator
19from .plugin import PluginRegistry
20from .version import APP_NAME
21
22def get_color_string(widcol):
23    return('#%02x%02x%02x' % (widcol.red>>8, widcol.green>>8, widcol.blue>>8))
24
25def color2hex(widget):
26    """Pull the colour values out of a Gtk ColorPicker widget and return them
27    as 8bit hex values, sinces its default behaviour is to give 16bit values"""
28    return get_color_string(widget.get_color())
29
30def rgba2hex(widget):
31    return get_color_string(widget.get_rgba().to_color())
32
33NUM_PALETTE_COLORS = 16
34
35# FIXME: We need to check that we have represented all of Config() below
36class PrefsEditor:
37    """Class implementing the various parts of the preferences editor"""
38    config = None
39    registry = None
40    plugins = None
41    keybindings = None
42    window = None
43    calling_window = None
44    term = None
45    builder = None
46    layouteditor = None
47    previous_layout_selection = None
48    previous_profile_selection = None
49    colorschemevalues = {'black_on_yellow': 0,
50                         'black_on_white': 1,
51                         'grey_on_black': 2,
52                         'green_on_black': 3,
53                         'white_on_black': 4,
54                         'orange_on_black': 5,
55                         'ambience': 6,
56                         'solarized_light': 7,
57                         'solarized_dark': 8,
58                         'gruvbox_light': 9,
59                         'gruvbox_dark': 10,
60                         'custom': 11}
61    colourschemes = {'grey_on_black': ['#aaaaaa', '#000000'],
62                     'black_on_yellow': ['#000000', '#ffffdd'],
63                     'black_on_white': ['#000000', '#ffffff'],
64                     'white_on_black': ['#ffffff', '#000000'],
65                     'green_on_black': ['#00ff00', '#000000'],
66                     'orange_on_black': ['#e53c00', '#000000'],
67                     'ambience': ['#ffffff', '#300a24'],
68                     'solarized_light': ['#657b83', '#fdf6e3'],
69                     'solarized_dark': ['#839496', '#002b36'],
70                     'gruvbox_light': ['#3c3836', '#fbf1c7'],
71                     'gruvbox_dark': ['#ebdbb2', '#282828']}
72    palettevalues = {'tango': 0,
73                     'linux': 1,
74                     'xterm': 2,
75                     'rxvt': 3,
76                     'ambience': 4,
77                     'solarized': 5,
78                     'gruvbox_light': 6,
79                     'gruvbox_dark': 7,
80                     'custom': 8}
81    palettes = {'tango': '#000000:#cc0000:#4e9a06:#c4a000:#3465a4:\
82#75507b:#06989a:#d3d7cf:#555753:#ef2929:#8ae234:#fce94f:#729fcf:\
83#ad7fa8:#34e2e2:#eeeeec',
84                'linux': '#000000:#aa0000:#00aa00:#aa5500:#0000aa:\
85#aa00aa:#00aaaa:#aaaaaa:#555555:#ff5555:#55ff55:#ffff55:#5555ff:\
86#ff55ff:#55ffff:#ffffff',
87                'xterm': '#000000:#cd0000:#00cd00:#cdcd00:#0000ee:\
88#cd00cd:#00cdcd:#e5e5e5:#7f7f7f:#ff0000:#00ff00:#ffff00:#5c5cff:\
89#ff00ff:#00ffff:#ffffff',
90                'rxvt': '#000000:#cd0000:#00cd00:#cdcd00:#0000cd:\
91#cd00cd:#00cdcd:#faebd7:#404040:#ff0000:#00ff00:#ffff00:#0000ff:\
92#ff00ff:#00ffff:#ffffff',
93                'ambience': '#2e3436:#cc0000:#4e9a06:#c4a000:\
94#3465a4:#75507b:#06989a:#d3d7cf:#555753:#ef2929:#8ae234:#fce94f:\
95#729fcf:#ad7fa8:#34e2e2:#eeeeec',
96                'solarized': '#073642:#dc322f:#859900:#b58900:\
97#268bd2:#d33682:#2aa198:#eee8d5:#002b36:#cb4b16:#586e75:#657b83:\
98#839496:#6c71c4:#93a1a1:#fdf6e3',
99                'gruvbox_light': '#fbf1c7:#cc241d:#98971a:#d79921:\
100#458588:#b16286:#689d6a:#7c6f64:#928374:#9d0006:#79740e:#b57614:\
101#076678:#8f3f71:#427b58:#3c3836',
102                'gruvbox_dark': '#282828:#cc241d:#98971a:#d79921:\
103#458588:#b16286:#689d6a:#a89984:#928374:#fb4934:#b8bb26:#fabd2f:\
104#83a598:#d3869b:#8ec07c:#ebdbb2'}
105    keybindingnames = { 'zoom_in'          : _('Increase font size'),
106                        'zoom_out'         : _('Decrease font size'),
107                        'zoom_normal'      : _('Restore original font size'),
108						'zoom_in_all'	   : _('Increase font size on all terminals'),
109						'zoom_out_all'	   : _('Decrease font size on all terminals'),
110						'zoom_normal_all'  : _('Restore original font size on all terminals'),
111                        'new_tab'          : _('Create a new tab'),
112                        'cycle_next'       : _('Focus the next terminal'),
113                        'cycle_prev'       : _('Focus the previous terminal'),
114                        'go_next'          : _('Focus the next terminal'),
115                        'go_prev'          : _('Focus the previous terminal'),
116                        'go_up'            : _('Focus the terminal above'),
117                        'go_down'          : _('Focus the terminal below'),
118                        'go_left'          : _('Focus the terminal left'),
119                        'go_right'         : _('Focus the terminal right'),
120                        'rotate_cw'        : _('Rotate terminals clockwise'),
121                        'rotate_ccw'       : _('Rotate terminals counter-clockwise'),
122                        'split_horiz'      : _('Split horizontally'),
123                        'split_vert'       : _('Split vertically'),
124                        'close_term'       : _('Close terminal'),
125                        'copy'             : _('Copy selected text'),
126                        'paste'            : _('Paste clipboard'),
127                        'toggle_scrollbar' : _('Show/Hide the scrollbar'),
128                        'search'           : _('Search terminal scrollback'),
129                        'page_up'          : _('Scroll upwards one page'),
130                        'page_down'        : _('Scroll downwards one page'),
131                        'page_up_half'     : _('Scroll upwards half a page'),
132                        'page_down_half'   : _('Scroll downwards half a page'),
133                        'line_up'          : _('Scroll upwards one line'),
134                        'line_down'        : _('Scroll downwards one line'),
135                        'close_window'     : _('Close window'),
136                        'resize_up'        : _('Resize the terminal up'),
137                        'resize_down'      : _('Resize the terminal down'),
138                        'resize_left'      : _('Resize the terminal left'),
139                        'resize_right'     : _('Resize the terminal right'),
140                        'move_tab_right'   : _('Move the tab right'),
141                        'move_tab_left'    : _('Move the tab left'),
142                        'toggle_zoom'      : _('Maximize terminal'),
143                        'scaled_zoom'      : _('Zoom terminal'),
144                        'next_tab'         : _('Switch to the next tab'),
145                        'prev_tab'         : _('Switch to the previous tab'),
146                        'switch_to_tab_1'  : _('Switch to the first tab'),
147                        'switch_to_tab_2'  : _('Switch to the second tab'),
148                        'switch_to_tab_3'  : _('Switch to the third tab'),
149                        'switch_to_tab_4'  : _('Switch to the fourth tab'),
150                        'switch_to_tab_5'  : _('Switch to the fifth tab'),
151                        'switch_to_tab_6'  : _('Switch to the sixth tab'),
152                        'switch_to_tab_7'  : _('Switch to the seventh tab'),
153                        'switch_to_tab_8'  : _('Switch to the eighth tab'),
154                        'switch_to_tab_9'  : _('Switch to the ninth tab'),
155                        'switch_to_tab_10' : _('Switch to the tenth tab'),
156                        'full_screen'      : _('Toggle fullscreen'),
157                        'reset'            : _('Reset the terminal'),
158                        'reset_clear'      : _('Reset and clear the terminal'),
159                        'hide_window'      : _('Toggle window visibility'),
160                        'create_group'     : _('Create new group'),
161                        'group_all'        : _('Group all terminals'),
162                        'group_all_toggle' : _('Group/Ungroup all terminals'),
163                        'ungroup_all'      : _('Ungroup all terminals'),
164                        'group_tab'        : _('Group terminals in tab'),
165                        'group_tab_toggle' : _('Group/Ungroup terminals in tab'),
166                        'ungroup_tab'      : _('Ungroup terminals in tab'),
167                        'new_window'       : _('Create a new window'),
168                        'new_terminator'   : _('Spawn a new Terminator process'),
169                        'broadcast_off'    : _('Don\'t broadcast key presses'),
170                        'broadcast_group'  : _('Broadcast key presses to group'),
171                        'broadcast_all'    : _('Broadcast key events to all'),
172                        'insert_number'    : _('Insert terminal number'),
173                        'insert_padded'    : _('Insert padded terminal number'),
174                        'edit_window_title': _('Edit window title'),
175                        'edit_terminal_title': _('Edit terminal title'),
176                        'edit_tab_title'   : _('Edit tab title'),
177                        'layout_launcher'  : _('Open layout launcher window'),
178                        'next_profile'     : _('Switch to next profile'),
179                        'previous_profile' : _('Switch to previous profile'),
180			'preferences'	   : _('Open the Preferences window'),
181                        'help'             : _('Open the manual')
182            }
183
184    def __init__ (self, term):
185        self.config = config.Config()
186        self.config.base.reload()
187        self.term = term
188        self.calling_window = self.term.get_toplevel()
189        self.calling_window.preventHide = True
190        self.builder = Gtk.Builder()
191        self.builder.set_translation_domain(APP_NAME)
192        self.keybindings = Keybindings()
193        self.active_message_dialog = None
194        try:
195            # Figure out where our library is on-disk so we can open our
196            (head, _tail) = os.path.split(config.__file__)
197            librarypath = os.path.join(head, 'preferences.glade')
198            gladefile = open(librarypath, 'r')
199            gladedata = gladefile.read()
200        except Exception as ex:
201            print("Failed to find preferences.glade")
202            print(ex)
203            return
204
205        self.builder.add_from_string(gladedata)
206        self.window = self.builder.get_object('prefswin')
207
208        icon_theme = Gtk.IconTheme.get_default()
209        if icon_theme.lookup_icon('terminator-preferences', 48, 0):
210            self.window.set_icon_name('terminator-preferences')
211        else:
212            dbg('Unable to load Terminator preferences icon')
213            icon = self.window.render_icon(Gtk.STOCK_DIALOG_INFO, Gtk.IconSize.BUTTON)
214            self.window.set_icon(icon)
215
216        self.layouteditor = LayoutEditor(self.builder)
217        self.builder.connect_signals(self)
218        self.layouteditor.prepare()
219        self.window.show_all()
220        try:
221            self.config.inhibit_save()
222            self.set_values()
223        except Exception as e:
224            err('Unable to set values: %s' % e)
225        self.config.uninhibit_save()
226
227    def on_closebutton_clicked(self, _button):
228        """Close the window"""
229        terminator = Terminator()
230        terminator.reconfigure()
231        self.window.destroy()
232        self.calling_window.preventHide = False
233        del(self)
234
235    def set_values(self):
236        """Update the preferences window with all the configuration from
237        Config()"""
238        guiget = self.builder.get_object
239
240        ## Global tab
241        # Mouse focus
242        focus = self.config['focus']
243        active = 0
244        if focus == 'click':
245            active = 1
246        elif focus in ['sloppy', 'mouse']:
247            active = 2
248        widget = guiget('focuscombo')
249        widget.set_active(active)
250        # Terminal separator size
251        termsepsize = self.config['handle_size']
252        widget = guiget('handlesize')
253        widget.set_value(float(termsepsize))
254        widget = guiget('handlesize_value_label')
255        widget.set_text(str(termsepsize))
256        # Line Height
257        lineheightsize = self.config['line_height']
258        lineheightsize = round(float(lineheightsize),1)
259        widget = guiget('lineheight')
260        widget.set_value(lineheightsize)
261        widget = guiget('lineheight_value_label')
262        widget.set_text(str(lineheightsize))
263        # Window geometry hints
264        geomhint = self.config['geometry_hinting']
265        widget = guiget('wingeomcheck')
266        widget.set_active(geomhint)
267        # Window state
268        option = self.config['window_state']
269        if option == 'hidden':
270            active = 1
271        elif option == 'maximise':
272            active = 2
273        elif option == 'fullscreen':
274            active = 3
275        else:
276            active = 0
277        widget = guiget('winstatecombo')
278        widget.set_active(active)
279        # Window borders
280        widget = guiget('winbordercheck')
281        widget.set_active(not self.config['borderless'])
282        # Extra styling
283        widget = guiget('extrastylingcheck')
284        widget.set_active(self.config['extra_styling'])
285        # Tab bar position
286        option = self.config['tab_position']
287        widget = guiget('tabposcombo')
288        if option == 'bottom':
289            active = 1
290        elif option == 'left':
291            active = 2
292        elif option == 'right':
293            active = 3
294        elif option == 'hidden':
295            active = 4
296        else:
297            active = 0
298        widget.set_active(active)
299        # Broadcast default
300        option = self.config['broadcast_default']
301        widget = guiget('broadcastdefault')
302        if option == 'all':
303            active = 0
304        elif option == 'off':
305            active = 2
306        else:
307            active = 1
308        widget.set_active(active)
309        # Disable Ctrl+mousewheel zoom
310        widget = guiget('disablemousewheelzoom')
311        widget.set_active(self.config['disable_mousewheel_zoom'])
312        # scroll_tabbar
313        widget = guiget('scrolltabbarcheck')
314        widget.set_active(self.config['scroll_tabbar'])
315        # homogeneous_tabbar
316        widget = guiget('homogeneouscheck')
317        widget.set_active(self.config['homogeneous_tabbar'])
318        # DBus Server
319        widget = guiget('dbuscheck')
320        widget.set_active(self.config['dbus'])
321        #Hide from taskbar
322        widget = guiget('hidefromtaskbcheck')
323        widget.set_active(self.config['hide_from_taskbar'])
324        #Always on top
325        widget = guiget('alwaysontopcheck')
326        widget.set_active(self.config['always_on_top'])
327        #Hide on lose focus
328        widget = guiget('hideonlosefocuscheck')
329        widget.set_active(self.config['hide_on_lose_focus'])
330        #Show on all workspaces
331        widget = guiget('stickycheck')
332        widget.set_active(self.config['sticky'])
333        #Hide size text from the title bar
334        widget = guiget('title_hide_sizetextcheck')
335        widget.set_active(self.config['title_hide_sizetext'])
336
337        # title bar at bottom
338        widget = guiget('title_at_bottom_checkbutton')
339        widget.set_active(self.config['title_at_bottom'])
340
341        #Always split with profile
342        widget = guiget('always_split_with_profile')
343        widget.set_active(self.config['always_split_with_profile'])
344        # Putty paste style
345        widget = guiget('putty_paste_style')
346        widget.set_active(self.config['putty_paste_style'])
347        # Putty paste style source clipboard
348        if self.config['putty_paste_style_source_clipboard']:
349            widget = guiget('putty_paste_style_source_clipboard_radiobutton')
350        else:
351            widget = guiget('putty_paste_style_source_primary_radiobutton')
352        widget.set_active(True)
353        # Smart copy
354        widget = guiget('smart_copy')
355        widget.set_active(self.config['smart_copy'])
356        #Titlebar font selector
357        # Use system font
358        widget = guiget('title_system_font_checkbutton')
359        widget.set_active(self.config['title_use_system_font'])
360        self.on_title_system_font_checkbutton_toggled(widget)
361        # Font selector
362        widget = guiget('title_font_selector')
363        if self.config['title_use_system_font'] == True:
364            fontname = self.config.get_system_prop_font()
365            if fontname is not None:
366                widget.set_font_name(fontname)
367        else:
368            widget.set_font_name(self.config['title_font'])
369
370        ## Profile tab
371        # Populate the profile list
372        widget = guiget('profilelist')
373        liststore = widget.get_model()
374        profiles = self.config.list_profiles()
375        self.profileiters = {}
376        for profile in profiles:
377            if profile == 'default':
378                editable = False
379            else:
380                editable = True
381            self.profileiters[profile] = liststore.append([profile, editable])
382        selection = widget.get_selection()
383        selection.connect('changed', self.on_profile_selection_changed)
384        selection.select_iter(self.profileiters['default'])
385
386        ## Layouts tab
387        widget = guiget('layoutlist')
388        liststore = widget.get_model()
389        layouts = self.config.list_layouts()
390        self.layoutiters = {}
391        for layout in layouts:
392            if layout == 'default':
393                editable = False
394            else:
395                editable = True
396            self.layoutiters[layout] = liststore.append([layout, editable])
397        selection = widget.get_selection()
398        selection.connect('changed', self.on_layout_selection_changed)
399        terminator = Terminator()
400        if terminator.layoutname:
401            layout_to_highlight = terminator.layoutname
402        else:
403            layout_to_highlight = 'default'
404        selection.select_iter(self.layoutiters[layout_to_highlight])
405        # Now set up the selection changed handler for the layout itself
406        widget = guiget('LayoutTreeView')
407        selection = widget.get_selection()
408        selection.connect('changed', self.on_layout_item_selection_changed)
409
410        ## Keybindings tab
411        widget = guiget('keybindingtreeview')
412        liststore = widget.get_model()
413        liststore.set_sort_column_id(0, Gtk.SortType.ASCENDING)
414        keybindings = self.config['keybindings']
415        for keybinding in keybindings:
416            keyval = 0
417            mask = 0
418            value = keybindings[keybinding]
419            if value is not None and value != '':
420                try:
421                    (keyval, mask) = self.keybindings._parsebinding(value)
422                except KeymapError:
423                    pass
424            liststore.append([keybinding, self.keybindingnames[keybinding],
425                             keyval, mask])
426
427        ## Plugins tab
428        # Populate the plugin list
429        widget = guiget('pluginlist')
430        liststore = widget.get_model()
431        self.registry = PluginRegistry()
432        self.pluginiters = {}
433        pluginlist = self.registry.get_available_plugins()
434        self.plugins = {}
435        for plugin in pluginlist:
436            self.plugins[plugin] = self.registry.is_enabled(plugin)
437
438        for plugin in self.plugins:
439            self.pluginiters[plugin] = liststore.append([plugin,
440                                             self.plugins[plugin]])
441        selection = widget.get_selection()
442        selection.connect('changed', self.on_plugin_selection_changed)
443        if len(self.pluginiters) > 0:
444            selection.select_iter(liststore.get_iter_first())
445
446    def set_profile_values(self, profile):
447        """Update the profile values for a given profile"""
448        self.config.set_profile(profile)
449        guiget = self.builder.get_object
450
451        dbg('PrefsEditor::set_profile_values: Setting profile %s' % profile)
452
453        ## General tab
454        # Use system font
455        widget = guiget('system_font_checkbutton')
456        widget.set_active(self.config['use_system_font'])
457        self.on_system_font_checkbutton_toggled(widget)
458        # Font selector
459        widget = guiget('font_selector')
460
461        if self.config['use_system_font'] == True:
462            fontname = self.config.get_system_mono_font()
463            if fontname is not None:
464                widget.set_font_name(fontname)
465        else:
466            widget.set_font_name(self.config['font'])
467        # Allow bold text
468        widget = guiget('allow_bold_checkbutton')
469        widget.set_active(self.config['allow_bold'])
470        # Icon terminal bell
471        widget = guiget('icon_bell_checkbutton')
472        widget.set_active(self.config['icon_bell'])
473        # Visual terminal bell
474        widget = guiget('visual_bell_checkbutton')
475        widget.set_active(self.config['visible_bell'])
476        # Audible terminal bell
477        widget = guiget('audible_bell_checkbutton')
478        widget.set_active(self.config['audible_bell'])
479        # WM_URGENT terminal bell
480        widget = guiget('urgent_bell_checkbutton')
481        widget.set_active(self.config['urgent_bell'])
482        # Show titlebar
483        widget = guiget('show_titlebar')
484        widget.set_active(self.config['show_titlebar'])
485        # Copy on selection
486        widget = guiget('copy_on_selection')
487        widget.set_active(self.config['copy_on_selection'])
488        # Word chars
489        widget = guiget('word_chars_entry')
490        widget.set_text(self.config['word_chars'])
491        # Word char support was missing from vte 0.38, hide from the UI
492        if not hasattr(self.term.vte, 'set_word_char_exceptions'):
493            guiget('word_chars_hbox').hide()
494        # Cursor shape
495        widget = guiget('cursor_shape_combobox')
496        if self.config['cursor_shape'] == 'underline':
497            active = 1
498        elif self.config['cursor_shape'] == 'ibeam':
499            active = 2
500        else:
501            active = 0
502        widget.set_active(active)
503        # Cursor blink
504        widget = guiget('cursor_blink')
505        widget.set_active(self.config['cursor_blink'])
506        # Cursor colour - Radio values
507        if self.config['cursor_color_fg']:
508            widget = guiget('cursor_color_foreground_radiobutton')
509        else:
510            widget = guiget('cursor_color_custom_radiobutton')
511        widget.set_active(True)
512        # Cursor colour - swatch
513        widget = guiget('cursor_color')
514        widget.set_sensitive(not self.config['cursor_color_fg'])
515        try:
516            widget.set_color(Gdk.color_parse(self.config['cursor_color']))
517        except (ValueError, TypeError):
518            try:
519                self.config['cursor_color'] = self.config['foreground_color']
520                widget.set_color(Gdk.color_parse(self.config['cursor_color']))
521            except ValueError:
522                self.config['cursor_color'] = "#FFFFFF"
523                widget.set_color(Gdk.color_parse(self.config['cursor_color']))
524
525        ## Command tab
526        # Login shell
527        widget = guiget('login_shell_checkbutton')
528        widget.set_active(self.config['login_shell'])
529        # Use Custom command
530        widget = guiget('use_custom_command_checkbutton')
531        widget.set_active(self.config['use_custom_command'])
532        self.on_use_custom_command_checkbutton_toggled(widget)
533        # Custom Command
534        widget = guiget('custom_command_entry')
535        widget.set_text(self.config['custom_command'])
536        # Exit action
537        widget = guiget('exit_action_combobox')
538        if self.config['exit_action'] == 'restart':
539            widget.set_active(1)
540        elif self.config['exit_action'] == 'hold':
541            widget.set_active(2)
542        else:
543            # Default is to close the terminal
544            widget.set_active(0)
545
546        ## Colors tab
547        # Use system colors
548        widget = guiget('use_theme_colors_checkbutton')
549        widget.set_active(self.config['use_theme_colors'])
550        # Bold is bright
551        widget = guiget('bold_text_is_bright_checkbutton')
552        widget.set_active(self.config['bold_is_bright'])
553        # Colorscheme
554        widget = guiget('color_scheme_combobox')
555        scheme = None
556        for ascheme in self.colourschemes:
557            forecol = self.colourschemes[ascheme][0]
558            backcol = self.colourschemes[ascheme][1]
559            if self.config['foreground_color'].lower() == forecol and \
560               self.config['background_color'].lower() == backcol:
561                scheme = ascheme
562                break
563        if scheme not in self.colorschemevalues:
564            if self.config['foreground_color'] in [None, ''] or \
565               self.config['background_color'] in [None, '']:
566                scheme = 'grey_on_black'
567            else:
568                scheme = 'custom'
569        # NOTE: The scheme is set in the GUI widget after the fore/back colours
570        # Foreground color
571        widget = guiget('foreground_colorbutton')
572        widget.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
573
574        if scheme == 'custom':
575            widget.set_sensitive(True)
576        else:
577            widget.set_sensitive(False)
578        # Background color
579        widget = guiget('background_colorbutton')
580        widget.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
581        if scheme == 'custom':
582            widget.set_sensitive(True)
583        else:
584            widget.set_sensitive(False)
585        # Now actually set the scheme
586        widget = guiget('color_scheme_combobox')
587        widget.set_active(self.colorschemevalues[scheme])
588        # Palette scheme
589        widget = guiget('palette_combobox')
590        palette = None
591        for apalette in self.palettes:
592            if self.config['palette'].lower() == self.palettes[apalette]:
593                palette = apalette
594        if palette not in self.palettevalues:
595            if self.config['palette'] in [None, '']:
596                palette = 'rxvt'
597            else:
598                palette = 'custom'
599        # NOTE: The palette selector is set after the colour pickers
600        # Palette colour pickers
601        for palette_id in range(0, NUM_PALETTE_COLORS):
602            widget = self.get_palette_widget(palette_id)
603            widget.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
604            def on_palette_click(event, data, widget=widget):
605                self.edit_palette_button(widget)
606            widget.connect('button-press-event', on_palette_click)
607        self.load_palette()
608        # Now set the palette selector widget
609        widget = guiget('palette_combobox')
610        widget.set_active(self.palettevalues[palette])
611        # Titlebar colors
612        for bit in ['title_transmit_fg_color', 'title_transmit_bg_color',
613            'title_receive_fg_color', 'title_receive_bg_color',
614            'title_inactive_fg_color', 'title_inactive_bg_color']:
615            widget = guiget(bit)
616            widget.set_color(Gdk.color_parse(self.config[bit]))
617        # Inactive terminal shading
618        widget = guiget('inactive_color_offset')
619        widget.set_value(float(self.config['inactive_color_offset']))
620        widget = guiget('inactive_color_offset_value_label')
621        widget.set_text('%d%%' % (int(float(self.config['inactive_color_offset'])*100)))
622        # Use custom URL handler
623        widget = guiget('use_custom_url_handler_checkbox')
624        widget.set_active(self.config['use_custom_url_handler'])
625        self.on_use_custom_url_handler_checkbutton_toggled(widget)
626        # Custom URL handler
627        widget = guiget('custom_url_handler_entry')
628        widget.set_text(self.config['custom_url_handler'])
629
630        ## Background tab
631        # Radio values
632        if self.config['background_type'] == 'solid':
633            guiget('solid_radiobutton').set_active(True)
634        elif self.config['background_type'] == 'transparent':
635            guiget('transparent_radiobutton').set_active(True)
636        elif self.config['background_type'] == 'image':
637            guiget('image_radiobutton').set_active(True)
638        self.update_background_tab()
639        # Background shading
640        widget = guiget('background_darkness_scale')
641        widget.set_value(float(self.config['background_darkness']))
642        widget = guiget('background_image_file')
643        widget.set_filename(self.config['background_image'])
644
645        ## Scrolling tab
646        # Scrollbar position
647        widget = guiget('scrollbar_position_combobox')
648        value = self.config['scrollbar_position']
649        if value == 'left':
650            widget.set_active(0)
651        elif value in ['disabled', 'hidden']:
652            widget.set_active(2)
653        else:
654            widget.set_active(1)
655        # Scrollback lines
656        widget = guiget('scrollback_lines_spinbutton')
657        widget.set_value(self.config['scrollback_lines'])
658        # Scrollback infinite
659        widget = guiget('scrollback_infinite')
660        widget.set_active(self.config['scrollback_infinite'])
661        # Scroll on outut
662        widget = guiget('scroll_on_output_checkbutton')
663        widget.set_active(self.config['scroll_on_output'])
664        # Scroll on keystroke
665        widget = guiget('scroll_on_keystroke_checkbutton')
666        widget.set_active(self.config['scroll_on_keystroke'])
667
668        ## Compatibility tab
669        # Backspace key
670        widget = guiget('backspace_binding_combobox')
671        value = self.config['backspace_binding']
672        if value == 'control-h':
673            widget.set_active(1)
674        elif value == 'ascii-del':
675            widget.set_active(2)
676        elif value == 'escape-sequence':
677            widget.set_active(3)
678        else:
679            widget.set_active(0)
680        # Delete key
681        widget = guiget('delete_binding_combobox')
682        value = self.config['delete_binding']
683        if value == 'control-h':
684            widget.set_active(1)
685        elif value == 'ascii-del':
686            widget.set_active(2)
687        elif value == 'escape-sequence':
688            widget.set_active(3)
689        else:
690            widget.set_active(0)
691        # Encoding
692        rowiter = None
693        widget = guiget('encoding_combobox')
694        encodingstore = guiget('EncodingListStore')
695        value = self.config['encoding']
696        encodings = TerminatorEncoding().get_list()
697        encodings.sort(key=lambda x: x[2].lower())
698
699        for encoding in encodings:
700            if encoding[1] is None:
701                continue
702
703            label = "%s %s" % (encoding[2], encoding[1])
704            rowiter = encodingstore.append([label, encoding[1]])
705
706            if encoding[1] == value:
707                widget.set_active_iter(rowiter)
708
709    def set_layout(self, layout_name):
710        """Set a layout"""
711        self.layouteditor.set_layout(layout_name)
712
713    def on_wingeomcheck_toggled(self, widget):
714        """Window geometry setting changed"""
715        self.config['geometry_hinting'] = widget.get_active()
716        self.config.save()
717
718    def on_homogeneous_toggled(self, widget):
719        """homogeneous_tabbar setting changed"""
720        guiget = self.builder.get_object
721        self.config['homogeneous_tabbar'] = widget.get_active()
722        scroll_toggled = guiget('scrolltabbarcheck')
723        if widget.get_active():
724            scroll_toggled.set_sensitive(True)
725        else:
726            scroll_toggled.set_active(True)
727            scroll_toggled.set_sensitive(False)
728        self.config.save()
729
730    def on_scroll_toggled(self, widget):
731        """scroll_tabbar setting changed"""
732        self.config['scroll_tabbar'] = widget.get_active()
733        self.config.save()
734
735    def on_dbuscheck_toggled(self, widget):
736        """DBus server setting changed"""
737        self.config['dbus'] = widget.get_active()
738        self.config.save()
739
740    def on_disable_mousewheel_zoom_toggled(self, widget):
741        """Ctrl+mousewheel zoom setting changed"""
742        self.config['disable_mousewheel_zoom'] = widget.get_active()
743        self.config.save()
744
745    def on_winbordercheck_toggled(self, widget):
746        """Window border setting changed"""
747        self.config['borderless'] = not widget.get_active()
748        self.config.save()
749
750    def on_extrastylingcheck_toggled(self, widget):
751        """Extra styling setting changed"""
752        self.config['extra_styling'] = widget.get_active()
753        self.config.save()
754
755    def on_hidefromtaskbcheck_toggled(self, widget):
756        """Hide from taskbar setting changed"""
757        self.config['hide_from_taskbar'] = widget.get_active()
758        self.config.save()
759
760    def on_alwaysontopcheck_toggled(self, widget):
761        """Always on top setting changed"""
762        self.config['always_on_top'] = widget.get_active()
763        self.config.save()
764
765    def on_hideonlosefocuscheck_toggled(self, widget):
766        """Hide on lose focus setting changed"""
767        self.config['hide_on_lose_focus'] = widget.get_active()
768        self.config.save()
769
770    def on_stickycheck_toggled(self, widget):
771        """Sticky setting changed"""
772        self.config['sticky'] = widget.get_active()
773        self.config.save()
774
775    def on_title_hide_sizetextcheck_toggled(self, widget):
776        """Window geometry setting changed"""
777        self.config['title_hide_sizetext'] = widget.get_active()
778        self.config.save()
779
780    def on_title_at_bottom_checkbutton_toggled(self, widget):
781        """Title at bottom setting changed"""
782        self.config['title_at_bottom'] = widget.get_active()
783        self.config.save()
784
785    def on_always_split_with_profile_toggled(self, widget):
786        """Always split with profile setting changed"""
787        self.config['always_split_with_profile'] = widget.get_active()
788        self.config.save()
789
790    def on_allow_bold_checkbutton_toggled(self, widget):
791        """Allow bold setting changed"""
792        self.config['allow_bold'] = widget.get_active()
793        self.config.save()
794
795    def on_show_titlebar_toggled(self, widget):
796        """Show titlebar setting changed"""
797        self.config['show_titlebar'] = widget.get_active()
798        self.config.save()
799
800    def on_copy_on_selection_toggled(self, widget):
801        """Copy on selection setting changed"""
802        self.config['copy_on_selection'] = widget.get_active()
803        self.config.save()
804
805    def on_putty_paste_style_toggled(self, widget):
806        """Putty paste style setting changed"""
807        self.config['putty_paste_style'] = widget.get_active()
808        self.config.save()
809
810    def on_putty_paste_style_source_clipboard_toggled(self, widget):
811        """PuTTY paste style source changed"""
812        guiget = self.builder.get_object
813        clipboardwidget = guiget('putty_paste_style_source_clipboard_radiobutton')
814        self.config['putty_paste_style_source_clipboard'] = clipboardwidget.get_active()
815        self.config.save()
816
817    def on_smart_copy_toggled(self, widget):
818        """Putty paste style setting changed"""
819        self.config['smart_copy'] = widget.get_active()
820        self.config.save()
821
822    def on_clear_select_on_copy_toggled(self,widget):
823        """Clear selection on smart copy"""
824        self.config['clear_select_on_copy'] = widget.get_active()
825        self.config.save()
826
827    def on_cursor_blink_toggled(self, widget):
828        """Cursor blink setting changed"""
829        self.config['cursor_blink'] = widget.get_active()
830        self.config.save()
831
832    def on_icon_bell_checkbutton_toggled(self, widget):
833        """Icon bell setting changed"""
834        self.config['icon_bell'] = widget.get_active()
835        self.config.save()
836
837    def on_visual_bell_checkbutton_toggled(self, widget):
838        """Visual bell setting changed"""
839        self.config['visible_bell'] = widget.get_active()
840        self.config.save()
841
842    def on_audible_bell_checkbutton_toggled(self, widget):
843        """Audible bell setting changed"""
844        self.config['audible_bell'] = widget.get_active()
845        self.config.save()
846
847    def on_urgent_bell_checkbutton_toggled(self, widget):
848        """Window manager bell setting changed"""
849        self.config['urgent_bell'] = widget.get_active()
850        self.config.save()
851
852    def on_login_shell_checkbutton_toggled(self, widget):
853        """Login shell setting changed"""
854        self.config['login_shell'] = widget.get_active()
855        self.config.save()
856
857    def on_scroll_background_checkbutton_toggled(self, widget):
858        """Scroll background setting changed"""
859        self.config['scroll_background'] = widget.get_active()
860        self.config.save()
861
862    def on_scroll_on_keystroke_checkbutton_toggled(self, widget):
863        """Scroll on keystrong setting changed"""
864        self.config['scroll_on_keystroke'] = widget.get_active()
865        self.config.save()
866
867    def on_scroll_on_output_checkbutton_toggled(self, widget):
868        """Scroll on output setting changed"""
869        self.config['scroll_on_output'] = widget.get_active()
870        self.config.save()
871
872    def on_delete_binding_combobox_changed(self, widget):
873        """Delete binding setting changed"""
874        selected = widget.get_active()
875        if selected == 1:
876            value = 'control-h'
877        elif selected == 2:
878            value = 'ascii-del'
879        elif selected == 3:
880            value = 'escape-sequence'
881        else:
882            value = 'automatic'
883        self.config['delete_binding'] = value
884        self.config.save()
885
886    def on_backspace_binding_combobox_changed(self, widget):
887        """Backspace binding setting changed"""
888        selected = widget.get_active()
889        if selected == 1:
890            value = 'control-h'
891        elif selected == 2:
892            value = 'ascii-del'
893        elif selected == 3:
894            value = 'escape-sequence'
895        else:
896            value = 'automatic'
897        self.config['backspace_binding'] = value
898        self.config.save()
899
900    def on_encoding_combobox_changed(self, widget):
901        """Encoding setting changed"""
902        selected = widget.get_active_iter()
903        liststore = widget.get_model()
904        value = liststore.get_value(selected, 1)
905
906        self.config['encoding'] = value
907        self.config.save()
908
909    def on_scrollback_lines_spinbutton_value_changed(self, widget):
910        """Scrollback lines setting changed"""
911        value = widget.get_value_as_int()
912        self.config['scrollback_lines'] = value
913        self.config.save()
914
915    def on_scrollback_infinite_toggled(self, widget):
916        """Scrollback infiniteness changed"""
917        spinbutton = self.builder.get_object('scrollback_lines_spinbutton')
918        value = widget.get_active()
919        if value == True:
920            spinbutton.set_sensitive(False)
921        else:
922            spinbutton.set_sensitive(True)
923        self.config['scrollback_infinite'] = value
924        self.config.save()
925
926    def on_scrollbar_position_combobox_changed(self, widget):
927        """Scrollbar position setting changed"""
928        selected = widget.get_active()
929        if selected == 1:
930            value = 'right'
931        elif selected == 2:
932            value = 'hidden'
933        else:
934            value = 'left'
935        self.config['scrollbar_position'] = value
936        self.config.save()
937
938    def on_background_image_file_set(self,widget):
939        print(widget.get_filename())
940        self.config['background_image'] = widget.get_filename()
941        self.config.save()
942
943    def on_darken_background_scale_value_changed(self, widget):
944        """Background darkness setting changed"""
945        value = widget.get_value()  # This one is rounded according to the UI.
946        if value > 1.0:
947          value = 1.0
948        self.config['background_darkness'] = value
949        self.config.save()
950
951    def on_palette_combobox_changed(self, widget):
952        """Palette selector changed"""
953        value = None
954        active = widget.get_active()
955
956        for key in list(self.palettevalues.keys()):
957            if self.palettevalues[key] == active:
958                value = key
959
960        sensitive = value == 'custom'
961        for palette_id in range(0, NUM_PALETTE_COLORS):
962            self.get_palette_widget(palette_id).set_sensitive(sensitive)
963
964        if value in self.palettes:
965            palette = self.palettes[value]
966            palettebits = palette.split(':')
967            for palette_id in range(0, NUM_PALETTE_COLORS):
968                # Update the visible elements
969                color = Gdk.color_parse(palettebits[palette_id])
970                self.load_palette_color(palette_id, color)
971        elif value == 'custom':
972            palettebits = []
973            for palette_id in range(0, NUM_PALETTE_COLORS):
974                # Save the custom values into the configuration.
975                palettebits.append(get_color_string(self.get_palette_color(palette_id)))
976            palette = ':'.join(palettebits)
977        else:
978            err('Unknown palette value: %s' % value)
979            return
980
981        self.config['palette'] = palette
982        self.config.save()
983
984    def on_foreground_colorbutton_draw(self, widget, cr):
985        width = widget.get_allocated_width()
986        height = widget.get_allocated_height()
987        col = Gdk.color_parse(self.config['foreground_color'])
988        cr.rectangle(0, 0, width, height)
989        cr.set_source_rgba(0.7, 0.7, 0.7, 1)
990        cr.fill()
991        cr.rectangle(1, 1, width-2, height-2)
992        cr.set_source_rgba(col.red_float, col.green_float, col.blue_float)
993        cr.fill()
994
995    def on_foreground_colorbutton_click(self, event, data):
996        dialog = Gtk.ColorChooserDialog("Choose Terminal Text Color")
997        fg = self.config['foreground_color']
998        dialog.set_rgba(Gdk.RGBA.from_color(Gdk.color_parse(self.config['foreground_color'])))
999        dialog.connect('notify::rgba', self.on_foreground_colorpicker_color_change)
1000        res = dialog.run()
1001        if res != Gtk.ResponseType.OK:
1002            self.config['foreground_color'] = fg
1003            self.config.save()
1004            terminator = Terminator()
1005            terminator.reconfigure()
1006        dialog.destroy()
1007
1008    def on_foreground_colorpicker_color_change(self, widget, color):
1009        """Foreground color changed"""
1010        self.config['foreground_color'] = rgba2hex(widget)
1011        self.config.save()
1012        terminator = Terminator()
1013        terminator.reconfigure()
1014
1015    def on_background_colorbutton_draw(self, widget, cr):
1016        width = widget.get_allocated_width()
1017        height = widget.get_allocated_height()
1018        col = Gdk.color_parse(self.config['background_color'])
1019        cr.rectangle(0, 0, width, height)
1020        cr.set_source_rgba(0.7, 0.7, 0.7, 1)
1021        cr.fill()
1022        cr.rectangle(1, 1, width-2, height-2)
1023        cr.set_source_rgba(col.red_float, col.green_float, col.blue_float)
1024        cr.fill()
1025
1026    def on_background_colorbutton_click(self, event, data):
1027        dialog = Gtk.ColorChooserDialog("Choose Terminal Background Color")
1028        orig = self.config['background_color']
1029        dialog.connect('notify::rgba', self.on_background_colorpicker_color_change)
1030        dialog.set_rgba(Gdk.RGBA.from_color(Gdk.color_parse(orig)))
1031        res = dialog.run()
1032        if res != Gtk.ResponseType.OK:
1033            self.config['background_color'] = orig
1034            self.config.save()
1035            terminator = Terminator()
1036            terminator.reconfigure()
1037        dialog.destroy()
1038
1039    def on_background_colorpicker_color_change(self, widget, color):
1040        """Background color changed"""
1041        self.config['background_color'] = rgba2hex(widget)
1042        self.config.save()
1043        terminator = Terminator()
1044        terminator.reconfigure()
1045
1046    def get_palette_widget(self, palette_id):
1047        """Returns the palette widget for the given palette ID."""
1048        guiget = self.builder.get_object
1049        return guiget('palette_colorpicker_%d' % (palette_id + 1))
1050
1051    def get_palette_id(self, widget):
1052        """Returns the palette ID for the given palette widget."""
1053        for palette_id in range(0, NUM_PALETTE_COLORS):
1054            if widget == self.get_palette_widget(palette_id):
1055                return palette_id
1056        return None
1057
1058    def get_palette_color(self, palette_id):
1059        """Returns the configured Gdk color for the given palette ID."""
1060        if self.config['palette'] in self.palettes:
1061            colourpalette = self.palettes[self.config['palette']]
1062        else:
1063            colourpalette = self.config['palette'].split(':')
1064        return Gdk.color_parse(colourpalette[palette_id])
1065
1066    def on_palette_colorpicker_draw(self, widget, cr):
1067        width = widget.get_allocated_width()
1068        height = widget.get_allocated_height()
1069        cr.rectangle(0, 0, width, height)
1070        cr.set_source_rgba(0.7, 0.7, 0.7, 1)
1071        cr.fill()
1072        cr.rectangle(1, 1, width-2, height-2)
1073        col = self.get_palette_color(self.get_palette_id(widget))
1074        cr.set_source_rgba(col.red_float, col.green_float, col.blue_float)
1075        cr.fill()
1076
1077    def load_palette_color(self, palette_id, color):
1078        """Given a palette ID and a Gdk color, load that color into the
1079        specified widget."""
1080        widget = self.get_palette_widget(palette_id)
1081        widget.queue_draw()
1082
1083    def replace_palette_color(self, palette_id, color):
1084        """Replace the configured palette color for the given palette ID
1085        with the given color."""
1086        palettebits = self.config['palette'].split(':')
1087        palettebits[palette_id] = get_color_string(color)
1088        self.config['palette'] = ':'.join(palettebits)
1089        self.config.save()
1090
1091    def load_palette(self):
1092        """Load the palette from the configuration into the color buttons."""
1093        colourpalette = self.config['palette'].split(':')
1094        for palette_id in range(0, NUM_PALETTE_COLORS):
1095            color = Gdk.color_parse(colourpalette[palette_id])
1096            self.load_palette_color(palette_id, color)
1097
1098    def edit_palette_button(self, widget):
1099        """When the palette colorbutton is clicked, open a dialog to
1100        configure a custom color."""
1101        terminator = Terminator()
1102        palette_id = self.get_palette_id(widget)
1103        orig = self.get_palette_color(palette_id)
1104
1105        try:
1106            # Create the dialog to choose a custom color
1107            dialog = Gtk.ColorChooserDialog("Choose Palette Color")
1108            dialog.set_rgba(Gdk.RGBA.from_color(orig))
1109
1110            def on_color_set(_, color):
1111                # The color is set, so save the palette config and refresh Terminator
1112                self.replace_palette_color(palette_id, dialog.get_rgba().to_color())
1113                terminator.reconfigure()
1114            dialog.connect('notify::rgba', on_color_set)
1115
1116            # Show the dialog
1117            res = dialog.run()
1118            if res != Gtk.ResponseType.OK:
1119                # User cancelled the color change, so reset to the original.
1120                self.replace_palette_color(palette_id, orig)
1121                terminator.reconfigure()
1122        finally:
1123            if dialog:
1124                dialog.destroy()
1125
1126    def on_exit_action_combobox_changed(self, widget):
1127        """Exit action changed"""
1128        selected = widget.get_active()
1129        if selected == 1:
1130            value = 'restart'
1131        elif selected == 2:
1132            value = 'hold'
1133        else:
1134            value = 'close'
1135        self.config['exit_action'] = value
1136        self.config.save()
1137
1138    def on_custom_url_handler_entry_changed(self, widget):
1139        """Custom URL handler value changed"""
1140        self.config['custom_url_handler'] = widget.get_text()
1141        self.config.save()
1142
1143    def on_custom_command_entry_changed(self, widget):
1144        """Custom command value changed"""
1145        self.config['custom_command'] = widget.get_text()
1146        self.config.save()
1147
1148    def on_cursor_color_type_toggled(self, widget):
1149        guiget = self.builder.get_object
1150
1151        customwidget = guiget('cursor_color_custom_radiobutton')
1152        colorwidget = guiget('cursor_color')
1153
1154        colorwidget.set_sensitive(customwidget.get_active())
1155        self.config['cursor_color_fg'] = not customwidget.get_active()
1156
1157        try:
1158            colorwidget.set_color(Gdk.color_parse(self.config['cursor_color']))
1159        except (ValueError, TypeError):
1160            try:
1161                self.config['cursor_color'] = self.config['foreground_color']
1162                colorwidget.set_color(Gdk.color_parse(self.config['cursor_color']))
1163            except ValueError:
1164                self.config['cursor_color'] = "#FFFFFF"
1165                colorwidget.set_color(Gdk.color_parse(self.config['cursor_color']))
1166        self.config.save()
1167
1168    def on_cursor_color_color_set(self, widget):
1169        """Cursor colour changed"""
1170        self.config['cursor_color'] = color2hex(widget)
1171        self.config.save()
1172
1173    def on_cursor_shape_combobox_changed(self, widget):
1174        """Cursor shape changed"""
1175        selected = widget.get_active()
1176        if selected == 1:
1177            value = 'underline'
1178        elif selected == 2:
1179            value = 'ibeam'
1180        else:
1181            value = 'block'
1182        self.config['cursor_shape'] = value
1183        self.config.save()
1184
1185    def on_word_chars_entry_changed(self, widget):
1186        """Word characters changed"""
1187        self.config['word_chars'] = widget.get_text()
1188        self.config.save()
1189
1190    def on_font_selector_font_set(self, widget):
1191        """Font changed"""
1192        self.config['font'] = widget.get_font_name()
1193        self.config.save()
1194
1195    def on_title_font_selector_font_set(self, widget):
1196        """Titlebar Font changed"""
1197        self.config['title_font'] = widget.get_font_name()
1198        self.config.save()
1199
1200    def on_title_receive_bg_color_color_set(self, widget):
1201        """Title receive background colour changed"""
1202        self.config['title_receive_bg_color'] = color2hex(widget)
1203        self.config.save()
1204
1205    def on_title_receive_fg_color_color_set(self, widget):
1206        """Title receive foreground colour changed"""
1207        self.config['title_receive_fg_color'] = color2hex(widget)
1208        self.config.save()
1209
1210    def on_title_inactive_bg_color_color_set(self, widget):
1211        """Title inactive background colour changed"""
1212        self.config['title_inactive_bg_color'] = color2hex(widget)
1213        self.config.save()
1214
1215    def on_title_transmit_bg_color_color_set(self, widget):
1216        """Title transmit backgruond colour changed"""
1217        self.config['title_transmit_bg_color'] = color2hex(widget)
1218        self.config.save()
1219
1220    def on_title_inactive_fg_color_color_set(self, widget):
1221        """Title inactive foreground colour changed"""
1222        self.config['title_inactive_fg_color'] = color2hex(widget)
1223        self.config.save()
1224
1225    def on_title_transmit_fg_color_color_set(self, widget):
1226        """Title transmit foreground colour changed"""
1227        self.config['title_transmit_fg_color'] = color2hex(widget)
1228        self.config.save()
1229
1230    def on_inactive_color_offset_value_changed(self, widget):
1231        """Inactive color offset setting changed"""
1232        value = widget.get_value()  # This one is rounded according to the UI.
1233        if value > 1.0:
1234          value = 1.0
1235        self.config['inactive_color_offset'] = value
1236        self.config.save()
1237        guiget = self.builder.get_object
1238        label_widget = guiget('inactive_color_offset_value_label')
1239        label_widget.set_text('%d%%' % (int(value * 100)))
1240
1241    def on_handlesize_value_changed(self, widget):
1242        """Handle size changed"""
1243        value = widget.get_value()  # This one is rounded according to the UI.
1244        value = int(value)          # Cast to int.
1245        if value > 20:
1246            value = 20
1247        self.config['handle_size'] = value
1248        self.config.save()
1249        guiget = self.builder.get_object
1250        label_widget = guiget('handlesize_value_label')
1251        label_widget.set_text(str(value))
1252
1253    def on_lineheight_value_changed(self, widget):
1254        """Handles line height changed"""
1255        value = widget.get_value()
1256        value = round(float(value), 1)
1257        if value > 2.0:
1258            value = 2.0
1259        self.config['line_height'] = value
1260        self.config.save()
1261        guiget = self.builder.get_object
1262        label_widget = guiget('lineheight_value_label')
1263        label_widget.set_text(str(value))
1264
1265    def on_focuscombo_changed(self, widget):
1266        """Focus type changed"""
1267        selected = widget.get_active()
1268        if selected == 1:
1269            value = 'click'
1270        elif selected == 2:
1271            value = 'mouse'
1272        else:
1273            value = 'system'
1274        self.config['focus'] = value
1275        self.config.save()
1276
1277    def on_tabposcombo_changed(self, widget):
1278        """Tab position changed"""
1279        selected = widget.get_active()
1280        if selected == 1:
1281            value = 'bottom'
1282        elif selected == 2:
1283            value = 'left'
1284        elif selected == 3:
1285            value = 'right'
1286        elif selected == 4:
1287            value = 'hidden'
1288        else:
1289            value = 'top'
1290        self.config['tab_position'] = value
1291        self.config.save()
1292
1293    def on_broadcastdefault_changed(self, widget):
1294        """Broadcast default changed"""
1295        selected = widget.get_active()
1296        if selected == 0:
1297            value = 'all'
1298        elif selected == 2:
1299            value = 'off'
1300        else:
1301            value = 'group'
1302        self.config['broadcast_default'] = value
1303        self.config.save()
1304
1305    def on_winstatecombo_changed(self, widget):
1306        """Window state changed"""
1307        selected = widget.get_active()
1308        if selected == 1:
1309            value = 'hidden'
1310        elif selected == 2:
1311            value = 'maximise'
1312        elif selected == 3:
1313            value = 'fullscreen'
1314        else:
1315            value = 'normal'
1316        self.config['window_state'] = value
1317        self.config.save()
1318
1319    def on_profileaddbutton_clicked(self, _button):
1320        """Add a new profile to the list"""
1321        guiget = self.builder.get_object
1322
1323        treeview = guiget('profilelist')
1324        model = treeview.get_model()
1325        values = [ r[0] for r in model ]
1326
1327        newprofile = _('New Profile')
1328        if newprofile in values:
1329            i = 1
1330            while newprofile in values:
1331                i = i + 1
1332                newprofile = '%s %d' % (_('New Profile'), i)
1333
1334        if self.config.add_profile(newprofile):
1335            res = model.append([newprofile, True])
1336            if res:
1337                path = model.get_path(res)
1338                treeview.set_cursor(path, column=treeview.get_column(0),
1339                                    start_editing=True)
1340
1341        self.layouteditor.update_profiles()
1342
1343    def on_profileremovebutton_clicked(self, _button):
1344        """Remove a profile from the list"""
1345        guiget = self.builder.get_object
1346
1347        treeview = guiget('profilelist')
1348        selection = treeview.get_selection()
1349        (model, rowiter) = selection.get_selected()
1350        profile = model.get_value(rowiter, 0)
1351
1352        if profile == 'default':
1353            # We shouldn't let people delete this profile
1354            return
1355
1356        self.previous_profile_selection = None
1357        self.config.del_profile(profile)
1358        model.remove(rowiter)
1359        selection.select_iter(model.get_iter_first())
1360        self.layouteditor.update_profiles()
1361
1362    def on_layoutaddbutton_clicked(self, _button):
1363        """Add a new layout to the list"""
1364        terminator = Terminator()
1365        current_layout = terminator.describe_layout()
1366        guiget = self.builder.get_object
1367
1368        treeview = guiget('layoutlist')
1369        model = treeview.get_model()
1370        values = [ r[0] for r in model ]
1371
1372        name = _('New Layout')
1373        if name in values:
1374            i = 0
1375            while name in values:
1376                i = i + 1
1377                name = '%s %d' % (_('New Layout'), i)
1378
1379        if self.config.add_layout(name, current_layout):
1380            res = model.append([name, True])
1381            if res:
1382                path = model.get_path(res)
1383                treeview.set_cursor(path, start_editing=True)
1384
1385        self.config.save()
1386
1387    def on_layoutrefreshbutton_clicked(self, _button):
1388        """Refresh the terminals status and update"""
1389        terminator = Terminator()
1390        current_layout = terminator.describe_layout()
1391
1392        guiget = self.builder.get_object
1393        treeview = guiget('layoutlist')
1394        selected = treeview.get_selection()
1395        (model, rowiter) = selected.get_selected()
1396        name = model.get_value(rowiter, 0)
1397
1398        if self.config.replace_layout(name, current_layout):
1399            treeview.set_cursor(model.get_path(rowiter), column=treeview.get_column(0), start_editing=False)
1400        self.config.save()
1401        self.layouteditor.set_layout(name)
1402
1403    def on_layoutremovebutton_clicked(self, _button):
1404        """Remove a layout from the list"""
1405        guiget = self.builder.get_object
1406
1407        treeview = guiget('layoutlist')
1408        selection = treeview.get_selection()
1409        (model, rowiter) = selection.get_selected()
1410        layout = model.get_value(rowiter, 0)
1411
1412        if layout == 'default':
1413            # We shouldn't let people delete this layout
1414            return
1415
1416        self.previous_selection = None
1417        self.config.del_layout(layout)
1418        model.remove(rowiter)
1419        selection.select_iter(model.get_iter_first())
1420        self.config.save()
1421
1422    def on_use_custom_url_handler_checkbutton_toggled(self, checkbox):
1423        """Toggling the use_custom_url_handler checkbox needs to alter the
1424        sensitivity of the custom_url_handler entrybox"""
1425        guiget = self.builder.get_object
1426        widget = guiget('custom_url_handler_entry')
1427        value = checkbox.get_active()
1428
1429        widget.set_sensitive(value)
1430        self.config['use_custom_url_handler'] = value
1431        self.config.save()
1432
1433    def on_use_custom_command_checkbutton_toggled(self, checkbox):
1434        """Toggling the use_custom_command checkbox needs to alter the
1435        sensitivity of the custom_command entrybox"""
1436        guiget = self.builder.get_object
1437        widget = guiget('custom_command_entry')
1438        value = checkbox.get_active()
1439
1440        widget.set_sensitive(value)
1441        self.config['use_custom_command'] = value
1442        self.config.save()
1443
1444    def on_system_font_checkbutton_toggled(self, checkbox):
1445        """Toggling the use_system_font checkbox needs to alter the
1446        sensitivity of the font selector"""
1447        guiget = self.builder.get_object
1448        widget = guiget('font_selector')
1449        value = checkbox.get_active()
1450
1451        widget.set_sensitive(not value)
1452        self.config['use_system_font'] = value
1453        self.config.save()
1454
1455        if self.config['use_system_font'] == True:
1456            fontname = self.config.get_system_mono_font()
1457            if fontname is not None:
1458                widget.set_font_name(fontname)
1459        else:
1460            widget.set_font_name(self.config['font'])
1461
1462    def on_title_system_font_checkbutton_toggled(self, checkbox):
1463        """Toggling the title_use_system_font checkbox needs to alter the
1464        sensitivity of the font selector"""
1465        guiget = self.builder.get_object
1466        widget = guiget('title_font_selector')
1467        value = checkbox.get_active()
1468
1469        widget.set_sensitive(not value)
1470        self.config['title_use_system_font'] = value
1471        self.config.save()
1472
1473        if self.config['title_use_system_font'] == True:
1474            fontname = self.config.get_system_prop_font()
1475            if fontname is not None:
1476                widget.set_font_name(fontname)
1477        else:
1478            widget.set_font_name(self.config['title_font'])
1479
1480    def on_reset_compatibility_clicked(self, widget):
1481        """Reset the confusing and annoying backspace/delete options to the
1482        safest values"""
1483        guiget = self.builder.get_object
1484
1485        widget = guiget('backspace_binding_combobox')
1486        widget.set_active(2)
1487        widget = guiget('delete_binding_combobox')
1488        widget.set_active(3)
1489
1490    def on_background_type_toggled(self, _widget):
1491        """The background type was toggled"""
1492        self.update_background_tab()
1493
1494    def update_background_tab(self):
1495        """Update the background tab"""
1496        guiget = self.builder.get_object
1497
1498        # Background type
1499        backtype = None
1500        imagewidget = guiget('image_radiobutton')
1501        transwidget = guiget('transparent_radiobutton')
1502
1503        if imagewidget.get_active() == True:
1504            backtype = 'image'
1505        elif transwidget.get_active() == True:
1506            backtype = 'transparent'
1507        else:
1508            backtype = 'solid'
1509        self.config['background_type'] = backtype
1510        self.config.save()
1511
1512        if backtype == 'image':
1513                guiget('background_image_file').set_sensitive(True)
1514        else:
1515                guiget('background_image_file').set_sensitive(False)
1516
1517        if backtype in ('transparent', 'image'):
1518            guiget('darken_background_scale').set_sensitive(True)
1519        else:
1520            guiget('darken_background_scale').set_sensitive(False)
1521
1522    def on_profile_selection_changed(self, selection):
1523        """A different profile was selected"""
1524        (listmodel, rowiter) = selection.get_selected()
1525        if not rowiter:
1526            # Something is wrong, just jump to the first item in the list
1527            treeview = selection.get_tree_view()
1528            liststore = treeview.get_model()
1529            selection.select_iter(liststore.get_iter_first())
1530            return
1531        profile = listmodel.get_value(rowiter, 0)
1532        self.set_profile_values(profile)
1533        self.previous_profile_selection = profile
1534
1535        widget = self.builder.get_object('profileremovebutton')
1536        if profile == 'default':
1537            widget.set_sensitive(False)
1538        else:
1539            widget.set_sensitive(True)
1540
1541    def on_plugin_selection_changed(self, selection):
1542        """A different plugin was selected"""
1543        (listmodel, rowiter) = selection.get_selected()
1544        if not rowiter:
1545            # Something is wrong, just jump to the first item in the list
1546            treeview = selection.get_tree_view()
1547            liststore = treeview.get_model()
1548            selection.select_iter(liststore.get_iter_first())
1549            return
1550        plugin = listmodel.get_value(rowiter, 0)
1551        self.set_plugin(plugin)
1552        self.previous_plugin_selection = plugin
1553
1554        widget = self.builder.get_object('plugintogglebutton')
1555
1556    def on_plugin_toggled(self, cell, path):
1557        """A plugin has been enabled or disabled"""
1558        treeview = self.builder.get_object('pluginlist')
1559        model = treeview.get_model()
1560        plugin = model[path][0]
1561
1562        if not self.plugins[plugin]:
1563            # Plugin is currently disabled, load it
1564            self.registry.enable(plugin)
1565        else:
1566            # Plugin is currently enabled, unload it
1567            self.registry.disable(plugin)
1568
1569        self.plugins[plugin] = not self.plugins[plugin]
1570        # Update the treeview
1571        model[path][1] = self.plugins[plugin]
1572
1573        enabled_plugins = [x for x in self.plugins if self.plugins[x] == True]
1574        self.config['enabled_plugins'] = enabled_plugins
1575        self.config.save()
1576
1577    def set_plugin(self, plugin):
1578        """Show the preferences for the selected plugin, if any"""
1579        pluginpanelabel = self.builder.get_object('pluginpanelabel')
1580        pluginconfig = self.config.plugin_get_config(plugin)
1581        # FIXME: Implement this, we need to auto-construct a UI for the plugin
1582
1583    def on_profile_name_edited(self, cell, path, newtext):
1584        """Update a profile name"""
1585        oldname = cell.get_property('text')
1586        if oldname == newtext or oldname == 'default':
1587            return
1588        dbg('PrefsEditor::on_profile_name_edited: Changing %s to %s' %
1589        (oldname, newtext))
1590        self.config.rename_profile(oldname, newtext)
1591        self.config.save()
1592
1593        widget = self.builder.get_object('profilelist')
1594        model = widget.get_model()
1595        itera = model.get_iter(path)
1596        model.set_value(itera, 0, newtext)
1597
1598        if oldname == self.previous_profile_selection:
1599            self.previous_profile_selection = newtext
1600
1601    def on_layout_selection_changed(self, selection):
1602        """A different layout was selected"""
1603        self.layouteditor.on_layout_selection_changed(selection)
1604
1605    def on_layout_item_selection_changed(self, selection):
1606        """A different item in the layout was selected"""
1607        self.layouteditor.on_layout_item_selection_changed(selection)
1608
1609    def on_layout_profile_chooser_changed(self, widget):
1610        """A different profile has been selected for this item"""
1611        self.layouteditor.on_layout_profile_chooser_changed(widget)
1612
1613    def on_layout_profile_command_changed(self, widget):
1614        """A different command has been entered for this item"""
1615        self.layouteditor.on_layout_profile_command_activate(widget)
1616
1617    def on_layout_profile_workingdir_changed(self, widget):
1618        """A different working directory has been entered for this item"""
1619        self.layouteditor.on_layout_profile_workingdir_activate(widget)
1620
1621    def on_layout_name_edited(self, cell, path, newtext):
1622        """Update a layout name"""
1623        oldname = cell.get_property('text')
1624        if oldname == newtext or oldname == 'default':
1625            return
1626        dbg('Changing %s to %s' % (oldname, newtext))
1627        self.config.rename_layout(oldname, newtext)
1628        self.config.save()
1629
1630        widget = self.builder.get_object('layoutlist')
1631        model = widget.get_model()
1632        itera = model.get_iter(path)
1633        model.set_value(itera, 0, newtext)
1634
1635        if oldname == self.previous_layout_selection:
1636            self.previous_layout_selection = newtext
1637
1638        if oldname == self.layouteditor.layout_name:
1639            self.layouteditor.layout_name = newtext
1640
1641    def on_color_scheme_combobox_changed(self, widget):
1642        """Update the fore/background colour pickers"""
1643        value = None
1644        guiget = self.builder.get_object
1645        active = widget.get_active()
1646
1647        for key in list(self.colorschemevalues.keys()):
1648            if self.colorschemevalues[key] == active:
1649                value = key
1650
1651        fore = guiget('foreground_colorbutton')
1652        back = guiget('background_colorbutton')
1653        if value == 'custom':
1654            fore.set_sensitive(True)
1655            back.set_sensitive(True)
1656        else:
1657            fore.set_sensitive(False)
1658            back.set_sensitive(False)
1659
1660        forecol = None
1661        backcol = None
1662        if value in self.colourschemes:
1663            forecol = self.colourschemes[value][0]
1664            backcol = self.colourschemes[value][1]
1665            self.config['foreground_color'] = forecol
1666            self.config['background_color'] = backcol
1667        self.config.save()
1668
1669    def on_use_theme_colors_checkbutton_toggled(self, widget):
1670        """Update colour pickers"""
1671        guiget = self.builder.get_object
1672        active = widget.get_active()
1673
1674        scheme = guiget('color_scheme_combobox')
1675        fore = guiget('foreground_colorbutton')
1676        back = guiget('background_colorbutton')
1677
1678        if active:
1679            for widget in [scheme, fore, back]:
1680                widget.set_sensitive(False)
1681        else:
1682            scheme.set_sensitive(True)
1683            self.on_color_scheme_combobox_changed(scheme)
1684
1685        self.config['use_theme_colors'] = active
1686        self.config.save()
1687
1688    def on_bold_text_is_bright_checkbutton_toggled(self, widget):
1689        """Bold-is-bright setting changed"""
1690        self.config['bold_is_bright'] = widget.get_active()
1691        self.config.save()
1692
1693    def on_cellrenderer_accel_edited(self, liststore, path, key, mods, _code):
1694        """Handle an edited keybinding"""
1695        # Ignore `Gdk.KEY_Tab` so that `Shift+Tab` is displayed as `Shift+Tab`
1696        # in `Preferences>Keybindings` and NOT `Left Tab` (see `Gdk.KEY_ISO_Left_Tab`).
1697        if mods & Gdk.ModifierType.SHIFT_MASK and key != Gdk.KEY_Tab:
1698            key_with_shift = Gdk.Keymap.translate_keyboard_state(
1699                self.keybindings.keymap,
1700                hardware_keycode=_code,
1701                state=Gdk.ModifierType.SHIFT_MASK,
1702                group=0,
1703            )
1704            keyval_lower, keyval_upper = Gdk.keyval_convert_case(key)
1705
1706            # Remove the Shift modifier from `mods` if a new key binding doesn't
1707            # contain a letter and its key value (`key`) can't be modified by a
1708            # Shift key.
1709            if key_with_shift.level != 0 and keyval_lower == keyval_upper:
1710                mods = Gdk.ModifierType(mods & ~Gdk.ModifierType.SHIFT_MASK)
1711                key = key_with_shift.keyval
1712
1713        accel = Gtk.accelerator_name(key, mods)
1714        current_binding = liststore.get_value(liststore.get_iter(path), 0)
1715
1716        duplicate_bindings = []
1717        for conf_binding, conf_accel in self.config["keybindings"].items():
1718            parsed_accel = Gtk.accelerator_parse(accel)
1719            parsed_conf_accel = Gtk.accelerator_parse(conf_accel)
1720
1721            if (
1722                parsed_accel == parsed_conf_accel
1723                and current_binding != conf_binding
1724            ):
1725                duplicate_bindings.append((conf_binding, conf_accel))
1726
1727        if duplicate_bindings:
1728            dialog = Gtk.MessageDialog(
1729                transient_for=self.window,
1730                flags=Gtk.DialogFlags.MODAL,
1731                message_type=Gtk.MessageType.ERROR,
1732                buttons=Gtk.ButtonsType.CLOSE,
1733                text="Duplicate Key Bindings Are Not Allowed",
1734            )
1735
1736            accel_label = Gtk.accelerator_get_label(key, mods)
1737            # get the first found duplicate
1738            duplicate_keybinding_name = duplicate_bindings[0][0]
1739
1740            message = (
1741                "Key binding `{0}` is already in use to trigger the `{1}` action."
1742            ).format(accel_label, self.keybindingnames[duplicate_keybinding_name])
1743            dialog.format_secondary_text(message)
1744
1745            self.active_message_dialog = dialog
1746            dialog.run()
1747            dialog.destroy()
1748            self.active_message_dialog = None
1749
1750            return
1751
1752        celliter = liststore.get_iter_from_string(path)
1753        liststore.set(celliter, 2, key, 3, mods)
1754
1755        binding = liststore.get_value(liststore.get_iter(path), 0)
1756        accel = Gtk.accelerator_name(key, mods)
1757        self.config['keybindings'][binding] = accel
1758        self.config.save()
1759
1760    def on_cellrenderer_accel_cleared(self, liststore, path):
1761        """Handle the clearing of a keybinding accelerator"""
1762        celliter = liststore.get_iter_from_string(path)
1763        liststore.set(celliter, 2, 0, 3, 0)
1764
1765        binding = liststore.get_value(liststore.get_iter(path), 0)
1766        self.config['keybindings'][binding] = ""
1767        self.config.save()
1768
1769    def on_open_manual(self,  widget):
1770        """Open the fine manual"""
1771        self.term.key_help()
1772
1773class LayoutEditor:
1774    profile_ids_to_profile = None
1775    profile_profile_to_ids = None
1776    layout_name = None
1777    layout_item = None
1778    builder = None
1779    treeview = None
1780    treestore = None
1781    config = None
1782
1783    def __init__(self, builder):
1784        """Initialise ourself"""
1785        self.config = config.Config()
1786        self.builder = builder
1787
1788    def prepare(self, layout=None):
1789        """Do the things we can't do in __init__"""
1790        self.treeview = self.builder.get_object('LayoutTreeView')
1791        self.treestore = self.builder.get_object('LayoutTreeStore')
1792        self.update_profiles()
1793        if layout:
1794            self.set_layout(layout)
1795
1796    def set_layout(self, layout_name):
1797        """Load a particular layout"""
1798        self.layout_name = layout_name
1799        store = self.treestore
1800        layout = self.config.layout_get_config(layout_name)
1801        listitems = {}
1802        store.clear()
1803
1804        children = list(layout.keys())
1805        i = 0
1806        while children != []:
1807            child = children.pop()
1808            child_type = layout[child]['type']
1809            parent = layout[child]['parent']
1810
1811            if child_type != 'Window' and parent not in layout:
1812                # We have an orphan!
1813                err('%s is an orphan in this layout. Discarding' % child)
1814                continue
1815            try:
1816                parentiter = listitems[parent]
1817            except KeyError:
1818                if child_type == 'Window':
1819                    parentiter = None
1820                else:
1821                    # We're not ready for this widget yet
1822                    children.insert(0, child)
1823                    continue
1824
1825            if child_type == 'VPaned':
1826                child_type = 'Vertical split'
1827            elif child_type == 'HPaned':
1828                child_type = 'Horizontal split'
1829
1830            listitems[child] = store.append(parentiter, [child, child_type])
1831
1832        treeview = self.builder.get_object('LayoutTreeView')
1833        treeview.expand_all()
1834
1835    def update_profiles(self):
1836        """Update the list of profiles"""
1837        self.profile_ids_to_profile = {}
1838        self.profile_profile_to_ids= {}
1839        chooser = self.builder.get_object('layout_profile_chooser')
1840
1841        profiles = self.config.list_profiles()
1842        profiles.sort()
1843        i = 0
1844        for profile in profiles:
1845            self.profile_ids_to_profile[i] = profile
1846            self.profile_profile_to_ids[profile] = i
1847            chooser.append_text(profile)
1848            i = i + 1
1849
1850    def on_layout_selection_changed(self, selection):
1851        """A different layout was selected"""
1852        (listmodel, rowiter) = selection.get_selected()
1853        if not rowiter:
1854            # Something is wrong, just jump to the first item in the list
1855            selection.select_iter(self.treestore.get_iter_first())
1856            return
1857        layout = listmodel.get_value(rowiter, 0)
1858        self.set_layout(layout)
1859        self.previous_layout_selection = layout
1860
1861        widget = self.builder.get_object('layoutremovebutton')
1862        if layout == 'default':
1863            widget.set_sensitive(False)
1864        else:
1865            widget.set_sensitive(True)
1866
1867        command = self.builder.get_object('layout_profile_command')
1868        chooser = self.builder.get_object('layout_profile_chooser')
1869        workdir = self.builder.get_object('layout_profile_workingdir')
1870        command.set_sensitive(False)
1871        chooser.set_sensitive(False)
1872        workdir.set_sensitive(False)
1873
1874    def on_layout_item_selection_changed(self, selection):
1875        """A different item in the layout was selected"""
1876        (treemodel, rowiter) = selection.get_selected()
1877        if not rowiter:
1878            return
1879        item = treemodel.get_value(rowiter, 0)
1880        self.layout_item = item
1881        self.set_layout_item(item)
1882
1883    def set_layout_item(self, item_name):
1884        """Set a layout item"""
1885        layout = self.config.layout_get_config(self.layout_name)
1886        layout_item = layout[self.layout_item]
1887        command = self.builder.get_object('layout_profile_command')
1888        chooser = self.builder.get_object('layout_profile_chooser')
1889        workdir = self.builder.get_object('layout_profile_workingdir')
1890
1891        if layout_item['type'] != 'Terminal':
1892            command.set_sensitive(False)
1893            chooser.set_sensitive(False)
1894            workdir.set_sensitive(False)
1895            return
1896
1897        command.set_sensitive(True)
1898        chooser.set_sensitive(True)
1899        workdir.set_sensitive(True)
1900        if 'command' in layout_item and layout_item['command'] != '':
1901            command.set_text(layout_item['command'])
1902        else:
1903            command.set_text('')
1904
1905        if 'profile' in layout_item and layout_item['profile'] != '':
1906            chooser.set_active(self.profile_profile_to_ids[layout_item['profile']])
1907        else:
1908            chooser.set_active(0)
1909
1910        if 'directory' in layout_item and layout_item['directory'] != '':
1911            workdir.set_text(layout_item['directory'])
1912        else:
1913            workdir.set_text('')
1914
1915    def on_layout_profile_chooser_changed(self, widget):
1916        """A new profile has been selected for this item"""
1917        if not self.layout_item:
1918            return
1919        profile = widget.get_active_text()
1920        layout = self.config.layout_get_config(self.layout_name)
1921        layout[self.layout_item]['profile'] = profile
1922        self.config.save()
1923
1924    def on_layout_profile_command_activate(self, widget):
1925        """A new command has been entered for this item"""
1926        command = widget.get_text()
1927        layout = self.config.layout_get_config(self.layout_name)
1928        layout[self.layout_item]['command'] = command
1929        self.config.save()
1930
1931    def on_layout_profile_workingdir_activate(self, widget):
1932        """A new working directory has been entered for this item"""
1933        workdir = widget.get_text()
1934        layout = self.config.layout_get_config(self.layout_name)
1935        layout[self.layout_item]['directory'] = workdir
1936        self.config.save()
1937
1938if __name__ == '__main__':
1939    from . import util
1940    util.DEBUG = True
1941    from . import terminal
1942    TERM = terminal.Terminal()
1943    PREFEDIT = PrefsEditor(TERM)
1944
1945    Gtk.main()
1946