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 "&": "&", 307 '"': """, 308 "'": "'", 309 ">": ">", 310 "<": "<", 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