1# -*- coding: utf-8; -*-
2"""
3Copyright (C) 2007-2013 Guake authors
4
5This program is free software; you can redistribute it and/or
6modify it under the terms of the GNU General Public License as
7published by the Free Software Foundation; either version 2 of the
8License, or (at your option) any later version.
9
10This program is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13General Public License for more details.
14
15You should have received a copy of the GNU General Public
16License along with this program; if not, write to the
17Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18Boston, MA 02110-1301 USA
19"""
20import logging
21import os
22import re
23import shutil
24import warnings
25
26from textwrap import dedent
27
28import gi
29gi.require_version('Gtk', '3.0')
30gi.require_version('Keybinder', '3.0')
31gi.require_version('Vte', '2.91')  # vte-0.38
32from gi.repository import GLib
33from gi.repository import GObject
34from gi.repository import Gdk
35from gi.repository import Gtk
36from gi.repository import Keybinder
37from gi.repository import Pango
38from gi.repository import Vte
39
40from guake.common import ShowableError
41from guake.common import get_binaries_from_path
42from guake.common import gladefile
43from guake.common import hexify_color
44from guake.common import pixmapfile
45from guake.globals import ALIGN_BOTTOM
46from guake.globals import ALIGN_CENTER
47from guake.globals import ALIGN_LEFT
48from guake.globals import ALIGN_RIGHT
49from guake.globals import ALIGN_TOP
50from guake.globals import ALWAYS_ON_PRIMARY
51from guake.globals import NAME
52from guake.globals import QUICK_OPEN_MATCHERS
53from guake.globals import bindtextdomain
54from guake.palettes import PALETTES
55from guake.paths import AUTOSTART_FOLDER
56from guake.paths import LOCALE_DIR
57from guake.paths import LOGIN_DESTOP_PATH
58from guake.simplegladeapp import SimpleGladeApp
59from guake.terminal import GuakeTerminal
60from guake.theme import list_all_themes
61from guake.theme import select_gtk_theme
62from gettext import gettext as _
63
64# pylint: disable=unsubscriptable-object
65
66log = logging.getLogger(__name__)
67
68# A regular expression to match possible python interpreters when
69# filling interpreters combo in preferences (including bpython and ipython)
70PYTHONS = re.compile(r'^[a-z]python$|^python\d\.\d$')
71
72# Path to the shells file, it will be used to start to populate
73# interpreters combo, see the next variable, its important to fill the
74# rest of the combo too.
75SHELLS_FILE = '/etc/shells'
76
77# string to show in prefereces dialog for user shell option
78USER_SHELL_VALUE = _('<user shell>')
79
80# translating our types to vte types
81ERASE_BINDINGS = {
82    'ASCII DEL': 'ascii-delete',
83    'Escape sequence': 'delete-sequence',
84    'Control-H': 'ascii-backspace'
85}
86
87HOTKEYS = [
88    {
89        'label': _('General'),
90        'key': 'general',
91        'keys': [
92            {
93                'key': 'show-hide',
94                'label': _('Toggle Guake visibility')
95            },
96            {
97                'key': 'show-focus',
98                'label': _('Show and focus Guake window')
99            },
100            {
101                'key': 'toggle-fullscreen',
102                'label': _('Toggle Fullscreen')
103            },
104            {
105                'key': 'toggle-hide-on-lose-focus',
106                'label': _('Toggle Hide on Lose Focus')
107            },
108            {
109                'key': 'quit',
110                'label': _('Quit')
111            },
112            {
113                'key': 'reset-terminal',
114                'label': _('Reset terminal')
115            },
116        ]
117    },
118    {
119        'label': _('Tab management'),
120        'key': 'tab',
121        'keys': [
122            {
123                'key': 'new-tab',
124                'label': _('New tab')
125            },
126            {
127                'key': 'close-tab',
128                'label': _('Close tab')
129            },
130            {
131                'key': 'rename-current-tab',
132                'label': _('Rename current tab')
133            },
134        ]
135    },
136    {
137        'label': _('Split management'),
138        'key': 'split',
139        'keys': [
140            {
141                'key': 'split-tab-vertical',
142                'label': _('Split tab vertical')
143            },
144            {
145                'key': 'split-tab-horizontal',
146                'label': _('Split tab horizontal')
147            },
148            {
149                'key': 'close-terminal',
150                'label': _('Close terminal')
151            },
152            {
153                'key': 'focus-terminal-up',
154                'label': _('Focus terminal above')
155            },
156            {
157                'key': 'focus-terminal-down',
158                'label': _('Focus terminal below')
159            },
160            {
161                'key': 'focus-terminal-left',
162                'label': _('Focus terminal on the left')
163            },
164            {
165                'key': 'focus-terminal-right',
166                'label': _('Focus terminal on the right')
167            },
168            {
169                'key': 'move-terminal-split-up',
170                'label': _('Move the terminal split handle up')
171            },
172            {
173                'key': 'move-terminal-split-down',
174                'label': _('Move the terminal split handle down')
175            },
176            {
177                'key': 'move-terminal-split-right',
178                'label': _('Move the terminal split handle right')
179            },
180            {
181                'key': 'move-terminal-split-left',
182                'label': _('Move the terminal split handle left')
183            },
184        ]
185    },
186    {
187        'label': _('Navigation'),
188        'key': 'nav',
189        'keys': [
190            {
191                'key': 'previous-tab',
192                'label': _('Go to previous tab')
193            },
194            {
195                'key': 'next-tab',
196                'label': _('Go to next tab')
197            },
198            {
199                'key': 'move-tab-left',
200                'label': _('Move current tab left')
201            },
202            {
203                'key': 'move-tab-right',
204                'label': _('Move current tab right')
205            },
206            {
207                'key': 'switch-tab1',
208                'label': _('Go to first tab')
209            },
210            {
211                'key': 'switch-tab2',
212                'label': _('Go to second tab')
213            },
214            {
215                'key': 'switch-tab3',
216                'label': _('Go to third tab')
217            },
218            {
219                'key': 'switch-tab4',
220                'label': _('Go to fourth tab')
221            },
222            {
223                'key': 'switch-tab5',
224                'label': _('Go to fifth tab')
225            },
226            {
227                'key': 'switch-tab6',
228                'label': _('Go to sixth tab')
229            },
230            {
231                'key': 'switch-tab7',
232                'label': _('Go to seventh tab')
233            },
234            {
235                'key': 'switch-tab8',
236                'label': _('Go to eighth tab')
237            },
238            {
239                'key': 'switch-tab9',
240                'label': _('Go to ninth tab')
241            },
242            {
243                'key': 'switch-tab10',
244                'label': _('Go to tenth tab')
245            },
246            {
247                'key': 'switch-tab-last',
248                'label': _('Go to last tab')
249            },
250        ]
251    },
252    {
253        'label': _('Appearance'),
254        'key': 'appearance',
255        'keys': [{
256            'key': 'zoom-out',
257            'label': _('Zoom out')
258        }, {
259            'key': 'zoom-in',
260            'label': _('Zoom in')
261        }, {
262            'key': 'zoom-in-alt',
263            'label': _('Zoom in (alternative)')
264        }, {
265            'key': 'increase-height',
266            'label': _('Increase height')
267        }, {
268            'key': 'decrease-height',
269            'label': _('Decrease height')
270        }, {
271            'key': 'increase-transparency',
272            'label': _('Increase transparency')
273        }, {
274            'key': 'decrease-transparency',
275            'label': _('Decrease transparency')
276        }, {
277            'key': 'toggle-transparency',
278            'label': _('Toggle transparency')
279        }]
280    },
281    {
282        'label': _('Clipboard'),
283        'key': 'clipboard',
284        'keys': [
285            {
286                'key': 'clipboard-copy',
287                'label': _('Copy text to clipboard')
288            },
289            {
290                'key': 'clipboard-paste',
291                'label': _('Paste text from clipboard')
292            },
293        ]
294    },
295    {
296        'label': _('Extra features'),
297        'key': 'extra',
298        'keys': [{
299            'key': 'search-on-web',
300            'label': _('Search select text on web')
301        }, ]
302    },
303]
304
305html_escape_table = {
306    "&": "&amp;",
307    '"': "&quot;",
308    "'": "&apos;",
309    ">": "&gt;",
310    "<": "&lt;",
311}
312
313HOTKET_MODEL_INDEX_DCONF = 0
314HOTKET_MODEL_INDEX_LABEL = 1
315HOTKET_MODEL_INDEX_HUMAN_ACCEL = 2
316HOTKET_MODEL_INDEX_ACCEL = 3
317
318
319def html_escape(text):
320    """Produce entities within text."""
321    return "".join(html_escape_table.get(c, c) for c in text)
322
323
324def refresh_user_start(settings):
325    if not AUTOSTART_FOLDER or not LOGIN_DESTOP_PATH:
326        return
327    if settings.general.get_boolean('start-at-login'):
328        autostart_path = os.path.expanduser(AUTOSTART_FOLDER)
329        os.makedirs(autostart_path, exist_ok=True)
330        shutil.copyfile(
331            os.path.join(LOGIN_DESTOP_PATH, "autostart-guake.desktop"),
332            os.path.join(os.path.expanduser(AUTOSTART_FOLDER), "guake.desktop")
333        )
334    else:
335        desktop_file = os.path.join(os.path.expanduser(AUTOSTART_FOLDER), "guake.desktop")
336        if os.path.exists(desktop_file):
337            os.remove(desktop_file)
338
339
340class PrefsCallbacks():
341
342    """Holds callbacks that will be used in the PrefsDialg class.
343    """
344
345    def __init__(self, prefDlg):
346        self.prefDlg = prefDlg
347        self.settings = prefDlg.settings
348
349    # general tab
350
351    def on_default_shell_changed(self, combo):
352        """Changes the activity of default_shell in dconf
353        """
354        citer = combo.get_active_iter()
355        if not citer:
356            return
357        shell = combo.get_model().get_value(citer, 0)
358        # we unset the value (restore to default) when user chooses to use
359        # user shell as guake shell interpreter.
360        if shell == USER_SHELL_VALUE:
361            self.settings.general.reset('default-shell')
362        else:
363            self.settings.general.set_string('default-shell', shell)
364
365    def on_use_login_shell_toggled(self, chk):
366        """Changes the activity of use_login_shell in dconf
367        """
368        self.settings.general.set_boolean('use-login-shell', chk.get_active())
369
370    def on_open_tab_cwd_toggled(self, chk):
371        """Changes the activity of open_tab_cwd in dconf
372        """
373        self.settings.general.set_boolean('open-tab-cwd', chk.get_active())
374
375    def on_use_trayicon_toggled(self, chk):
376        """Changes the activity of use_trayicon in dconf
377        """
378        self.settings.general.set_boolean('use-trayicon', chk.get_active())
379
380    def on_use_popup_toggled(self, chk):
381        """Changes the activity of use_popup in dconf
382        """
383        self.settings.general.set_boolean('use-popup', chk.get_active())
384
385    def on_prompt_on_quit_toggled(self, chk):
386        """Set the `prompt on quit' property in dconf
387        """
388        self.settings.general.set_boolean('prompt-on-quit', chk.get_active())
389
390    def on_prompt_on_close_tab_changed(self, combo):
391        """Set the `prompt_on_close_tab' property in dconf
392        """
393        self.settings.general.set_int('prompt-on-close-tab', combo.get_active())
394
395    def on_gtk_theme_name_changed(self, combo):
396        """Set the `gtk_theme_name' property in dconf
397        """
398        citer = combo.get_active_iter()
399        if not citer:
400            return
401        theme_name = combo.get_model().get_value(citer, 0)
402        self.settings.general.set_string('gtk-theme-name', theme_name)
403        select_gtk_theme(self.settings)
404
405    def on_gtk_prefer_dark_theme_toggled(self, chk):
406        """Set the `gtk_prefer_dark_theme' property in dconf
407        """
408        self.settings.general.set_boolean('gtk-prefer-dark-theme', chk.get_active())
409        select_gtk_theme(self.settings)
410
411    def on_window_ontop_toggled(self, chk):
412        """Changes the activity of window_ontop in dconf
413        """
414        self.settings.general.set_boolean('window-ontop', chk.get_active())
415
416    def on_tab_ontop_toggled(self, chk):
417        """Changes the activity of tab_ontop in dconf
418        """
419        self.settings.general.set_boolean('tab-ontop', chk.get_active())
420
421    def on_quick_open_enable_toggled(self, chk):
422        """Changes the activity of quick_open_enable in dconf
423        """
424        self.settings.general.set_boolean('quick-open-enable', chk.get_active())
425
426    def on_quick_open_in_current_terminal_toggled(self, chk):
427        self.settings.general.set_boolean('quick-open-in-current-terminal', chk.get_active())
428
429    def on_startup_script_changed(self, edt):
430        self.settings.general.set_string('startup-script', edt.get_text())
431
432    def on_window_refocus_toggled(self, chk):
433        """Changes the activity of window_refocus in dconf
434        """
435        self.settings.general.set_boolean('window-refocus', chk.get_active())
436
437    def on_window_losefocus_toggled(self, chk):
438        """Changes the activity of window_losefocus in dconf
439        """
440        self.settings.general.set_boolean('window-losefocus', chk.get_active())
441
442    def on_quick_open_command_line_changed(self, edt):
443        self.settings.general.set_string('quick-open-command-line', edt.get_text())
444
445    def on_hook_show_changed(self, edt):
446        self.settings.hooks.set_string('show', edt.get_text())
447
448    def on_window_tabbar_toggled(self, chk):
449        """Changes the activity of window_tabbar in dconf
450        """
451        self.settings.general.set_boolean('window-tabbar', chk.get_active())
452
453    def on_start_fullscreen_toggled(self, chk):
454        """Changes the activity of start_fullscreen in dconf
455        """
456        self.settings.general.set_boolean('start-fullscreen', chk.get_active())
457
458    def on_start_at_login_toggled(self, chk):
459        """Changes the activity of start_at_login in dconf
460        """
461        self.settings.general.set_boolean('start-at-login', chk.get_active())
462        refresh_user_start(self.settings)
463
464    def on_use_vte_titles_toggled(self, chk):
465        """Save `use_vte_titles` property value in dconf
466        """
467        self.settings.general.set_boolean('use-vte-titles', chk.get_active())
468
469    def on_set_window_title_toggled(self, chk):
470        """Save `set_window_title` property value in dconf
471        """
472        self.settings.general.set_boolean('set-window-title', chk.get_active())
473
474    def on_abbreviate_tab_names_toggled(self, chk):
475        """Save `abbreviate_tab_names` property value in dconf
476        """
477        self.settings.general.set_boolean('abbreviate-tab-names', chk.get_active())
478
479    def on_max_tab_name_length_changed(self, spin):
480        """Changes the value of max_tab_name_length in dconf
481        """
482        val = int(spin.get_value())
483        self.settings.general.set_int('max-tab-name-length', val)
484        self.prefDlg.update_vte_subwidgets_states()
485
486    def on_mouse_display_toggled(self, chk):
487        """Set the 'appear on mouse display' preference in dconf. This
488        property supercedes any value stored in display_n.
489        """
490        self.settings.general.set_boolean('mouse-display', chk.get_active())
491
492    def on_right_align_toggled(self, chk):
493        """set the horizontal alignment setting.
494        """
495        v = chk.get_active()
496        self.settings.general.set_int('window-halignment', 1 if v else 0)
497
498    def on_bottom_align_toggled(self, chk):
499        """set the vertical alignment setting.
500        """
501        v = chk.get_active()
502        self.settings.general.set_int('window-valignment', ALIGN_BOTTOM if v else ALIGN_TOP)
503
504    def on_display_n_changed(self, combo):
505        """Set the destination display in dconf.
506        """
507
508        i = combo.get_active_iter()
509        if not i:
510            return
511
512        model = combo.get_model()
513        first_item_path = model.get_path(model.get_iter_first())
514
515        if model.get_path(i) == first_item_path:
516            val_int = ALWAYS_ON_PRIMARY
517        else:
518            val = model.get_value(i, 0)
519            val_int = int(val.split()[0])  # extracts 1 from '1' or from '1 (primary)'
520        self.settings.general.set_int('display-n', val_int)
521
522    def on_window_height_value_changed(self, hscale):
523        """Changes the value of window_height in dconf
524        """
525        val = hscale.get_value()
526        self.settings.general.set_int('window-height', int(val))
527
528    def on_window_width_value_changed(self, wscale):
529        """Changes the value of window_width in dconf
530        """
531        val = wscale.get_value()
532        self.settings.general.set_int('window-width', int(val))
533
534    def on_window_halign_value_changed(self, halign_button):
535        """Changes the value of window_halignment in dconf
536        """
537        which_align = {
538            'radiobutton_align_left': ALIGN_LEFT,
539            'radiobutton_align_right': ALIGN_RIGHT,
540            'radiobutton_align_center': ALIGN_CENTER
541        }
542        if halign_button.get_active():
543            self.settings.general.set_int(
544                'window-halignment', which_align[halign_button.get_name()]
545            )
546        self.prefDlg.get_widget("window_horizontal_displacement").set_sensitive(
547            which_align[halign_button.get_name()] != ALIGN_CENTER
548        )
549
550    def on_use_audible_bell_toggled(self, chk):
551        """Changes the value of use_audible_bell in dconf
552        """
553        self.settings.general.set_boolean('use-audible-bell', chk.get_active())
554
555    # scrolling tab
556
557    def on_use_scrollbar_toggled(self, chk):
558        """Changes the activity of use_scrollbar in dconf
559        """
560        self.settings.general.set_boolean('use-scrollbar', chk.get_active())
561
562    def on_history_size_value_changed(self, spin):
563        """Changes the value of history_size in dconf
564        """
565        val = int(spin.get_value())
566        self.settings.general.set_int('history-size', val)
567        self._update_history_widgets()
568
569    def on_infinite_history_toggled(self, chk):
570        self.settings.general.set_boolean('infinite-history', chk.get_active())
571        self._update_history_widgets()
572
573    def _update_history_widgets(self):
574        infinite = self.prefDlg.get_widget('infinite_history').get_active()
575        self.prefDlg.get_widget('history_size').set_sensitive(not infinite)
576
577    def on_scroll_output_toggled(self, chk):
578        """Changes the activity of scroll_output in dconf
579        """
580        self.settings.general.set_boolean('scroll-output', chk.get_active())
581
582    def on_scroll_keystroke_toggled(self, chk):
583        """Changes the activity of scroll_keystroke in dconf
584        """
585        self.settings.general.set_boolean('scroll-keystroke', chk.get_active())
586
587    # appearance tab
588
589    def on_use_default_font_toggled(self, chk):
590        """Changes the activity of use_default_font in dconf
591        """
592        self.settings.general.set_boolean('use-default-font', chk.get_active())
593
594    def on_allow_bold_toggled(self, chk):
595        """Changes the value of allow_bold in dconf
596        """
597        self.settings.styleFont.set_boolean('allow-bold', chk.get_active())
598
599    def on_font_style_font_set(self, fbtn):
600        """Changes the value of font_style in dconf
601        """
602        self.settings.styleFont.set_string('style', fbtn.get_font_name())
603
604    def on_transparency_value_changed(self, hscale):
605        """Changes the value of background_transparency in dconf
606        """
607        value = hscale.get_value()
608        self.prefDlg.set_colors_from_settings()
609        self.settings.styleBackground.set_int('transparency', int(value))
610
611    # compatibility tab
612
613    def on_backspace_binding_changed(self, combo):
614        """Changes the value of compat_backspace in dconf
615        """
616        val = combo.get_active_text()
617        self.settings.general.set_string('compat-backspace', ERASE_BINDINGS[val])
618
619    def on_delete_binding_changed(self, combo):
620        """Changes the value of compat_delete in dconf
621        """
622        val = combo.get_active_text()
623        self.settings.general.set_string('compat-delete', ERASE_BINDINGS[val])
624
625    def on_custom_command_file_chooser_file_changed(self, filechooser):
626        self.settings.general.set_string('custom-command-file', filechooser.get_filename())
627
628    def toggle_prompt_on_quit_sensitivity(self, combo):
629        self.prefDlg.toggle_prompt_on_quit_sensitivity(combo)
630
631    def toggle_style_sensitivity(self, chk):
632        self.prefDlg.toggle_style_sensitivity(chk)
633
634    def toggle_use_font_background_sensitivity(self, chk):
635        self.prefDlg.toggle_use_font_background_sensitivity(chk)
636
637    def toggle_display_n_sensitivity(self, chk):
638        self.prefDlg.toggle_display_n_sensitivity(chk)
639
640    def toggle_quick_open_command_line_sensitivity(self, chk):
641        self.prefDlg.toggle_quick_open_command_line_sensitivity(chk)
642
643    def toggle_use_vte_titles(self, chk):
644        self.prefDlg.toggle_use_vte_titles(chk)
645
646    def update_vte_subwidgets_states(self):
647        self.prefDlg.update_vte_subwidgets_states()
648
649    def on_reset_compat_defaults_clicked(self, btn):
650        self.prefDlg.on_reset_compat_defaults_clicked(btn)
651
652    def on_palette_name_changed(self, combo):
653        self.prefDlg.on_palette_name_changed(combo)
654
655    def on_cursor_shape_changed(self, combo):
656        self.prefDlg.on_cursor_shape_changed(combo)
657
658    def on_blink_cursor_toggled(self, chk):
659        self.prefDlg.on_blink_cursor_toggled(chk)
660
661    def on_palette_color_set(self, btn):
662        self.prefDlg.on_palette_color_set(btn)
663
664    def on_window_vertical_displacement_value_changed(self, spin):
665        """Changes the value of window-vertical-displacement
666        """
667        self.settings.general.set_int('window-vertical-displacement', int(spin.get_value()))
668
669    def on_window_horizontal_displacement_value_changed(self, spin):
670        """Changes the value of window-horizontal-displacement
671        """
672        self.settings.general.set_int('window-horizontal-displacement', int(spin.get_value()))
673
674    def reload_erase_combos(self, btn=None):
675        self.prefDlg.reload_erase_combos(btn)
676
677    def gtk_widget_destroy(self, btn):
678        self.prefDlg.gtk_widget_destroy(btn)
679
680
681class PrefsDialog(SimpleGladeApp):
682
683    """The Guake Preferences dialog.
684    """
685
686    def __init__(self, settings):
687        """Setup the preferences dialog interface, loading images,
688        adding filters to file choosers and connecting some signals.
689        """
690        self.hotkey_alread_used = False
691        self.store = None
692
693        super(PrefsDialog, self).__init__(gladefile('prefs.glade'), root='config-window')
694        style_provider = Gtk.CssProvider()
695        css_data = dedent(
696            """
697            .monospace{
698              font-family: monospace;
699            }
700            """
701        ).encode()
702        style_provider.load_from_data(css_data)
703        Gtk.StyleContext.add_provider_for_screen(
704            Gdk.Screen.get_default(), style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
705        )
706        self.get_widget("quick_open_command_line").get_style_context().add_class("monospace")
707        self.get_widget("quick_open_supported_patterns").get_style_context().add_class("monospace")
708        self.settings = settings
709
710        self.add_callbacks(PrefsCallbacks(self))
711
712        # window cleanup handler
713        self.window = self.get_widget('config-window')
714        self.get_widget('config-window').connect('destroy', self.on_destroy)
715
716        # images
717        ipath = pixmapfile('guake-notification.png')
718        self.get_widget('image_logo').set_from_file(ipath)
719        ipath = pixmapfile('quick-open.png')
720        self.get_widget('image_quick_open').set_from_file(ipath)
721
722        # Model format:
723        # 0: the keybinding path in gsettings (str, hidden),
724        # 1: label (str)
725        # 2: human readable accelerator (str)
726        # 3: gtk accelerator (str, hidden)
727        self.store = Gtk.TreeStore(str, str, str, str)
728        treeview = self.get_widget('treeview-keys')
729        treeview.set_model(self.store)
730        treeview.set_rules_hint(True)
731
732        treeview.connect('button-press-event', self.start_editing)
733
734        treeview.set_activate_on_single_click(True)
735
736        renderer = Gtk.CellRendererText()
737        column = Gtk.TreeViewColumn(_('Action'), renderer, text=1)
738        column.set_property('expand', True)
739        treeview.append_column(column)
740
741        renderer = Gtk.CellRendererAccel()
742        renderer.set_property('editable', True)
743        renderer.connect('accel-edited', self.on_accel_edited)
744        renderer.connect('accel-cleared', self.on_accel_cleared)
745        column = Gtk.TreeViewColumn(_('Shortcut'), renderer, text=2)
746        column.pack_start(renderer, True)
747        column.set_property('expand', False)
748        column.add_attribute(renderer, "accel-mods", 0)
749        column.add_attribute(renderer, "accel-key", 1)
750        treeview.append_column(column)
751
752        class fake_guake():
753            pass
754
755        fg = fake_guake()
756        fg.window = self.window
757        fg.settings = self.settings
758        self.demo_terminal = GuakeTerminal(fg)
759        self.demo_terminal_box = self.get_widget('demo_terminal_box')
760        self.demo_terminal_box.add(self.demo_terminal)
761
762        pid = self.spawn_sync_pid(None, self.demo_terminal)
763
764        self.demo_terminal.pid = pid
765
766        self.populate_shell_combo()
767        self.populate_keys_tree()
768        self.populate_display_n()
769        self.populate_gtk_theme_names()
770        self.load_configs()
771        self.get_widget('config-window').hide()
772
773    def spawn_sync_pid(self, directory=None, terminal=None):
774        argv = list()
775        user_shell = self.settings.general.get_string('default-shell')
776        if user_shell and os.path.exists(user_shell):
777            argv.append(user_shell)
778        else:
779            argv.append(os.environ['SHELL'])
780
781        login_shell = self.settings.general.get_boolean('use-login-shell')
782        if login_shell:
783            argv.append('--login')
784
785        if isinstance(directory, str):
786            wd = directory
787        else:
788            wd = os.environ['HOME']
789
790        pid = terminal.spawn_sync(
791            Vte.PtyFlags.DEFAULT, wd, argv, [], GLib.SpawnFlags.DO_NOT_REAP_CHILD, None, None, None
792        )
793
794        try:
795            tuple_type = gi._gi.ResultTuple  # pylint: disable=c-extension-no-member
796        except:  # pylint: disable=bare-except
797            tuple_type = tuple
798        if isinstance(pid, (tuple, tuple_type)):
799            # Return a tuple in 2.91
800            # https://lazka.github.io/pgi-docs/Vte-2.91/classes/Terminal.html#Vte.Terminal.spawn_sync
801            pid = pid[1]
802        assert isinstance(pid, int)
803        return pid
804
805    def show(self):
806        """Calls the main window show_all method and presents the
807        window in the desktop.
808        """
809        self.get_widget('config-window').show_all()
810        self.get_widget('config-window').present()
811
812    def hide(self):
813        """Calls the main window hide function.
814        """
815        self.get_widget('config-window').hide()
816
817    def on_destroy(self, window):
818        self.demo_terminal.kill()
819        self.demo_terminal.destroy()
820
821    def toggle_prompt_on_quit_sensitivity(self, combo):
822        """If toggle_on_close_tabs is set to 2 (Always), prompt_on_quit has no
823        effect.
824        """
825        self.get_widget('prompt_on_quit').set_sensitive(combo.get_active() != 2)
826
827    def toggle_style_sensitivity(self, chk):
828        """If the user chooses to use the gnome default font
829        configuration it means that he will not be able to use the
830        font selector.
831        """
832        self.get_widget('font_style').set_sensitive(not chk.get_active())
833
834    def toggle_use_font_background_sensitivity(self, chk):
835        """If the user chooses to use the gnome default font
836        configuration it means that he will not be able to use the
837        font selector.
838        """
839        self.get_widget('palette_16').set_sensitive(chk.get_active())
840        self.get_widget('palette_17').set_sensitive(chk.get_active())
841
842    def toggle_display_n_sensitivity(self, chk):
843        """When the user unchecks 'on mouse display', the option to select an
844        alternate display should be enabeld.
845        """
846        self.get_widget('display_n').set_sensitive(not chk.get_active())
847
848    def toggle_quick_open_command_line_sensitivity(self, chk):
849        """When the user unchecks 'enable quick open', the command line should be disabled
850        """
851        self.get_widget('quick_open_command_line').set_sensitive(chk.get_active())
852        self.get_widget('quick_open_in_current_terminal').set_sensitive(chk.get_active())
853
854    def toggle_use_vte_titles(self, chk):
855        """When vte titles aren't used, there is nothing to abbreviate
856        """
857        self.update_vte_subwidgets_states()
858
859    def update_vte_subwidgets_states(self):
860        do_use_vte_titles = self.get_widget('use_vte_titles').get_active()
861        max_tab_name_length_wdg = self.get_widget('max_tab_name_length')
862        max_tab_name_length_wdg.set_sensitive(do_use_vte_titles)
863        self.get_widget('lbl_max_tab_name_length').set_sensitive(do_use_vte_titles)
864        self.get_widget('abbreviate_tab_names').set_sensitive(do_use_vte_titles)
865
866    def on_reset_compat_defaults_clicked(self, bnt):
867        """Reset default values to compat_{backspace,delete} dconf
868        keys. The default values are retrivied from the guake.schemas
869        file.
870        """
871        self.settings.general.reset('compat-backspace')
872        self.settings.general.reset('compat-delete')
873        self.reload_erase_combos()
874
875    def on_palette_name_changed(self, combo):
876        """Changes the value of palette in dconf
877        """
878        palette_name = combo.get_active_text()
879        if palette_name not in PALETTES:
880            return
881        self.settings.styleFont.set_string('palette', PALETTES[palette_name])
882        self.settings.styleFont.set_string('palette-name', palette_name)
883        self.set_palette_colors(PALETTES[palette_name])
884        self.update_demo_palette(PALETTES[palette_name])
885
886    def on_cursor_shape_changed(self, combo):
887        """Changes the value of cursor_shape in dconf
888        """
889        index = combo.get_active()
890        self.settings.style.set_int('cursor-shape', index)
891
892    def on_blink_cursor_toggled(self, chk):
893        """Changes the value of blink_cursor in dconf
894        """
895        self.settings.style.set_int('cursor-blink-mode', chk.get_active())
896
897    def on_palette_color_set(self, btn):
898        """Changes the value of palette in dconf
899        """
900
901        palette = []
902        for i in range(18):
903            palette.append(hexify_color(self.get_widget('palette_%d' % i).get_color()))
904        palette = ':'.join(palette)
905        self.settings.styleFont.set_string('palette', palette)
906        self.settings.styleFont.set_string('palette-name', _('Custom'))
907        self.set_palette_name('Custom')
908        self.update_demo_palette(palette)
909
910    # this methods should be moved to the GuakeTerminal class FROM HERE
911
912    def set_palette_name(self, palette_name):
913        """If the given palette matches an existing one, shows it in the
914        combobox
915        """
916        combo = self.get_widget('palette_name')
917        found = False
918        log.debug("wanting palette: %r", palette_name)
919        for i in combo.get_model():
920            if i[0] == palette_name:
921                combo.set_active_iter(i.iter)
922                found = True
923                break
924        if not found:
925            combo.set_active(self.custom_palette_index)
926
927    def update_demo_palette(self, palette):
928        self.set_colors_from_settings()
929
930    def set_colors_from_settings(self):
931        transparency = self.settings.styleBackground.get_int('transparency')
932        colorRGBA = Gdk.RGBA(0, 0, 0, 0)
933        palette_list = list()
934        for color in self.settings.styleFont.get_string("palette").split(':'):
935            colorRGBA.parse(color)
936            palette_list.append(colorRGBA.copy())
937
938        if len(palette_list) > 16:
939            bg_color = palette_list[17]
940        else:
941            bg_color = Gdk.RGBA(255, 255, 255, 0)
942
943            bg_color.alpha = 1 / 100 * transparency
944
945        if len(palette_list) > 16:
946            font_color = palette_list[16]
947        else:
948            font_color = Gdk.RGBA(0, 0, 0, 0)
949
950        self.demo_terminal.set_color_foreground(font_color)
951        self.demo_terminal.set_color_bold(font_color)
952        self.demo_terminal.set_colors(font_color, bg_color, palette_list[:16])
953
954    # TO HERE (see above)
955    def fill_palette_names(self):
956        combo = self.get_widget('palette_name')
957        for palette in sorted(PALETTES):
958            combo.append_text(palette)
959        self.custom_palette_index = len(PALETTES)
960        combo.append_text(_('Custom'))
961
962    def set_cursor_shape(self, shape_index):
963        self.get_widget('cursor_shape').set_active(shape_index)
964
965    def set_cursor_blink_mode(self, mode_index):
966        self.get_widget('cursor_blink_mode').set_active(mode_index)
967
968    def set_palette_colors(self, palette):
969        """Updates the color buttons with the given palette
970        """
971        palette = palette.split(':')
972        for i, pal in enumerate(palette):
973            x, color = Gdk.Color.parse(pal)
974            self.get_widget('palette_%d' % i).set_color(color)
975
976    def reload_erase_combos(self, btn=None):
977        """Read from dconf the value of compat_{backspace,delete} vars
978        and select the right option in combos.
979        """
980        # backspace erase binding
981        combo = self.get_widget('backspace-binding-combobox')
982        binding = self.settings.general.get_string('compat-backspace')
983        for i in combo.get_model():
984            if ERASE_BINDINGS.get(i[0]) == binding:
985                combo.set_active_iter(i.iter)
986                break
987
988        # delete erase binding
989        combo = self.get_widget('delete-binding-combobox')
990        binding = self.settings.general.get_string('compat-delete')
991        for i in combo.get_model():
992            if ERASE_BINDINGS.get(i[0]) == binding:
993                combo.set_active_iter(i.iter)
994                break
995
996    def _load_hooks_settings(self):
997        """load hooks settings"""
998        log.debug("executing _load_hooks_settings")
999        hook_show_widget = self.get_widget("hook_show")
1000        hook_show_setting = self.settings.hooks.get_string("show")
1001        if hook_show_widget is not None:
1002            if hook_show_setting is not None:
1003                hook_show_widget.set_text(hook_show_setting)
1004
1005    def _load_default_shell_settings(self):
1006        combo = self.get_widget('default_shell')
1007        # get the value for defualt shell. If unset, set to USER_SHELL_VALUE.
1008        value = self.settings.general.get_string('default-shell') or USER_SHELL_VALUE
1009        for i in combo.get_model():
1010            if i[0] == value:
1011                combo.set_active_iter(i.iter)
1012                break
1013
1014    def _load_screen_settings(self):
1015        """Load screen settings"""
1016        # display number / use primary display
1017        combo = self.get_widget('display_n')
1018        dest_screen = self.settings.general.get_int('display-n')
1019        # If Guake is configured to use a screen that is not currently attached,
1020        # default to 'primary display' option.
1021        screen = self.get_widget('config-window').get_screen()
1022        n_screens = screen.get_n_monitors()
1023        if dest_screen > n_screens - 1:
1024            self.settings.general.set_boolean('mouse-display', False)
1025            dest_screen = screen.get_primary_monitor()
1026            self.settings.general.set_int('display-n', dest_screen)
1027
1028        if dest_screen == ALWAYS_ON_PRIMARY:
1029            first_item = combo.get_model().get_iter_first()
1030            combo.set_active_iter(first_item)
1031        else:
1032            seen_first = False  # first item "always on primary" is special
1033            for i in combo.get_model():
1034                if seen_first:
1035                    i_int = int(i[0].split()[0])  # extracts 1 from '1' or from '1 (primary)'
1036                    if i_int == dest_screen:
1037                        combo.set_active_iter(i.iter)
1038                else:
1039                    seen_first = True
1040
1041    def load_configs(self):
1042        """Load configurations for all widgets in General, Scrolling
1043        and Appearance tabs from dconf.
1044        """
1045        self._load_default_shell_settings()
1046
1047        # login shell
1048        value = self.settings.general.get_boolean('use-login-shell')
1049        self.get_widget('use_login_shell').set_active(value)
1050
1051        # tray icon
1052        value = self.settings.general.get_boolean('use-trayicon')
1053        self.get_widget('use_trayicon').set_active(value)
1054
1055        # popup
1056        value = self.settings.general.get_boolean('use-popup')
1057        self.get_widget('use_popup').set_active(value)
1058
1059        # prompt on quit
1060        value = self.settings.general.get_boolean('prompt-on-quit')
1061        self.get_widget('prompt_on_quit').set_active(value)
1062
1063        # prompt on close_tab
1064        value = self.settings.general.get_int('prompt-on-close-tab')
1065        self.get_widget('prompt_on_close_tab').set_active(value)
1066        self.get_widget('prompt_on_quit').set_sensitive(value != 2)
1067
1068        # gtk theme theme
1069        value = self.settings.general.get_string('gtk-theme-name')
1070        combo = self.get_widget('gtk_theme_name')
1071        for i in combo.get_model():
1072            if i[0] == value:
1073                combo.set_active_iter(i.iter)
1074                break
1075
1076        # prefer gtk theme theme
1077        value = self.settings.general.get_boolean('gtk-prefer-dark-theme')
1078        self.get_widget('gtk_prefer_dark_theme').set_active(value)
1079
1080        # ontop
1081        value = self.settings.general.get_boolean('window-ontop')
1082        self.get_widget('window_ontop').set_active(value)
1083
1084        # tab ontop
1085        value = self.settings.general.get_boolean('tab-ontop')
1086        self.get_widget('tab_ontop').set_active(value)
1087
1088        # refocus
1089        value = self.settings.general.get_boolean('window-refocus')
1090        self.get_widget('window_refocus').set_active(value)
1091
1092        # losefocus
1093        value = self.settings.general.get_boolean('window-losefocus')
1094        self.get_widget('window_losefocus').set_active(value)
1095
1096        # use VTE titles
1097        value = self.settings.general.get_boolean('use-vte-titles')
1098        self.get_widget('use_vte_titles').set_active(value)
1099
1100        # set window title
1101        value = self.settings.general.get_boolean('set-window-title')
1102        self.get_widget('set_window_title').set_active(value)
1103
1104        # abbreviate tab names
1105        self.get_widget('abbreviate_tab_names').set_sensitive(value)
1106        value = self.settings.general.get_boolean('abbreviate-tab-names')
1107        self.get_widget('abbreviate_tab_names').set_active(value)
1108
1109        # max tab name length
1110        value = self.settings.general.get_int('max-tab-name-length')
1111        self.get_widget('max_tab_name_length').set_value(value)
1112
1113        self.update_vte_subwidgets_states()
1114
1115        value = self.settings.general.get_int('window-height')
1116        self.get_widget('window_height').set_value(value)
1117
1118        value = self.settings.general.get_int('window-width')
1119        self.get_widget('window_width').set_value(value)
1120
1121        # window displacements
1122        value = self.settings.general.get_int('window-vertical-displacement')
1123        self.get_widget('window_vertical_displacement').set_value(value)
1124
1125        value = self.settings.general.get_int('window-horizontal-displacement')
1126        self.get_widget('window_horizontal_displacement').set_value(value)
1127
1128        value = self.settings.general.get_int('window-halignment')
1129        which_button = {
1130            ALIGN_RIGHT: 'radiobutton_align_right',
1131            ALIGN_LEFT: 'radiobutton_align_left',
1132            ALIGN_CENTER: 'radiobutton_align_center'
1133        }
1134        self.get_widget(which_button[value]).set_active(True)
1135        self.get_widget("window_horizontal_displacement").set_sensitive(value != ALIGN_CENTER)
1136
1137        value = self.settings.general.get_boolean('open-tab-cwd')
1138        self.get_widget('open_tab_cwd').set_active(value)
1139
1140        # tab bar
1141        value = self.settings.general.get_boolean('window-tabbar')
1142        self.get_widget('window_tabbar').set_active(value)
1143
1144        # start fullscreen
1145        value = self.settings.general.get_boolean('start-fullscreen')
1146        self.get_widget('start_fullscreen').set_active(value)
1147
1148        # start at GNOME login
1149        value = self.settings.general.get_boolean('start-at-login')
1150        self.get_widget('start_at_login').set_active(value)
1151
1152        # use audible bell
1153        value = self.settings.general.get_boolean('use-audible-bell')
1154        self.get_widget('use_audible_bell').set_active(value)
1155
1156        self._load_screen_settings()
1157
1158        value = self.settings.general.get_boolean('quick-open-enable')
1159        self.get_widget('quick_open_enable').set_active(value)
1160        self.get_widget('quick_open_command_line').set_sensitive(value)
1161        self.get_widget('quick_open_in_current_terminal').set_sensitive(value)
1162        text = Gtk.TextBuffer()
1163        text = self.get_widget('quick_open_supported_patterns').get_buffer()
1164        for title, matcher, _useless in QUICK_OPEN_MATCHERS:
1165            text.insert_at_cursor("%s: %s\n" % (title, matcher))
1166        self.get_widget('quick_open_supported_patterns').set_buffer(text)
1167
1168        value = self.settings.general.get_string('quick-open-command-line')
1169        if value is None:
1170            value = "subl %(file_path)s:%(line_number)s"
1171        self.get_widget('quick_open_command_line').set_text(value)
1172
1173        value = self.settings.general.get_boolean('quick-open-in-current-terminal')
1174        self.get_widget('quick_open_in_current_terminal').set_active(value)
1175
1176        value = self.settings.general.get_string('startup-script')
1177        if value:
1178            self.get_widget('startup_script').set_text(value)
1179
1180        # use display where the mouse is currently
1181        value = self.settings.general.get_boolean('mouse-display')
1182        self.get_widget('mouse_display').set_active(value)
1183
1184        # scrollbar
1185        value = self.settings.general.get_boolean('use-scrollbar')
1186        self.get_widget('use_scrollbar').set_active(value)
1187
1188        # history size
1189        value = self.settings.general.get_int('history-size')
1190        self.get_widget('history_size').set_value(value)
1191
1192        # infinite history
1193        value = self.settings.general.get_boolean('infinite-history')
1194        self.get_widget('infinite_history').set_active(value)
1195
1196        # scroll output
1197        value = self.settings.general.get_boolean('scroll-output')
1198        self.get_widget('scroll_output').set_active(value)
1199
1200        # scroll keystroke
1201        value = self.settings.general.get_boolean('scroll-keystroke')
1202        self.get_widget('scroll_keystroke').set_active(value)
1203
1204        # default font
1205        value = self.settings.general.get_boolean('use-default-font')
1206        self.get_widget('use_default_font').set_active(value)
1207        self.get_widget('font_style').set_sensitive(not value)
1208
1209        # font
1210        value = self.settings.styleFont.get_string('style')
1211        if value:
1212            self.get_widget('font_style').set_font_name(value)
1213
1214        # allow bold font
1215        value = self.settings.styleFont.get_boolean('allow-bold')
1216        self.get_widget('allow_bold').set_active(value)
1217
1218        # palette
1219        self.fill_palette_names()
1220        value = self.settings.styleFont.get_string('palette-name')
1221        self.set_palette_name(value)
1222        value = self.settings.styleFont.get_string('palette')
1223        self.set_palette_colors(value)
1224        self.update_demo_palette(value)
1225
1226        # cursor shape
1227        value = self.settings.style.get_int('cursor-shape')
1228        self.set_cursor_shape(value)
1229
1230        # cursor blink
1231        value = self.settings.style.get_int('cursor-blink-mode')
1232        self.set_cursor_blink_mode(value)
1233
1234        value = self.settings.styleBackground.get_int('transparency')
1235        self.get_widget('background_transparency').set_value(value)
1236
1237        value = self.settings.general.get_int('window-valignment')
1238        self.get_widget('top_align').set_active(value)
1239
1240        # it's a separated method, to be reused.
1241        self.reload_erase_combos()
1242
1243        # custom command context-menu configuration file
1244        custom_command_file = self.settings.general.get_string('custom-command-file')
1245        if custom_command_file:
1246            custom_command_file_name = os.path.expanduser(custom_command_file)
1247        else:
1248            custom_command_file_name = None
1249        custom_cmd_filter = Gtk.FileFilter()
1250        custom_cmd_filter.set_name(_("JSON files"))
1251        custom_cmd_filter.add_pattern("*.json")
1252        self.get_widget('custom_command_file_chooser').add_filter(custom_cmd_filter)
1253        all_files_filter = Gtk.FileFilter()
1254        all_files_filter.set_name(_("All files"))
1255        all_files_filter.add_pattern("*")
1256        self.get_widget('custom_command_file_chooser').add_filter(all_files_filter)
1257        if custom_command_file_name:
1258            self.get_widget('custom_command_file_chooser').set_filename(custom_command_file_name)
1259
1260        # hooks
1261        self._load_hooks_settings()
1262
1263    # -- populate functions --
1264
1265    def populate_shell_combo(self):
1266        """Read the /etc/shells and looks for installed shells to
1267        fill the default_shell combobox.
1268        """
1269        cb = self.get_widget('default_shell')
1270        # append user shell as first option
1271        cb.append_text(USER_SHELL_VALUE)
1272        if os.path.exists(SHELLS_FILE):
1273            lines = open(SHELLS_FILE).readlines()
1274            for i in lines:
1275                possible = i.strip()
1276                if possible and not possible.startswith('#') and os.path.exists(possible):
1277                    cb.append_text(possible)
1278
1279        for i in get_binaries_from_path(PYTHONS):
1280            cb.append_text(i)
1281
1282    def populate_gtk_theme_names(self):
1283        cb = self.get_widget('gtk_theme_name')
1284        for name in list_all_themes():
1285            name = name.strip()
1286            cb.append_text(name)
1287
1288    def populate_keys_tree(self):
1289        """Reads the HOTKEYS global variable and insert all data in
1290        the TreeStore used by the preferences window treeview.
1291        """
1292        for group in HOTKEYS:
1293            parent = self.store.append(None, [None, group['label'], None, None])
1294            for item in group['keys']:
1295                if item['key'] == "show-hide" or item['key'] == "show-focus":
1296                    accel = self.settings.keybindingsGlobal.get_string(item['key'])
1297                else:
1298                    accel = self.settings.keybindingsLocal.get_string(item['key'])
1299                gsettings_path = item['key']
1300                keycode, mask = Gtk.accelerator_parse(accel)
1301                keylabel = Gtk.accelerator_get_label(keycode, mask)
1302                self.store.append(parent, [gsettings_path, item['label'], keylabel, accel])
1303        self.get_widget('treeview-keys').expand_all()
1304
1305    def populate_display_n(self):
1306        """Get the number of displays and populate this drop-down box
1307        with them all. Prepend the "always on primary" option.
1308        """
1309        cb = self.get_widget('display_n')
1310        screen = self.get_widget('config-window').get_screen()
1311
1312        cb.append_text("always on primary")
1313
1314        for m in range(0, int(screen.get_n_monitors())):
1315            if m == int(screen.get_primary_monitor()):
1316                # TODO l10n
1317                cb.append_text(str(m) + ' ' + '(primary)')
1318            else:
1319                cb.append_text(str(m))
1320
1321    # -- key handling --
1322
1323    def on_accel_edited(self, cellrendereraccel, path, key, mods, hwcode):
1324        """Callback that handles key edition in cellrenderer. It makes
1325        some tests to validate the key, like looking for already in
1326        use keys and look for [A-Z][a-z][0-9] to avoid problems with
1327        these common keys. If all tests are ok, the value will be
1328        stored in dconf.
1329        """
1330        accelerator = Gtk.accelerator_name(key, mods)
1331
1332        dconf_path = self.store[path][HOTKET_MODEL_INDEX_DCONF]
1333        old_accel = self.store[path][HOTKET_MODEL_INDEX_HUMAN_ACCEL]
1334        keylabel = Gtk.accelerator_get_label(key, mods)
1335
1336        # we needn't to change anything, the user is trying to set the
1337        # same key that is already set.
1338        if old_accel == accelerator:
1339            return False
1340
1341        self.hotkey_alread_used = False
1342
1343        # looking for already used keybindings
1344
1345        def each_key(model, path, subiter):
1346            keyentry = model.get_value(subiter, HOTKET_MODEL_INDEX_ACCEL)
1347            if keyentry and keyentry == accelerator:
1348                self.hotkey_alread_used = True
1349                msg = _("The shortcut \"%s\" is already in use.") % html_escape(accelerator)
1350                ShowableError(self.window, _('Error setting keybinding.'), msg, -1)
1351                raise Exception('This is ok, we just use it to break the foreach loop!')
1352
1353        self.store.foreach(each_key)
1354        if self.hotkey_alread_used:
1355            return False
1356
1357        # avoiding problems with common keys
1358        if ((mods == 0 and key != 0) and
1359            ((ord('a') <= key <= ord('z')) or (ord('A') <= key <= ord('Z')) or
1360             (ord('0') <= key <= ord('9')))):
1361            dialog = Gtk.MessageDialog(
1362                self.get_widget('config-window'),
1363                Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
1364                Gtk.MessageType.WARNING, Gtk.ButtonsType.OK,
1365                _(
1366                    "The shortcut \"%s\" cannot be used "
1367                    "because it will become impossible to "
1368                    "type using this key.\n\n"
1369                    "Please try with a key such as "
1370                    "Control, Alt or Shift at the same "
1371                    "time.\n"
1372                ) % html_escape(key)
1373            )
1374            dialog.run()
1375            dialog.destroy()
1376            return False
1377
1378        self.store[path][HOTKET_MODEL_INDEX_HUMAN_ACCEL] = keylabel
1379
1380        if dconf_path in ("show-hide", "show-focus"):
1381            self.settings.keybindingsGlobal.set_string(dconf_path, accelerator)
1382        else:
1383            self.settings.keybindingsLocal.set_string(dconf_path, accelerator)
1384        self.store[path][HOTKET_MODEL_INDEX_ACCEL] = accelerator
1385
1386    def on_accel_cleared(self, cellrendereraccel, path):
1387        """If the user tries to clear a keybinding with the backspace
1388        key this callback will be called and it just fill the model
1389        with an empty key and set the 'disabled' string in dconf path.
1390        """
1391        dconf_path = self.store[path][HOTKET_MODEL_INDEX_DCONF]
1392
1393        if dconf_path == "show-hide":
1394            # cannot disable 'show-hide' hotkey
1395            log.warn("Cannot disable 'show-hide' hotkey")
1396            self.settings.keybindingsGlobal.set_string(dconf_path, old_accel)
1397        else:
1398            self.store[path][HOTKET_MODEL_INDEX_HUMAN_ACCEL] = ""
1399            self.store[path][HOTKET_MODEL_INDEX_ACCEL] = "None"
1400            if dconf_path == "show-focus":
1401                self.settings.keybindingsGlobal.set_string(dconf_path, 'disabled')
1402            else:
1403                self.settings.keybindingsLocal.set_string(dconf_path, 'disabled')
1404
1405    def start_editing(self, treeview, event):
1406        """Make the treeview grab the focus and start editing the cell
1407        that the user has clicked to avoid confusion with two or three
1408        clicks before editing a keybinding.
1409
1410        Thanks to gnome-keybinding-properties.c =)
1411        """
1412        x, y = int(event.x), int(event.y)
1413        ret = treeview.get_path_at_pos(x, y)
1414        if not ret:
1415            return False
1416
1417        path, column, cellx, celly = ret
1418
1419        treeview.row_activated(path, Gtk.TreeViewColumn(None))
1420        treeview.set_cursor(path)
1421
1422        return False
1423
1424
1425class KeyEntry():
1426
1427    def __init__(self, keycode, mask):
1428        self.keycode = keycode
1429        self.mask = mask
1430
1431    def __repr__(self):
1432        return u'KeyEntry(%d, %d)' % (self.keycode, self.mask)
1433
1434    def __eq__(self, rval):
1435        return self.keycode == rval.keycode and self.mask == rval.mask
1436
1437
1438def setup_standalone_signals(instance):
1439    """Called when prefs dialog is running in standalone mode. It
1440    makes the delete event of dialog and click on close button finish
1441    the application.
1442    """
1443    window = instance.get_widget('config-window')
1444    window.connect('delete-event', Gtk.main_quit)
1445
1446    # We need to block the execution of the already associated
1447    # callback before connecting the new handler.
1448    button = instance.get_widget('button1')
1449    button.handler_block_by_func(instance.gtk_widget_destroy)
1450    button.connect('clicked', Gtk.main_quit)
1451
1452    return instance
1453
1454
1455if __name__ == '__main__':
1456    bindtextdomain(NAME, LOCALE_DIR)
1457    setup_standalone_signals(PrefsDialog(None)).show()
1458    Gtk.main()
1459