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