# coding=utf-8 # Dindo Bot # Copyright (c) 2018 - 2019 AXeL import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk, GdkPixbuf from lib import tools, shared, settings, accounts, maps from .custom import CustomComboBox, CustomTreeView, TextValueComboBox, ButtonBox, MessageBox, MiniMap, SpinButton class AboutDialog(Gtk.AboutDialog): def __init__(self, transient_for): Gtk.AboutDialog.__init__(self, transient_for=transient_for) self.set_program_name(shared.__program_name__) self.set_version(shared.__version__) self.set_comments(shared.__program_desc__) self.set_website(shared.__website__) self.set_website_label(shared.__website_label__) self.set_authors(shared.__authors__) logo = GdkPixbuf.Pixbuf.new_from_file_at_size(tools.get_full_path('icons/logo.png'), 64, 64) self.set_logo(logo) self.connect('response', lambda dialog, response: self.destroy()) class CustomDialog(Gtk.Dialog): def __init__(self, title, transient_for=None, destroy_on_response=True): Gtk.Dialog.__init__(self, modal=True, transient_for=transient_for, title=title) self.set_border_width(10) self.set_resizable(False) if destroy_on_response: self.connect('response', lambda dialog, response: self.destroy()) # Header Bar hb = Gtk.HeaderBar(title=title) hb.set_show_close_button(True) self.set_titlebar(hb) class AlertDialog(CustomDialog): def __init__(self, transient_for, message): CustomDialog.__init__(self, transient_for=transient_for, title='Alert') # message content_area = self.get_content_area() content_area.set_spacing(5) content_area.add(Gtk.Label(message, use_markup=True)) # Ok button self.action_area.set_layout(Gtk.ButtonBoxStyle.CENTER) ok_button = Gtk.Button('Ok') self.add_action_widget(ok_button, Gtk.ResponseType.OK) self.show_all() self.run() class CopyTextDialog(CustomDialog): def __init__(self, transient_for, text): CustomDialog.__init__(self, transient_for=transient_for, title='Copy Text') # text entry content_area = self.get_content_area() content_area.set_spacing(5) entry = Gtk.Entry() entry.set_text(text) entry.set_width_chars(60) content_area.add(entry) # Close button self.action_area.set_layout(Gtk.ButtonBoxStyle.CENTER) close_button = Gtk.Button('Close') self.add_action_widget(close_button, Gtk.ResponseType.OK) self.show_all() self.run() class LoadMapDialog(CustomDialog): def __init__(self, transient_for): CustomDialog.__init__(self, transient_for=transient_for, title='Load Map', destroy_on_response=False) self.parent = transient_for self.data = maps.load() self.set_size_request(300, -1) self.connect('delete-event', lambda dialog, response: self.destroy()) # Map combobox content_area = self.get_content_area() content_area.set_spacing(5) content_area.add(Gtk.Label('<b>Map</b>', xalign=0, use_markup=True)) self.maps_combo = CustomComboBox(self.data, sort=True) self.maps_combo.set_margin_left(10) self.maps_combo.connect('changed', self.on_maps_combo_changed) content_area.add(self.maps_combo) # Error box self.error_box = MessageBox(color='red') self.error_box.set_margin_left(10) content_area.add(self.error_box) # Load button self.action_area.set_layout(Gtk.ButtonBoxStyle.CENTER) load_button = Gtk.Button('Load') load_button.connect('clicked', self.on_load_button_clicked) self.add_action_widget(load_button, Gtk.ResponseType.OK) self.show_all() self.reset() def reset(self): self.error_box.hide() def on_maps_combo_changed(self, combo): self.reset() if not self.parent.map_data_listbox.is_empty(): self.error_box.print_message('Your current data will be erased !') def on_load_button_clicked(self, button): selected = self.maps_combo.get_active_text() if selected: # clear listbox & view self.parent.map_data_listbox.clear() self.parent.map_view.clear() # append to listbox text = maps.to_string(self.data[selected]) lines = text[1:-1].split('},') # [1:-1] to remove '[]' for line in lines: text = line.strip() if text.startswith('{') and not text.endswith('}'): text += '}' self.parent.map_data_listbox.append_text(text) # append to view if self.parent.show_selected_data_only_check.get_active(): self.parent.map_view.add_point(self.data[selected][-1], 'Resource', MiniMap.point_colors['Resource']) else: self.parent.map_view.add_points(self.data[selected], 'Resource', MiniMap.point_colors['Resource']) # destroy dialog self.destroy() else: self.error_box.print_message('Please select a map') class DeleteMapDialog(CustomDialog): def __init__(self, transient_for): CustomDialog.__init__(self, transient_for=transient_for, title='Delete Map', destroy_on_response=False) self.parent = transient_for self.data = maps.load() self.set_size_request(300, -1) self.connect('delete-event', lambda dialog, response: self.destroy()) # Map combobox content_area = self.get_content_area() content_area.set_spacing(5) content_area.add(Gtk.Label('<b>Map</b>', xalign=0, use_markup=True)) self.maps_combo = CustomComboBox(self.data, sort=True) self.maps_combo.set_margin_left(10) self.maps_combo.connect('changed', lambda combo: self.reset()) content_area.add(self.maps_combo) # Error box self.error_box = MessageBox(color='red', enable_buttons=True) self.error_box.set_margin_left(10) self.error_box.yes_button.connect('clicked', lambda button: self.delete_data()) self.error_box.no_button.connect('clicked', lambda button: self.reset()) content_area.add(self.error_box) # Delete button self.action_area.set_layout(Gtk.ButtonBoxStyle.CENTER) delete_button = Gtk.Button('Delete') delete_button.connect('clicked', self.on_delete_button_clicked) self.add_action_widget(delete_button, Gtk.ResponseType.OK) self.show_all() self.reset() def reset(self): self.error_box.hide() self.action_area.show() def delete_data(self): selected = self.maps_combo.get_active() if selected != -1: # delete map_name = self.maps_combo.get_active_text() del self.data[map_name] self.maps_combo.remove(selected) # save data maps.save(self.data) # update parent window self.parent.collect_map_combo.append_list(self.data, sort=True, clear=True) def on_delete_button_clicked(self, button): if self.maps_combo.get_active() != -1: self.action_area.hide() self.error_box.print_message('Confirm delete?', True) else: self.error_box.print_message('Please select a map') class SaveMapDialog(CustomDialog): def __init__(self, transient_for): CustomDialog.__init__(self, transient_for=transient_for, title='Save Map', destroy_on_response=False) self.parent = transient_for self.data = maps.load() self.set_size_request(300, -1) self.connect('delete-event', lambda dialog, response: self.destroy()) # Map Name entry content_area = self.get_content_area() content_area.set_spacing(5) content_area.add(Gtk.Label('<b>Map Name</b>', xalign=0, use_markup=True)) self.entry = Gtk.Entry() self.entry.set_margin_left(10) self.entry.connect('focus-in-event', lambda entry, event: self.reset()) content_area.add(self.entry) # Error box self.error_box = MessageBox(color='red', enable_buttons=True) self.error_box.set_margin_left(10) self.error_box.yes_button.connect('clicked', lambda button: self.save_data(self.get_map_name(), self.data)) self.error_box.no_button.connect('clicked', lambda button: self.reset()) content_area.add(self.error_box) # Save button self.action_area.set_layout(Gtk.ButtonBoxStyle.CENTER) save_button = Gtk.Button('Save') save_button.connect('clicked', self.on_save_button_clicked) self.add_action_widget(save_button, Gtk.ResponseType.OK) self.show_all() self.reset() def reset(self): self.error_box.hide() self.action_area.show() def get_map_name(self): return self.entry.get_text().strip() def save_data(self, name, data): # get map data map_data = [] for row in self.parent.map_data_listbox.get_rows(): text = self.parent.map_data_listbox.get_row_text(row) map_data.append(maps.to_array(text)) data[name] = map_data # save data maps.save(data) # update parent window self.parent.collect_map_combo.append_list(data, sort=True, clear=True) # destroy dialog self.destroy() def on_save_button_clicked(self, button): # get map name name = self.get_map_name() # check if empty if not name: self.error_box.print_message('Please type a name') else: # check if already exists if name in self.data: self.action_area.hide() self.error_box.print_message('Map name already exists, would you like to override it?', True) else: self.save_data(name, self.data) class OpenFileDialog(Gtk.FileChooserDialog): def __init__(self, title, transient_for=None, filter=None): Gtk.FileChooserDialog.__init__(self, title=title, transient_for=transient_for, action=Gtk.FileChooserAction.OPEN) if filter is not None and len(filter) > 1: name, pattern = filter file_filter = Gtk.FileFilter() file_filter.set_name('%s (%s)' % (name, pattern)) file_filter.add_pattern(pattern) self.add_filter(file_filter) self.add_button('_Cancel', Gtk.ResponseType.CANCEL) self.add_button('_Open', Gtk.ResponseType.OK) self.set_default_response(Gtk.ResponseType.OK) class SaveFileDialog(Gtk.FileChooserDialog): def __init__(self, title, transient_for=None, filter=None): Gtk.FileChooserDialog.__init__(self, title=title, transient_for=transient_for, action=Gtk.FileChooserAction.SAVE) if filter is not None and len(filter) > 1: name, pattern = filter file_filter = Gtk.FileFilter() file_filter.set_name('%s (%s)' % (name, pattern)) file_filter.add_pattern(pattern) self.add_filter(file_filter) self.add_button('_Cancel', Gtk.ResponseType.CANCEL) self.add_button('_Save', Gtk.ResponseType.OK) self.set_default_response(Gtk.ResponseType.OK) class PreferencesDialog(CustomDialog): def __init__(self, transient_for, title='Preferences'): CustomDialog.__init__(self, transient_for=transient_for, title=title) self.parent = transient_for # Stack & Stack switcher vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) content_area = self.get_content_area() content_area.add(vbox) stack = Gtk.Stack() stack.set_margin_top(5) #stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT) #stack.set_transition_duration(1000) stack_switcher = Gtk.StackSwitcher() stack_switcher.set_stack(stack) vbox.pack_start(stack_switcher, True, True, 0) vbox.pack_start(stack, True, True, 0) ### General box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) stack.add_titled(box, 'general', 'General') ## Debug hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) box.add(hbox) hbox.add(Gtk.Label('<b>Debug</b>', xalign=0, use_markup=True)) debug_switch = Gtk.Switch() debug_switch.set_active(self.parent.settings['Debug']['Enabled']) debug_switch.connect('notify::active', self.on_debug_switch_activated) hbox.pack_end(debug_switch, False, False, 0) # Level self.debug_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) self.debug_box.set_margin_left(10) self.debug_box.set_sensitive(self.parent.settings['Debug']['Enabled']) box.add(self.debug_box) self.debug_box.add(Gtk.Label('Level')) debug_level_combo = CustomComboBox(['High', 'Normal', 'Low']) debug_level_combo.set_active(self.parent.settings['Debug']['Level']) debug_level_combo.connect('changed', lambda combo: settings.update_and_save(self.parent.settings, key='Debug', subkey='Level', value=combo.get_active())) self.debug_box.pack_end(debug_level_combo, False, False, 0) ## Game box.add(Gtk.Label('<b>Game</b>', xalign=0, use_markup=True)) # Version hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) hbox.set_margin_left(10) box.add(hbox) hbox.add(Gtk.Label('Version')) game_version_list = [ { 'label': 'Retro', 'value': shared.GameVersion.Retro }, { 'label': '2.x', 'value': shared.GameVersion.Two } ] game_version_combo = TextValueComboBox(game_version_list, model=Gtk.ListStore(str, int), text_key='label', value_key='value') game_version_combo.set_active_value(self.parent.settings['Game']['Version']) game_version_combo.connect('changed', self.on_game_version_combo_changed) hbox.pack_end(game_version_combo, False, False, 0) # Window decoration height hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) hbox.set_margin_left(10) use_custom_window_decoration_height_check = Gtk.CheckButton('Window decoration height') use_custom_window_decoration_height_check.set_active(self.parent.settings['Game']['UseCustomWindowDecorationHeight']) use_custom_window_decoration_height_check.set_tooltip_text('(in pixels)') use_custom_window_decoration_height_check.connect('clicked', lambda check: ( settings.update_and_save(self.parent.settings, key='Game', subkey='UseCustomWindowDecorationHeight', value=check.get_active()), self.window_decoration_height_spin_button.set_sensitive(check.get_active()) ) ) hbox.add(use_custom_window_decoration_height_check) self.window_decoration_height_spin_button = SpinButton(min=5, max=100) self.window_decoration_height_spin_button.set_value(self.parent.settings['Game']['WindowDecorationHeight']) self.window_decoration_height_spin_button.set_sensitive(self.parent.settings['Game']['UseCustomWindowDecorationHeight']) self.window_decoration_height_spin_button.connect('value-changed', lambda button: settings.update_and_save(self.parent.settings, key='Game', subkey='WindowDecorationHeight', value=button.get_value_as_int())) hbox.pack_end(self.window_decoration_height_spin_button, False, False, 0) box.add(hbox) # Keep game checkbox keep_game_on_unplug_check = Gtk.CheckButton('Keep game open when unbinding') keep_game_on_unplug_check.set_margin_left(10) keep_game_on_unplug_check.set_active(self.parent.settings['Game']['KeepOpen']) keep_game_on_unplug_check.connect('clicked', lambda check: settings.update_and_save(self.parent.settings, key='Game', subkey='KeepOpen', value=check.get_active())) box.add(keep_game_on_unplug_check) ## Account box.add(Gtk.Label('<b>Account</b>', xalign=0, use_markup=True)) # Exit game checkbox exit_game_on_disconnect_check = Gtk.CheckButton('Exit game when disconnecting') exit_game_on_disconnect_check.set_margin_left(10) exit_game_on_disconnect_check.set_active(self.parent.settings['Account']['ExitGame']) exit_game_on_disconnect_check.connect('clicked', lambda check: settings.update_and_save(self.parent.settings, key='Account', subkey='ExitGame', value=check.get_active())) box.add(exit_game_on_disconnect_check) ### Bot box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) stack.add_titled(box, 'bot', 'Bot') ## State box.add(Gtk.Label('<b>State</b>', xalign=0, use_markup=True)) # PodBar hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) hbox.set_margin_left(10) hbox.add(Gtk.Label('PodBar')) podbar_switch = Gtk.Switch() podbar_switch.set_active(self.parent.settings['State']['EnablePodBar']) podbar_switch.connect('notify::active', self.on_podbar_switch_activated) hbox.pack_end(podbar_switch, False, False, 0) box.add(hbox) # MiniMap hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) hbox.set_margin_left(10) hbox.add(Gtk.Label('MiniMap')) minimap_switch = Gtk.Switch() minimap_switch.set_active(self.parent.settings['State']['EnableMiniMap']) minimap_switch.connect('notify::active', self.on_minimap_switch_activated) hbox.pack_end(minimap_switch, False, False, 0) box.add(hbox) ## Farming box.add(Gtk.Label('<b>Farming</b>', xalign=0, use_markup=True)) # Save dragodindes images save_dragodindes_images_check = Gtk.CheckButton('Save dragodindes images') save_dragodindes_images_check.set_margin_left(10) save_dragodindes_images_check.set_active(self.parent.settings['Farming']['SaveDragodindesImages']) save_dragodindes_images_check.connect('clicked', lambda check: settings.update_and_save(self.parent.settings, key='Farming', subkey='SaveDragodindesImages', value=check.get_active())) box.add(save_dragodindes_images_check) # Check resources color self.verify_resources_color_check = Gtk.CheckButton('Check resources color') self.verify_resources_color_check.set_margin_left(10) self.verify_resources_color_check.set_active(self.parent.settings['Farming']['CheckResourcesColor']) self.verify_resources_color_check.connect('clicked', lambda check: settings.update_and_save(self.parent.settings, key='Farming', subkey='CheckResourcesColor', value=check.get_active())) box.add(self.verify_resources_color_check) # Auto close popups self.auto_close_popups_check = Gtk.CheckButton('Auto close popups (level up, invitation, ...)') self.auto_close_popups_check.set_margin_left(10) self.auto_close_popups_check.set_active(self.parent.settings['Farming']['AutoClosePopups']) self.auto_close_popups_check.connect('clicked', lambda check: settings.update_and_save(self.parent.settings, key='Farming', subkey='AutoClosePopups', value=check.get_active())) box.add(self.auto_close_popups_check) # Collection time hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) hbox.set_margin_left(10) label = Gtk.Label('Collection time') label.set_tooltip_text('(in seconds)') hbox.add(label) self.collection_time_spin_button = SpinButton(min=1, max=60) self.collection_time_spin_button.set_value(self.parent.settings['Farming']['CollectionTime']) self.collection_time_spin_button.connect('value-changed', lambda button: settings.update_and_save(self.parent.settings, key='Farming', subkey='CollectionTime', value=button.get_value_as_int())) hbox.pack_end(self.collection_time_spin_button, False, False, 0) box.add(hbox) # First resource additional collection time hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) hbox.set_margin_left(10) label = Gtk.Label('1st resource collection time') label.set_tooltip_text('(additional time for 1st resource in seconds)') hbox.add(label) first_resource_collection_time_spin_button = SpinButton(min=1, max=60) first_resource_collection_time_spin_button.set_value(self.parent.settings['Farming']['FirstResourceAdditionalCollectionTime']) first_resource_collection_time_spin_button.connect('value-changed', lambda button: settings.update_and_save(self.parent.settings, key='Farming', subkey='FirstResourceAdditionalCollectionTime', value=button.get_value_as_int())) hbox.pack_end(first_resource_collection_time_spin_button, False, False, 0) box.add(hbox) ### Shortcuts box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) stack.add_titled(box, 'shortcuts', 'Shortcuts') ## Keyboard Shortcuts hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) hbox.add(Gtk.Label('<b>Keyboard Shortcuts</b>', xalign=0, use_markup=True)) box.add(hbox) # Switch shortcuts_switch = Gtk.Switch() shortcuts_switch.set_active(self.parent.settings['EnableShortcuts']) shortcuts_switch.connect('notify::active', self.on_shortcuts_switch_activated) hbox.pack_end(shortcuts_switch, False, False, 0) # TreeView model = Gtk.ListStore(str, str) text_renderer = Gtk.CellRendererText() columns = [ Gtk.TreeViewColumn('Action', text_renderer, text=0), Gtk.TreeViewColumn('Shortcut', text_renderer, text=1) ] self.shortcuts_tree_view = CustomTreeView(model, columns) self.shortcuts_tree_view.vbox.set_sensitive(self.parent.settings['EnableShortcuts']) self.shortcuts_tree_view.connect('button-press-event', self.on_shortcuts_tree_view_double_clicked) self.shortcuts_tree_view.connect('selection-changed', self.on_shortcuts_tree_view_selection_changed) # fill treeview for action in sorted(self.parent.settings['Shortcuts']): shortcut = self.parent.settings['Shortcuts'][action] self.shortcuts_tree_view.append_row([action, shortcut], select=False, scroll_to=False) box.pack_start(self.shortcuts_tree_view, True, True, 0) # ActionBar actionbar = Gtk.ActionBar() self.shortcuts_edit_button = Gtk.Button() self.shortcuts_edit_button.set_tooltip_text('Edit') self.shortcuts_edit_button.set_image(Gtk.Image(icon_name='document-edit-symbolic')) self.shortcuts_edit_button.set_sensitive(False) self.shortcuts_edit_button.connect('clicked', self.on_shortcuts_edit_button_clicked) actionbar.add(self.shortcuts_edit_button) self.shortcuts_tree_view.vbox.pack_end(actionbar, False, False, 0) self.show_all() def on_game_version_combo_changed(self, combo): value = combo.get_active_value() settings.update_and_save(self.parent.settings, key='Game', subkey='Version', value=value) if value == shared.GameVersion.Retro: self.window_decoration_height_spin_button.set_value(10) self.verify_resources_color_check.set_active(False) self.collection_time_spin_button.set_value(11) else: self.window_decoration_height_spin_button.set_value(36) self.verify_resources_color_check.set_active(True) self.collection_time_spin_button.set_value(4) def on_shortcuts_switch_activated(self, switch, pspec): value = switch.get_active() self.shortcuts_tree_view.vbox.set_sensitive(value) settings.update_and_save(self.parent.settings, 'EnableShortcuts', value) def show_shortcuts_dialog(self): selected_row = self.shortcuts_tree_view.get_selected_row() if selected_row: dialog = ShortcutsDialog(self, selected_row[0]) dialog.run() def on_shortcuts_edit_button_clicked(self, button): self.show_shortcuts_dialog() def on_shortcuts_tree_view_double_clicked(self, widget, event): if event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS: self.show_shortcuts_dialog() def on_shortcuts_tree_view_selection_changed(self, selection): if not self.shortcuts_edit_button.get_sensitive(): self.shortcuts_edit_button.set_sensitive(True) def on_podbar_switch_activated(self, switch, pspec): value = switch.get_active() if value: self.parent.podbar_box.show() else: self.parent.podbar_box.hide() settings.update_and_save(self.parent.settings, key='State', subkey='EnablePodBar', value=value) def on_minimap_switch_activated(self, switch, pspec): value = switch.get_active() if value: self.parent.minimap_box.show() else: self.parent.minimap_box.hide() settings.update_and_save(self.parent.settings, key='State', subkey='EnableMiniMap', value=value) def on_debug_switch_activated(self, switch, pspec): value = switch.get_active() self.debug_box.set_sensitive(value) if value: self.parent.debug_page.show() else: self.parent.debug_page.hide() settings.update_and_save(self.parent.settings, key='Debug', subkey='Enabled', value=value) class ShortcutsDialog(CustomDialog): def __init__(self, transient_for, action_name): title = '%s Shortcut' % action_name CustomDialog.__init__(self, transient_for=transient_for, title=title, destroy_on_response=False) self.parent = transient_for self.action_name = action_name self.connect('key-press-event', self.on_key_press) self.connect('response', self.on_response) # Action vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) content_area = self.get_content_area() content_area.add(vbox) action_label = Gtk.Label(xalign=0) action_label.set_line_wrap(True) action_label.set_max_width_chars(40) action_label.set_markup('Press keys on keyboard that you want to use to trigger « <b>%s</b> » action' % action_name) vbox.add(action_label) # Shortcut hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) vbox.add(hbox) hbox.add(Gtk.Label('Shortcut:', xalign=0)) self.shortcut_label = Gtk.Label() shortcut = self.parent.parent.settings['Shortcuts'][action_name] if shortcut is not None: self.shortcut_label.set_markup('<b>%s</b>' % shortcut) hbox.add(self.shortcut_label) # Error box self.error_box = MessageBox(color='red') vbox.add(self.error_box) # Clear self.action_area.set_margin_top(10) clear_button = Gtk.Button('Clear') clear_button.set_image(Gtk.Image(icon_name='edit-clear')) self.add_action_widget(clear_button, Gtk.ResponseType.CANCEL) # Save save_button = Gtk.Button('Save') save_button.set_image(Gtk.Image(icon_name='document-save')) self.add_action_widget(save_button, Gtk.ResponseType.OK) self.show_all() self.error_box.hide() def on_key_press(self, widget, event): # get keyname keyname = Gdk.keyval_name(event.keyval) # check combinations if event.state & Gdk.ModifierType.CONTROL_MASK: shortcut = 'Ctrl+%s' % keyname elif event.state & Gdk.ModifierType.MOD1_MASK: shortcut = 'Alt+%s' % keyname elif event.state & Gdk.ModifierType.SHIFT_MASK: shortcut = 'Shift+%s' % keyname else: shortcut = keyname # show shortcut self.shortcut_label.set_markup('<b>%s</b>' % shortcut) # hide error box self.error_box.hide() # stop event propagation return True def on_response(self, dialog, response): # Save if response == Gtk.ResponseType.OK: # get shortcut shortcut = self.shortcut_label.get_text() if shortcut: # check if duplicated for action in self.parent.parent.settings['Shortcuts']: value = self.parent.parent.settings['Shortcuts'][action] if shortcut == value and action != self.action_name: self.error_box.print_message('Duplicate shortcut, please choose another one') return else: shortcut = None # save shortcut settings.update_and_save(self.parent.parent.settings, key='Shortcuts', subkey=self.action_name, value=shortcut) # apply to treeview for row in self.parent.shortcuts_tree_view.model: if row[0] == self.action_name: row[1] = shortcut break # Clear elif response == Gtk.ResponseType.CANCEL: # clear shortcut label self.shortcut_label.set_text('') # hide error box self.error_box.hide() return # Close self.destroy() class AccountsDialog(CustomDialog): def __init__(self, transient_for, title='Accounts'): CustomDialog.__init__(self, transient_for=transient_for, title=title) self.parent = transient_for ### New vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) content_area = self.get_content_area() content_area.add(vbox) vbox.add(Gtk.Label('<b>New</b>', xalign=0, use_markup=True)) new_hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) vbox.add(new_hbox) new_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) new_hbox.pack_start(new_vbox, True, False, 0) ## Login hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) new_vbox.add(hbox) hbox.add(Gtk.Label('Login')) # Entry self.login_entry = Gtk.Entry() self.login_entry.connect('focus-in-event', lambda entry, event: self.error_box.hide()) hbox.pack_end(self.login_entry, False, False, 0) ## Password hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) new_vbox.add(hbox) hbox.add(Gtk.Label('Password')) # Show password show_password_button = Gtk.Button() show_password_button.set_tooltip_text('Show password') show_password_button.set_image(Gtk.Image(icon_name='channel-insecure-symbolic')) show_password_button.set_relief(Gtk.ReliefStyle.NONE) show_password_button.connect('clicked', self.on_show_password_button_clicked) hbox.add(show_password_button) # Entry self.password_entry = Gtk.Entry(visibility=False) self.password_entry.connect('focus-in-event', lambda entry, event: self.error_box.hide()) hbox.pack_end(self.password_entry, False, False, 0) ## Error box self.error_box = MessageBox(color='red') new_vbox.add(self.error_box) ## Add add_button = Gtk.Button('Add') add_button.set_image(Gtk.Image(icon_name='list-add-symbolic')) add_button.connect('clicked', self.on_add_button_clicked) button_box = Gtk.ButtonBox(orientation=Gtk.Orientation.HORIZONTAL, layout_style=Gtk.ButtonBoxStyle.CENTER) button_box.add(add_button) new_vbox.add(button_box) ### Accounts vbox.add(Gtk.Label('<b>Accounts</b>', xalign=0, use_markup=True)) ## TreeView model = Gtk.ListStore(int, str, str) text_renderer = Gtk.CellRendererText() columns = [ Gtk.TreeViewColumn('ID', text_renderer, text=0), Gtk.TreeViewColumn('Login', text_renderer, text=1), Gtk.TreeViewColumn('Password', text_renderer, text=2) ] self.tree_view = CustomTreeView(model, columns) self.tree_view.set_size_request(400, 160) self.tree_view.connect('selection-changed', self.on_tree_view_selection_changed) vbox.pack_start(self.tree_view, True, True, 0) # fill treeview for account in sorted(accounts.load(), key=lambda item: item['position']): pwd = '*' * len(account['pwd']) self.tree_view.append_row([account['id'], account['login'], pwd], select=False) ## ActionBar actionbar = Gtk.ActionBar() self.tree_view.vbox.pack_end(actionbar, False, False, 0) buttons_box = ButtonBox(linked=True) actionbar.add(buttons_box) # Move up self.move_up_button = Gtk.Button() self.move_up_button.set_tooltip_text('Move up') self.move_up_button.set_image(Gtk.Image(icon_name='go-up-symbolic')) self.move_up_button.set_sensitive(False) self.move_up_button.connect('clicked', self.on_move_up_button_clicked) buttons_box.add(self.move_up_button) # Move down self.move_down_button = Gtk.Button() self.move_down_button.set_tooltip_text('Move down') self.move_down_button.set_image(Gtk.Image(icon_name='go-down-symbolic')) self.move_down_button.set_sensitive(False) self.move_down_button.connect('clicked', self.on_move_down_button_clicked) buttons_box.add(self.move_down_button) # Delete self.delete_button = Gtk.Button() self.delete_button.set_tooltip_text('Delete') self.delete_button.set_image(Gtk.Image(icon_name='edit-delete-symbolic')) self.delete_button.set_sensitive(False) self.delete_button.connect('clicked', self.on_delete_button_clicked) buttons_box.add(self.delete_button) self.show_all() self.error_box.hide() def update_parent_window(self, accounts_list): self.parent.accounts_combo.append_list(accounts_list, text_key='login', value_key='id', sort_key='position', clear=True) self.parent.connect_accounts_combo.append_list(accounts_list, text_key='login', value_key='id', sort_key='position', clear=True) def on_add_button_clicked(self, button): login = self.login_entry.get_text() password = self.password_entry.get_text() if not login: self.error_box.print_message('Please type a login') elif not password: self.error_box.print_message('Please type a password') elif accounts.is_duplicate(login): self.error_box.print_message('Duplicate login, please choose another one') else: # hide error box self.error_box.hide() # add account id, accounts_list = accounts.add(login, password) # append to treeview pwd = '*' * len(password) self.tree_view.append_row([id, login, pwd]) # update parent window self.update_parent_window(accounts_list) def swap_accounts(self, old_index, new_index): # get accounts ids account1_id = self.tree_view.model[old_index][0] account2_id = self.tree_view.model[new_index][0] # swap accounts positions accounts_list = accounts.swap(account1_id, account2_id) # update parent window self.update_parent_window(accounts_list) # set move buttons sensitivity self.set_move_buttons_sensitivity(new_index) def on_move_up_button_clicked(self, button): model, tree_iter = self.tree_view.selection.get_selected() if tree_iter: previous_iter = model.iter_previous(tree_iter) if previous_iter is not None: # get row index index = self.tree_view.get_row_index(tree_iter) # move up model.move_before(tree_iter, previous_iter) # swap accounts self.swap_accounts(index, index - 1) def on_move_down_button_clicked(self, button): model, tree_iter = self.tree_view.selection.get_selected() if tree_iter: next_iter = model.iter_next(tree_iter) if next_iter is not None: # get row index index = self.tree_view.get_row_index(tree_iter) # move down model.move_after(tree_iter, next_iter) # swap accounts self.swap_accounts(index, index + 1) def on_delete_button_clicked(self, button): # remove selected account id = self.tree_view.get_selected_row()[0] accounts_list = accounts.remove(id) # remove from treeview also self.tree_view.remove_selected_row() # update parent window self.update_parent_window(accounts_list) def on_show_password_button_clicked(self, button): if self.password_entry.get_visibility(): self.password_entry.set_visibility(False) button.set_image(Gtk.Image(icon_name='channel-insecure-symbolic')) button.set_tooltip_text('Show password') else: self.password_entry.set_visibility(True) button.set_image(Gtk.Image(icon_name='channel-secure-symbolic')) button.set_tooltip_text('Hide password') def set_move_buttons_sensitivity(self, index): # Move up if index <= 0: self.move_up_button.set_sensitive(False) elif not self.move_up_button.get_sensitive(): self.move_up_button.set_sensitive(True) # Move down rows_count = self.tree_view.get_rows_count() if index >= rows_count - 1: self.move_down_button.set_sensitive(False) elif not self.move_down_button.get_sensitive(): self.move_down_button.set_sensitive(True) def on_tree_view_selection_changed(self, selection): model, tree_iter = selection.get_selected() if tree_iter is None: self.move_up_button.set_sensitive(False) self.move_down_button.set_sensitive(False) self.delete_button.set_sensitive(False) else: # Move buttons index = self.tree_view.get_row_index(tree_iter) self.set_move_buttons_sensitivity(index) # Delete button if not self.delete_button.get_sensitive(): self.delete_button.set_sensitive(True)