diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 4fbb907d..09e4adbb 100644 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -66,7 +66,7 @@ >>> config.options_set({}) >>> config.options_get() {} ->>> +>>> """ @@ -81,211 +81,212 @@ from gi.repository import Gio DEFAULTS = { - 'global_config': { - 'dbus' : True, - 'focus' : 'click', - 'handle_size' : -1, - 'geometry_hinting' : False, - 'window_state' : 'normal', - 'borderless' : False, - 'extra_styling' : True, - 'tab_position' : 'top', - 'broadcast_default' : 'group', - 'close_button_on_tab' : True, - 'scroll_tabbar' : False, - 'homogeneous_tabbar' : True, - 'hide_from_taskbar' : False, - 'always_on_top' : False, - 'hide_on_lose_focus' : False, - 'sticky' : False, - 'use_custom_url_handler': False, - 'custom_url_handler' : '', - 'inactive_color_offset': 0.8, - 'inactive_bg_color_offset': 1.0, - 'enabled_plugins' : ['LaunchpadBugURLHandler', - 'LaunchpadCodeURLHandler', - 'APTURLHandler'], - 'suppress_multiple_term_dialog': False, - 'always_split_with_profile': False, - 'putty_paste_style' : False, - 'putty_paste_style_source_clipboard': False, - 'disable_mouse_paste' : False, - 'smart_copy' : True, - 'clear_select_on_copy' : False, - 'cell_width' : 1.0, - 'cell_height' : 1.0, - 'case_sensitive' : True, - 'invert_search' : False, - 'link_single_click' : False, - 'title_at_bottom' : False, - 'detachable_tabs' : True, - - 'new_tab_after_current_tab': False, - }, - 'keybindings': { - 'zoom_in' : 'plus', - 'zoom_out' : 'minus', - 'zoom_normal' : '0', - 'zoom_in_all' : '', - 'zoom_out_all' : '', - 'zoom_normal_all' : '', - 'new_tab' : 't', - 'cycle_next' : 'Tab', - 'cycle_prev' : 'Tab', - 'go_next' : 'n', - 'go_prev' : 'p', - 'go_up' : 'Up', - 'go_down' : 'Down', - 'go_left' : 'Left', - 'go_right' : 'Right', - 'rotate_cw' : 'r', - 'rotate_ccw' : 'r', - 'split_auto' : 'a', - 'split_horiz' : 'o', - 'split_vert' : 'e', - 'close_term' : 'w', - 'copy' : 'c', - 'paste' : 'v', - 'paste_selection' : '', - 'toggle_scrollbar' : 's', - 'search' : 'f', - 'page_up' : '', - 'page_down' : '', - 'page_up_half' : '', - 'page_down_half' : '', - 'line_up' : '', - 'line_down' : '', - 'close_window' : 'q', - 'resize_up' : 'Up', - 'resize_down' : 'Down', - 'resize_left' : 'Left', - 'resize_right' : 'Right', - 'move_tab_right' : 'Page_Down', - 'move_tab_left' : 'Page_Up', - 'toggle_zoom' : 'x', - 'scaled_zoom' : 'z', - 'next_tab' : 'Page_Down', - 'prev_tab' : 'Page_Up', - 'switch_to_tab_1' : '', - 'switch_to_tab_2' : '', - 'switch_to_tab_3' : '', - 'switch_to_tab_4' : '', - 'switch_to_tab_5' : '', - 'switch_to_tab_6' : '', - 'switch_to_tab_7' : '', - 'switch_to_tab_8' : '', - 'switch_to_tab_9' : '', - 'switch_to_tab_10' : '', - 'full_screen' : 'F11', - 'reset' : 'r', - 'reset_clear' : 'g', - 'hide_window' : 'a', - 'create_group' : '', - 'group_all' : 'g', - 'group_all_toggle' : '', - 'ungroup_all' : 'g', - 'group_win' : '', - 'group_win_toggle' : '', - 'ungroup_win' : 'w', - 'group_tab' : 't', - 'group_tab_toggle' : '', - 'ungroup_tab' : 't', - 'new_window' : 'i', - 'new_terminator' : 'i', - 'broadcast_off' : '', - 'broadcast_group' : '', - 'broadcast_all' : '', - 'insert_number' : '1', - 'insert_padded' : '0', - 'edit_window_title': 'w', - 'edit_tab_title' : 'a', - 'edit_terminal_title': 'x', - 'layout_launcher' : 'l', - 'next_profile' : '', - 'previous_profile' : '', - 'preferences' : '', - 'preferences_keybindings' : 'k', - 'help' : 'F1' - }, - 'profiles': { - 'default': { - 'allow_bold' : True, - 'audible_bell' : False, - 'visible_bell' : False, - 'urgent_bell' : False, - 'icon_bell' : True, - 'background_color' : '#000000', - 'background_darkness' : 0.5, - 'background_type' : 'solid', - 'background_image' : '', - 'background_image_mode' : 'stretch_and_fill', - 'background_image_align_horiz': 'center', - 'background_image_align_vert' : 'middle', - 'backspace_binding' : 'ascii-del', - 'delete_binding' : 'escape-sequence', - 'cursor_blink' : True, - 'cursor_shape' : 'block', - 'cursor_fg_color' : '', - 'cursor_bg_color' : '', - 'cursor_color_default' : True, - 'term' : 'xterm-256color', - 'colorterm' : 'truecolor', - 'font' : 'Mono 10', - 'foreground_color' : '#aaaaaa', - 'show_titlebar' : True, - 'scrollbar_position' : "right", - 'scroll_on_keystroke' : True, - 'scroll_on_output' : False, - 'scrollback_lines' : 500, - 'scrollback_infinite' : False, - 'disable_mousewheel_zoom': False, - 'exit_action' : 'close', - 'palette' : '#2e3436:#cc0000:#4e9a06:#c4a000:\ + 'global_config': { + 'dbus': True, + 'focus': 'click', + 'handle_size': -1, + 'geometry_hinting': False, + 'window_state': 'normal', + 'borderless': False, + 'extra_styling': True, + 'tab_position': 'top', + 'broadcast_default': 'group', + 'close_button_on_tab': True, + 'scroll_tabbar': False, + 'homogeneous_tabbar': True, + 'hide_from_taskbar': False, + 'always_on_top': False, + 'hide_on_lose_focus': False, + 'sticky': False, + 'use_custom_url_handler': False, + 'custom_url_handler': '', + 'inactive_color_offset': 0.8, + 'inactive_bg_color_offset': 1.0, + 'enabled_plugins': ['LaunchpadBugURLHandler', + 'LaunchpadCodeURLHandler', + 'APTURLHandler'], + 'suppress_multiple_term_dialog': False, + 'always_split_with_profile': False, + 'putty_paste_style': False, + 'putty_paste_style_source_clipboard': False, + 'disable_mouse_paste': False, + 'smart_copy': True, + 'clear_select_on_copy': False, + 'cell_width': 1.0, + 'cell_height': 1.0, + 'case_sensitive': True, + 'invert_search': False, + 'link_single_click': False, + 'title_at_bottom': False, + 'detachable_tabs': True, + + 'new_tab_after_current_tab': False, + }, + 'keybindings': { + 'zoom_in': ['plus', ''], + 'zoom_out': ['minus', ''], + 'zoom_normal': ['0', ''], + 'zoom_in_all': ['', ''], + 'zoom_out_all': ['', ''], + 'zoom_normal_all': ['', ''], + 'new_tab': ['t', ''], + 'cycle_next': ['Tab', ''], + 'cycle_prev': ['Tab', ''], + 'go_next': ['n', ''], + 'go_prev': ['p', ''], + 'go_up': ['Up', ''], + 'go_down': ['Down', ''], + 'go_left': ['Left', ''], + 'go_right': ['Right', ''], + 'rotate_cw': ['r', ''], + 'rotate_ccw': ['r', ''], + 'split_auto': ['a', ''], + 'split_horiz': ['o', ''], + 'split_vert': ['e', ''], + 'close_term': ['w', ''], + 'copy': ['c', ''], + 'paste': ['v', ''], + 'paste_selection': ['', ''], + 'toggle_scrollbar': ['s', ''], + 'search': ['f', ''], + 'page_up': ['', ''], + 'page_down': ['', ''], + 'page_up_half': ['', ''], + 'page_down_half': ['', ''], + 'line_up': ['', ''], + 'line_down': ['', ''], + 'close_window': ['q', ''], + 'resize_up': ['Up', ''], + 'resize_down': ['Down', ''], + 'resize_left': ['Left', ''], + 'resize_right': ['Right', ''], + 'move_tab_right': ['Page_Down', ''], + 'move_tab_left': ['Page_Up', ''], + 'toggle_zoom': ['x', ''], + 'scaled_zoom': ['z', ''], + 'next_tab': ['Page_Down', ''], + 'prev_tab': ['Page_Up', ''], + 'switch_to_tab_1': ['', ''], + 'switch_to_tab_2': ['', ''], + 'switch_to_tab_3': ['', ''], + 'switch_to_tab_4': ['', ''], + 'switch_to_tab_5': ['', ''], + 'switch_to_tab_6': ['', ''], + 'switch_to_tab_7': ['', ''], + 'switch_to_tab_8': ['', ''], + 'switch_to_tab_9': ['', ''], + 'switch_to_tab_10': ['', ''], + 'full_screen': ['F11', ''], + 'reset': ['r', ''], + 'reset_clear': ['g', ''], + 'hide_window': ['a', ''], + 'create_group': ['', ''], + 'group_all': ['g', ''], + 'group_all_toggle': ['', ''], + 'ungroup_all': ['g', ''], + 'group_win': ['', ''], + 'group_win_toggle': ['', ''], + 'ungroup_win': ['w', ''], + 'group_tab': ['t', ''], + 'group_tab_toggle': ['', ''], + 'ungroup_tab': ['t', ''], + 'new_window': ['i', ''], + 'new_terminator': ['i', ''], + 'broadcast_off': ['', ''], + 'broadcast_group': ['', ''], + 'broadcast_all': ['', ''], + 'insert_number': ['1', ''], + 'insert_padded': ['0', ''], + 'edit_window_title': ['w', ''], + 'edit_tab_title': ['a', ''], + 'edit_terminal_title': ['x', ''], + 'layout_launcher': ['l', ''], + 'next_profile': ['', ''], + 'previous_profile': ['', ''], + 'preferences': ['', ''], + 'preferences_keybindings': ['k', ''], + 'help': ['F1', ''] + }, + 'profiles': { + 'default': { + 'allow_bold': True, + 'audible_bell': False, + 'visible_bell': False, + 'urgent_bell': False, + 'icon_bell': True, + 'background_color': '#000000', + 'background_darkness': 0.5, + 'background_type': 'solid', + 'background_image': '', + 'background_image_mode': 'stretch_and_fill', + 'background_image_align_horiz': 'center', + 'background_image_align_vert': 'middle', + 'backspace_binding': 'ascii-del', + 'delete_binding': 'escape-sequence', + 'cursor_blink': True, + 'cursor_shape': 'block', + 'cursor_fg_color': '', + 'cursor_bg_color': '', + 'cursor_color_default': True, + 'term': 'xterm-256color', + 'colorterm': 'truecolor', + 'font': 'Mono 10', + 'foreground_color': '#aaaaaa', + 'show_titlebar': True, + 'scrollbar_position': "right", + 'scroll_on_keystroke': True, + 'scroll_on_output': False, + 'scrollback_lines': 500, + 'scrollback_infinite': False, + 'disable_mousewheel_zoom': False, + 'exit_action': 'close', + 'palette': '#2e3436:#cc0000:#4e9a06:#c4a000:\ #3465a4:#75507b:#06989a:#d3d7cf:#555753:#ef2929:#8ae234:#fce94f:\ #729fcf:#ad7fa8:#34e2e2:#eeeeec', - 'word_chars' : '-,./?%&#:_', - 'mouse_autohide' : True, - 'login_shell' : False, - 'use_custom_command' : False, - 'custom_command' : '', - 'use_system_font' : True, - 'use_theme_colors' : False, - 'bold_is_bright' : False, - 'cell_height' : 1.0, - 'cell_width' : 1.0, - 'force_no_bell' : False, - 'copy_on_selection' : False, - 'split_to_group' : False, - 'autoclean_groups' : True, - 'http_proxy' : '', - # Titlebar - 'title_hide_sizetext' : False, - 'title_transmit_fg_color' : '#ffffff', - 'title_transmit_bg_color' : '#c80003', - 'title_receive_fg_color' : '#ffffff', - 'title_receive_bg_color' : '#0076c9', - 'title_inactive_fg_color' : '#000000', - 'title_inactive_bg_color' : '#c0bebf', - 'title_use_system_font' : True, - 'title_font' : 'Sans 9' - }, - }, - 'layouts': { - 'default': { - 'window0': { - 'type': 'Window', - 'parent': '' - }, - 'child1': { - 'type': 'Terminal', - 'parent': 'window0' - } - } - }, - 'plugins': { + 'word_chars': '-,./?%&#:_', + 'mouse_autohide': True, + 'login_shell': False, + 'use_custom_command': False, + 'custom_command': '', + 'use_system_font': True, + 'use_theme_colors': False, + 'bold_is_bright': False, + 'cell_height': 1.0, + 'cell_width': 1.0, + 'force_no_bell': False, + 'copy_on_selection': False, + 'split_to_group': False, + 'autoclean_groups': True, + 'http_proxy': '', + # Titlebar + 'title_hide_sizetext': False, + 'title_transmit_fg_color': '#ffffff', + 'title_transmit_bg_color': '#c80003', + 'title_receive_fg_color': '#ffffff', + 'title_receive_bg_color': '#0076c9', + 'title_inactive_fg_color': '#000000', + 'title_inactive_bg_color': '#c0bebf', + 'title_use_system_font': True, + 'title_font': 'Sans 9' }, + }, + 'layouts': { + 'default': { + 'window0': { + 'type': 'Window', + 'parent': '' + }, + 'child1': { + 'type': 'Terminal', + 'parent': 'window0' + } + } + }, + 'plugins': { + }, } + class Config(object): """Class to provide a slightly richer config API above ConfigBase""" base = None @@ -293,8 +294,9 @@ class Config(object): system_mono_font = None system_prop_font = None system_focus = None + system_font = None inhibited = None - + def __init__(self, profile='default'): self.base = ConfigBase() self.set_profile(profile) @@ -303,45 +305,45 @@ def __init__(self, profile='default'): def __getitem__(self, key, default=None): """Look up a configuration item""" - return(self.base.get_item(key, self.profile, default=default)) + return self.base.get_item(key, self.profile, default=default) def __setitem__(self, key, value): """Set a particular configuration item""" - return(self.base.set_item(key, value, self.profile)) + return self.base.set_item(key, value, self.profile) def get_profile(self): """Get our profile""" - return(self.profile) + return self.profile def get_profile_by_name(self, profile): """Get the profile with the specified name""" - return(self.base.profiles[profile]) + return self.base.profiles[profile] def set_profile(self, profile, force=False): """Set our profile (which usually means change it)""" options = self.options_get() if not force and options and options.profile and profile == 'default': - dbg('overriding default profile to %s' % options.profile) + dbg(f'overriding default profile to {options.profile}') profile = options.profile - dbg('Changing profile to %s' % profile) + dbg(f'Changing profile to {profile}') self.profile = profile if profile not in self.base.profiles: - dbg('%s does not exist, creating' % profile) + dbg(f'{profile} does not exist, creating') self.base.profiles[profile] = copy(DEFAULTS['profiles']['default']) def add_profile(self, profile, toclone): """Add a new profile""" - return(self.base.add_profile(profile, toclone)) + return self.base.add_profile(profile, toclone) def del_profile(self, profile): """Delete a profile""" if profile == self.profile: # FIXME: We should solve this problem by updating terminals when we # remove a profile - err('Config::del_profile: Deleting in-use profile %s.' % profile) + err(f'Config::del_profile: Deleting in-use profile {profile}.') self.set_profile('default') if profile in self.base.profiles: - del(self.base.profiles[profile]) + del self.base.profiles[profile] options = self.options_get() if options and options.profile == profile: options.profile = None @@ -351,89 +353,86 @@ def rename_profile(self, profile, newname): """Rename a profile""" if profile in self.base.profiles: self.base.profiles[newname] = self.base.profiles[profile] - del(self.base.profiles[profile]) + del self.base.profiles[profile] if profile == self.profile: self.profile = newname def list_profiles(self): """List all configured profiles""" - return(list(self.base.profiles.keys())) + return list(self.base.profiles.keys()) def add_layout(self, name, layout): """Add a new layout""" - return(self.base.add_layout(name, layout)) + return self.base.add_layout(name, layout) def replace_layout(self, name, layout): """Replace an existing layout""" - return(self.base.replace_layout(name, layout)) + return self.base.replace_layout(name, layout) def del_layout(self, layout): """Delete a layout""" if layout in self.base.layouts: - del(self.base.layouts[layout]) + del self.base.layouts[layout] def rename_layout(self, layout, newname): """Rename a layout""" if layout in self.base.layouts: self.base.layouts[newname] = self.base.layouts[layout] - del(self.base.layouts[layout]) + del self.base.layouts[layout] def list_layouts(self): """List all configured layouts""" - return(list(self.base.layouts.keys())) + return list(self.base.layouts.keys()) def connect_gsetting_callbacks(self): """Get system settings and create callbacks for changes""" dbg("GSetting connects for system changes") # Have to preserve these to self, or callbacks don't happen - self.gsettings_interface=Gio.Settings.new('org.gnome.desktop.interface') + self.gsettings_interface = Gio.Settings.new('org.gnome.desktop.interface') self.gsettings_interface.connect("changed::font-name", self.on_gsettings_change_event) self.gsettings_interface.connect("changed::monospace-font-name", self.on_gsettings_change_event) - self.gsettings_wm=Gio.Settings.new('org.gnome.desktop.wm.preferences') + self.gsettings_wm = Gio.Settings.new('org.gnome.desktop.wm.preferences') self.gsettings_wm.connect("changed::focus-mode", self.on_gsettings_change_event) def get_system_prop_font(self): """Look up the system font""" if self.system_prop_font is not None: - return(self.system_prop_font) - elif 'org.gnome.desktop.interface' not in Gio.Settings.list_schemas(): - return + return self.system_prop_font + if 'org.gnome.desktop.interface' not in Gio.Settings.list_schemas(): + return '' + gsettings = Gio.Settings.new('org.gnome.desktop.interface') + value = gsettings.get_value('font-name') + if value: + self.system_prop_font = value.get_string() else: - gsettings=Gio.Settings.new('org.gnome.desktop.interface') - value = gsettings.get_value('font-name') - if value: - self.system_prop_font = value.get_string() - else: - self.system_prop_font = "Sans 10" - return(self.system_prop_font) + self.system_prop_font = "Sans 10" + return self.system_prop_font def get_system_mono_font(self): """Look up the system font""" if self.system_mono_font is not None: - return(self.system_mono_font) - elif 'org.gnome.desktop.interface' not in Gio.Settings.list_schemas(): - return + return self.system_mono_font + if 'org.gnome.desktop.interface' not in Gio.Settings.list_schemas(): + return '' + gsettings = Gio.Settings.new('org.gnome.desktop.interface') + value = gsettings.get_value('monospace-font-name') + if value: + self.system_mono_font = value.get_string() else: - gsettings=Gio.Settings.new('org.gnome.desktop.interface') - value = gsettings.get_value('monospace-font-name') - if value: - self.system_mono_font = value.get_string() - else: - self.system_mono_font = "Mono 10" - return(self.system_mono_font) + self.system_mono_font = "Mono 10" + return self.system_mono_font def get_system_focus(self): """Look up the system focus setting""" if self.system_focus is not None: - return(self.system_focus) - elif 'org.gnome.desktop.interface' not in Gio.Settings.list_schemas(): - return - else: - gsettings=Gio.Settings.new('org.gnome.desktop.wm.preferences') - value = gsettings.get_value('focus-mode') - if value: - self.system_focus = value.get_string() - return(self.system_focus) + return self.system_focus + if 'org.gnome.desktop.interface' not in Gio.Settings.list_schemas(): + return '' + gsettings = Gio.Settings.new('org.gnome.desktop.wm.preferences') + value = gsettings.get_value('focus-mode') + if value: + self.system_focus = value.get_string() + return self.system_focus def on_gsettings_change_event(self, settings, key): """Handle a gsetting change event""" @@ -449,9 +448,8 @@ def on_gsettings_change_event(self, settings, key): def save(self): """Cause ConfigBase to save our config to file""" if self.inhibited is True: - return(True) - else: - return(self.base.save()) + return True + return self.base.save() def inhibit_save(self): """Prevent calls to save() being honoured""" @@ -467,37 +465,38 @@ def options_set(self, options): def options_get(self): """Get the command line options""" - return(self.base.command_line_options) + return self.base.command_line_options def plugin_get(self, pluginname, key, default=None): """Get a plugin config value, if doesn't exist return default if specified """ - return(self.base.get_item(key, plugin=pluginname, default=default)) + return self.base.get_item(key, plugin=pluginname, default=default) def plugin_set(self, pluginname, key, value): """Set a plugin config value""" - return(self.base.set_item(key, value, plugin=pluginname)) + return self.base.set_item(key, value, plugin=pluginname) def plugin_get_config(self, plugin): """Return a whole config tree for a given plugin""" - return(self.base.get_plugin(plugin)) + return self.base.get_plugin(plugin) def plugin_set_config(self, plugin, tree): """Set a whole config tree for a given plugin""" - return(self.base.set_plugin(plugin, tree)) + return self.base.set_plugin(plugin, tree) def plugin_del_config(self, plugin): """Delete a whole config tree for a given plugin""" - return(self.base.del_plugin(plugin)) + return self.base.del_plugin(plugin) def layout_get_config(self, layout): """Return a layout""" - return(self.base.get_layout(layout)) + return self.base.get_layout(layout) def layout_set_config(self, layout, tree): """Set a layout""" - return(self.base.set_layout(layout, tree)) + return self.base.set_layout(layout, tree) + class ConfigBase(Borg): """Class to provide access to our user configuration""" @@ -563,9 +562,9 @@ def defaults_to_configspec(self): if keytype in keymap: keytype = keymap[keytype] elif keytype == 'list': - value = 'list(%s)' % ','.join(value) + value = f"list({','.join(value)})" - keytype = '%s(default=%s)' % (keytype, value) + keytype = f'{keytype}(default={value})' if key == 'custom_url_handler': keytype = 'string(default="")' @@ -574,11 +573,16 @@ def defaults_to_configspec(self): configspecdata['global_config'] = section section = {} - for key in DEFAULTS['keybindings']: - value = DEFAULTS['keybindings'][key] - if value is None or value == '': - continue - section[key] = 'string(default=%s)' % value + for key, val in DEFAULTS['keybindings'].items(): + if isinstance(val, str): + value = list(val, '') + if value is None or value == '': + continue + elif isinstance(val, list): + if len(val) < 2: + val.append('') + value = val + section[key] = f'list(default=list{tuple(value)})' configspecdata['keybindings'] = section section = {} @@ -588,11 +592,11 @@ def defaults_to_configspec(self): if keytype in keymap: keytype = keymap[keytype] elif keytype == 'list': - value = 'list(%s)' % ','.join(value) + value = f"list({','.join(value)})" if keytype == 'string': - value = '"%s"' % value + value = f'"{value}"' - keytype = '%s(default=%s)' % (keytype, value) + keytype = f'{keytype}(default={value})' section[key] = keytype configspecdata['profiles'] = {} @@ -612,9 +616,9 @@ def defaults_to_configspec(self): configspecdata['plugins'] = {} configspec = ConfigObj(configspecdata) - if DEBUG == True: + if DEBUG: configspec.write(open('/tmp/terminator_configspec_debug.txt', 'wb')) - return(configspec) + return configspec def load(self): """Load configuration data from our various sources""" @@ -628,7 +632,7 @@ def load(self): filename = os.path.join(get_config_dir(), 'config') if not os.path.exists(filename): filename = os.path.join(get_system_config_dir(), 'config') - dbg('looking for config file: %s' % filename) + dbg(f'looking for config file: {filename}') try: # # Make sure we attempt to update the ‘cell_height’ config @@ -638,10 +642,10 @@ def load(self): update_config_to_cell_height(filename) self.config_file_updated_to_cell_height = True - configfile = open(filename, 'r') + configfile = open(filename, 'r', encoding='utf-8') except Exception as ex: if not self.whined: - err('ConfigBase::load: Unable to open %s (%s)' % (filename, ex)) + err(f'ConfigBase::load: Unable to open {filename} ({ex})') self.whined = True return # If we have successfully loaded a config, allow future whining @@ -653,25 +657,25 @@ def load(self): validator = Validator() result = parser.validate(validator, preserve_errors=True) except Exception as ex: - err('Unable to load configuration: %s' % ex) + err(f'Unable to load configuration: {ex}') return - if result != True: + if result is not True: err('ConfigBase::load: config format is not valid') for (section_list, key, _other) in flatten_errors(parser, result): if key is not None: - err('[%s]: %s is invalid' % (','.join(section_list), key)) + err(f"[{','.join(section_list)}]: {key} is invalid") else: - err('[%s] missing' % ','.join(section_list)) + err(f"[{','.join(section_list)}] missing") else: dbg('config validated successfully') for section_name in self.sections: - dbg('Processing section: %s' % section_name) + dbg(f'Processing section: {section_name}') section = getattr(self, section_name) if section_name == 'profiles': for profile in parser[section_name]: - dbg('Processing profile: %s' % profile) + dbg(f'Processing profile: {profile}') if section_name not in section: # FIXME: Should this be outside the loop? section[profile] = copy(DEFAULTS['profiles']['default']) @@ -680,20 +684,20 @@ def load(self): if section_name not in parser: continue for part in parser[section_name]: - dbg('Processing %s: %s' % (section_name, part)) + dbg(f'Processing {section_name}: {part}') section[part] = parser[section_name][part] elif section_name == 'layouts': for layout in parser[section_name]: - dbg('Processing %s: %s' % (section_name, layout)) + dbg(f'Processing {section_name}: {layout}') if layout == 'default' and \ parser[section_name][layout] == {}: - continue + continue section[layout] = parser[section_name][layout] elif section_name == 'keybindings': if section_name not in parser: continue for part in parser[section_name]: - dbg('Processing %s: %s' % (section_name, part)) + dbg(f'Processing {section_name}: {part}') if parser[section_name][part] == 'None': section[part] = None else: @@ -701,8 +705,8 @@ def load(self): else: try: section.update(parser[section_name]) - except KeyError as ex: - dbg('skipping missing section %s' % section_name) + except KeyError: + dbg(f'skipping missing section {section_name}') self.loaded = True @@ -718,16 +722,16 @@ def save(self): parser.indent_type = ' ' for section_name in ['global_config', 'keybindings']: - dbg('Processing section: %s' % section_name) + dbg(f'Processing section: {section_name}') section = getattr(self, section_name) if section_name == 'keybindings': from terminatorlib.plugin import KeyBindUtil # for plugin KeyBindUtil assist in plugin_util - keybindutil = KeyBindUtil(); - keyb_keys = keybindutil.get_all_act_to_keys() + keybindutil = KeyBindUtil() + keyb_keys = keybindutil.get_all_act_to_keys() # we only need keys as a reference so to match them # against new values - keyb_keys = dict.fromkeys(keyb_keys, "") + keyb_keys = dict.fromkeys(keyb_keys, "") default_merged_section = {**keyb_keys, **DEFAULTS[section_name]} merged_section = {**keyb_keys, **section} @@ -738,24 +742,24 @@ def save(self): from .configjson import JSON_PROFILE_NAME, JSON_LAYOUT_NAME parser['profiles'] = {} - for profile in self.profiles: + for profile, profile_value in self.profiles.items(): if profile == JSON_PROFILE_NAME: continue - dbg('Processing profile: %s' % profile) + dbg(f'Processing profile: {profile}') parser['profiles'][profile] = dict_diff( - DEFAULTS['profiles']['default'], self.profiles[profile]) + DEFAULTS['profiles']['default'], profile_value) parser['layouts'] = {} - for layout in self.layouts: + for layout, layout_value in self.layouts.items(): if layout == JSON_LAYOUT_NAME: continue - dbg('Processing layout: %s' % layout) - parser['layouts'][layout] = self.layouts[layout] + dbg(f'Processing layout: {layout}') + parser['layouts'][layout] = layout_value parser['plugins'] = {} - for plugin in self.plugins: - dbg('Processing plugin: %s' % plugin) - parser['plugins'][plugin] = self.plugins[plugin] + for plugin, plugin_value in self.plugins.items(): + dbg(f'Processing plugin: {plugin}') + parser['plugins'][plugin] = plugin_value config_dir = get_config_dir() if not os.path.isdir(config_dir): @@ -764,21 +768,21 @@ def save(self): try: if self.command_line_options.config: filename = self.command_line_options.config - else: - filename = os.path.join(config_dir,'config') + else: + filename = os.path.join(config_dir, 'config') if not os.path.isfile(filename): - open(filename, 'a').close() + open(filename, 'a', encoding='utf-8').close() backup_file = filename + '~' shutil.copy2(filename, backup_file) - with open(filename, 'wb') as fh: - parser.write(fh) + with open(filename, 'wb') as file: + parser.write(file) os.remove(backup_file) except Exception as ex: - err('ConfigBase::save: Unable to save config: %s' % ex) + err(f'ConfigBase::save: Unable to save config: {ex}') def get_item(self, key, profile='default', plugin=None, default=None): """Look up a configuration item""" @@ -787,28 +791,23 @@ def get_item(self, key, profile='default', plugin=None, default=None): profile = 'default' if key in self.global_config: - dbg('%s found in globals: %s' % - (key, self.global_config[key])) - return(self.global_config[key]) - elif key in self.profiles[profile]: - dbg('%s found in profile %s: %s' % ( - key, profile, self.profiles[profile][key])) - return(self.profiles[profile][key]) - elif key == 'keybindings': - return(self.keybindings) - elif plugin and plugin in self.plugins and key in self.plugins[plugin]: - dbg('%s found in plugin %s: %s' % ( - key, plugin, self.plugins[plugin][key])) - return(self.plugins[plugin][key]) - elif default: + dbg(f'{key} found in globals: {self.global_config[key]}') + return self.global_config[key] + if key in self.profiles[profile]: + dbg(f'{key} found in profile {profile}: {self.profiles[profile][key]}') + return self.profiles[profile][key] + if key == 'keybindings': + return self.keybindings + if plugin and plugin in self.plugins and key in self.plugins[plugin]: + dbg(f'{key} found in plugin {plugin}: {self.plugins[plugin][key]}') + return self.plugins[plugin][key] + if default: return default - else: - raise KeyError('ConfigBase::get_item: unknown key %s' % key) + raise KeyError(f'ConfigBase::get_item: unknown key {key}') def set_item(self, key, value, profile='default', plugin=None): """Set a configuration item""" - dbg('Setting %s=%s (profile=%s, plugin=%s)' % - (key, value, profile, plugin)) + dbg(f'Setting {key}={value} ({profile=}, {plugin=})') if key in self.global_config: self.global_config[key] = value @@ -821,14 +820,13 @@ def set_item(self, key, value, profile='default', plugin=None): self.plugins[plugin] = {} self.plugins[plugin][key] = value else: - raise KeyError('ConfigBase::set_item: unknown key %s' % key) + raise KeyError(f'ConfigBase::set_item: unknown key {key}') - return(True) + return True def get_plugin(self, plugin): """Return a whole tree for a plugin""" - if plugin in self.plugins: - return(self.plugins[plugin]) + return self.plugins.get(plugin) def set_plugin(self, plugin, tree): """Set a whole tree for a plugin""" @@ -842,34 +840,34 @@ def del_plugin(self, plugin): def add_profile(self, profile, toclone): """Add a new profile""" if profile in self.profiles: - return(False) + return False if toclone is not None: newprofile = copy(toclone) else: newprofile = copy(DEFAULTS['profiles']['default']) self.profiles[profile] = newprofile - return(True) + return True def add_layout(self, name, layout): """Add a new layout""" if name in self.layouts: - return(False) + return False self.layouts[name] = layout - return(True) + return True def replace_layout(self, name, layout): """Replaces a layout with the given name""" - if not name in self.layouts: - return(False) + if name not in self.layouts: + return False self.layouts[name] = layout - return(True) + return True def get_layout(self, layout): """Return a layout""" if layout in self.layouts: - return(self.layouts[layout]) - else: - err('layout does not exist: %s' % layout) + return self.layouts[layout] + err(f'layout does not exist: {layout}') + return '' def set_layout(self, layout, tree): """Set a layout""" diff --git a/terminatorlib/keybindings.py b/terminatorlib/keybindings.py index 61551c8e..24e72ff8 100644 --- a/terminatorlib/keybindings.py +++ b/terminatorlib/keybindings.py @@ -16,19 +16,23 @@ """Terminator by Chris Jones -Validator and functions for dealing with Terminator's customisable +Validator and functions for dealing with Terminator's customisable keyboard shortcuts. """ import re -from gi.repository import Gtk, Gdk +from gi.repository import Gdk from .util import err + class KeymapError(Exception): """Custom exception for errors in keybinding configurations""" + MODIFIER = re.compile('<([^<]+)>') + + class Keybindings: """Class to handle loading and lookup of Terminator keybindings""" @@ -37,7 +41,7 @@ class Keybindings: 'control': Gdk.ModifierType.CONTROL_MASK, 'primary': Gdk.ModifierType.CONTROL_MASK, 'shift': Gdk.ModifierType.SHIFT_MASK, - 'alt': Gdk.ModifierType.MOD1_MASK, + 'alt': Gdk.ModifierType.MOD1_MASK, # Gdk.ModifierType.ALT_MASK ? 'super': Gdk.ModifierType.SUPER_MASK, 'hyper': Gdk.ModifierType.HYPER_MASK, 'mod2': Gdk.ModifierType.MOD2_MASK @@ -62,19 +66,19 @@ def reload(self): self._lookup = {} self._masks = 0 for action, bindings in list(self.keys.items()): - if not isinstance(bindings, tuple): - bindings = (bindings,) + if not isinstance(bindings, list): + bindings = [bindings, ''] for binding in bindings: if not binding or binding == "None": continue try: - keyval, mask = self._parsebinding(binding) + keyval, mask = self.parsebinding(binding) # Does much the same, but with poorer error handling. - #keyval, mask = Gtk.accelerator_parse(binding) - except KeymapError as e: - err ("keybindings.reload failed to parse binding '%s': %s" % (binding, e)) + # keyval, mask = Gtk.accelerator_parse(binding) + except KeymapError as exc: + err(f"keybindings.reload failed to parse binding '{binding}': {exc}") else: if mask & Gdk.ModifierType.SHIFT_MASK: if keyval == Gdk.KEY_Tab: @@ -91,7 +95,7 @@ def reload(self): self._lookup[mask][keyval] = action self._masks |= mask - def _parsebinding(self, binding): + def parsebinding(self, binding): """Parse an individual binding using gtk's binding function""" mask = 0 modifiers = re.findall(MODIFIER, binding) @@ -103,27 +107,25 @@ def _parsebinding(self, binding): raise KeymapError('No key found') keyval = Gdk.keyval_from_name(key) if keyval == 0: - raise KeymapError("Key '%s' is unrecognised" % key) + raise KeymapError(f"Key '{key}' is unrecognised") return (keyval, mask) def _lookup_modifier(self, modifier): """Map modifier names to gtk values""" try: return self.modifiers[modifier.lower()] - except KeyError: - raise KeymapError("Unhandled modifier '<%s>'" % modifier) + except KeyError as exc: + raise KeymapError(f"Unhandled modifier '<{modifier}>'") from exc def lookup(self, event): """Translate a keyboard event into a mapped key""" try: - _found, keyval, _egp, _lvl, consumed = self.keymap.translate_keyboard_state( - event.hardware_keycode, + _, keyval, _, _, consumed = self.keymap.translate_keyboard_state( + event.hardware_keycode, Gdk.ModifierType(event.get_state() & ~Gdk.ModifierType.LOCK_MASK), event.group) except TypeError: - err ("keybindings.lookup failed to translate keyboard event: %s" % - dir(event)) + err(f"keybindings.lookup failed to translate keyboard event: {dir(event)}") return None mask = (event.get_state() & ~consumed) & self._masks return self._lookup.get(mask, self.empty).get(keyval, None) - diff --git a/terminatorlib/plugin.py b/terminatorlib/plugin.py index 49a972e7..1eb328f0 100644 --- a/terminatorlib/plugin.py +++ b/terminatorlib/plugin.py @@ -30,17 +30,17 @@ from .util import dbg, err, get_config_dir from .terminator import Terminator -class Plugin(object): + +class Plugin: """Definition of our base plugin class""" capabilities = None def __init__(self): """Class initialiser.""" - pass def unload(self): """Prepare to be unloaded""" - pass + class PluginRegistry(borg.Borg): """Definition of a class to store plugin instances""" @@ -63,7 +63,7 @@ def prepare_attributes(self): (head, _tail) = os.path.split(borg.__file__) self.path.append(os.path.join(head, 'plugins')) self.path.append(os.path.join(get_config_dir(), 'plugins')) - dbg('Plugin path: %s' % self.path) + dbg(f'Plugin path: {self.path}') if not self.done: self.done = False if not self.available_plugins: @@ -75,7 +75,7 @@ def load_plugins(self, force=False): dbg('Already loaded') return - dbg('loading plugins, force:(%s)' % force) + dbg(f'loading plugins, force:({force})') config = Config() @@ -91,73 +91,73 @@ def load_plugins(self, force=False): continue pluginpath = os.path.join(plugindir, plugin) if os.path.isfile(pluginpath) and plugin[-3:] == '.py': - dbg('Importing plugin %s' % plugin) + dbg(f'Importing plugin {plugin}') try: module = __import__(plugin[:-3], None, None, ['']) for item in getattr(module, 'AVAILABLE'): func = getattr(module, item) - if item not in list(self.available_plugins.keys()): + if item not in self.available_plugins: self.available_plugins[item] = func if item not in config['enabled_plugins']: - dbg('plugin %s not enabled, skipping' % item) + dbg(f'plugin {item} not enabled, skipping') continue if item not in self.instances: self.instances[item] = func() elif force: - #instead of multiple copies of loaded - #plugin objects, unload where plugins - #can clean up and then re-init so there - #is one plugin object + # instead of multiple copies of loaded + # plugin objects, unload where plugins + # can clean up and then re-init so there + # is one plugin object self.instances[item].unload() self.instances.pop(item, None) self.instances[item] = func() except Exception as ex: - err('PluginRegistry::load_plugins: Importing plugin %s \ -failed: %s' % (plugin, ex)) + err(f'PluginRegistry::load_plugins: Importing plugin {plugin} \ +failed: {ex}') self.done = True def get_plugins_by_capability(self, capability): """Return a list of plugins with a particular capability""" result = [] - dbg('searching %d plugins \ -for %s' % (len(self.instances), capability)) - for plugin in self.instances: - if capability in self.instances[plugin].capabilities: - result.append(self.instances[plugin]) + dbg(f'searching {len(self.instances)} plugins for {capability}') + for plugin_value in self.instances.values(): + if capability in plugin_value.capabilities: + result.append(plugin_value) return result def get_all_plugins(self): """Return all plugins""" - return(self.instances) + return self.instances def get_available_plugins(self): """Return a list of all available plugins whether they are enabled or disabled""" - return(list(self.available_plugins.keys())) + return list(self.available_plugins.keys()) def is_enabled(self, plugin): """Return a boolean value indicating whether a plugin is enabled or not""" - return(plugin in self.instances) + return plugin in self.instances def enable(self, plugin): """Enable a plugin""" if plugin in self.instances: - err("Cannot enable plugin %s, already enabled" % plugin) - dbg("Enabling %s" % plugin) + err(f"Cannot enable plugin {plugin}, already enabled") + dbg(f"Enabling {plugin}") self.instances[plugin] = self.available_plugins[plugin]() def disable(self, plugin): """Disable a plugin""" - dbg("Disabling %s" % plugin) + dbg(f"Disabling {plugin}") self.instances[plugin].unload() - del(self.instances[plugin]) + del self.instances[plugin] # This is where we should define a base class for each type of plugin we # support + # URLHandler - This adds a regex match to the Terminal widget and provides a # callback to turn that into a URL. class URLHandler(Plugin): @@ -188,6 +188,7 @@ def unload(self): for terminal in terminator.terminals: terminal.match_remove(self.handler_name) + # MenuItem - This is able to execute code during the construction of the # context menu of a Terminal. class MenuItem(Plugin): @@ -199,32 +200,31 @@ def callback(self, menuitems, menu, terminal): raise NotImplementedError -""" --Basic plugin util for key-press handling, has all mapping to be used -in layout keybindings - -Vishweshwar Saran Singh Deo vssdeo@gmail.com -""" - -from gi.repository import Gtk, Gdk -from terminatorlib.keybindings import Keybindings, KeymapError +from gi.repository import Gdk +from terminatorlib.keybindings import Keybindings PLUGIN_UTIL_DESC = 0 -PLUGIN_UTIL_ACT = 1 +PLUGIN_UTIL_ACT = 1 PLUGIN_UTIL_KEYS = 2 + class KeyBindUtil: + """ + -Basic plugin util for key-press handling, has all mapping to be used + in layout keybindings + Vishweshwar Saran Singh Deo vssdeo@gmail.com + """ keybindings = Keybindings() - map_key_to_act = {} + map_key_to_act = {} map_act_to_keys = {} map_act_to_desc = {} def __init__(self, config=None): self.config = config - #Example + # Example # bind # first param is desc, second is action str # self.keyb.bindkey([PluginUrlFindNext , PluginUrlActFindNext, "j"]) @@ -234,68 +234,63 @@ def __init__(self, config=None): # if act == "url_find_next": - - #check map key_val_mask -> action def _check_keybind_change(self, key): + '''Check map key_val_mask -> action ''' act = key[PLUGIN_UTIL_ACT] - for key_val_mask in self.map_key_to_act: - old_act = self.map_key_to_act[key_val_mask] - if act == old_act: + for key_val_mask, mask_value in self.map_key_to_act.items(): + if act == mask_value: return key_val_mask return None - #check in config before binding def bindkey_check_config(self, key): + '''Check in config before binding''' if not self.config: raise Warning("bindkey_check_config called without config init") actstr = key[PLUGIN_UTIL_ACT] kbsect = self.config.base.get_item('keybindings') keystr = kbsect[actstr] if actstr in kbsect else "" - dbg("bindkey_check_config:action (%s) key str:(%s)" % (actstr, keystr)) + dbg(f"bindkey_check_config:action ({actstr}) key str:({keystr})") if len(keystr): key[PLUGIN_UTIL_KEYS] = keystr - dbg("found new Action->KeyVal in config: (%s, %s)" - % (actstr, keystr)); + dbg(f"found new Action->KeyVal in config: ({actstr}, {keystr})") self.bindkey(key) def bindkey(self, key): - (keyval, mask) = self.keybindings._parsebinding(key[PLUGIN_UTIL_KEYS]) + keyval, mask = self.keybindings.parsebinding(key[PLUGIN_UTIL_KEYS]) keyval = Gdk.keyval_to_lower(keyval) mask = Gdk.ModifierType(mask) ret = (keyval, mask) - dbg("bindkey: (%s) (%s)" % (key[PLUGIN_UTIL_KEYS], str(ret))) + dbg(f"bindkey: ({key[PLUGIN_UTIL_KEYS]}) ({str(ret)})") - #remove if any old key_val_mask + # remove if any old key_val_mask old_key_val_mask = self._check_keybind_change(key) if old_key_val_mask: - dbg("found old key binding, removing: (%s)" % str(old_key_val_mask)) + dbg(f"found old key binding, removing: ({str(old_key_val_mask)})") del self.map_key_to_act[old_key_val_mask] - #map key-val-mask to action, used to ease key-press management + # map key-val-mask to action, used to ease key-press management self.map_key_to_act[ret] = key[PLUGIN_UTIL_ACT] - - #map action to key-combo-str, used in preferences->keybinding - self.map_act_to_keys[key[PLUGIN_UTIL_ACT]] = key[PLUGIN_UTIL_KEYS] - #map action to key-combo description, in used preferences->keybinding + # map action to key-combo-str, used in preferences->keybinding + self.map_act_to_keys[key[PLUGIN_UTIL_ACT]] = key[PLUGIN_UTIL_KEYS] + # map action to key-combo description, in used preferences->keybinding self.map_act_to_desc[key[PLUGIN_UTIL_ACT]] = key[PLUGIN_UTIL_DESC] def unbindkey(self, key): - # Suppose user changed the key-combo and its diff from # what the plugin had set by default, we need to get # current key-combo - act = key[PLUGIN_UTIL_ACT] + act = key[PLUGIN_UTIL_ACT] act_keys = self.map_act_to_keys[act] - (keyval, mask) = self.keybindings._parsebinding(act_keys) + keyval, mask = self.keybindings.parsebinding(act_keys) keyval = Gdk.keyval_to_lower(keyval) mask = Gdk.ModifierType(mask) ret = (keyval, mask) - dbg("unbindkey: (%s) (%s)" % (key[PLUGIN_UTIL_KEYS], str(ret))) + dbg(f"unbindkey: ({key[PLUGIN_UTIL_KEYS]}) ({str(ret)})") # FIXME keys should always be there, can also use .pop(key, None) # lets do it after testing @@ -303,14 +298,13 @@ def unbindkey(self, key): del self.map_act_to_keys[key[PLUGIN_UTIL_ACT]] del self.map_act_to_desc[key[PLUGIN_UTIL_ACT]] - def keyaction(self, event): - #FIXME MOD2 mask comes in the event, remove - event.state &= ~Gdk.ModifierType.MOD2_MASK + # FIXME MOD2 mask comes in the event, remove + event.state &= ~Gdk.ModifierType.MOD2_MASK keyval = Gdk.keyval_to_lower(event.keyval) ret = (keyval, event.state) - dbg("keyaction: (%s)" % str(ret)) + dbg(f"keyaction: ({str(ret)})") return self.map_key_to_act.get(ret, None) def get_act_to_keys(self, key): @@ -325,7 +319,7 @@ def get_all_act_to_desc(self): def get_act_to_desc(self, act): return self.map_act_to_desc.get(act) - #get action to key binding from config + # get action to key binding from config def get_act_to_keys_config(self, act): if not self.config: raise Warning("get_keyvalmask_for_act called without config init") diff --git a/terminatorlib/preferences.glade b/terminatorlib/preferences.glade index e0e8a2a5..e27b8805 100644 --- a/terminatorlib/preferences.glade +++ b/terminatorlib/preferences.glade @@ -214,9 +214,13 @@ - + - + + + + + @@ -4081,13 +4085,13 @@ - Keybinding + Keybinding 1 True other - - + + 2 @@ -4096,6 +4100,23 @@ + + + Keybinding 2 + + + True + other + + + + + 4 + 5 + + + + diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index d5933c4b..8ff09c6c 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -"""Preferences Editor for Terminator. +"""Preferences Editor for Terminator. Load a UIBuilder config file, display it, populate it with our current config, then optionally read that back out and @@ -8,7 +8,8 @@ """ import os -from gi.repository import GObject, Gtk, Gdk +import re +from gi.repository import Gdk, Gtk from .util import dbg, err from . import config @@ -20,19 +21,27 @@ from .plugin import KeyBindUtil + def get_color_string(widcol): - return('#%02x%02x%02x' % (widcol.red>>8, widcol.green>>8, widcol.blue>>8)) + red_bit = widcol.red >> 8 + green_bit = widcol.green >> 8 + blue_bit = widcol.blue >> 8 + return f'#{red_bit:x}{green_bit:x}{blue_bit:x}' + def color2hex(widget): """Pull the colour values out of a Gtk ColorPicker widget and return them - as 8bit hex values, sinces its default behaviour is to give 16bit values""" + as 8bit hex values, since its default behaviour is to give 16bit values""" return get_color_string(widget.get_color()) + def rgba2hex(widget): return get_color_string(widget.get_rgba().to_color()) + NUM_PALETTE_COLORS = 16 + # FIXME: We need to check that we have represented all of Config() below class PrefsEditor: """Class implementing the various parts of the preferences editor""" @@ -47,6 +56,8 @@ class PrefsEditor: layouteditor = None previous_layout_selection = None previous_profile_selection = None + previous_selection = None + previous_plugin_selection = None colorschemevalues = {'black_on_yellow': 0, 'black_on_white': 1, 'grey_on_black': 2, @@ -103,92 +114,92 @@ class PrefsEditor: 'gruvbox_dark': '#282828:#cc241d:#98971a:#d79921:\ #458588:#b16286:#689d6a:#a89984:#928374:#fb4934:#b8bb26:#fabd2f:\ #83a598:#d3869b:#8ec07c:#ebdbb2'} - keybindingnames = { 'zoom_in' : _('Increase font size'), - 'zoom_out' : _('Decrease font size'), - 'zoom_normal' : _('Restore original font size'), - 'zoom_in_all' : _('Increase font size on all terminals'), - 'zoom_out_all' : _('Decrease font size on all terminals'), - 'zoom_normal_all' : _('Restore original font size on all terminals'), - 'new_tab' : _('Create a new tab'), - 'cycle_next' : _('Focus the next terminal'), - 'cycle_prev' : _('Focus the previous terminal'), - 'go_next' : _('Focus the next terminal'), - 'go_prev' : _('Focus the previous terminal'), - 'go_up' : _('Focus the terminal above'), - 'go_down' : _('Focus the terminal below'), - 'go_left' : _('Focus the terminal left'), - 'go_right' : _('Focus the terminal right'), - 'rotate_cw' : _('Rotate terminals clockwise'), - 'rotate_ccw' : _('Rotate terminals counter-clockwise'), - 'split_auto' : _('Split automatically'), - 'split_horiz' : _('Split horizontally'), - 'split_vert' : _('Split vertically'), - 'close_term' : _('Close terminal'), - 'copy' : _('Copy selected text'), - 'paste' : _('Paste clipboard'), - 'paste_selection' : _('Paste primary selection'), - 'toggle_scrollbar' : _('Show/Hide the scrollbar'), - 'search' : _('Search terminal scrollback'), - 'page_up' : _('Scroll upwards one page'), - 'page_down' : _('Scroll downwards one page'), - 'page_up_half' : _('Scroll upwards half a page'), - 'page_down_half' : _('Scroll downwards half a page'), - 'line_up' : _('Scroll upwards one line'), - 'line_down' : _('Scroll downwards one line'), - 'close_window' : _('Close window'), - 'resize_up' : _('Resize the terminal up'), - 'resize_down' : _('Resize the terminal down'), - 'resize_left' : _('Resize the terminal left'), - 'resize_right' : _('Resize the terminal right'), - 'move_tab_right' : _('Move the tab right'), - 'move_tab_left' : _('Move the tab left'), - 'toggle_zoom' : _('Maximize terminal'), - 'scaled_zoom' : _('Zoom terminal'), - 'next_tab' : _('Switch to the next tab'), - 'prev_tab' : _('Switch to the previous tab'), - 'switch_to_tab_1' : _('Switch to the first tab'), - 'switch_to_tab_2' : _('Switch to the second tab'), - 'switch_to_tab_3' : _('Switch to the third tab'), - 'switch_to_tab_4' : _('Switch to the fourth tab'), - 'switch_to_tab_5' : _('Switch to the fifth tab'), - 'switch_to_tab_6' : _('Switch to the sixth tab'), - 'switch_to_tab_7' : _('Switch to the seventh tab'), - 'switch_to_tab_8' : _('Switch to the eighth tab'), - 'switch_to_tab_9' : _('Switch to the ninth tab'), - 'switch_to_tab_10' : _('Switch to the tenth tab'), - 'full_screen' : _('Toggle fullscreen'), - 'reset' : _('Reset the terminal'), - 'reset_clear' : _('Reset and clear the terminal'), - 'hide_window' : _('Toggle window visibility'), - 'create_group' : _('Create new group'), - 'group_all' : _('Group all terminals'), - 'group_all_toggle' : _('Group/Ungroup all terminals'), - 'ungroup_all' : _('Ungroup all terminals'), - 'group_win' : _('Group terminals in window'), - 'group_win_toggle' : _('Group/Ungroup terminals in window'), - 'ungroup_win' : _('Ungroup terminals in window'), - 'group_tab' : _('Group terminals in tab'), - 'group_tab_toggle' : _('Group/Ungroup terminals in tab'), - 'ungroup_tab' : _('Ungroup terminals in tab'), - 'new_window' : _('Create a new window'), - 'new_terminator' : _('Spawn a new Terminator process'), - 'broadcast_off' : _('Don\'t broadcast key presses'), - 'broadcast_group' : _('Broadcast key presses to group'), - 'broadcast_all' : _('Broadcast key events to all'), - 'insert_number' : _('Insert terminal number'), - 'insert_padded' : _('Insert zero padded terminal number'), - 'edit_window_title': _('Edit window title'), - 'edit_terminal_title': _('Edit terminal title'), - 'edit_tab_title' : _('Edit tab title'), - 'layout_launcher' : _('Open layout launcher window'), - 'next_profile' : _('Switch to next profile'), - 'previous_profile' : _('Switch to previous profile'), - 'preferences' : _('Open the Preferences window'), - 'preferences_keybindings' : _('Open the Preferences-Keybindings window'), - 'help' : _('Open the manual') - } - - def __init__ (self, term, cur_page=0): + keybindingnames = {'zoom_in': _('Increase font size'), + 'zoom_out': _('Decrease font size'), + 'zoom_normal': _('Restore original font size'), + 'zoom_in_all': _('Increase font size on all terminals'), + 'zoom_out_all': _('Decrease font size on all terminals'), + 'zoom_normal_all': _('Restore original font size on all terminals'), + 'new_tab': _('Create a new tab'), + 'cycle_next': _('Focus the next terminal'), + 'cycle_prev': _('Focus the previous terminal'), + 'go_next': _('Focus the next terminal'), + 'go_prev': _('Focus the previous terminal'), + 'go_up': _('Focus the terminal above'), + 'go_down': _('Focus the terminal below'), + 'go_left': _('Focus the terminal left'), + 'go_right': _('Focus the terminal right'), + 'rotate_cw': _('Rotate terminals clockwise'), + 'rotate_ccw': _('Rotate terminals counter-clockwise'), + 'split_auto': _('Split automatically'), + 'split_horiz': _('Split horizontally'), + 'split_vert': _('Split vertically'), + 'close_term': _('Close terminal'), + 'copy': _('Copy selected text'), + 'paste': _('Paste clipboard'), + 'paste_selection': _('Paste primary selection'), + 'toggle_scrollbar': _('Show/Hide the scrollbar'), + 'search': _('Search terminal scrollback'), + 'page_up': _('Scroll upwards one page'), + 'page_down': _('Scroll downwards one page'), + 'page_up_half': _('Scroll upwards half a page'), + 'page_down_half': _('Scroll downwards half a page'), + 'line_up': _('Scroll upwards one line'), + 'line_down': _('Scroll downwards one line'), + 'close_window': _('Close window'), + 'resize_up': _('Resize the terminal up'), + 'resize_down': _('Resize the terminal down'), + 'resize_left': _('Resize the terminal left'), + 'resize_right': _('Resize the terminal right'), + 'move_tab_right': _('Move the tab right'), + 'move_tab_left': _('Move the tab left'), + 'toggle_zoom': _('Maximize terminal'), + 'scaled_zoom': _('Zoom terminal'), + 'next_tab': _('Switch to the next tab'), + 'prev_tab': _('Switch to the previous tab'), + 'switch_to_tab_1': _('Switch to the first tab'), + 'switch_to_tab_2': _('Switch to the second tab'), + 'switch_to_tab_3': _('Switch to the third tab'), + 'switch_to_tab_4': _('Switch to the fourth tab'), + 'switch_to_tab_5': _('Switch to the fifth tab'), + 'switch_to_tab_6': _('Switch to the sixth tab'), + 'switch_to_tab_7': _('Switch to the seventh tab'), + 'switch_to_tab_8': _('Switch to the eighth tab'), + 'switch_to_tab_9': _('Switch to the ninth tab'), + 'switch_to_tab_10': _('Switch to the tenth tab'), + 'full_screen': _('Toggle fullscreen'), + 'reset': _('Reset the terminal'), + 'reset_clear': _('Reset and clear the terminal'), + 'hide_window': _('Toggle window visibility'), + 'create_group': _('Create new group'), + 'group_all': _('Group all terminals'), + 'group_all_toggle': _('Group/Ungroup all terminals'), + 'ungroup_all': _('Ungroup all terminals'), + 'group_win': _('Group terminals in window'), + 'group_win_toggle': _('Group/Ungroup terminals in window'), + 'ungroup_win': _('Ungroup terminals in window'), + 'group_tab': _('Group terminals in tab'), + 'group_tab_toggle': _('Group/Ungroup terminals in tab'), + 'ungroup_tab': _('Ungroup terminals in tab'), + 'new_window': _('Create a new window'), + 'new_terminator': _('Spawn a new Terminator process'), + 'broadcast_off': _('Don\'t broadcast key presses'), + 'broadcast_group': _('Broadcast key presses to group'), + 'broadcast_all': _('Broadcast key events to all'), + 'insert_number': _('Insert terminal number'), + 'insert_padded': _('Insert zero padded terminal number'), + 'edit_window_title': _('Edit window title'), + 'edit_terminal_title': _('Edit terminal title'), + 'edit_tab_title': _('Edit tab title'), + 'layout_launcher': _('Open layout launcher window'), + 'next_profile': _('Switch to next profile'), + 'previous_profile': _('Switch to previous profile'), + 'preferences': _('Open the Preferences window'), + 'preferences_keybindings': _('Open the Preferences-Keybindings window'), + 'help': _('Open the manual') + } + + def __init__(self, term, cur_page=0): self.config = config.Config() self.config.base.reload() self.term = term @@ -198,15 +209,15 @@ def __init__ (self, term, cur_page=0): self.builder.set_translation_domain(APP_NAME) self.keybindings = Keybindings() self.active_message_dialog = None + self.keybind_filter_str = None try: # Figure out where our library is on-disk so we can open our (head, _tail) = os.path.split(config.__file__) librarypath = os.path.join(head, 'preferences.glade') - gladefile = open(librarypath, 'r') + gladefile = open(librarypath, 'r', encoding='utf-8') gladedata = gladefile.read() except Exception as ex: - print("Failed to find preferences.glade") - print(ex) + print(f"Failed to find preferences.glade:\n{ex}") return self.builder.add_from_string(gladedata) @@ -227,13 +238,13 @@ def __init__ (self, term, cur_page=0): try: self.config.inhibit_save() self.set_values() - except Exception as e: - err('Unable to set values: %s' % e) + except Exception as exc: + err(f'Unable to set values: {exc}') self.config.uninhibit_save() guiget = self.builder.get_object - nb = guiget('notebook1') - nb.set_current_page(cur_page) + notebook = guiget('notebook1') + notebook.set_current_page(cur_page) def on_closebutton_clicked(self, _button): """Close the window""" @@ -241,14 +252,14 @@ def on_closebutton_clicked(self, _button): terminator.reconfigure() self.window.destroy() self.calling_window.preventHide = False - del(self) + del self def set_values(self): """Update the preferences window with all the configuration from Config()""" guiget = self.builder.get_object - ## Global tab + # GLOBAL TAB # Mouse focus focus = self.config['focus'] active = 0 @@ -267,7 +278,7 @@ def set_values(self): # Cell Height cellheightsize = self.config['cell_height'] - cellheightsize = round(float(cellheightsize),1) + cellheightsize = round(float(cellheightsize), 1) widget = guiget('cellheight') widget.set_value(cellheightsize) widget = guiget('cellheight_value_label') @@ -275,7 +286,7 @@ def set_values(self): # Cell Width cellwidthsize = self.config['cell_width'] - cellwidthsize = round(float(cellwidthsize),1) + cellwidthsize = round(float(cellwidthsize), 1) widget = guiget('cellwidth') widget.set_value(cellwidthsize) widget = guiget('cellwidth_value_label') @@ -342,16 +353,16 @@ def set_values(self): # Detachable tabs widget = guiget('detachable_tabs') widget.set_active(self.config['detachable_tabs']) - #Hide from taskbar + # Hide from taskbar widget = guiget('hidefromtaskbcheck') widget.set_active(self.config['hide_from_taskbar']) - #Always on top + # Always on top widget = guiget('alwaysontopcheck') widget.set_active(self.config['always_on_top']) - #Hide on lose focus + # Hide on lose focus widget = guiget('hideonlosefocuscheck') widget.set_active(self.config['hide_on_lose_focus']) - #Show on all workspaces + # Show on all workspaces widget = guiget('stickycheck') widget.set_active(self.config['sticky']) @@ -363,7 +374,7 @@ def set_values(self): widget = guiget('new_tab_after_current_checkbutton') widget.set_active(self.config['new_tab_after_current_tab']) - #Always split with profile + # Always split with profile widget = guiget('always_split_with_profile') widget.set_active(self.config['always_split_with_profile']) # Putty paste style @@ -385,7 +396,7 @@ def set_values(self): widget = guiget('disable_mouse_paste') widget.set_active(self.config['disable_mouse_paste']) - ## Profile tab + # PROFILE TAB # Populate the profile list widget = guiget('profilelist') liststore = widget.get_model() @@ -403,7 +414,7 @@ def set_values(self): selection.connect('changed', self.on_profile_selection_changed) selection.select_iter(self.profileiters['default']) - ## Layouts tab + # LAYOUTS TAB widget = guiget('layoutlist') liststore = widget.get_model() layouts = self.config.list_layouts() @@ -429,39 +440,43 @@ def set_values(self): selection = widget.get_selection() selection.connect('changed', self.on_layout_item_selection_changed) - ## Keybindings tab - widget = guiget('keybindingtreeview') + # KEYBINDINGS TAB + widget = guiget('keybindingtreeview') kbsearch = guiget('keybindingsearchentry') self.keybind_filter_str = "" - #lets hide whatever we can in nested scope + # let's hide whatever we can in nested scope def filter_visible(model, treeiter, data): - act = model[treeiter][0] + act = model[treeiter][0] keys = data[act] if act in data else "" desc = model[treeiter][1] - kval = model[treeiter][2] - mask = model[treeiter][3] - #so user can search for disabled keys also - if not (len(keys) and kval and mask): + kval0 = model[treeiter][2] + mask0 = model[treeiter][3] + kval1 = model[treeiter][4] + mask1 = model[treeiter][5] + # so user can search for disabled keys also + if isinstance(keys, str): + keys = [keys, ''] + if not (any(keys) and any([kval0, mask0, kval1, mask1])): act = "Disabled" self.keybind_filter_str = self.keybind_filter_str.lower() - searchtxt = (act + " " + keys + " " + desc).lower() + keys_str = re.sub(r'[\[,\]]', '', str(keys)) + searchtxt = f'{act} {keys_str} {desc}'.lower() pos = searchtxt.find(self.keybind_filter_str) - if (pos >= 0): - dbg("filter find:%s in search text: %s" % - (self.keybind_filter_str, searchtxt)) + if pos >= 0: + dbg(f"filter find:{self.keybind_filter_str} in search text: {searchtxt}") return True return False def on_search(widget, text): - MAX_SEARCH_LEN = 10 + max_search_len = 10 self.keybind_filter_str = widget.get_text() - ln = len(self.keybind_filter_str) - #its a small list & we are eager for quick search, but limit - if (ln >=2 and ln < MAX_SEARCH_LEN): - dbg("filter search str: %s" % self.keybind_filter_str) + length = len(self.keybind_filter_str) + # it's a small list & we are eager for quick search, but limit + if 2 <= length < max_search_len: + dbg(f"filter search str: {self.keybind_filter_str}") self.treemodelfilter.refilter() def on_search_refilter(widget): @@ -475,31 +490,44 @@ def on_search_refilter(widget): liststore.set_sort_column_id(0, Gtk.SortType.ASCENDING) keybindings = self.config['keybindings'] - keybindutil = KeyBindUtil() - plugin_keyb_act = keybindutil.get_all_act_to_keys() - plugin_keyb_desc = keybindutil.get_all_act_to_desc() - #merge give preference to main bindings over plugin - keybindings = {**plugin_keyb_act, **keybindings} + keybindutil = KeyBindUtil() + plugin_keyb_act = keybindutil.get_all_act_to_keys() + plugin_keyb_desc = keybindutil.get_all_act_to_desc() + # merge give preference to main bindings over plugin + keybindings = {**plugin_keyb_act, **keybindings} self.keybindingnames = {**plugin_keyb_desc, **self.keybindingnames} - #dbg("appended actions %s names %s" % (keybindings, self.keybindingnames)) + # dbg(f"appended actions {keybindings} names {self.keybindingnames}") - for keybinding in keybindings: + for keybinding, value in keybindings.items(): keyval = 0 mask = 0 - value = keybindings[keybinding] - if value is not None and value != '': - try: - (keyval, mask) = self.keybindings._parsebinding(value) - except KeymapError: - pass - liststore.append([keybinding, self.keybindingnames[keybinding], - keyval, mask]) + if isinstance(value, str): + if value is not None and value != '': + try: + keyval, mask = self.keybindings.parsebinding(value) + except KeymapError: + pass + liststore.append([keybinding, self.keybindingnames[keybinding], + keyval, mask, 0, 0]) + self.config['keybindings'][keybinding] = [value, ''] + if isinstance(value, list): + bindings_list = [(0, 0), (0, 0)] + for index, val in enumerate(value): + if val is not None and val != '': + try: + keyval, mask = self.keybindings.parsebinding(val) + bindings_list[index] = (keyval, mask) + except KeymapError: + pass + liststore.append([keybinding, self.keybindingnames[keybinding], + bindings_list[0][0], bindings_list[0][1], + bindings_list[1][0], bindings_list[1][1]]) self.treemodelfilter = liststore.filter_new() self.treemodelfilter.set_visible_func(filter_visible, keybindings) widget.set_model(self.treemodelfilter) - ## Plugins tab + # PLUGINS TAB # Populate the plugin list widget = guiget('pluginlist') liststore = widget.get_model() @@ -508,12 +536,11 @@ def on_search_refilter(widget): pluginlist = self.registry.get_available_plugins() self.plugins = {} for plugin in pluginlist: - if plugin[0] != "_": # Do not display hidden plugins + if plugin[0] != "_": # Do not display hidden plugins self.plugins[plugin] = self.registry.is_enabled(plugin) - for plugin in self.plugins: - self.pluginiters[plugin] = liststore.append([plugin, - self.plugins[plugin]]) + for plugin, plugin_value in self.plugins.items(): + self.pluginiters[plugin] = liststore.append([plugin, plugin_value]) selection = widget.get_selection() selection.connect('changed', self.on_plugin_selection_changed) if len(self.pluginiters) > 0: @@ -524,9 +551,9 @@ def set_profile_values(self, profile): self.config.set_profile(profile) guiget = self.builder.get_object - dbg('Setting profile %s' % profile) + dbg(f'Setting profile {profile}') - ## General tab + # GENERAL TAB # Use system font widget = guiget('system_font_checkbutton') widget.set_active(self.config['use_system_font']) @@ -534,7 +561,7 @@ def set_profile_values(self, profile): # Font selector widget = guiget('font_selector') - if self.config['use_system_font'] == True: + if self.config['use_system_font'] is True: fontname = self.config.get_system_mono_font() if fontname is not None: widget.set_font_name(fontname) @@ -601,7 +628,7 @@ def set_profile_values(self, profile): except: widget.set_color(Gdk.color_parse('#ffffff')) - ## Command tab + # COMMAND TAB # Login shell widget = guiget('login_shell_checkbutton') widget.set_active(self.config['login_shell']) @@ -622,7 +649,7 @@ def set_profile_values(self, profile): # Default is to close the terminal widget.set_active(0) - ## Colors tab + # COLORS TAB # Use system colors widget = guiget('use_theme_colors_checkbutton') widget.set_active(self.config['use_theme_colors']) @@ -632,9 +659,9 @@ def set_profile_values(self, profile): # Colorscheme widget = guiget('color_scheme_combobox') scheme = None - for ascheme in self.colourschemes: - forecol = self.colourschemes[ascheme][0] - backcol = self.colourschemes[ascheme][1] + for ascheme, scheme_value in self.colourschemes.items(): + forecol = scheme_value[0] + backcol = scheme_value[1] if self.config['foreground_color'].lower() == forecol and \ self.config['background_color'].lower() == backcol: scheme = ascheme @@ -667,8 +694,8 @@ def set_profile_values(self, profile): # Palette scheme widget = guiget('palette_combobox') palette = None - for apalette in self.palettes: - if self.config['palette'].lower() == self.palettes[apalette]: + for apalette, palette_value in self.palettes.items(): + if self.config['palette'].lower() == palette_value: palette = apalette if palette not in self.palettevalues: if self.config['palette'] in [None, '']: @@ -680,6 +707,7 @@ def set_profile_values(self, profile): for palette_id in range(0, NUM_PALETTE_COLORS): widget = self.get_palette_widget(palette_id) widget.set_events(Gdk.EventMask.BUTTON_PRESS_MASK) + def on_palette_click(event, data, widget=widget): self.edit_palette_button(widget) widget.connect('button-press-event', on_palette_click) @@ -691,11 +719,11 @@ def on_palette_click(event, data, widget=widget): widget = guiget('inactive_color_offset') widget.set_value(float(self.config['inactive_color_offset'])) widget = guiget('inactive_color_offset_value_label') - widget.set_text('%d%%' % (int(float(self.config['inactive_color_offset'])*100))) + widget.set_text(f"{int(float(self.config['inactive_color_offset'])*100)}%%") widget = guiget('inactive_bg_color_offset') widget.set_value(float(self.config['inactive_bg_color_offset'])) widget = guiget('inactive_bg_color_offset_value_label') - widget.set_text('%d%%' % (int(float(self.config['inactive_bg_color_offset'])*100))) + widget.set_text(f"{int(float(self.config['inactive_bg_color_offset'])*100)}%%") # Open links with a single click (instead of a Ctrl-left click) widget = guiget('link_single_click') widget.set_active(self.config['link_single_click']) @@ -707,7 +735,7 @@ def on_palette_click(event, data, widget=widget): widget = guiget('custom_url_handler_entry') widget.set_text(self.config['custom_url_handler']) - ## Background tab + # BACKGROUND TAB # Radio values if self.config['background_type'] == 'solid': guiget('solid_radiobutton').set_active(True) @@ -752,8 +780,8 @@ def on_palette_click(event, data, widget=widget): # Background shading widget = guiget('background_darkness_scale') widget.set_value(float(self.config['background_darkness'])) - - ## Scrolling tab + + # SCROLLING TAB # Scrollbar position widget = guiget('scrollbar_position_combobox') value = self.config['scrollbar_position'] @@ -776,7 +804,7 @@ def on_palette_click(event, data, widget=widget): widget = guiget('scroll_on_keystroke_checkbutton') widget.set_active(self.config['scroll_on_keystroke']) - ## Compatibility tab + # COMPATIBILITY TAB # Backspace key widget = guiget('backspace_binding_combobox') value = self.config['backspace_binding'] @@ -800,11 +828,11 @@ def on_palette_click(event, data, widget=widget): else: widget.set_active(0) - ## Titlebar tab + # TITLEBAR TAB # Titlebar colors for bit in ['title_transmit_fg_color', 'title_transmit_bg_color', - 'title_receive_fg_color', 'title_receive_bg_color', - 'title_inactive_fg_color', 'title_inactive_bg_color']: + 'title_receive_fg_color', 'title_receive_bg_color', + 'title_inactive_fg_color', 'title_inactive_bg_color']: widget = guiget(bit) widget.set_color(Gdk.color_parse(self.config[bit])) # Hide size text from the title bar @@ -816,7 +844,7 @@ def on_palette_click(event, data, widget=widget): self.on_title_system_font_checkbutton_toggled(widget) # Font selector widget = guiget('title_font_selector') - if self.config['title_use_system_font'] == True: + if self.config['title_use_system_font'] is True: fontname = self.config.get_system_prop_font() if fontname is not None: widget.set_font_name(fontname) @@ -855,6 +883,7 @@ def on_dbuscheck_toggled(self, widget): self.config.save() def on_detachable_tabs_toggled(self, widget): + """Detachable tabs setting changed""" self.config['detachable_tabs'] = widget.get_active() self.config.save() @@ -945,7 +974,7 @@ def on_smart_copy_toggled(self, widget): self.config['smart_copy'] = widget.get_active() self.config.save() - def on_clear_select_on_copy_toggled(self,widget): + def on_clear_select_on_copy_toggled(self, widget): """Clear selection on smart copy""" self.config['clear_select_on_copy'] = widget.get_active() self.config.save() @@ -1033,7 +1062,7 @@ def on_scrollback_infinite_toggled(self, widget): """Scrollback infiniteness changed""" spinbutton = self.builder.get_object('scrollback_lines_spinbutton') value = widget.get_active() - if value == True: + if value is True: spinbutton.set_sensitive(False) else: spinbutton.set_sensitive(True) @@ -1052,11 +1081,13 @@ def on_scrollbar_position_combobox_changed(self, widget): self.config['scrollbar_position'] = value self.config.save() - def on_background_image_file_set(self,widget): + def on_background_image_file_set(self, widget): + """Background image setting changed""" self.config['background_image'] = widget.get_filename() self.config.save() def on_background_image_mode_changed(self, widget): + """Background image mode setting changed""" selected = widget.get_active() if selected == 1: value = 'scale_and_fit' @@ -1070,6 +1101,7 @@ def on_background_image_mode_changed(self, widget): self.config.save() def on_background_image_align_horiz_changed(self, widget): + """Background image horizontal alignment setting changed""" selected = widget.get_active() if selected == 1: value = 'center' @@ -1081,6 +1113,7 @@ def on_background_image_align_horiz_changed(self, widget): self.config.save() def on_background_image_align_vert_changed(self, widget): + """Background image vertical alignment setting changed""" selected = widget.get_active() if selected == 1: value = 'middle' @@ -1094,8 +1127,7 @@ def on_background_image_align_vert_changed(self, widget): def on_darken_background_scale_value_changed(self, widget): """Background darkness setting changed""" value = widget.get_value() # This one is rounded according to the UI. - if value > 1.0: - value = 1.0 + value = min(value, 1.0) self.config['background_darkness'] = value self.config.save() @@ -1122,7 +1154,7 @@ def on_palette_combobox_changed(self, widget): palettebits.append(get_color_string(self.get_palette_color(palette_id))) palette = ':'.join(palettebits) else: - err('Unknown palette value: %s' % value) + err(f'Unknown palette value: {value}') return self.config['palette'] = palette @@ -1140,7 +1172,7 @@ def on_foreground_colorbutton_draw(self, widget, cr): cr.fill() def on_foreground_colorbutton_click(self, event, data): - dialog = Gtk.ColorChooserDialog("Choose Terminal Text Color") + dialog = Gtk.ColorChooserDialog(_("Choose Terminal Text Color")) fg = self.config['foreground_color'] dialog.set_rgba(Gdk.RGBA.from_color(Gdk.color_parse(self.config['foreground_color']))) dialog.connect('notify::rgba', self.on_foreground_colorpicker_color_change) @@ -1193,7 +1225,7 @@ def on_background_colorpicker_color_change(self, widget, color): def get_palette_widget(self, palette_id): """Returns the palette widget for the given palette ID.""" guiget = self.builder.get_object - return guiget('palette_colorpicker_%d' % (palette_id + 1)) + return guiget(f'palette_colorpicker_{palette_id + 1}') def get_palette_id(self, widget): """Returns the palette ID for the given palette widget.""" @@ -1378,31 +1410,28 @@ def on_title_transmit_fg_color_color_set(self, widget): def on_inactive_color_offset_value_changed(self, widget): """Inactive color offset setting changed""" value = widget.get_value() # This one is rounded according to the UI. - if value > 1.0: - value = 1.0 + value = min(value, 1.0) self.config['inactive_color_offset'] = value self.config.save() guiget = self.builder.get_object label_widget = guiget('inactive_color_offset_value_label') - label_widget.set_text('%d%%' % (int(value * 100))) + label_widget.set_text(f'{int(value * 100)}%%') def on_inactive_bg_color_offset_value_changed(self, widget): """Inactive background color offset setting changed""" value = widget.get_value() # This one is rounded according to the UI. - if value > 1.0: - value = 1.0 + value = min(value, 1.0) self.config['inactive_bg_color_offset'] = value self.config.save() guiget = self.builder.get_object label_widget = guiget('inactive_bg_color_offset_value_label') - label_widget.set_text('%d%%' % (int(value * 100))) + label_widget.set_text(f'{int(value * 100)}%%') def on_handlesize_value_changed(self, widget): """Handle size changed""" value = widget.get_value() # This one is rounded according to the UI. value = int(value) # Cast to int. - if value > 20: - value = 20 + value = min(value, 20) self.config['handle_size'] = value self.config.save() guiget = self.builder.get_object @@ -1413,8 +1442,7 @@ def on_cellheight_value_changed(self, widget): """Handles cell height changed""" value = widget.get_value() value = round(float(value), 1) - if value > 2.0: - value = 2.0 + value = min(value, 2.0) self.config['cell_height'] = value self.config.save() guiget = self.builder.get_object @@ -1425,8 +1453,7 @@ def on_cellwidth_value_changed(self, widget): """Handles cell width changed""" value = widget.get_value() value = round(float(value), 1) - if value > 2.0: - value = 2.0 + value = min(value, 2.0) self.config['cell_width'] = value self.config.save() guiget = self.builder.get_object @@ -1494,14 +1521,14 @@ def addprofile(self, name, toclone): treeview = guiget('profilelist') model = treeview.get_model() - values = [ r[0] for r in model ] + values = [r[0] for r in model] newprofile = name if newprofile in values: i = 1 while newprofile in values: i = i + 1 - newprofile = '%s %d' % (name, i) + newprofile = f'{name} {i}' if self.config.add_profile(newprofile, toclone): res = model.append([newprofile, True]) @@ -1555,14 +1582,15 @@ def on_layoutaddbutton_clicked(self, _button): treeview = guiget('layoutlist') model = treeview.get_model() - values = [ r[0] for r in model ] + values = [r[0] for r in model] name = _('New Layout') if name in values: i = 0 while name in values: i = i + 1 - name = '%s %d' % (_('New Layout'), i) + new_layout_str = _('New Layout') + name = f'{new_layout_str} {i}' if self.config.add_layout(name, current_layout): res = model.append([name, True]) @@ -1584,7 +1612,9 @@ def on_layoutrefreshbutton_clicked(self, _button): name = model.get_value(rowiter, 0) if self.config.replace_layout(name, current_layout): - treeview.set_cursor(model.get_path(rowiter), column=treeview.get_column(0), start_editing=False) + treeview.set_cursor(model.get_path(rowiter), + column=treeview.get_column(0), + start_editing=False) self.config.save() self.layouteditor.set_layout(name) @@ -1646,8 +1676,8 @@ def on_system_font_checkbutton_toggled(self, checkbox): widget.set_sensitive(not value) self.config['use_system_font'] = value self.config.save() - - if self.config['use_system_font'] == True: + + if self.config['use_system_font'] is True: fontname = self.config.get_system_mono_font() if fontname is not None: widget.set_font_name(fontname) @@ -1665,7 +1695,7 @@ def on_title_system_font_checkbutton_toggled(self, checkbox): self.config['title_use_system_font'] = value self.config.save() - if self.config['title_use_system_font'] == True: + if self.config['title_use_system_font'] is True: fontname = self.config.get_system_prop_font() if fontname is not None: widget.set_font_name(fontname) @@ -1695,9 +1725,9 @@ def update_background_tab(self): imagewidget = guiget('image_radiobutton') transwidget = guiget('transparent_radiobutton') - if imagewidget.get_active() == True: + if imagewidget.get_active() is True: backtype = 'image' - elif transwidget.get_active() == True: + elif transwidget.get_active() is True: backtype = 'transparent' else: backtype = 'solid' @@ -1748,7 +1778,7 @@ def on_plugin_selection_changed(self, selection): self.set_plugin(plugin) self.previous_plugin_selection = plugin - widget = self.builder.get_object('plugintogglebutton') + # widget = self.builder.get_object('plugintogglebutton') def on_plugin_toggled(self, cell, path): """A plugin has been enabled or disabled""" @@ -1767,22 +1797,22 @@ def on_plugin_toggled(self, cell, path): # Update the treeview model[path][1] = self.plugins[plugin] - enabled_plugins = [x for x in self.plugins if self.plugins[x] == True] + enabled_plugins = [x for x, val in self.plugins.items() if val is True] self.config['enabled_plugins'] = enabled_plugins self.config.save() def set_plugin(self, plugin): """Show the preferences for the selected plugin, if any""" - pluginpanelabel = self.builder.get_object('pluginpanelabel') - pluginconfig = self.config.plugin_get_config(plugin) + # pluginpanelabel = self.builder.get_object('pluginpanelabel') + # pluginconfig = self.config.plugin_get_config(plugin) # FIXME: Implement this, we need to auto-construct a UI for the plugin def on_profile_name_edited(self, cell, path, newtext): """Update a profile name""" oldname = cell.get_property('text') - if oldname == newtext or oldname == 'default': + if oldname in (newtext, 'default'): return - dbg('Changing %s to %s' % (oldname, newtext)) + dbg(f'Changing {oldname} to {newtext}') self.config.rename_profile(oldname, newtext) self.config.save() @@ -1817,9 +1847,9 @@ def on_layout_profile_workingdir_changed(self, widget): def on_layout_name_edited(self, cell, path, newtext): """Update a layout name""" oldname = cell.get_property('text') - if oldname == newtext or oldname == 'default': + if oldname in (newtext, 'default'): return - dbg('Changing %s to %s' % (oldname, newtext)) + dbg(f'Changing {oldname} to {newtext}') self.config.rename_layout(oldname, newtext) self.config.save() @@ -1868,8 +1898,8 @@ def on_use_theme_colors_checkbutton_toggled(self, widget): back = guiget('background_colorbutton') if active: - for widget in [scheme, fore, back]: - widget.set_sensitive(False) + for _widget in [scheme, fore, back]: + _widget.set_sensitive(False) else: scheme.set_sensitive(True) self.on_color_scheme_combobox_changed(scheme) @@ -1882,15 +1912,26 @@ def on_bold_text_is_bright_checkbutton_toggled(self, widget): self.config['bold_is_bright'] = widget.get_active() self.config.save() - def on_cellrenderer_accel_edited(self, liststore, path, key, mods, _code): - inpath = path #save for debugging - trpath = Gtk.TreePath.new_from_string(inpath) - path = str(self.treemodelfilter.convert_path_to_child_path(trpath)) - dbg("convert path with filter from: %s to: %s" % - (inpath, path)) + def on_cellrenderer_accel_edited_keybind1(self, liststore, path, key, mods, _code): + """Handle an edited keybinding 1""" + self.on_cellrenderer_accel_edited(liststore, path, key, mods, _code, + 2, 3, 0) + + def on_cellrenderer_accel_edited_keybind2(self, liststore, path, key, mods, _code): + """Handle an edited keybinding 2""" + self.on_cellrenderer_accel_edited(liststore, path, key, mods, _code, + 4, 5, 1) + + def on_cellrenderer_accel_edited(self, liststore, path, key, mods, _code, key_index, mod_index, binding_index): """Handle an edited keybinding""" + inpath = path # save for debugging + trpath = Gtk.TreePath.new_from_string(inpath) + path = str(self.treemodelfilter.convert_path_to_child_path(trpath)) + dbg(f"convert path with filter from: {inpath} to: {path}") + # Ignore `Gdk.KEY_Tab` so that `Shift+Tab` is displayed as `Shift+Tab` - # in `Preferences>Keybindings` and NOT `Left Tab` (see `Gdk.KEY_ISO_Left_Tab`). + # in `Preferences>Keybindings` and NOT `Left Tab` + # (see `Gdk.KEY_ISO_Left_Tab`). if mods & Gdk.ModifierType.SHIFT_MASK and key != Gdk.KEY_Tab: key_with_shift = Gdk.Keymap.translate_keyboard_state( self.keybindings.keymap, @@ -1900,7 +1941,7 @@ def on_cellrenderer_accel_edited(self, liststore, path, key, mods, _code): ) keyval_lower, keyval_upper = Gdk.keyval_convert_case(key) - # Remove the Shift modifier from `mods` if a new key binding doesn't + # Remove the Shift modifier from `mods` if a new keybinding doesn't # contain a letter and its key value (`key`) can't be modified by a # Shift key. if key_with_shift.level != 0 and keyval_lower == keyval_upper: @@ -1911,24 +1952,22 @@ def on_cellrenderer_accel_edited(self, liststore, path, key, mods, _code): current_binding = liststore.get_value(liststore.get_iter(path), 0) parsed_accel = Gtk.accelerator_parse(accel) - keybindutil = KeyBindUtil() - keybindings = self.config["keybindings"] - #merge give preference to main bindings over plugin - plugin_keyb_act = keybindutil.get_all_act_to_keys() - keybindings = {**plugin_keyb_act, **keybindings} + keybindutil = KeyBindUtil() + keybindings = self.config["keybindings"] + # merge give preference to main bindings over plugin + plugin_keyb_act = keybindutil.get_all_act_to_keys() + keybindings = {**plugin_keyb_act, **keybindings} duplicate_bindings = [] - for conf_binding, conf_accel in keybindings.items(): - if conf_accel is None: - continue + for conf_binding, conf_accels in keybindings.items(): + for conf_accel in conf_accels: + if conf_accel is None: + continue - parsed_conf_accel = Gtk.accelerator_parse(conf_accel) + parsed_conf_accel = Gtk.accelerator_parse(conf_accel) - if ( - parsed_accel == parsed_conf_accel - and current_binding != conf_binding - ): - duplicate_bindings.append((conf_binding, conf_accel)) + if (parsed_accel == parsed_conf_accel and current_binding != conf_binding): + duplicate_bindings.append((conf_binding, conf_accel)) if duplicate_bindings: dialog = Gtk.MessageDialog( @@ -1936,16 +1975,14 @@ def on_cellrenderer_accel_edited(self, liststore, path, key, mods, _code): flags=Gtk.DialogFlags.MODAL, message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.CLOSE, - text="Duplicate Key Bindings Are Not Allowed", + text=_("Duplicate keybindings are not allowed."), ) accel_label = Gtk.accelerator_get_label(key, mods) # get the first found duplicate duplicate_keybinding_name = duplicate_bindings[0][0] - message = ( - "Key binding `{0}` is already in use to trigger the `{1}` action." - ).format(accel_label, self.keybindingnames[duplicate_keybinding_name]) + message = f"Key binding `{accel_label}` is already in use to trigger the `{self.keybindingnames[duplicate_keybinding_name]}` action." dialog.format_secondary_text(message) self.active_message_dialog = dialog @@ -1955,43 +1992,66 @@ def on_cellrenderer_accel_edited(self, liststore, path, key, mods, _code): return - celliter = liststore.get_iter_from_string(path) - liststore.set(celliter, 2, key, 3, mods) - binding = liststore.get_value(liststore.get_iter(path), 0) accel = Gtk.accelerator_name(key, mods) - self.config['keybindings'][binding] = accel + alt_index = abs(binding_index - 1) + if self.config['keybindings'][binding][alt_index] != accel: + self.config['keybindings'][binding][binding_index] = accel + else: + dialog = Gtk.MessageDialog( + transient_for=self.window, + flags=Gtk.DialogFlags.MODAL, + message_type=Gtk.MessageType.ERROR, + buttons=Gtk.ButtonsType.CLOSE, + text=_("Duplicate keybindings are not allowed."), + ) + + self.active_message_dialog = dialog + dialog.run() + dialog.destroy() + self.active_message_dialog = None + + return + + celliter = liststore.get_iter_from_string(path) + liststore.set(celliter, key_index, key, mod_index, mods) plugin_keyb_desc = keybindutil.get_act_to_desc(binding) if plugin_keyb_desc: - dbg("modifying plugin binding: %s, %s, %s" % - (plugin_keyb_desc, binding, accel)) + dbg(f"modifying plugin binding: %{plugin_keyb_desc}, {binding}, {accel}") keybindutil.bindkey([plugin_keyb_desc, binding, accel]) else: - dbg("skipping: %s" % binding) - pass + dbg(f"skipping: {binding}") self.config.save() - def on_cellrenderer_accel_cleared(self, liststore, path): - inpath = path #save for debugging - trpath = Gtk.TreePath.new_from_string(inpath) - path = str(self.treemodelfilter.convert_path_to_child_path(trpath)) - dbg("convert path with filter from: %s to: %s" % - (inpath, path)) + def on_cellrenderer_accel_cleared_keybind1(self, liststore, path): + """Handle the clearing of keybinding 1 accelerator""" + self.on_cellrenderer_accel_cleared(liststore, path, 2, 3, 0) + def on_cellrenderer_accel_cleared_keybind2(self, liststore, path): + """Handle the clearing of keybinding 2 accelerator""" + self.on_cellrenderer_accel_cleared(liststore, path, 4, 5, 1) + + def on_cellrenderer_accel_cleared(self, liststore, path, key_index, mod_index, binding_index): """Handle the clearing of a keybinding accelerator""" + inpath = path # save for debugging + trpath = Gtk.TreePath.new_from_string(inpath) + path = str(self.treemodelfilter.convert_path_to_child_path(trpath)) + dbg(f"convert path with filter from: {inpath} to: {path}") + celliter = liststore.get_iter_from_string(path) - liststore.set(celliter, 2, 0, 3, 0) + liststore.set(celliter, key_index, 0, mod_index, 0) binding = liststore.get_value(liststore.get_iter(path), 0) - self.config['keybindings'][binding] = "" + self.config['keybindings'][binding][binding_index] = '' self.config.save() - def on_open_manual(self, widget): + def on_open_manual(self, widget): """Open the fine manual""" self.term.key_help() + class LayoutEditor: profile_ids_to_profile = None profile_profile_to_ids = None @@ -2001,6 +2061,7 @@ class LayoutEditor: treeview = None treestore = None config = None + previous_layout_selection = None def __init__(self, builder): """Initialise ourself""" @@ -2024,15 +2085,14 @@ def set_layout(self, layout_name): store.clear() children = list(layout.keys()) - i = 0 - while children != []: + while children: child = children.pop() child_type = layout[child]['type'] parent = layout[child]['parent'] if child_type != 'Window' and parent not in layout: # We have an orphan! - err('%s is an orphan in this layout. Discarding' % child) + err(f'{child} is an orphan in this layout. Discarding') continue try: parentiter = listitems[parent] @@ -2057,7 +2117,7 @@ def set_layout(self, layout_name): def update_profiles(self): """Update the list of profiles""" self.profile_ids_to_profile = {} - self.profile_profile_to_ids= {} + self.profile_profile_to_ids = {} chooser = self.builder.get_object('layout_profile_chooser') profiles = self.config.list_profiles() @@ -2157,6 +2217,7 @@ def on_layout_profile_workingdir_activate(self, widget): layout[self.layout_item]['directory'] = workdir self.config.save() + if __name__ == '__main__': from . import util util.DEBUG = True diff --git a/terminatorlib/terminal_popup_menu.py b/terminatorlib/terminal_popup_menu.py index dc7f95f7..32b52eb6 100644 --- a/terminatorlib/terminal_popup_menu.py +++ b/terminatorlib/terminal_popup_menu.py @@ -1,6 +1,6 @@ # Terminator by Chris Jones # GPL v2 only -"""terminal_popup_menu.py - classes necessary to provide a terminal context +"""terminal_popup_menu.py - classes necessary to provide a terminal context menu""" from gi.repository import Gtk, Gdk @@ -12,7 +12,7 @@ from .config import Config from .prefseditor import PrefsEditor from . import plugin -from .layoutlauncher import LayoutLauncher + class TerminalPopupMenu(object): """Class implementing the Terminal context menu""" @@ -20,6 +20,7 @@ class TerminalPopupMenu(object): terminator = None config = None accelgrp = None + popup_menu = None def __init__(self, terminal): """Class initialiser""" @@ -35,53 +36,51 @@ def get_menu_item_mask(self, maskstr): maskstr = maskstr.lower() if maskstr.find(''.lower()) >= 0: mask = mask | Gdk.ModifierType.SHIFT_MASK - dbg("adding mask %s" % mask) + dbg(f"adding mask {mask}") ctrl = (maskstr.find(''.lower()) >= 0 or maskstr.find(''.lower()) >= 0) if ctrl: mask = mask | Gdk.ModifierType.CONTROL_MASK - dbg("adding mask %s" % mask) + dbg(f"adding mask {mask}") if maskstr.find(''.lower()) >= 0: mask = mask | Gdk.ModifierType.MOD1_MASK - dbg("adding mask %s" % mask) + dbg(f"adding mask {mask}") mask = Gdk.ModifierType(mask) - dbg("menu_item_mask :%d" % mask) + dbg(f"menu_item_mask :{mask}") return mask def menu_item(self, menutype, actstr, menustr): - act = self.config.base.get_item('keybindings', actstr) - maskstr = act[actstr] if actstr in act else "" - mask = self.get_menu_item_mask(maskstr) + act = self.config.base.get_item('keybindings', actstr) + maskstr = act[actstr][0] if actstr in act else "" + mask = self.get_menu_item_mask(maskstr) accelchar = "" pos = menustr.lower().find("_") if (pos >= 0 and pos+1 < len(menustr)): accelchar = menustr.lower()[pos+1] - #this may require tweak. what about shortcut function keys ? + # this may require tweak. what about shortcut function keys ? if maskstr: mpos = maskstr.rfind(">") - #can't have a char at 0 position as <> is len 2 + # can't have a char at 0 position as <> is len 2 if mpos >= 0 and mpos+1 < len(maskstr): configaccelchar = maskstr[mpos+1:] - #ensure to take only 1 char else ignore + # ensure to take only 1 char else ignore if len(configaccelchar) == 1: - dbg("found accelchar in config:%s override:%s" - % (configaccelchar, accelchar)) + dbg(f"found accelchar in config:{configaccelchar} override:{accelchar}") accelchar = configaccelchar - dbg("action from config:%s for item:%s with shortcut accelchar:(%s)" - % (maskstr, menustr, accelchar)) + dbg(f"action from config:{maskstr} for item:{menustr} with shortcut accelchar:({accelchar})") item = menutype.new_with_mnemonic(_(menustr)) if mask: item.add_accelerator("activate", - self.accelgrp, - Gdk.keyval_from_name(accelchar), - mask, - Gtk.AccelFlags.VISIBLE) + self.accelgrp, + Gdk.keyval_from_name(accelchar), + mask, + Gtk.AccelFlags.VISIBLE) return item def show(self, widget, event=None): @@ -91,24 +90,17 @@ def show(self, widget, event=None): menu = Gtk.Menu() self.popup_menu = menu url = None - button = None - time = None self.config.set_profile(terminal.get_profile()) if event: url = terminal.vte.match_check_event(event) - button = event.button - time = event.time - else: - time = 0 - button = 3 if url and url[0]: - dbg("URL matches id: %d" % url[1]) + dbg(f"URL matches id: {url[1]}") if not url[1] in list(terminal.matches.values()): - err("Unknown URL match id: %d" % url[1]) - dbg("Available matches: %s" % terminal.matches) + err(f"Unknown URL match id: {url[1]}") + dbg(f"Available matches: {terminal.matches}") nameopen = None namecopy = None @@ -120,19 +112,19 @@ def show(self, widget, event=None): namecopy = _('_Copy VoIP address') elif url[1] in list(terminal.matches.values()): # This is a plugin match + plugin_name = '' for pluginname in terminal.matches: if terminal.matches[pluginname] == url[1]: + dbg(f"Found match ID ({url[1]}) in terminal.matches plugin {pluginname}") + plugin_name = pluginname break - dbg("Found match ID (%d) in terminal.matches plugin %s" % - (url[1], pluginname)) registry = plugin.PluginRegistry() registry.load_plugins() plugins = registry.get_plugins_by_capability('url_handler') for urlplugin in plugins: - if urlplugin.handler_name == pluginname: - dbg("Identified matching plugin: %s" % - urlplugin.handler_name) + if urlplugin.handler_name == plugin_name: + dbg(f"Identified matching plugin: {urlplugin.handler_name}") nameopen = _(urlplugin.nameopen) namecopy = _(urlplugin.namecopy) break @@ -150,7 +142,7 @@ def show(self, widget, event=None): menu.append(item) item = Gtk.MenuItem.new_with_mnemonic(namecopy) - item.connect('activate', + item.connect('activate', lambda x: terminal.clipboard.set_text(terminal.prepare_url(url), len(terminal.prepare_url(url)))) menu.append(item) @@ -172,22 +164,14 @@ def show(self, widget, event=None): 'Set _Window Title') item.connect('activate', lambda x: terminal.key_edit_window_title()) menu.append(item) - + if not terminal.is_zoomed(): item = self.menu_item(Gtk.ImageMenuItem, 'split_auto', 'Split _Auto') - """ - image = Gtk.Image() - image.set_from_icon_name(APP_NAME + '_auto', Gtk.IconSize.MENU) - item.set_image(image) - if hasattr(item, 'set_always_show_image'): - item.set_always_show_image(True) - """ item.connect('activate', lambda x: terminal.emit('split-auto', - self.terminal.get_cwd())) + self.terminal.get_cwd())) menu.append(item) - item = self.menu_item(Gtk.ImageMenuItem, 'split_horiz', 'Split H_orizontally') image = Gtk.Image() @@ -196,7 +180,7 @@ def show(self, widget, event=None): if hasattr(item, 'set_always_show_image'): item.set_always_show_image(True) item.connect('activate', lambda x: terminal.emit('split-horiz', - self.terminal.get_cwd())) + self.terminal.get_cwd())) menu.append(item) item = self.menu_item(Gtk.ImageMenuItem, 'split_vert', @@ -207,18 +191,18 @@ def show(self, widget, event=None): if hasattr(item, 'set_always_show_image'): item.set_always_show_image(True) item.connect('activate', lambda x: terminal.emit('split-vert', - self.terminal.get_cwd())) + self.terminal.get_cwd())) menu.append(item) item = self.menu_item(Gtk.MenuItem, 'new_tab', 'Open _Tab') item.connect('activate', lambda x: terminal.emit('tab-new', False, - terminal)) + terminal)) menu.append(item) if self.terminator.debug_address is not None: item = Gtk.MenuItem.new_with_mnemonic(_('Open _Debug Tab')) item.connect('activate', lambda x: - terminal.emit('tab-new', True, terminal)) + terminal.emit('tab-new', True, terminal)) menu.append(item) menu.append(Gtk.SeparatorMenuItem()) @@ -250,7 +234,7 @@ def show(self, widget, event=None): menu.append(Gtk.SeparatorMenuItem()) - if self.config['show_titlebar'] == False: + if self.config['show_titlebar'] is False: item = Gtk.MenuItem.new_with_mnemonic(_('Grouping')) submenu = self.terminal.populate_group_menu() submenu.show_all() @@ -265,12 +249,12 @@ def show(self, widget, event=None): menu.append(Gtk.SeparatorMenuItem()) item = self.menu_item(Gtk.CheckMenuItem, 'toggle_readonly', '_Read only') - item.set_active(not(terminal.vte.get_input_enabled())) + item.set_active(not terminal.vte.get_input_enabled()) item.connect('toggled', lambda x: terminal.do_readonly_toggle()) menu.append(item) item = self.menu_item(Gtk.CheckMenuItem, 'toggle_scrollbar', - 'Show _scrollbar') + 'Show _scrollbar') item.set_active(terminal.scrollbar.get_property('visible')) item.connect('toggled', lambda x: terminal.do_scrollbar_toggle()) menu.append(item) @@ -312,19 +296,19 @@ def show(self, widget, event=None): plugins = registry.get_plugins_by_capability('terminal_menu') for menuplugin in plugins: menuplugin.callback(menuitems, menu, terminal) - + if len(menuitems) > 0: menu.append(Gtk.SeparatorMenuItem()) for menuitem in menuitems: menu.append(menuitem) except Exception as ex: - err('TerminalPopupMenu::show: %s' % ex) + err(f'TerminalPopupMenu::show: {ex}') menu.show_all() menu.popup_at_pointer(None) - return(True) + return True def add_layout_launcher(self, menu): """Add the layout list to the menu""" @@ -334,6 +318,6 @@ def add_layout_launcher(self, menu): item.set_submenu(submenu) layouts = self.config.list_layouts() for layout in layouts: - item = Gtk.MenuItem(layout) - item.connect('activate', lambda x: spawn_new_terminator(self.terminator.origcwd, ['-u', '-l', x.get_label()])) - submenu.append(item) + item = Gtk.MenuItem(layout) + item.connect('activate', lambda x: spawn_new_terminator(self.terminator.origcwd, ['-u', '-l', x.get_label()])) + submenu.append(item)