[meld/ui-next] chunkmap, dirdiff: Use new overview map widget for folder comparisons



commit 7e3d96ca2a32ad324a5c64ed00bd1df6752a5170
Author: Kai Willadsen <kai willadsen gmail com>
Date:   Sat Apr 13 07:45:27 2019 +1000

    chunkmap, dirdiff: Use new overview map widget for folder comparisons
    
    This includes the same grid packing as file comparisons and the same
    visualisation, as well as the visibility control.
    
    In an ideal world, `DirDiff` would use the existing `chunks` property
    on `ChunkMap`. While this is completely do-able, expanding and
    collapsing the tree completely ruins performance because of recursive
    expand/collapse behaviour, and because in a naive implementation (like
    the one we currently use in `TreeViewChunkMap`) the tree model state
    must be refetched at each expand/collapse. It's possible to avoid this,
    but the caching in `DirDiff` became much too complex for the relative
    pay-off of not having the chunk map do these calculations lazily.

 meld/chunkmap.py             |  99 ++++++++++++++++++++++++++++
 meld/dirdiff.py              |  88 ++++++++-----------------
 meld/resources/ui/dirdiff.ui | 152 ++++++++++++++++++++++++++-----------------
 3 files changed, 218 insertions(+), 121 deletions(-)
---
diff --git a/meld/chunkmap.py b/meld/chunkmap.py
index c4eb4031..0239a529 100644
--- a/meld/chunkmap.py
+++ b/meld/chunkmap.py
@@ -24,6 +24,7 @@ from gi.repository import Gtk
 
 from meld.misc import get_common_theme
 from meld.settings import meldsettings
+from meld.tree import STATE_ERROR, STATE_MODIFIED, STATE_NEW
 
 log = logging.getLogger(__name__)
 
@@ -250,3 +251,101 @@ class TextViewChunkMap(ChunkMap):
 
         _, it = self.textview.get_iter_at_location(0, location)
         self.textview.scroll_to_iter(it, 0.0, True, 1.0, 0.5)
+
+
+class TreeViewChunkMap(ChunkMap):
+
+    __gtype_name__ = 'TreeViewChunkMap'
+
+    treeview = GObject.Property(
+        type=Gtk.TreeView,
+        nick='Treeview being mapped',
+        flags=(
+            GObject.ParamFlags.READWRITE |
+            GObject.ParamFlags.CONSTRUCT_ONLY
+        ),
+    )
+
+    treeview_idx = GObject.Property(
+        type=int,
+        nick='Index of the Treeview within the store',
+        flags=(
+            GObject.ParamFlags.READWRITE |
+            GObject.ParamFlags.CONSTRUCT_ONLY
+        ),
+    )
+
+    chunk_type_map = {
+        STATE_NEW: "insert",
+        STATE_ERROR: "error",
+        STATE_MODIFIED: "replace",
+    }
+
+    def __init__(self):
+        super().__init__()
+        self.model_signal_ids = []
+
+    def do_realize(self):
+        self.treeview.connect('row-collapsed', self.clear_cached_map)
+        self.treeview.connect('row-expanded', self.clear_cached_map)
+        self.treeview.connect('notify::model', self.connect_model)
+        self.connect_model()
+
+        return ChunkMap.do_realize(self)
+
+    def connect_model(self, *args):
+        for model, signal_id in self.model_signal_ids:
+            model.disconnect(signal_id)
+
+        model = self.treeview.get_model()
+        self.model_signal_ids = [
+            (model, model.connect('row-changed', self.clear_cached_map)),
+            (model, model.connect('row-deleted', self.clear_cached_map)),
+            (model, model.connect('row-inserted', self.clear_cached_map)),
+            (model, model.connect('rows-reordered', self.clear_cached_map)),
+        ]
+
+    def clear_cached_map(self, *args):
+        self._cached_map = None
+
+    def chunk_coords_by_tag(self):
+        def recurse_tree_states(rowiter):
+            row_states.append(
+                model.get_state(rowiter.iter, self.treeview_idx))
+            if self.treeview.row_expanded(rowiter.path):
+                for row in rowiter.iterchildren():
+                    recurse_tree_states(row)
+
+        row_states = []
+        model = self.treeview.get_model()
+        recurse_tree_states(next(iter(model)))
+        # Terminating mark to force the last chunk to be added
+        row_states.append(None)
+
+        tagged_diffs: Mapping[str, List[Tuple[float, float]]]
+        tagged_diffs = collections.defaultdict(list)
+
+        numlines = len(row_states) - 1
+        chunkstart, laststate = 0, row_states[0]
+        for index, state in enumerate(row_states):
+            if state != laststate:
+                action = self.chunk_type_map.get(laststate)
+                if action is not None:
+                    chunk = (chunkstart / numlines, index / numlines)
+                    tagged_diffs[action].append(chunk)
+                chunkstart, laststate = index, state
+
+        return tagged_diffs
+
+    def do_draw(self, context: cairo.Context) -> bool:
+        if not self.treeview:
+            return False
+
+        return ChunkMap.do_draw(self, context)
+
+    def _scroll_to_location(self, location: float):
+        if not self.treeview or self.adjustment.get_upper() <= 0:
+            return
+
+        location -= self.adjustment.get_page_size() / 2
+        self.treeview.scroll_to_point(-1, location)
diff --git a/meld/dirdiff.py b/meld/dirdiff.py
index 4b2af03b..a1e09637 100644
--- a/meld/dirdiff.py
+++ b/meld/dirdiff.py
@@ -348,6 +348,11 @@ class DirDiff(Gtk.VBox, tree.TreeviewCommon, MeldDoc):
         default=100,
     )
 
+    show_overview_map = GObject.Property(type=bool, default=True)
+
+    chunkmap0 = Template.Child()
+    chunkmap1 = Template.Child()
+    chunkmap2 = Template.Child()
     treeview0 = Template.Child()
     treeview1 = Template.Child()
     treeview2 = Template.Child()
@@ -357,34 +362,22 @@ class DirDiff(Gtk.VBox, tree.TreeviewCommon, MeldDoc):
     scrolledwindow0 = Template.Child()
     scrolledwindow1 = Template.Child()
     scrolledwindow2 = Template.Child()
-    diffmap0 = Template.Child()
-    diffmap1 = Template.Child()
     linkmap0 = Template.Child()
     linkmap1 = Template.Child()
     msgarea_mgr0 = Template.Child()
     msgarea_mgr1 = Template.Child()
     msgarea_mgr2 = Template.Child()
+    overview_map_revealer = Template.Child()
     vbox0 = Template.Child()
     vbox1 = Template.Child()
     vbox2 = Template.Child()
+    dummy_toolbar_overview_map = Template.Child()
     dummy_toolbar_linkmap0 = Template.Child()
     dummy_toolbar_linkmap1 = Template.Child()
     file_toolbar0 = Template.Child()
     file_toolbar1 = Template.Child()
     file_toolbar2 = Template.Child()
 
-    """Dictionary mapping tree states to corresponding difflib-like terms"""
-    chunk_type_map = {
-        tree.STATE_NORMAL: None,
-        tree.STATE_NOCHANGE: None,
-        tree.STATE_NEW: "insert",
-        tree.STATE_ERROR: "error",
-        tree.STATE_EMPTY: None,
-        tree.STATE_MODIFIED: "replace",
-        tree.STATE_MISSING: "delete",
-        tree.STATE_NONEXIST: "delete",
-    }
-
     state_actions = {
         tree.STATE_NORMAL: ("normal", "folder-status-same"),
         tree.STATE_NOCHANGE: ("normal", "folder-status-same"),
@@ -406,6 +399,15 @@ class DirDiff(Gtk.VBox, tree.TreeviewCommon, MeldDoc):
         self.init_template()
         bind_settings(self)
 
+        self.view_action_group = Gio.SimpleActionGroup()
+
+        property_actions = (
+            ('show-overview-map', self, 'show-overview-map'),
+        )
+        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 = (
             ('find', self.action_find),
@@ -422,7 +424,6 @@ class DirDiff(Gtk.VBox, tree.TreeviewCommon, MeldDoc):
             ('previous-pane', self.action_prev_pane),
             ('refresh', self.action_refresh),
         )
-        self.view_action_group = Gio.SimpleActionGroup()
         for name, callback in actions:
             action = Gio.SimpleAction.new(name, None)
             action.connect('activate', callback)
@@ -464,10 +465,18 @@ class DirDiff(Gtk.VBox, tree.TreeviewCommon, MeldDoc):
                                  self.on_text_filters_changed)
         ]
 
+        # Handle overview map visibility binding
+        self.bind_property(
+            'show-overview-map', self.overview_map_revealer, 'reveal-child',
+            GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE,
+        )
+        self.overview_map_revealer.bind_property(
+            'child-revealed', self.dummy_toolbar_overview_map, 'visible')
+
         map_widgets_into_lists(
             self,
             [
-                "treeview", "fileentry", "scrolledwindow", "diffmap",
+                "treeview", "fileentry", "scrolledwindow", "chunkmap",
                 "linkmap", "msgarea_mgr", "vbox", "dummy_toolbar_linkmap",
                 "file_toolbar",
             ]
@@ -576,8 +585,6 @@ class DirDiff(Gtk.VBox, tree.TreeviewCommon, MeldDoc):
     def queue_draw(self):
         for treeview in self.treeview:
             treeview.queue_draw()
-        for diffmap in self.diffmap:
-            diffmap.queue_draw()
 
     def update_comparator(self, *args):
         comparison_args = {
@@ -688,7 +695,6 @@ class DirDiff(Gtk.VBox, tree.TreeviewCommon, MeldDoc):
             self._update_item_state(it)
         else:  # nope its gone
             self.model.remove(it)
-        self._update_diffmaps()
 
     def file_created(self, path, pane):
         it = self.model.get_iter(path)
@@ -696,7 +702,6 @@ class DirDiff(Gtk.VBox, tree.TreeviewCommon, MeldDoc):
         while it and self.model.get_path(it) != root:
             self._update_item_state(it)
             it = self.model.iter_parent(it)
-        self._update_diffmaps()
 
     @Template.Callback()
     def on_fileentry_file_set(self, entry):
@@ -727,7 +732,6 @@ class DirDiff(Gtk.VBox, tree.TreeviewCommon, MeldDoc):
         self.recompute_label()
         self.scheduler.remove_all_tasks()
         self.recursively_update(Gtk.TreePath.new_first())
-        self._update_diffmaps()
 
     def get_comparison(self):
         root = self.model.get_iter_first()
@@ -919,7 +923,6 @@ class DirDiff(Gtk.VBox, tree.TreeviewCommon, MeldDoc):
         self._scan_in_progress -= 1
         self.force_cursor_recalculate = True
         self.treeview[0].set_cursor(Gtk.TreePath.new_first())
-        self._update_diffmaps()
 
     def _show_identical_status(self):
         primary = _("Folders have no differences")
@@ -1271,13 +1274,11 @@ class DirDiff(Gtk.VBox, tree.TreeviewCommon, MeldDoc):
                 view.expand_row(row.path, False)
 
         self._do_to_others(view, self.treeview, "expand_row", (path, False))
-        self._update_diffmaps()
 
     @Template.Callback()
     def on_treeview_row_collapsed(self, view, me, path):
         self.row_expansions.discard(str(path))
         self._do_to_others(view, self.treeview, "collapse_row", (path,))
-        self._update_diffmaps()
 
     @Template.Callback()
     def on_treeview_focus_in_event(self, tree, event):
@@ -1524,31 +1525,6 @@ class DirDiff(Gtk.VBox, tree.TreeviewCommon, MeldDoc):
                 })
         return different
 
-    def get_state_traversal(self, diffmapindex):
-        def tree_state_iter():
-            treeindex = (0, self.num_panes-1)[diffmapindex]
-            treeview = self.treeview[treeindex]
-            row_states = []
-
-            def recurse_tree_states(rowiter):
-                row_states.append(
-                    self.model.get_state(rowiter.iter, treeindex))
-                if treeview.row_expanded(rowiter.path):
-                    for row in rowiter.iterchildren():
-                        recurse_tree_states(row)
-            recurse_tree_states(next(iter(self.model)))
-            row_states.append(None)
-
-            numlines = float(len(row_states) - 1)
-            chunkstart, laststate = 0, row_states[0]
-            for index, state in enumerate(row_states):
-                if state != laststate:
-                    action = self.chunk_type_map[laststate]
-                    if action is not None:
-                        yield (action, chunkstart / numlines, index / numlines)
-                    chunkstart, laststate = index, state
-        return tree_state_iter
-
     def set_num_panes(self, num_panes):
         if num_panes == self.num_panes or num_panes not in (1, 2, 3):
             return
@@ -1558,20 +1534,16 @@ class DirDiff(Gtk.VBox, tree.TreeviewCommon, MeldDoc):
         for treeview in self.treeview:
             treeview.set_model(self.model)
 
-        for (w, i) in zip(self.diffmap, (0, num_panes - 1)):
-            scroll = self.scrolledwindow[i].get_vscrollbar()
-            idx = 1 if i else 0
-            w.setup(scroll, self.get_state_traversal(idx))
 
         for widget in (
                 self.vbox[:num_panes] + self.file_toolbar[:num_panes] +
-                self.diffmap[:num_panes] + self.linkmap[:num_panes - 1] +
+                self.chunkmap[:num_panes] + self.linkmap[:num_panes - 1] +
                 self.dummy_toolbar_linkmap[:num_panes - 1]):
             widget.show()
 
         for widget in (
                 self.vbox[num_panes:] + self.file_toolbar[num_panes:] +
-                self.diffmap[num_panes:] + self.linkmap[num_panes - 1:] +
+                self.chunkmap[num_panes:] + self.linkmap[num_panes - 1:] +
                 self.dummy_toolbar_linkmap[num_panes - 1:]):
             widget.hide()
 
@@ -1604,11 +1576,6 @@ class DirDiff(Gtk.VBox, tree.TreeviewCommon, MeldDoc):
         self.custom_labels = labels
         self.recompute_label()
 
-    def _update_diffmaps(self):
-        for diffmap in self.diffmap:
-            diffmap.on_diffs_changed()
-            diffmap.queue_draw()
-
     def on_file_changed(self, changed_filename):
         """When a file has changed, try to find it in our tree
            and update its status if necessary
@@ -1647,7 +1614,6 @@ class DirDiff(Gtk.VBox, tree.TreeviewCommon, MeldDoc):
         # do the update
         for path in changed_paths:
             self._update_item_state(model.get_iter(path))
-        self._update_diffmaps()
         self.force_cursor_recalculate = True
 
     @Template.Callback()
diff --git a/meld/resources/ui/dirdiff.ui b/meld/resources/ui/dirdiff.ui
index 603868ff..cdb47e19 100644
--- a/meld/resources/ui/dirdiff.ui
+++ b/meld/resources/ui/dirdiff.ui
@@ -3,6 +3,9 @@
 <interface>
   <requires lib="gtk+" version="3.20"/>
   <requires lib="meld.ui.gladesupport" version="0.0"/>
+  <object class="GtkAdjustment" id="pane_adjustment0"/>
+  <object class="GtkAdjustment" id="pane_adjustment1"/>
+  <object class="GtkAdjustment" id="pane_adjustment2"/>
   <template class="DirDiff" parent="GtkVBox">
     <property name="visible">True</property>
     <property name="can_focus">False</property>
@@ -40,7 +43,7 @@
             </style>
           </object>
           <packing>
-            <property name="left_attach">1</property>
+            <property name="left_attach">0</property>
             <property name="top_attach">0</property>
           </packing>
         </child>
@@ -74,7 +77,7 @@
             </style>
           </object>
           <packing>
-            <property name="left_attach">3</property>
+            <property name="left_attach">2</property>
             <property name="top_attach">0</property>
           </packing>
         </child>
@@ -108,34 +111,10 @@
             </style>
           </object>
           <packing>
-            <property name="left_attach">5</property>
-            <property name="top_attach">0</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkToolbar" id="dummy_toolbar_diffmap1">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <style>
-              <class name="meld-notebook-toolbar"/>
-            </style>
-          </object>
-          <packing>
-            <property name="left_attach">6</property>
+            <property name="left_attach">4</property>
             <property name="top_attach">0</property>
           </packing>
         </child>
-        <child>
-          <object class="DiffMap" id="diffmap1">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="vexpand">True</property>
-          </object>
-          <packing>
-            <property name="left_attach">6</property>
-            <property name="top_attach">1</property>
-          </packing>
-        </child>
         <child>
           <object class="GtkToolbar" id="dummy_toolbar_linkmap1">
             <property name="visible">True</property>
@@ -145,7 +124,7 @@
             </style>
           </object>
           <packing>
-            <property name="left_attach">4</property>
+            <property name="left_attach">3</property>
             <property name="top_attach">0</property>
           </packing>
         </child>
@@ -159,31 +138,7 @@
             <signal name="scroll-event" handler="on_linkmap_scroll_event" swapped="no"/>
           </object>
           <packing>
-            <property name="left_attach">4</property>
-            <property name="top_attach">1</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkToolbar" id="dummy_toolbar_diffmap0">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <style>
-              <class name="meld-notebook-toolbar"/>
-            </style>
-          </object>
-          <packing>
-            <property name="left_attach">0</property>
-            <property name="top_attach">0</property>
-          </packing>
-        </child>
-        <child>
-          <object class="DiffMap" id="diffmap0">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="vexpand">True</property>
-          </object>
-          <packing>
-            <property name="left_attach">0</property>
+            <property name="left_attach">3</property>
             <property name="top_attach">1</property>
           </packing>
         </child>
@@ -196,7 +151,7 @@
             </style>
           </object>
           <packing>
-            <property name="left_attach">2</property>
+            <property name="left_attach">1</property>
             <property name="top_attach">0</property>
           </packing>
         </child>
@@ -210,7 +165,7 @@
             <signal name="scroll-event" handler="on_linkmap_scroll_event" swapped="no"/>
           </object>
           <packing>
-            <property name="left_attach">2</property>
+            <property name="left_attach">1</property>
             <property name="top_attach">1</property>
           </packing>
         </child>
@@ -238,8 +193,7 @@
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="vexpand">True</property>
-                <property name="window_placement">top-right</property>
-                <property name="overlay_scrolling">False</property>
+                <property name="vadjustment">pane_adjustment0</property>
                 <child>
                   <object class="GtkTreeView" id="treeview0">
                     <property name="visible">True</property>
@@ -267,7 +221,7 @@
             </child>
           </object>
           <packing>
-            <property name="left_attach">1</property>
+            <property name="left_attach">0</property>
             <property name="top_attach">1</property>
           </packing>
         </child>
@@ -295,7 +249,7 @@
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="vexpand">True</property>
-                <property name="overlay_scrolling">False</property>
+                <property name="vadjustment">pane_adjustment1</property>
                 <child>
                   <object class="GtkTreeView" id="treeview1">
                     <property name="visible">True</property>
@@ -323,7 +277,7 @@
             </child>
           </object>
           <packing>
-            <property name="left_attach">3</property>
+            <property name="left_attach">2</property>
             <property name="top_attach">1</property>
           </packing>
         </child>
@@ -351,6 +305,7 @@
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="vexpand">True</property>
+                <property name="vadjustment">pane_adjustment2</property>
                 <property name="overlay_scrolling">False</property>
                 <child>
                   <object class="GtkTreeView" id="treeview2">
@@ -378,11 +333,88 @@
               </packing>
             </child>
           </object>
+          <packing>
+            <property name="left_attach">4</property>
+            <property name="top_attach">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkRevealer" id="overview_map_revealer">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="transition_type">slide-right</property>
+            <property name="reveal_child">True</property>
+            <child>
+              <object class="GtkBox" id="chunkmap_hbox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="spacing">2</property>
+                <child>
+                  <object class="TreeViewChunkMap" id="chunkmap0">
+                    <property name="visible">True</property>
+                    <property name="width_request">15</property>
+                    <property name="adjustment">pane_adjustment0</property>
+                    <property name="treeview">treeview0</property>
+                    <property name="treeview_idx">0</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="TreeViewChunkMap" id="chunkmap1">
+                    <property name="visible">True</property>
+                    <property name="width_request">15</property>
+                    <property name="adjustment">pane_adjustment1</property>
+                    <property name="treeview">treeview1</property>
+                    <property name="treeview_idx">1</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="TreeViewChunkMap" id="chunkmap2">
+                    <property name="visible">True</property>
+                    <property name="width_request">15</property>
+                    <property name="adjustment">pane_adjustment2</property>
+                    <property name="treeview">treeview2</property>
+                    <property name="treeview_idx">2</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+                <style>
+                  <class name="sourcemap-container"/>
+                </style>
+              </object>
+            </child>
+          </object>
           <packing>
             <property name="left_attach">5</property>
             <property name="top_attach">1</property>
           </packing>
         </child>
+        <child>
+          <object class="GtkToolbar" id="dummy_toolbar_overview_map">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <style>
+              <class name="meld-notebook-toolbar"/>
+            </style>
+          </object>
+          <packing>
+            <property name="left_attach">5</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
       </object>
       <packing>
         <property name="expand">True</property>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]