diff --git a/src/diffuse/main.py b/src/diffuse/main.py index 24ef668..9a10a91 100644 --- a/src/diffuse/main.py +++ b/src/diffuse/main.py @@ -47,8 +47,8 @@ gi.require_version('GdkPixbuf', '2.0') gi.require_version('Pango', '1.0') gi.require_version('PangoCairo', '1.0') -from gi.repository import GObject, Gtk, Gdk, GdkPixbuf, Pango # type: ignore # noqa: E402 - +from gi.repository import (Gdk, GdkPixbuf, Gio, # type: ignore # noqa: E402 + GLib, GObject, Gtk, Pango) theVCSs = VcsRegistry() @@ -121,7 +121,7 @@ def __init__(self, name=None, encoding=None, vcs=None, revision=None, label=None # the main application class containing a set of file viewers # this class displays tab for switching between viewers and dispatches menu # commands to the current viewer -class Diffuse(Gtk.Window): +class Diffuse(Gtk.ApplicationWindow): # specialization of FileDiffViewerBase for Diffuse class FileDiffViewer(FileDiffViewerBase): # pane header @@ -665,8 +665,8 @@ def cursor_changed_cb(self, widget): def format_changed_cb(self, widget, f, fmt): self.footers[f].setFormat(fmt) - def __init__(self, rc_dir): - super().__init__(type=Gtk.WindowType.TOPLEVEL) + def __init__(self, rc_dir, *args, **kwargs): + super().__init__(type=Gtk.WindowType.TOPLEVEL, *args, **kwargs) self.prefs = Preferences(os.path.join(rc_dir, 'prefs')) # number of created viewers (used to label some tabs) @@ -698,7 +698,6 @@ def __init__(self, rc_dir): self.search_history: List[str] = [] self.connect('delete-event', self.delete_cb) - accel_group = Gtk.AccelGroup() # create a Box for our contents vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) @@ -768,127 +767,146 @@ def __init__(self, rc_dir): menuspecs = [] menuspecs.append([_('_File'), [ + [ [_('_Open File...'), self.open_file_cb, None, Gtk.STOCK_OPEN, 'open_file'], [_('Open File In New _Tab...'), self.open_file_in_new_tab_cb, None, None, 'open_file_in_new_tab'], # noqa: E501 [_('Open _Modified Files...'), self.open_modified_files_cb, None, None, 'open_modified_files'], # noqa: E501 [_('Open Commi_t...'), self.open_commit_cb, None, None, 'open_commit'], [_('_Reload File'), self.reload_file_cb, None, Gtk.STOCK_REFRESH, 'reload_file'], - [], + ], [ [_('_Save File'), self.save_file_cb, None, Gtk.STOCK_SAVE, 'save_file'], [_('Save File _As...'), self.save_file_as_cb, None, Gtk.STOCK_SAVE_AS, 'save_file_as'], [_('Save A_ll'), self.save_all_cb, None, None, 'save_all'], - [], + ], [ [_('New _2-Way File Merge'), self.new_2_way_file_merge_cb, None, DIFFUSE_STOCK_NEW_2WAY_MERGE, 'new_2_way_file_merge'], # noqa: E501 [_('New _3-Way File Merge'), self.new_3_way_file_merge_cb, None, DIFFUSE_STOCK_NEW_3WAY_MERGE, 'new_3_way_file_merge'], # noqa: E501 [_('New _N-Way File Merge...'), self.new_n_way_file_merge_cb, None, None, 'new_n_way_file_merge'], # noqa: E501 - [], + ], [ [_('_Close Tab'), self.close_tab_cb, None, Gtk.STOCK_CLOSE, 'close_tab'], [_('_Undo Close Tab'), self.undo_close_tab_cb, None, None, 'undo_close_tab'], [_('_Quit'), self.quit_cb, None, Gtk.STOCK_QUIT, 'quit'] + ] ]]) menuspecs.append([_('_Edit'), [ + [ [_('_Undo'), self.button_cb, 'undo', Gtk.STOCK_UNDO, 'undo'], [_('_Redo'), self.button_cb, 'redo', Gtk.STOCK_REDO, 'redo'], - [], + ], [ [_('Cu_t'), self.button_cb, 'cut', Gtk.STOCK_CUT, 'cut'], [_('_Copy'), self.button_cb, 'copy', Gtk.STOCK_COPY, 'copy'], [_('_Paste'), self.button_cb, 'paste', Gtk.STOCK_PASTE, 'paste'], - [], + ], [ [_('Select _All'), self.button_cb, 'select_all', None, 'select_all'], [_('C_lear Edits'), self.button_cb, 'clear_edits', Gtk.STOCK_CLEAR, 'clear_edits'], [_('_Dismiss All Edits'), self.button_cb, 'dismiss_all_edits', None, 'dismiss_all_edits'], # noqa: E501 - [], + ], [ [_('_Find...'), self.find_cb, None, Gtk.STOCK_FIND, 'find'], [_('Find _Next'), self.find_next_cb, None, None, 'find_next'], [_('Find Pre_vious'), self.find_previous_cb, None, None, 'find_previous'], [_('_Go To Line...'), self.go_to_line_cb, None, Gtk.STOCK_JUMP_TO, 'go_to_line'], - [], + ], [ [_('Pr_eferences...'), self.preferences_cb, None, Gtk.STOCK_PREFERENCES, 'preferences'] + ] ]]) - submenudef = [ - [_('None'), self.syntax_cb, None, None, 'no_syntax_highlighting', True, None, ('syntax', None)] # noqa: E501 - ] + submenudef = [] names = theResources.getSyntaxNames() + variant = GLib.Variant.new_string("") + self.syntax_action = Gio.SimpleAction.new_stateful( + "syntax_highlighting", variant.get_type(), variant + ) + self.syntax_action.connect("change-state", self.syntax_cb) + self.add_action(self.syntax_action) if len(names) > 0: - submenudef.append([]) names.sort(key=str.lower) for name in names: - submenudef.append([ - name, - self.syntax_cb, - name, - None, - 'syntax_highlighting_' + name, - True, - None, - ('syntax', name) - ]) + submenudef.append( + [ + name, + None, + name, + None, + "syntax_highlighting", + ] + ) menuspecs.append([_('_View'), [ - [_('_Syntax Highlighting'), None, None, None, None, True, submenudef], - [], + [ + [_('_Syntax Highlighting'), None, None, None, None, [ + [ + [_('None'), None, '', None, 'syntax_highlighting'] # noqa: E501 + ], + submenudef + ] + ] + ], [ [_('Re_align All'), self.button_cb, 'realign_all', Gtk.STOCK_EXECUTE, 'realign_all'], [_('_Isolate'), self.button_cb, 'isolate', None, 'isolate'], - [], + ], [ [_('_First Difference'), self.button_cb, 'first_difference', Gtk.STOCK_GOTO_TOP, 'first_difference'], # noqa: E501 [_('_Previous Difference'), self.button_cb, 'previous_difference', Gtk.STOCK_GO_UP, 'previous_difference'], # noqa: E501 [_('_Next Difference'), self.button_cb, 'next_difference', Gtk.STOCK_GO_DOWN, 'next_difference'], # noqa: E501 [_('_Last Difference'), self.button_cb, 'last_difference', Gtk.STOCK_GOTO_BOTTOM, 'last_difference'], # noqa: E501 - [], + ], [ [_('Fir_st Tab'), self.first_tab_cb, None, None, 'first_tab'], [_('Pre_vious Tab'), self.previous_tab_cb, None, None, 'previous_tab'], [_('Next _Tab'), self.next_tab_cb, None, None, 'next_tab'], [_('Las_t Tab'), self.last_tab_cb, None, None, 'last_tab'], - [], + ], [ [_('Shift Pane _Right'), self.button_cb, 'shift_pane_right', None, 'shift_pane_right'], [_('Shift Pane _Left'), self.button_cb, 'shift_pane_left', None, 'shift_pane_left'] + ] ]]) menuspecs.append([_('F_ormat'), [ + [ [_('Convert To _Upper Case'), self.button_cb, 'convert_to_upper_case', None, 'convert_to_upper_case'], # noqa: E501 [_('Convert To _Lower Case'), self.button_cb, 'convert_to_lower_case', None, 'convert_to_lower_case'], # noqa: E501 - [], + ], [ [_('Sort Lines In _Ascending Order'), self.button_cb, 'sort_lines_in_ascending_order', Gtk.STOCK_SORT_ASCENDING, 'sort_lines_in_ascending_order'], # noqa: E501 [_('Sort Lines In D_escending Order'), self.button_cb, 'sort_lines_in_descending_order', Gtk.STOCK_SORT_DESCENDING, 'sort_lines_in_descending_order'], # noqa: E501 - [], + ], [ [_('Remove Trailing _White Space'), self.button_cb, 'remove_trailing_white_space', None, 'remove_trailing_white_space'], # noqa: E501 [_('Convert Tabs To _Spaces'), self.button_cb, 'convert_tabs_to_spaces', None, 'convert_tabs_to_spaces'], # noqa: E501 [_('Convert Leading Spaces To _Tabs'), self.button_cb, 'convert_leading_spaces_to_tabs', None, 'convert_leading_spaces_to_tabs'], # noqa: E501 - [], + ], [ [_('_Increase Indenting'), self.button_cb, 'increase_indenting', Gtk.STOCK_INDENT, 'increase_indenting'], # noqa: E501 [_('De_crease Indenting'), self.button_cb, 'decrease_indenting', Gtk.STOCK_UNINDENT, 'decrease_indenting'], # noqa: E501 - [], + ], [ [_('Convert To _DOS Format'), self.button_cb, 'convert_to_dos', None, 'convert_to_dos'], # noqa: E501 [_('Convert To _Mac Format'), self.button_cb, 'convert_to_mac', None, 'convert_to_mac'], # noqa: E501 [_('Convert To Uni_x Format'), self.button_cb, 'convert_to_unix', None, 'convert_to_unix'] # noqa: E501 + ] ]]) menuspecs.append([_('_Merge'), [ + [ [_('Copy Selection _Right'), self.button_cb, 'copy_selection_right', Gtk.STOCK_GOTO_LAST, 'copy_selection_right'], # noqa: E501 [_('Copy Selection _Left'), self.button_cb, 'copy_selection_left', Gtk.STOCK_GOTO_FIRST, 'copy_selection_left'], # noqa: E501 - [], + ], [ [_('Copy Left _Into Selection'), self.button_cb, 'copy_left_into_selection', Gtk.STOCK_GO_FORWARD, 'copy_left_into_selection'], # noqa: E501 [_('Copy Right I_nto Selection'), self.button_cb, 'copy_right_into_selection', Gtk.STOCK_GO_BACK, 'copy_right_into_selection'], # noqa: E501 [_('_Merge From Left Then Right'), self.button_cb, 'merge_from_left_then_right', DIFFUSE_STOCK_LEFT_RIGHT, 'merge_from_left_then_right'], # noqa: E501 [_('M_erge From Right Then Left'), self.button_cb, 'merge_from_right_then_left', DIFFUSE_STOCK_RIGHT_LEFT, 'merge_from_right_then_left'] # noqa: E501 + ] ]]) menuspecs.append([_('_Help'), [ + [ [_('_Help Contents...'), self.help_contents_cb, None, Gtk.STOCK_HELP, 'help_contents'], - [], + ], [ [_('_About %s...') % (constants.APP_NAME, ), self.about_cb, None, Gtk.STOCK_ABOUT, 'about'] # noqa: E501 + ] ]]) # used to disable menu events when switching tabs self.menu_update_depth = 0 - # build list of radio menu items so we can update them to match the - # currently viewed pane - self.radio_menus = radio_menus = {} - menu_bar = _create_menu_bar(menuspecs, radio_menus, accel_group) - vbox.pack_start(menu_bar, False, False, 0) - menu_bar.show() + + menubar = Gio.Menu() + for label, sections in menuspecs: + menubar.append_submenu(label, self.create_submenu(sections)) + self.get_application().set_menubar(menubar) # create button bar hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) @@ -933,10 +951,38 @@ def __init__(self, rc_dir): vbox.pack_start(statusbar, False, False, 0) statusbar.show() - self.add_accel_group(accel_group) self.add(vbox) vbox.show() - self.connect('focus-in-event', self.focus_in_cb) + self.connect("focus-in-event", self.focus_in_cb) + + def create_submenu(self, sections): + sub = Gio.Menu.new() + for section in sections: + s = Gio.Menu.new() + for label, cb, data, icon, accel, *submenu in section: + if submenu: + (submenu,) = submenu + s.append_submenu(label, self.create_submenu(submenu)) + else: + if data is not None: + data = GLib.Variant.new_string(data) + if cb is not None: + action = Gio.SimpleAction.new(accel, data and data.get_type()) + action.connect("activate", cb) + self.add_action(action) + item = Gio.MenuItem.new(label) + item.set_action_and_target_value("win." + accel, data) + if icon: + item.set_icon(Gio.Icon.new_for_string(icon)) + a = theResources.getKeyBindings("menu", accel) + if len(a) > 0: + key, modifier = a[0] + self.get_application().set_accels_for_action( + "win." + accel, [Gtk.accelerator_name(key, modifier)] + ) + s.append_item(item) + sub.append_section(None, s) + return sub # notifies all viewers on focus changes so they may check for external # changes to files @@ -1194,13 +1240,7 @@ def setStatus(self, s: Optional[str]) -> None: # update the label in the status bar def setSyntax(self, s): # update menu - t = self.radio_menus.get('syntax', None) - if t is not None: - t = t[1] - if s in t: - self.menu_update_depth += 1 - t[s].set_active(True) - self.menu_update_depth -= 1 + self.syntax_action.set_state(GLib.Variant.new_string(s or "")) # callback used when switching notebook pages def switch_page_cb(self, widget, ptr, page_num): @@ -1387,7 +1427,7 @@ def confirmQuit(self) -> bool: # respond to close window request from the window manager def delete_cb(self, widget, event): if self.confirmQuit(): - Gtk.main_quit() + self.get_application().quit() return False return True @@ -1534,7 +1574,7 @@ def undo_close_tab_cb(self, widget, data): # callback for the quit menu item def quit_cb(self, widget, data): if self.confirmQuit(): - Gtk.main_quit() + self.get_application().quit() # request search parameters if force=True and then perform a search in the # current viewer pane @@ -1711,18 +1751,6 @@ def about_cb(self, widget, data): dialog.destroy() -# convenience method for creating a menu bar according to a template -def _create_menu_bar(specs, radio, accel_group): - menu_bar = Gtk.MenuBar() - for label, spec in specs: - menu = Gtk.MenuItem.new_with_mnemonic(label) - menu.set_submenu(createMenu(spec, radio, accel_group)) - menu.set_use_underline(True) - menu.show() - menu_bar.append(menu) - return menu_bar - - # convenience method for packing buttons into a container according to a # template def _append_buttons(box, size, specs): @@ -1789,206 +1817,286 @@ def _assign_file_labels(items, labels): GObject.signal_new('save-as', Diffuse.FileDiffViewer.PaneHeader, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ()) # noqa: E501 -def main(version, sysconfigdir): - # app = Application() - # return app.run(sys.argv) - - args = sys.argv - argc = len(args) - - constants.VERSION = version - - if argc == 2 and args[1] in ['-v', '--version']: - print('%s %s\n%s' % (constants.APP_NAME, constants.VERSION, constants.COPYRIGHT)) - return 0 - - if argc == 2 and args[1] in ['-h', '-?', '--help']: - print(_('''Usage: - diffuse [OPTION...] [FILE...] - diffuse ( -h | -? | --help | -v | --version ) - -Diffuse is a graphical tool for merging and comparing text files. Diffuse is -able to compare an arbitrary number of files side-by-side and gives users the -ability to manually adjust line matching and directly edit files. Diffuse can -also retrieve revisions of files from several VCSs for comparison and merging. - -Help Options: - ( -h | -? | --help ) Display this usage information - ( -v | --version ) Display version and copyright information - -Configuration Options: - --no-rcfile Do not read any resource files - --rcfile Specify explicit resource file - -General Options: - ( -c | --commit ) File revisions and - ( -D | --close-if-same ) Close all tabs with no differences - ( -e | --encoding ) Use to read and write files - ( -L | --label )