From 9763675c0e60167babe229cd4378a1fff80f5471 Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Mon, 3 Apr 2023 09:42:30 +1200 Subject: [PATCH 01/31] Add image comparisons (WIP). --- meld/imagediff.py | 285 +++++++++++++++++++++++++ meld/meldapp.py | 1 + meld/meldwindow.py | 16 +- meld/resources/ui/imagediff-actions.ui | 61 ++++++ meld/resources/ui/imagediff-menus.ui | 16 ++ meld/resources/ui/imagediff.ui | 27 +++ 6 files changed, 405 insertions(+), 1 deletion(-) create mode 100644 meld/imagediff.py create mode 100644 meld/resources/ui/imagediff-actions.ui create mode 100644 meld/resources/ui/imagediff-menus.ui create mode 100644 meld/resources/ui/imagediff.ui diff --git a/meld/imagediff.py b/meld/imagediff.py new file mode 100644 index 00000000..51e11c10 --- /dev/null +++ b/meld/imagediff.py @@ -0,0 +1,285 @@ +# Copyright (C) 2002-2006 Stephen Kennedy +# Copyright (C) 2009-2019 Kai Willadsen +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or (at +# your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import copy +import functools +import logging +import math +from enum import Enum +from typing import Optional, Tuple, Type + +from gi.repository import Gdk, Gio, GLib, GObject, Gtk, GtkSource + +# TODO: Don't from-import whole modules +from meld import misc +from meld.conf import _ +from meld.const import ( + NEWLINES, + TEXT_FILTER_ACTION_FORMAT, + ActionMode, + ChunkAction, + FileComparisonMode, +) +from meld.iohelpers import find_shared_parent_path, prompt_save_filename +from meld.melddoc import ComparisonState, MeldDoc, open_files_external +from meld.menuhelpers import replace_menu_section +from meld.misc import user_critical, with_focused_pane +from meld.recent import RecentType +from meld.settings import bind_settings, get_meld_settings +from meld.ui.util import ( + make_multiobject_property_action, + map_widgets_into_lists, +) +from meld.undo import UndoSequence + +log = logging.getLogger(__name__) + + +def with_scroll_lock(lock_attr): + """Decorator for locking a callback based on an instance attribute + + This is used when scrolling panes. Since a scroll event in one pane + causes us to set the scroll position in other panes, we need to + stop these other panes re-scrolling the initial one. + + Unlike a threading-style lock, this decorator discards any calls + that occur while the lock is held, rather than queuing them. + + :param lock_attr: The instance attribute used to lock access + """ + def wrap(function): + @functools.wraps(function) + def wrap_function(locked, *args, **kwargs): + force_locked = locked.props.lock_scrolling + if getattr(locked, lock_attr, False) or force_locked: + return + + try: + setattr(locked, lock_attr, True) + return function(locked, *args, **kwargs) + finally: + setattr(locked, lock_attr, False) + return wrap_function + return wrap + + +MASK_SHIFT, MASK_CTRL = 1, 2 +PANE_LEFT, PANE_RIGHT = -1, +1 + + +class CursorDetails: + __slots__ = ( + "pane", "pos", "line", "chunk", "prev", "next", + "prev_conflict", "next_conflict", + ) + + def __init__(self): + for var in self.__slots__: + setattr(self, var, None) + + +@Gtk.Template(resource_path='/org/gnome/meld/ui/imagediff.ui') +class ImageDiff(Gtk.VBox, MeldDoc): + """Two or three way comparison of image files""" + + __gtype_name__ = "ImageDiff" + + close_signal = MeldDoc.close_signal + create_diff_signal = MeldDoc.create_diff_signal + file_changed_signal = MeldDoc.file_changed_signal + label_changed = MeldDoc.label_changed + move_diff = MeldDoc.move_diff + tab_state_changed = MeldDoc.tab_state_changed + + # ~ __gsettings_bindings_view__ = ( + # ~ ('ignore-blank-lines', 'ignore-blank-lines'), + # ~ ('show-overview-map', 'show-overview-map'), + # ~ ('overview-map-style', 'overview-map-style'), + # ~ ) + + show_overview_map = GObject.Property(type=bool, default=True) + overview_map_style = GObject.Property(type=str, default='chunkmap') + + keylookup = { + Gdk.KEY_Shift_L: MASK_SHIFT, + Gdk.KEY_Shift_R: MASK_SHIFT, + Gdk.KEY_Control_L: MASK_CTRL, + Gdk.KEY_Control_R: MASK_CTRL, + } + + # Identifiers for MsgArea messages + (MSG_SAME, MSG_SLOW_HIGHLIGHT, MSG_SYNCPOINTS) = list(range(3)) + # Transient messages that should be removed if any file in the + # comparison gets reloaded. + TRANSIENT_MESSAGES = {MSG_SAME, MSG_SLOW_HIGHLIGHT} + + action_mode = GObject.Property( + type=int, + nick='Action mode for chunk change actions', + default=ActionMode.Replace, + ) + + lock_scrolling = GObject.Property( + type=bool, + nick='Lock scrolling of all panes', + default=False, + ) + + def __init__( + self, + num_panes, + *, + comparison_mode: FileComparisonMode = FileComparisonMode.Compare, + ): + super().__init__() + + # FIXME: + # This unimaginable hack exists because GObject (or GTK+?) + # doesn't actually correctly chain init calls, even if they're + # not to GObjects. As a workaround, we *should* just be able to + # put our class first, but because of Gtk.Template we can't do + # that if it's a GObject, because GObject doesn't support + # multiple inheritance and we need to inherit from our Widget + # parent to make Template work. + MeldDoc.__init__(self) + bind_settings(self) + + # ~ widget_lists = [ + # ~ "sourcemap", "file_save_button", "file_toolbar", + # ~ "linkmap", "msgarea_mgr", "readonlytoggle", + # ~ "scrolledwindow", "textview", "vbox", + # ~ "dummy_toolbar_linkmap", "filelabel", + # ~ "file_open_button", "statusbar", + # ~ "actiongutter", "dummy_toolbar_actiongutter", + # ~ "chunkmap", + # ~ ] + # ~ map_widgets_into_lists(self, widget_lists) + + self.warned_bad_comparison = False + self._keymask = 0 + self.meta = {} + self.lines_removed = 0 + self.focus_pane = None + meld_settings = get_meld_settings() + + # ~ for (i, w) in enumerate(self.scrolledwindow): + # ~ w.get_vadjustment().connect("value-changed", self._sync_vscroll, i) + # ~ w.get_hadjustment().connect("value-changed", self._sync_hscroll) + self._sync_vscroll_lock = False + self._sync_hscroll_lock = False + + # ~ prop_action_group = Gio.SimpleActionGroup() + # ~ for prop in sourceview_prop_actions: + # ~ action = make_multiobject_property_action(self.textview, prop) + # ~ prop_action_group.add_action(action) + # ~ self.insert_action_group('view-local', prop_action_group) + + # Set up per-view action group for top-level menu insertion + self.view_action_group = Gio.SimpleActionGroup() + + property_actions = ( + ('show-overview-map', self, 'show-overview-map'), + ('lock-scrolling', self, 'lock_scrolling'), + ) + for action_name, obj, prop_name in property_actions: + action = Gio.PropertyAction.new(action_name, obj, prop_name) + self.view_action_group.add_action(action) + + # Manually handle GAction additions + # ~ actions = ( + # ~ ('copy', self.action_copy), + # ~ ('copy-full-path', self.action_copy_full_path), + # ~ ('next-pane', self.action_next_pane), + # ~ ('open-external', self.action_open_external), + # ~ ('open-folder', self.action_open_folder), + # ~ ('previous-pane', self.action_prev_pane), + # ~ ('refresh', self.action_refresh), + # ~ ('swap-2-panes', self.action_swap), + # ~ ) + # ~ for name, callback in actions: + # ~ action = Gio.SimpleAction.new(name, None) + # ~ action.connect('activate', callback) + # ~ self.view_action_group.add_action(action) + + # ~ builder = Gtk.Builder.new_from_resource( + # ~ '/org/gnome/meld/ui/imagediff-menus.ui') + # ~ self.popup_menu_model = builder.get_object('imagediff-context-menu') + # ~ self.popup_menu = Gtk.Menu.new_from_model(self.popup_menu_model) + # ~ self.popup_menu.attach_to_widget(self) + + builder = Gtk.Builder.new_from_resource( + '/org/gnome/meld/ui/imagediff-actions.ui') + self.toolbar_actions = builder.get_object('view-toolbar') + self.copy_action_button = builder.get_object('copy_action_button') + + # Handle overview map visibility binding. Because of how we use + # grid packing, we need three revealers here instead of the + # more obvious one. + # ~ revealers = ( + # ~ self.toolbar_sourcemap_revealer, + # ~ self.sourcemap_revealer, + # ~ self.statusbar_sourcemap_revealer, + # ~ ) + # ~ for revealer in revealers: + # ~ self.bind_property( + # ~ 'show-overview-map', revealer, 'reveal-child', + # ~ ( + # ~ GObject.BindingFlags.DEFAULT | + # ~ GObject.BindingFlags.SYNC_CREATE + # ~ ), + # ~ ) + + # Handle overview map style mapping manually + # ~ self.connect( + # ~ 'notify::overview-map-style', self.on_overview_map_style_changed) + # ~ self.on_overview_map_style_changed() + + # ~ self.grid.attach(self.findbar, 0, 2, 10, 1) + + # ~ self.set_num_panes(num_panes) + + def do_realize(self): + Gtk.VBox().do_realize(self) + + builder = Gtk.Builder.new_from_resource( + '/org/gnome/meld/ui/imagediff-menus.ui') + # ~ filter_menu = builder.get_object('file-copy-actions-menu') + + # ~ self.copy_action_button.set_popover( + # ~ Gtk.Popover.new_from_model(self.copy_action_button, filter_menu)) + + def set_files(self, gfiles, encodings=None): + """Load the given files + + If an element is None, the text of a pane is left as is. + """ + if len(gfiles) != self.num_panes: + return + + encodings = encodings or ((None,) * len(gfiles)) + + files = [] + for pane, (gfile, encoding) in enumerate(zip(gfiles, encodings)): + if gfile: + files.append((pane, gfile, encoding)) + # ~ else: + # ~ self.textbuffer[pane].data.loaded = True + + # ~ if not files: + # ~ self.scheduler.add_task(self._compare_files_internal()) + + # ~ for pane, gfile, encoding in files: + # ~ self.load_file_in_pane(pane, gfile, encoding) diff --git a/meld/meldapp.py b/meld/meldapp.py index 396415f5..1561d4c9 100644 --- a/meld/meldapp.py +++ b/meld/meldapp.py @@ -25,6 +25,7 @@ import meld.accelerators import meld.conf from meld.conf import _ from meld.filediff import FileDiff +from meld.imagediff import ImageDiff from meld.meldwindow import MeldWindow from meld.preferences import PreferencesDialog diff --git a/meld/meldwindow.py b/meld/meldwindow.py index 49c04ee3..68ab0b78 100644 --- a/meld/meldwindow.py +++ b/meld/meldwindow.py @@ -31,6 +31,7 @@ from meld.const import ( ) from meld.dirdiff import DirDiff from meld.filediff import FileDiff +from meld.imagediff import ImageDiff from meld.melddoc import ComparisonState, MeldDoc from meld.menuhelpers import replace_menu_section from meld.newdifftab import NewDiffTab @@ -366,7 +367,20 @@ class MeldWindow(Gtk.ApplicationWindow): def append_filediff( self, gfiles, *, encodings=None, merge_output=None, meta=None): assert len(gfiles) in (1, 2, 3) - doc = FileDiff(len(gfiles)) + + # Check whether to show image window or not. + filesAreImages = False + for gfile in gfiles: + basename = gfile.get_basename() + if basename.endswith(".png"): + filesAreImages = True + print ("MVZ: Files are images. Showing the image window...") + break + + if filesAreImages: + doc = ImageDiff(len(gfiles)) + else: + doc = FileDiff(len(gfiles)) self._append_page(doc) doc.set_files(gfiles, encodings) if merge_output is not None: diff --git a/meld/resources/ui/imagediff-actions.ui b/meld/resources/ui/imagediff-actions.ui new file mode 100644 index 00000000..c717a2a5 --- /dev/null +++ b/meld/resources/ui/imagediff-actions.ui @@ -0,0 +1,61 @@ + + + + + + True + False + 6 + + + True + False + False + False + Copy chunks + + + True + False + edit-copy-symbolic + + + + + + False + True + 1 + + + + + False + True + False + False + False + Delete change + view.file-delete + + + True + False + edit-delete-symbolic + 1 + + + + + + False + True + 2 + + + + diff --git a/meld/resources/ui/imagediff-menus.ui b/meld/resources/ui/imagediff-menus.ui new file mode 100644 index 00000000..627ced2c --- /dev/null +++ b/meld/resources/ui/imagediff-menus.ui @@ -0,0 +1,16 @@ + + + +
+ file-section + + Open Containing Folder + view.open-folder + + + Copy Full Path + view.copy-full-path + +
+
+
diff --git a/meld/resources/ui/imagediff.ui b/meld/resources/ui/imagediff.ui new file mode 100644 index 00000000..a613d260 --- /dev/null +++ b/meld/resources/ui/imagediff.ui @@ -0,0 +1,27 @@ + + + + + + + + + + -- GitLab From 39aef401bc4b50423a4770c8fcd82362ac083b33 Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Tue, 4 Apr 2023 05:53:31 +1200 Subject: [PATCH 02/31] Display two images. --- meld/imagediff.py | 113 +++++++++++++++++++++++---------- meld/resources/ui/imagediff.ui | 31 +++++++-- 2 files changed, 105 insertions(+), 39 deletions(-) diff --git a/meld/imagediff.py b/meld/imagediff.py index 51e11c10..9ed2de86 100644 --- a/meld/imagediff.py +++ b/meld/imagediff.py @@ -80,15 +80,15 @@ MASK_SHIFT, MASK_CTRL = 1, 2 PANE_LEFT, PANE_RIGHT = -1, +1 -class CursorDetails: - __slots__ = ( - "pane", "pos", "line", "chunk", "prev", "next", - "prev_conflict", "next_conflict", - ) +# ~ class CursorDetails: + # ~ __slots__ = ( + # ~ "pane", "pos", "line", "chunk", "prev", "next", + # ~ "prev_conflict", "next_conflict", + # ~ ) - def __init__(self): - for var in self.__slots__: - setattr(self, var, None) + # ~ def __init__(self): + # ~ for var in self.__slots__: + # ~ setattr(self, var, None) @Gtk.Template(resource_path='/org/gnome/meld/ui/imagediff.ui') @@ -113,6 +113,9 @@ class ImageDiff(Gtk.VBox, MeldDoc): show_overview_map = GObject.Property(type=bool, default=True) overview_map_style = GObject.Property(type=str, default='chunkmap') + image_main0 = Gtk.Template.Child() + image_main1 = Gtk.Template.Child() + keylookup = { Gdk.KEY_Shift_L: MASK_SHIFT, Gdk.KEY_Shift_R: MASK_SHIFT, @@ -168,6 +171,11 @@ class ImageDiff(Gtk.VBox, MeldDoc): # ~ ] # ~ map_widgets_into_lists(self, widget_lists) + widget_lists = [ + "image_main", + ] + map_widgets_into_lists(self, widget_lists) + self.warned_bad_comparison = False self._keymask = 0 self.meta = {} @@ -225,31 +233,7 @@ class ImageDiff(Gtk.VBox, MeldDoc): self.toolbar_actions = builder.get_object('view-toolbar') self.copy_action_button = builder.get_object('copy_action_button') - # Handle overview map visibility binding. Because of how we use - # grid packing, we need three revealers here instead of the - # more obvious one. - # ~ revealers = ( - # ~ self.toolbar_sourcemap_revealer, - # ~ self.sourcemap_revealer, - # ~ self.statusbar_sourcemap_revealer, - # ~ ) - # ~ for revealer in revealers: - # ~ self.bind_property( - # ~ 'show-overview-map', revealer, 'reveal-child', - # ~ ( - # ~ GObject.BindingFlags.DEFAULT | - # ~ GObject.BindingFlags.SYNC_CREATE - # ~ ), - # ~ ) - - # Handle overview map style mapping manually - # ~ self.connect( - # ~ 'notify::overview-map-style', self.on_overview_map_style_changed) - # ~ self.on_overview_map_style_changed() - - # ~ self.grid.attach(self.findbar, 0, 2, 10, 1) - - # ~ self.set_num_panes(num_panes) + self.set_num_panes(num_panes) def do_realize(self): Gtk.VBox().do_realize(self) @@ -266,6 +250,11 @@ class ImageDiff(Gtk.VBox, MeldDoc): If an element is None, the text of a pane is left as is. """ + # Debug. + print ("MVZ: Setting files....") + print ("gfiles:", gfiles) + print ("self.num_panes:", self.num_panes) + if len(gfiles) != self.num_panes: return @@ -281,5 +270,59 @@ class ImageDiff(Gtk.VBox, MeldDoc): # ~ if not files: # ~ self.scheduler.add_task(self._compare_files_internal()) - # ~ for pane, gfile, encoding in files: - # ~ self.load_file_in_pane(pane, gfile, encoding) + for pane, gfile, encoding in files: + self.load_file_in_pane(pane, gfile, encoding) + + def load_file_in_pane( + self, + pane: int, + gfile: Gio.File, + encoding: GtkSource.Encoding = None): + """Load a file into the given pane + + Don't call this directly; use `set_file()` or `set_files()`, + which handle sensitivity and signal connection. Even if you + don't care about those things, you need it because they'll be + unconditionally added after file load, which will cause + duplicate handlers, etc. if you don't do this thing. + """ + + print ("MVZ: Loading in pane....") + print ("self.image_main:", self.image_main) + # ~ self.image_main[pane].props.file = gfile + # ~ self.image_main[pane].set_from_file(gfile) # Causes error... + self.image_main[pane].set_from_file( gfile.get_path() ) + + # ~ self.msgarea_mgr[pane].clear() + + # ~ buf = self.textbuffer[pane] + # ~ buf.data.reset(gfile) + # ~ self.file_open_button[pane].props.file = gfile + + # FIXME: this was self.textbuffer[pane].data.label, which could be + # either a custom label or the fallback + # ~ self.filelabel[pane].props.gfile = gfile + + # ~ if buf.data.is_special: + # ~ loader = GtkSource.FileLoader.new_from_stream( + # ~ buf, buf.data.sourcefile, buf.data.gfile.read()) + # ~ else: + # ~ loader = GtkSource.FileLoader.new(buf, buf.data.sourcefile) + + # ~ custom_candidates = get_custom_encoding_candidates() + # ~ if encoding: + # ~ custom_candidates = [encoding] + # ~ if custom_candidates: + # ~ loader.set_candidate_encodings(custom_candidates) + + # ~ loader.load_async( + # ~ GLib.PRIORITY_HIGH, + # ~ callback=self.file_loaded, + # ~ user_data=(pane,) + # ~ ) + + def set_num_panes(self, n): + if n == self.num_panes or n not in (1, 2, 3): + return + + self.num_panes = n diff --git a/meld/resources/ui/imagediff.ui b/meld/resources/ui/imagediff.ui index a613d260..b2e6f786 100644 --- a/meld/resources/ui/imagediff.ui +++ b/meld/resources/ui/imagediff.ui @@ -2,22 +2,45 @@ - -- GitLab From e2c3828343e7b7b79d2394c509e0c2c22bba9448 Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Tue, 4 Apr 2023 06:06:18 +1200 Subject: [PATCH 03/31] Handle closing signal. --- meld/imagediff.py | 16 +++++++++++----- meld/meldwindow.py | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/meld/imagediff.py b/meld/imagediff.py index 9ed2de86..5cf563e2 100644 --- a/meld/imagediff.py +++ b/meld/imagediff.py @@ -251,9 +251,9 @@ class ImageDiff(Gtk.VBox, MeldDoc): If an element is None, the text of a pane is left as is. """ # Debug. - print ("MVZ: Setting files....") - print ("gfiles:", gfiles) - print ("self.num_panes:", self.num_panes) + # ~ print ("MVZ: Setting files....") + # ~ print ("gfiles:", gfiles) + # ~ print ("self.num_panes:", self.num_panes) if len(gfiles) != self.num_panes: return @@ -287,8 +287,8 @@ class ImageDiff(Gtk.VBox, MeldDoc): duplicate handlers, etc. if you don't do this thing. """ - print ("MVZ: Loading in pane....") - print ("self.image_main:", self.image_main) + # ~ print ("MVZ: Loading in pane....") + # ~ print ("self.image_main:", self.image_main) # ~ self.image_main[pane].props.file = gfile # ~ self.image_main[pane].set_from_file(gfile) # Causes error... self.image_main[pane].set_from_file( gfile.get_path() ) @@ -326,3 +326,9 @@ class ImageDiff(Gtk.VBox, MeldDoc): return self.num_panes = n + + + def on_delete_event(self): + self.state = ComparisonState.Closing + self.close_signal.emit(0) + return Gtk.ResponseType.OK diff --git a/meld/meldwindow.py b/meld/meldwindow.py index 68ab0b78..4943a83d 100644 --- a/meld/meldwindow.py +++ b/meld/meldwindow.py @@ -374,7 +374,7 @@ class MeldWindow(Gtk.ApplicationWindow): basename = gfile.get_basename() if basename.endswith(".png"): filesAreImages = True - print ("MVZ: Files are images. Showing the image window...") + # ~ print ("MVZ: Files are images. Showing the image window...") break if filesAreImages: -- GitLab From e4b6db98e120b4cb37316fee9d59d6b358e9cbe6 Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Tue, 4 Apr 2023 06:16:40 +1200 Subject: [PATCH 04/31] Handle either one or two images. --- meld/imagediff.py | 37 ++++++------------------------------- 1 file changed, 6 insertions(+), 31 deletions(-) diff --git a/meld/imagediff.py b/meld/imagediff.py index 5cf563e2..441664d3 100644 --- a/meld/imagediff.py +++ b/meld/imagediff.py @@ -289,44 +289,19 @@ class ImageDiff(Gtk.VBox, MeldDoc): # ~ print ("MVZ: Loading in pane....") # ~ print ("self.image_main:", self.image_main) - # ~ self.image_main[pane].props.file = gfile - # ~ self.image_main[pane].set_from_file(gfile) # Causes error... self.image_main[pane].set_from_file( gfile.get_path() ) - # ~ self.msgarea_mgr[pane].clear() - - # ~ buf = self.textbuffer[pane] - # ~ buf.data.reset(gfile) - # ~ self.file_open_button[pane].props.file = gfile - - # FIXME: this was self.textbuffer[pane].data.label, which could be - # either a custom label or the fallback - # ~ self.filelabel[pane].props.gfile = gfile - - # ~ if buf.data.is_special: - # ~ loader = GtkSource.FileLoader.new_from_stream( - # ~ buf, buf.data.sourcefile, buf.data.gfile.read()) - # ~ else: - # ~ loader = GtkSource.FileLoader.new(buf, buf.data.sourcefile) - - # ~ custom_candidates = get_custom_encoding_candidates() - # ~ if encoding: - # ~ custom_candidates = [encoding] - # ~ if custom_candidates: - # ~ loader.set_candidate_encodings(custom_candidates) - - # ~ loader.load_async( - # ~ GLib.PRIORITY_HIGH, - # ~ callback=self.file_loaded, - # ~ user_data=(pane,) - # ~ ) - def set_num_panes(self, n): if n == self.num_panes or n not in (1, 2, 3): return - self.num_panes = n + for widget in (self.image_main[:n]): + widget.show() + for widget in (self.image_main[n:]): + widget.hide() + + self.num_panes = n def on_delete_event(self): self.state = ComparisonState.Closing -- GitLab From 4f81139f770438f8e8afa71ba657d54854c282f3 Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Tue, 4 Apr 2023 06:27:27 +1200 Subject: [PATCH 05/31] Handle three images. --- meld/imagediff.py | 1 + meld/resources/ui/imagediff.ui | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/meld/imagediff.py b/meld/imagediff.py index 441664d3..f8eff3b2 100644 --- a/meld/imagediff.py +++ b/meld/imagediff.py @@ -115,6 +115,7 @@ class ImageDiff(Gtk.VBox, MeldDoc): image_main0 = Gtk.Template.Child() image_main1 = Gtk.Template.Child() + image_main2 = Gtk.Template.Child() keylookup = { Gdk.KEY_Shift_L: MASK_SHIFT, diff --git a/meld/resources/ui/imagediff.ui b/meld/resources/ui/imagediff.ui index b2e6f786..6b0852af 100644 --- a/meld/resources/ui/imagediff.ui +++ b/meld/resources/ui/imagediff.ui @@ -8,7 +8,7 @@ - + True False @@ -36,6 +36,17 @@ 0 + + + True + False + gtk-dialog-question + + + 2 + 0 + + True -- GitLab From f686c0d00e1672905e55a6051722192d721001d8 Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Tue, 4 Apr 2023 07:02:27 +1200 Subject: [PATCH 06/31] Set tab label and tooltip. --- meld/imagediff.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/meld/imagediff.py b/meld/imagediff.py index f8eff3b2..33ec24e1 100644 --- a/meld/imagediff.py +++ b/meld/imagediff.py @@ -274,6 +274,10 @@ class ImageDiff(Gtk.VBox, MeldDoc): for pane, gfile, encoding in files: self.load_file_in_pane(pane, gfile, encoding) + # Update tab label. + self.files = gfiles + self.recompute_label() + def load_file_in_pane( self, pane: int, @@ -308,3 +312,32 @@ class ImageDiff(Gtk.VBox, MeldDoc): self.state = ComparisonState.Closing self.close_signal.emit(0) return Gtk.ResponseType.OK + + def recompute_label(self): + # ~ filenames = [f.get_basename() for f in self.files] + filenames = [f.get_path() for f in self.files] + shortnames = misc.shorten_names(*filenames) + + # ~ for i, buf in enumerate(buffers): + # ~ if buf.get_modified(): + # ~ shortnames[i] += "*" + # ~ self.file_save_button[i].set_sensitive(buf.get_modified()) + # ~ self.file_save_button[i].get_child().props.icon_name = ( + # ~ 'document-save-symbolic' if buf.data.writable else + # ~ 'document-save-as-symbolic') + + # ~ parent_path = find_shared_parent_path( + # ~ self.files + # ~ ) + # ~ for pathlabel in self.filelabel: + # ~ pathlabel.props.parent_gfile = parent_path + + label = self.meta.get("tablabel", "") + if label: + self.label_text = label + tooltip_names = [label] + else: + self.label_text = " — ".join(shortnames) + tooltip_names = filenames + self.tooltip_text = "\n".join((_("File comparison:"), *tooltip_names)) + self.label_changed.emit(self.label_text, self.tooltip_text) -- GitLab From df82e054fd532519bfe48259593760e05405b505 Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Tue, 4 Apr 2023 08:11:51 +1200 Subject: [PATCH 07/31] Add popup menu (WIP). --- meld/imagediff.py | 116 +++++++++++++++------------ meld/resources/ui/imagediff-menus.ui | 2 +- meld/resources/ui/imagediff.ui | 12 ++- 3 files changed, 76 insertions(+), 54 deletions(-) diff --git a/meld/imagediff.py b/meld/imagediff.py index 33ec24e1..94e7ff7f 100644 --- a/meld/imagediff.py +++ b/meld/imagediff.py @@ -80,17 +80,6 @@ MASK_SHIFT, MASK_CTRL = 1, 2 PANE_LEFT, PANE_RIGHT = -1, +1 -# ~ class CursorDetails: - # ~ __slots__ = ( - # ~ "pane", "pos", "line", "chunk", "prev", "next", - # ~ "prev_conflict", "next_conflict", - # ~ ) - - # ~ def __init__(self): - # ~ for var in self.__slots__: - # ~ setattr(self, var, None) - - @Gtk.Template(resource_path='/org/gnome/meld/ui/imagediff.ui') class ImageDiff(Gtk.VBox, MeldDoc): """Two or three way comparison of image files""" @@ -104,15 +93,6 @@ class ImageDiff(Gtk.VBox, MeldDoc): move_diff = MeldDoc.move_diff tab_state_changed = MeldDoc.tab_state_changed - # ~ __gsettings_bindings_view__ = ( - # ~ ('ignore-blank-lines', 'ignore-blank-lines'), - # ~ ('show-overview-map', 'show-overview-map'), - # ~ ('overview-map-style', 'overview-map-style'), - # ~ ) - - show_overview_map = GObject.Property(type=bool, default=True) - overview_map_style = GObject.Property(type=str, default='chunkmap') - image_main0 = Gtk.Template.Child() image_main1 = Gtk.Template.Child() image_main2 = Gtk.Template.Child() @@ -150,6 +130,8 @@ class ImageDiff(Gtk.VBox, MeldDoc): ): super().__init__() + self.files = [None, None, None] + # FIXME: # This unimaginable hack exists because GObject (or GTK+?) # doesn't actually correctly chain init calls, even if they're @@ -161,17 +143,6 @@ class ImageDiff(Gtk.VBox, MeldDoc): MeldDoc.__init__(self) bind_settings(self) - # ~ widget_lists = [ - # ~ "sourcemap", "file_save_button", "file_toolbar", - # ~ "linkmap", "msgarea_mgr", "readonlytoggle", - # ~ "scrolledwindow", "textview", "vbox", - # ~ "dummy_toolbar_linkmap", "filelabel", - # ~ "file_open_button", "statusbar", - # ~ "actiongutter", "dummy_toolbar_actiongutter", - # ~ "chunkmap", - # ~ ] - # ~ map_widgets_into_lists(self, widget_lists) - widget_lists = [ "image_main", ] @@ -199,35 +170,35 @@ class ImageDiff(Gtk.VBox, MeldDoc): # Set up per-view action group for top-level menu insertion self.view_action_group = Gio.SimpleActionGroup() - property_actions = ( - ('show-overview-map', self, 'show-overview-map'), - ('lock-scrolling', self, 'lock_scrolling'), - ) - for action_name, obj, prop_name in property_actions: - action = Gio.PropertyAction.new(action_name, obj, prop_name) - self.view_action_group.add_action(action) + # ~ property_actions = ( + # ~ ('show-overview-map', self, 'show-overview-map'), + # ~ ('lock-scrolling', self, 'lock_scrolling'), + # ~ ) + # ~ for action_name, obj, prop_name in property_actions: + # ~ action = Gio.PropertyAction.new(action_name, obj, prop_name) + # ~ self.view_action_group.add_action(action) # Manually handle GAction additions - # ~ actions = ( + actions = ( # ~ ('copy', self.action_copy), # ~ ('copy-full-path', self.action_copy_full_path), # ~ ('next-pane', self.action_next_pane), # ~ ('open-external', self.action_open_external), - # ~ ('open-folder', self.action_open_folder), + ('open-folder', self.action_open_folder), # ~ ('previous-pane', self.action_prev_pane), # ~ ('refresh', self.action_refresh), # ~ ('swap-2-panes', self.action_swap), - # ~ ) - # ~ for name, callback in actions: - # ~ action = Gio.SimpleAction.new(name, None) - # ~ action.connect('activate', callback) - # ~ self.view_action_group.add_action(action) + ) + for name, callback in actions: + action = Gio.SimpleAction.new(name, None) + action.connect('activate', callback) + self.view_action_group.add_action(action) - # ~ builder = Gtk.Builder.new_from_resource( - # ~ '/org/gnome/meld/ui/imagediff-menus.ui') - # ~ self.popup_menu_model = builder.get_object('imagediff-context-menu') - # ~ self.popup_menu = Gtk.Menu.new_from_model(self.popup_menu_model) - # ~ self.popup_menu.attach_to_widget(self) + builder = Gtk.Builder.new_from_resource( + '/org/gnome/meld/ui/imagediff-menus.ui') + self.popup_menu_model = builder.get_object('imagediff-context-menu') + self.popup_menu = Gtk.Menu.new_from_model(self.popup_menu_model) + self.popup_menu.attach_to_widget(self) builder = Gtk.Builder.new_from_resource( '/org/gnome/meld/ui/imagediff-actions.ui') @@ -341,3 +312,48 @@ class ImageDiff(Gtk.VBox, MeldDoc): tooltip_names = filenames self.tooltip_text = "\n".join((_("File comparison:"), *tooltip_names)) self.label_changed.emit(self.label_text, self.tooltip_text) + + @with_focused_pane + def action_open_folder(self, pane, *args): + gfile = self.files[pane] + if not gfile: + return + + parent = gfile.get_parent() + if parent: + open_files_external(gfiles=[parent]) + + @Gtk.Template.Callback() + def on_imageview_popup_menu(self, imageview): + print ("MVZ: on_imageview_popup_menu called...") + # ~ buffer = textview.get_buffer() + # ~ cursor_it = buffer.get_iter_at_mark(buffer.get_insert()) + # ~ location = textview.get_iter_location(cursor_it) + + # ~ rect = Gdk.Rectangle() + # ~ rect.x, rect.y = textview.buffer_to_window_coords( + # ~ Gtk.TextWindowType.WIDGET, location.x, location.y) + + # ~ pane = self.imageview.index(imageview) + # ~ self.set_syncpoint_menuitem(pane) + + # ~ self.popup_menu.popup_at_rect( + # ~ Gtk.Widget.get_window(textview), + # ~ rect, + # ~ Gdk.Gravity.SOUTH_EAST, + # ~ Gdk.Gravity.NORTH_WEST, + # ~ None, + # ~ ) + self.popup_menu.popup_at_pointer() + return True + + @Gtk.Template.Callback() + def on_imageview_button_press_event(self, imageview, event): + print ("MVZ: Button press event...") + if event.button == 3: + imageview.grab_focus() + pane = self.imageview.index(textview) + # ~ self.set_syncpoint_menuitem(pane) + self.popup_menu.popup_at_pointer(event) + return True + return False diff --git a/meld/resources/ui/imagediff-menus.ui b/meld/resources/ui/imagediff-menus.ui index 627ced2c..f0e7c96e 100644 --- a/meld/resources/ui/imagediff-menus.ui +++ b/meld/resources/ui/imagediff-menus.ui @@ -1,6 +1,6 @@ - +
file-section diff --git a/meld/resources/ui/imagediff.ui b/meld/resources/ui/imagediff.ui index 6b0852af..476344d6 100644 --- a/meld/resources/ui/imagediff.ui +++ b/meld/resources/ui/imagediff.ui @@ -17,8 +17,10 @@ True - False + True gtk-dialog-question + + 0 @@ -28,8 +30,10 @@ True - False + True gtk-dialog-question + + 1 @@ -39,8 +43,10 @@ True - False + True gtk-dialog-question + + 2 -- GitLab From d33f025b618d55464c406af4d40b62d3610ffabc Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Tue, 4 Apr 2023 11:27:38 +1200 Subject: [PATCH 08/31] Add popup menu with "open containing folder" item. --- meld/imagediff.py | 78 +++++++--------------------------- meld/resources/ui/imagediff.ui | 41 ++++++++++++++---- 2 files changed, 49 insertions(+), 70 deletions(-) diff --git a/meld/imagediff.py b/meld/imagediff.py index 94e7ff7f..340e99be 100644 --- a/meld/imagediff.py +++ b/meld/imagediff.py @@ -93,8 +93,11 @@ class ImageDiff(Gtk.VBox, MeldDoc): move_diff = MeldDoc.move_diff tab_state_changed = MeldDoc.tab_state_changed + image_event_box0 = Gtk.Template.Child() image_main0 = Gtk.Template.Child() + image_event_box1 = Gtk.Template.Child() image_main1 = Gtk.Template.Child() + image_event_box2 = Gtk.Template.Child() image_main2 = Gtk.Template.Child() keylookup = { @@ -144,7 +147,7 @@ class ImageDiff(Gtk.VBox, MeldDoc): bind_settings(self) widget_lists = [ - "image_main", + "image_main", "image_event_box" ] map_widgets_into_lists(self, widget_lists) @@ -161,23 +164,9 @@ class ImageDiff(Gtk.VBox, MeldDoc): self._sync_vscroll_lock = False self._sync_hscroll_lock = False - # ~ prop_action_group = Gio.SimpleActionGroup() - # ~ for prop in sourceview_prop_actions: - # ~ action = make_multiobject_property_action(self.textview, prop) - # ~ prop_action_group.add_action(action) - # ~ self.insert_action_group('view-local', prop_action_group) - # Set up per-view action group for top-level menu insertion self.view_action_group = Gio.SimpleActionGroup() - # ~ property_actions = ( - # ~ ('show-overview-map', self, 'show-overview-map'), - # ~ ('lock-scrolling', self, 'lock_scrolling'), - # ~ ) - # ~ for action_name, obj, prop_name in property_actions: - # ~ action = Gio.PropertyAction.new(action_name, obj, prop_name) - # ~ self.view_action_group.add_action(action) - # Manually handle GAction additions actions = ( # ~ ('copy', self.action_copy), @@ -222,10 +211,7 @@ class ImageDiff(Gtk.VBox, MeldDoc): If an element is None, the text of a pane is left as is. """ - # Debug. - # ~ print ("MVZ: Setting files....") - # ~ print ("gfiles:", gfiles) - # ~ print ("self.num_panes:", self.num_panes) + if len(gfiles) != self.num_panes: return @@ -263,18 +249,16 @@ class ImageDiff(Gtk.VBox, MeldDoc): duplicate handlers, etc. if you don't do this thing. """ - # ~ print ("MVZ: Loading in pane....") - # ~ print ("self.image_main:", self.image_main) self.image_main[pane].set_from_file( gfile.get_path() ) def set_num_panes(self, n): if n == self.num_panes or n not in (1, 2, 3): return - for widget in (self.image_main[:n]): + for widget in (self.image_main[:n] + self.image_event_box[:n]): widget.show() - for widget in (self.image_main[n:]): + for widget in (self.image_main[n:] + self.image_event_box[n:]): widget.hide() self.num_panes = n @@ -285,24 +269,9 @@ class ImageDiff(Gtk.VBox, MeldDoc): return Gtk.ResponseType.OK def recompute_label(self): - # ~ filenames = [f.get_basename() for f in self.files] filenames = [f.get_path() for f in self.files] shortnames = misc.shorten_names(*filenames) - # ~ for i, buf in enumerate(buffers): - # ~ if buf.get_modified(): - # ~ shortnames[i] += "*" - # ~ self.file_save_button[i].set_sensitive(buf.get_modified()) - # ~ self.file_save_button[i].get_child().props.icon_name = ( - # ~ 'document-save-symbolic' if buf.data.writable else - # ~ 'document-save-as-symbolic') - - # ~ parent_path = find_shared_parent_path( - # ~ self.files - # ~ ) - # ~ for pathlabel in self.filelabel: - # ~ pathlabel.props.parent_gfile = parent_path - label = self.meta.get("tablabel", "") if label: self.label_text = label @@ -325,35 +294,20 @@ class ImageDiff(Gtk.VBox, MeldDoc): @Gtk.Template.Callback() def on_imageview_popup_menu(self, imageview): - print ("MVZ: on_imageview_popup_menu called...") - # ~ buffer = textview.get_buffer() - # ~ cursor_it = buffer.get_iter_at_mark(buffer.get_insert()) - # ~ location = textview.get_iter_location(cursor_it) - - # ~ rect = Gdk.Rectangle() - # ~ rect.x, rect.y = textview.buffer_to_window_coords( - # ~ Gtk.TextWindowType.WIDGET, location.x, location.y) - - # ~ pane = self.imageview.index(imageview) - # ~ self.set_syncpoint_menuitem(pane) - - # ~ self.popup_menu.popup_at_rect( - # ~ Gtk.Widget.get_window(textview), - # ~ rect, - # ~ Gdk.Gravity.SOUTH_EAST, - # ~ Gdk.Gravity.NORTH_WEST, - # ~ None, - # ~ ) self.popup_menu.popup_at_pointer() return True @Gtk.Template.Callback() - def on_imageview_button_press_event(self, imageview, event): - print ("MVZ: Button press event...") + def on_imageview_button_press_event(self, event_box, event): if event.button == 3: - imageview.grab_focus() - pane = self.imageview.index(textview) - # ~ self.set_syncpoint_menuitem(pane) + event_box.grab_focus() + pane = self.image_event_box.index(event_box) self.popup_menu.popup_at_pointer(event) return True return False + + def _get_focused_pane(self): + for i in range(self.num_panes): + if self.image_event_box[i].is_focus(): + return i + return -1 diff --git a/meld/resources/ui/imagediff.ui b/meld/resources/ui/imagediff.ui index 476344d6..9290fc75 100644 --- a/meld/resources/ui/imagediff.ui +++ b/meld/resources/ui/imagediff.ui @@ -15,12 +15,21 @@ True True - + True True - gtk-dialog-question + True + + + True + True + gtk-dialog-question + + + + 0 @@ -28,28 +37,44 @@ - + True True - gtk-dialog-question + + + True + True + gtk-dialog-question + + + + - 1 + 2 0 - + True True - gtk-dialog-question + + + True + True + gtk-dialog-question + + + + - 2 + 1 0 -- GitLab From 658ad5a40bbdd69538594a6e21e77ed0c8fe331d Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Tue, 4 Apr 2023 11:29:55 +1200 Subject: [PATCH 09/31] Enable copy full path menu item. --- meld/imagediff.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/meld/imagediff.py b/meld/imagediff.py index 340e99be..2efc7b65 100644 --- a/meld/imagediff.py +++ b/meld/imagediff.py @@ -170,7 +170,7 @@ class ImageDiff(Gtk.VBox, MeldDoc): # Manually handle GAction additions actions = ( # ~ ('copy', self.action_copy), - # ~ ('copy-full-path', self.action_copy_full_path), + ('copy-full-path', self.action_copy_full_path), # ~ ('next-pane', self.action_next_pane), # ~ ('open-external', self.action_open_external), ('open-folder', self.action_open_folder), @@ -311,3 +311,14 @@ class ImageDiff(Gtk.VBox, MeldDoc): if self.image_event_box[i].is_focus(): return i return -1 + + @with_focused_pane + def action_copy_full_path(self, pane, *args): + gfile = self.files[pane] + if not gfile: + return + + path = gfile.get_path() or gfile.get_uri() + clip = Gtk.Clipboard.get_default(Gdk.Display.get_default()) + clip.set_text(path, -1) + clip.store() -- GitLab From fb4b3a2810205020d8b750ee0f7718f00baf61b4 Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Wed, 5 Apr 2023 05:23:17 +1200 Subject: [PATCH 10/31] Clean up code. --- meld/imagediff.py | 42 +++++------------------------------------- 1 file changed, 5 insertions(+), 37 deletions(-) diff --git a/meld/imagediff.py b/meld/imagediff.py index 2efc7b65..2d349592 100644 --- a/meld/imagediff.py +++ b/meld/imagediff.py @@ -27,10 +27,7 @@ from gi.repository import Gdk, Gio, GLib, GObject, Gtk, GtkSource from meld import misc from meld.conf import _ from meld.const import ( - NEWLINES, - TEXT_FILTER_ACTION_FORMAT, ActionMode, - ChunkAction, FileComparisonMode, ) from meld.iohelpers import find_shared_parent_path, prompt_save_filename @@ -43,7 +40,6 @@ from meld.ui.util import ( make_multiobject_property_action, map_widgets_into_lists, ) -from meld.undo import UndoSequence log = logging.getLogger(__name__) @@ -90,7 +86,6 @@ class ImageDiff(Gtk.VBox, MeldDoc): create_diff_signal = MeldDoc.create_diff_signal file_changed_signal = MeldDoc.file_changed_signal label_changed = MeldDoc.label_changed - move_diff = MeldDoc.move_diff tab_state_changed = MeldDoc.tab_state_changed image_event_box0 = Gtk.Template.Child() @@ -100,25 +95,12 @@ class ImageDiff(Gtk.VBox, MeldDoc): image_event_box2 = Gtk.Template.Child() image_main2 = Gtk.Template.Child() - keylookup = { - Gdk.KEY_Shift_L: MASK_SHIFT, - Gdk.KEY_Shift_R: MASK_SHIFT, - Gdk.KEY_Control_L: MASK_CTRL, - Gdk.KEY_Control_R: MASK_CTRL, - } - # Identifiers for MsgArea messages (MSG_SAME, MSG_SLOW_HIGHLIGHT, MSG_SYNCPOINTS) = list(range(3)) # Transient messages that should be removed if any file in the # comparison gets reloaded. TRANSIENT_MESSAGES = {MSG_SAME, MSG_SLOW_HIGHLIGHT} - action_mode = GObject.Property( - type=int, - nick='Action mode for chunk change actions', - default=ActionMode.Replace, - ) - lock_scrolling = GObject.Property( type=bool, nick='Lock scrolling of all panes', @@ -158,18 +140,20 @@ class ImageDiff(Gtk.VBox, MeldDoc): self.focus_pane = None meld_settings = get_meld_settings() + # TODO: Add synchronized scrolling for large images. # ~ for (i, w) in enumerate(self.scrolledwindow): # ~ w.get_vadjustment().connect("value-changed", self._sync_vscroll, i) # ~ w.get_hadjustment().connect("value-changed", self._sync_hscroll) - self._sync_vscroll_lock = False - self._sync_hscroll_lock = False + # ~ self._sync_vscroll_lock = False + # ~ self._sync_hscroll_lock = False # Set up per-view action group for top-level menu insertion self.view_action_group = Gio.SimpleActionGroup() # Manually handle GAction additions + # TODO: Highlight the selected image for the "Next Pane" and + # "Open External" menu items to make sense. actions = ( - # ~ ('copy', self.action_copy), ('copy-full-path', self.action_copy_full_path), # ~ ('next-pane', self.action_next_pane), # ~ ('open-external', self.action_open_external), @@ -196,23 +180,12 @@ class ImageDiff(Gtk.VBox, MeldDoc): self.set_num_panes(num_panes) - def do_realize(self): - Gtk.VBox().do_realize(self) - - builder = Gtk.Builder.new_from_resource( - '/org/gnome/meld/ui/imagediff-menus.ui') - # ~ filter_menu = builder.get_object('file-copy-actions-menu') - - # ~ self.copy_action_button.set_popover( - # ~ Gtk.Popover.new_from_model(self.copy_action_button, filter_menu)) - def set_files(self, gfiles, encodings=None): """Load the given files If an element is None, the text of a pane is left as is. """ - if len(gfiles) != self.num_panes: return @@ -222,11 +195,6 @@ class ImageDiff(Gtk.VBox, MeldDoc): for pane, (gfile, encoding) in enumerate(zip(gfiles, encodings)): if gfile: files.append((pane, gfile, encoding)) - # ~ else: - # ~ self.textbuffer[pane].data.loaded = True - - # ~ if not files: - # ~ self.scheduler.add_task(self._compare_files_internal()) for pane, gfile, encoding in files: self.load_file_in_pane(pane, gfile, encoding) -- GitLab From b622975d5e2115b69d4271b6bf8b48c186260dcb Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Wed, 5 Apr 2023 06:43:04 +1200 Subject: [PATCH 11/31] Fix lint issues from GitLab. --- meld/imagediff.py | 22 +++------------------- meld/meldapp.py | 1 - meld/meldwindow.py | 7 +++---- 3 files changed, 6 insertions(+), 24 deletions(-) diff --git a/meld/imagediff.py b/meld/imagediff.py index 2d349592..e8361b16 100644 --- a/meld/imagediff.py +++ b/meld/imagediff.py @@ -14,30 +14,21 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import copy import functools import logging -import math -from enum import Enum -from typing import Optional, Tuple, Type -from gi.repository import Gdk, Gio, GLib, GObject, Gtk, GtkSource +from gi.repository import Gdk, Gio, GObject, Gtk, GtkSource # TODO: Don't from-import whole modules from meld import misc from meld.conf import _ from meld.const import ( - ActionMode, FileComparisonMode, ) -from meld.iohelpers import find_shared_parent_path, prompt_save_filename from meld.melddoc import ComparisonState, MeldDoc, open_files_external -from meld.menuhelpers import replace_menu_section -from meld.misc import user_critical, with_focused_pane -from meld.recent import RecentType +from meld.misc import with_focused_pane from meld.settings import bind_settings, get_meld_settings from meld.ui.util import ( - make_multiobject_property_action, map_widgets_into_lists, ) @@ -138,14 +129,8 @@ class ImageDiff(Gtk.VBox, MeldDoc): self.meta = {} self.lines_removed = 0 self.focus_pane = None - meld_settings = get_meld_settings() # TODO: Add synchronized scrolling for large images. - # ~ for (i, w) in enumerate(self.scrolledwindow): - # ~ w.get_vadjustment().connect("value-changed", self._sync_vscroll, i) - # ~ w.get_hadjustment().connect("value-changed", self._sync_hscroll) - # ~ self._sync_vscroll_lock = False - # ~ self._sync_hscroll_lock = False # Set up per-view action group for top-level menu insertion self.view_action_group = Gio.SimpleActionGroup() @@ -217,7 +202,7 @@ class ImageDiff(Gtk.VBox, MeldDoc): duplicate handlers, etc. if you don't do this thing. """ - self.image_main[pane].set_from_file( gfile.get_path() ) + self.image_main[pane].set_from_file(gfile.get_path()) def set_num_panes(self, n): if n == self.num_panes or n not in (1, 2, 3): @@ -269,7 +254,6 @@ class ImageDiff(Gtk.VBox, MeldDoc): def on_imageview_button_press_event(self, event_box, event): if event.button == 3: event_box.grab_focus() - pane = self.image_event_box.index(event_box) self.popup_menu.popup_at_pointer(event) return True return False diff --git a/meld/meldapp.py b/meld/meldapp.py index 1561d4c9..396415f5 100644 --- a/meld/meldapp.py +++ b/meld/meldapp.py @@ -25,7 +25,6 @@ import meld.accelerators import meld.conf from meld.conf import _ from meld.filediff import FileDiff -from meld.imagediff import ImageDiff from meld.meldwindow import MeldWindow from meld.preferences import PreferencesDialog diff --git a/meld/meldwindow.py b/meld/meldwindow.py index 4943a83d..427576c5 100644 --- a/meld/meldwindow.py +++ b/meld/meldwindow.py @@ -369,15 +369,14 @@ class MeldWindow(Gtk.ApplicationWindow): assert len(gfiles) in (1, 2, 3) # Check whether to show image window or not. - filesAreImages = False + files_are_images = False for gfile in gfiles: basename = gfile.get_basename() if basename.endswith(".png"): - filesAreImages = True - # ~ print ("MVZ: Files are images. Showing the image window...") + files_are_images = True break - if filesAreImages: + if files_are_images: doc = ImageDiff(len(gfiles)) else: doc = FileDiff(len(gfiles)) -- GitLab From 35415f76de0db64b1420c6626525e4be50dccb3f Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Wed, 5 Apr 2023 07:11:39 +1200 Subject: [PATCH 12/31] Fix further lint errors. --- meld/imagediff.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/meld/imagediff.py b/meld/imagediff.py index e8361b16..4b89236c 100644 --- a/meld/imagediff.py +++ b/meld/imagediff.py @@ -22,15 +22,11 @@ from gi.repository import Gdk, Gio, GObject, Gtk, GtkSource # TODO: Don't from-import whole modules from meld import misc from meld.conf import _ -from meld.const import ( - FileComparisonMode, -) +from meld.const import FileComparisonMode from meld.melddoc import ComparisonState, MeldDoc, open_files_external from meld.misc import with_focused_pane -from meld.settings import bind_settings, get_meld_settings -from meld.ui.util import ( - map_widgets_into_lists, -) +from meld.settings import bind_settings +from meld.ui.util import map_widgets_into_lists log = logging.getLogger(__name__) -- GitLab From dbdc8fa13fa6d1d2c86d64c4c353584445888be9 Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Wed, 5 Apr 2023 12:33:59 +1200 Subject: [PATCH 13/31] Add more image formats. --- meld/meldwindow.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/meld/meldwindow.py b/meld/meldwindow.py index 427576c5..4809c5a2 100644 --- a/meld/meldwindow.py +++ b/meld/meldwindow.py @@ -44,6 +44,30 @@ from meld.windowstate import SavedWindowState log = logging.getLogger(__name__) +image_extensions = [ + ".bmp", + ".eps", + ".gif", + ".ico", + ".jpg", + ".jpeg", + ".png", + ".tif", +] + +def file_is_image(gfile): + """Check if file is an image.""" + + # Check for null value. + if not gfile: + return False + + basename = gfile.get_basename() + for extension in image_extensions: + if basename.endswith(extension): + return True + + return False @Gtk.Template(resource_path='/org/gnome/meld/ui/appwindow.ui') class MeldWindow(Gtk.ApplicationWindow): @@ -371,8 +395,7 @@ class MeldWindow(Gtk.ApplicationWindow): # Check whether to show image window or not. files_are_images = False for gfile in gfiles: - basename = gfile.get_basename() - if basename.endswith(".png"): + if file_is_image(gfile): files_are_images = True break -- GitLab From 27d36cdaf1a2ff45e8a68cde44be8e65dccad4cd Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Wed, 5 Apr 2023 13:01:21 +1200 Subject: [PATCH 14/31] Fix bug in recompute_label if one of the files is None. --- meld/imagediff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meld/imagediff.py b/meld/imagediff.py index 4b89236c..d924bd8f 100644 --- a/meld/imagediff.py +++ b/meld/imagediff.py @@ -218,7 +218,7 @@ class ImageDiff(Gtk.VBox, MeldDoc): return Gtk.ResponseType.OK def recompute_label(self): - filenames = [f.get_path() for f in self.files] + filenames = [f.get_path() for f in self.files if f] shortnames = misc.shorten_names(*filenames) label = self.meta.get("tablabel", "") -- GitLab From 9759e83ea02b2f5b14d2e3ad91f6b816fc8e866d Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Thu, 6 Apr 2023 06:22:02 +1200 Subject: [PATCH 15/31] Handle uppercase extensions. --- meld/meldwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meld/meldwindow.py b/meld/meldwindow.py index 4809c5a2..5d8ee3b1 100644 --- a/meld/meldwindow.py +++ b/meld/meldwindow.py @@ -62,7 +62,7 @@ def file_is_image(gfile): if not gfile: return False - basename = gfile.get_basename() + basename = gfile.get_basename().lower() for extension in image_extensions: if basename.endswith(extension): return True -- GitLab From d7346f13f0a6becb9d70bb73555732ad91b4cec0 Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Thu, 6 Apr 2023 06:39:31 +1200 Subject: [PATCH 16/31] Handle large images. --- meld/imagediff.py | 17 +++++-- meld/resources/ui/imagediff.ui | 93 ++++++++++++++++++++++++---------- 2 files changed, 80 insertions(+), 30 deletions(-) diff --git a/meld/imagediff.py b/meld/imagediff.py index d924bd8f..99357b4f 100644 --- a/meld/imagediff.py +++ b/meld/imagediff.py @@ -75,10 +75,16 @@ class ImageDiff(Gtk.VBox, MeldDoc): label_changed = MeldDoc.label_changed tab_state_changed = MeldDoc.tab_state_changed + scroll_window0 = Gtk.Template.Child() + viewport0 = Gtk.Template.Child() image_event_box0 = Gtk.Template.Child() image_main0 = Gtk.Template.Child() + scroll_window1 = Gtk.Template.Child() + viewport1 = Gtk.Template.Child() image_event_box1 = Gtk.Template.Child() image_main1 = Gtk.Template.Child() + scroll_window2 = Gtk.Template.Child() + viewport2 = Gtk.Template.Child() image_event_box2 = Gtk.Template.Child() image_main2 = Gtk.Template.Child() @@ -116,7 +122,10 @@ class ImageDiff(Gtk.VBox, MeldDoc): bind_settings(self) widget_lists = [ - "image_main", "image_event_box" + "image_main", + "image_event_box", + "scroll_window", + "viewport", ] map_widgets_into_lists(self, widget_lists) @@ -204,10 +213,12 @@ class ImageDiff(Gtk.VBox, MeldDoc): if n == self.num_panes or n not in (1, 2, 3): return - for widget in (self.image_main[:n] + self.image_event_box[:n]): + for widget in (self.image_main[:n] + self.image_event_box[:n] + + self.scroll_window[:n] + self.viewport[:n]): widget.show() - for widget in (self.image_main[n:] + self.image_event_box[n:]): + for widget in (self.image_main[n:] + self.image_event_box[n:] + + self.scroll_window[n:] + self.viewport[n:]): widget.hide() self.num_panes = n diff --git a/meld/resources/ui/imagediff.ui b/meld/resources/ui/imagediff.ui index 9290fc75..4a9e3227 100644 --- a/meld/resources/ui/imagediff.ui +++ b/meld/resources/ui/imagediff.ui @@ -15,19 +15,32 @@ True True - + True True - True - - + in - + True - True - gtk-dialog-question - - + False + + + True + True + True + + + + + True + True + gtk-dialog-question + + + + + + @@ -37,44 +50,70 @@ - + True True - - + in - + True - True - gtk-dialog-question - - + False + + + True + True + + + + + True + True + gtk-dialog-question + + + + + + - 2 + 1 0 - + True True - - + in - + True - True - gtk-dialog-question - - + False + + + True + True + + + + + True + True + gtk-dialog-question + + + + + + - 1 + 2 0 -- GitLab From 8a321a48b9dd339ed98a70059d64b8ac27335be9 Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Thu, 6 Apr 2023 07:26:54 +1200 Subject: [PATCH 17/31] Use "missing image" icon by default. --- meld/resources/ui/imagediff.ui | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/meld/resources/ui/imagediff.ui b/meld/resources/ui/imagediff.ui index 4a9e3227..869c65f9 100644 --- a/meld/resources/ui/imagediff.ui +++ b/meld/resources/ui/imagediff.ui @@ -34,7 +34,7 @@ True True - gtk-dialog-question + gtk-missing-image @@ -68,7 +68,7 @@ True True - gtk-dialog-question + gtk-missing-image @@ -102,7 +102,7 @@ True True - gtk-dialog-question + gtk-missing-image -- GitLab From 2e47012fbf241959981a93037b838f3b191e9c39 Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Tue, 11 Apr 2023 08:02:34 +1200 Subject: [PATCH 18/31] Fix lint issues. --- meld/imagediff.py | 10 ++++++---- meld/meldwindow.py | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/meld/imagediff.py b/meld/imagediff.py index 99357b4f..5913750e 100644 --- a/meld/imagediff.py +++ b/meld/imagediff.py @@ -213,12 +213,14 @@ class ImageDiff(Gtk.VBox, MeldDoc): if n == self.num_panes or n not in (1, 2, 3): return - for widget in (self.image_main[:n] + self.image_event_box[:n] + - self.scroll_window[:n] + self.viewport[:n]): + for widget in ( + self.image_main[:n] + self.image_event_box[:n] + + self.scroll_window[:n] + self.viewport[:n]): widget.show() - for widget in (self.image_main[n:] + self.image_event_box[n:] + - self.scroll_window[n:] + self.viewport[n:]): + for widget in ( + self.image_main[n:] + self.image_event_box[n:] + + self.scroll_window[n:] + self.viewport[n:]): widget.hide() self.num_panes = n diff --git a/meld/meldwindow.py b/meld/meldwindow.py index 5d8ee3b1..4e662991 100644 --- a/meld/meldwindow.py +++ b/meld/meldwindow.py @@ -55,6 +55,7 @@ image_extensions = [ ".tif", ] + def file_is_image(gfile): """Check if file is an image.""" @@ -69,6 +70,7 @@ def file_is_image(gfile): return False + @Gtk.Template(resource_path='/org/gnome/meld/ui/appwindow.ui') class MeldWindow(Gtk.ApplicationWindow): -- GitLab From 03cec1980aaddabaa2659d5cd652a5f5f8fdfd90 Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Sat, 20 May 2023 13:51:58 +1200 Subject: [PATCH 19/31] Remove unneccessary toolbox actions. --- meld/resources/ui/imagediff-actions.ui | 52 +------------------------- 1 file changed, 1 insertion(+), 51 deletions(-) diff --git a/meld/resources/ui/imagediff-actions.ui b/meld/resources/ui/imagediff-actions.ui index c717a2a5..af586de1 100644 --- a/meld/resources/ui/imagediff-actions.ui +++ b/meld/resources/ui/imagediff-actions.ui @@ -6,56 +6,6 @@ True False 6 - - - True - False - False - False - Copy chunks - - - True - False - edit-copy-symbolic - - - - - - False - True - 1 - - - - - False - True - False - False - False - Delete change - view.file-delete - - - True - False - edit-delete-symbolic - 1 - - - - - - False - True - 2 - - + -- GitLab From 7b63278ba521ed526b57cbda610ea1a8aeaddf95 Mon Sep 17 00:00:00 2001 From: Martin van Zijl Date: Mon, 22 May 2023 08:08:06 +1200 Subject: [PATCH 20/31] Remove unused signals in UI file. --- meld/resources/ui/imagediff.ui | 2 -- 1 file changed, 2 deletions(-) diff --git a/meld/resources/ui/imagediff.ui b/meld/resources/ui/imagediff.ui index 869c65f9..450c3c12 100644 --- a/meld/resources/ui/imagediff.ui +++ b/meld/resources/ui/imagediff.ui @@ -5,8 +5,6 @@