[meld] diffgrid: Allow resizing file and folder comparison panes (bgo#576934)



commit c9cd394c7f48deca8b4dfa70c4e0247f8e188ba4
Author: Marco Brito <bcaza null net>
Date:   Wed Jun 4 15:12:42 2014 +0100

    diffgrid: Allow resizing file and folder comparison panes (bgo#576934)
    
    To individual adjust the width of each pane in a file or folder
    comparison is something that can be pratical good to have. But by using
    GtkGrid for layout its view creates a limitation of not allowing pane
    resizing. Here is an implementation of a GtkGrid based custom widget,
    by overriding its childrens size allocation and adding in the row at
    the top of each LinkMap a drag handle, the size and position can easy
    be set.

 data/ui/dirdiff.ui      |   54 +--------
 data/ui/filediff.ui     |    2 +-
 meld/diffgrid.py        |  312 +++++++++++++++++++++++++++++++++++++++++++++++
 meld/ui/catalog.xml     |    6 +
 meld/ui/gladesupport.py |    1 +
 5 files changed, 321 insertions(+), 54 deletions(-)
---
diff --git a/data/ui/dirdiff.ui b/data/ui/dirdiff.ui
index 4ee0f01..4478b01 100644
--- a/data/ui/dirdiff.ui
+++ b/data/ui/dirdiff.ui
@@ -102,7 +102,7 @@
         <property name="visible">True</property>
         <property name="can_focus">False</property>
         <child>
-          <object class="GtkGrid" id="grid">
+          <object class="DiffGrid" id="grid">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
             <child>
@@ -330,58 +330,6 @@
               </packing>
             </child>
             <child>
-              <object class="GtkLabel" id="label5">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="xalign">0</property>
-              </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">0</property>
-                <property name="width">1</property>
-                <property name="height">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel" id="label6">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="xalign">0</property>
-              </object>
-              <packing>
-                <property name="left_attach">2</property>
-                <property name="top_attach">0</property>
-                <property name="width">1</property>
-                <property name="height">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel" id="label7">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="xalign">0</property>
-              </object>
-              <packing>
-                <property name="left_attach">4</property>
-                <property name="top_attach">0</property>
-                <property name="width">1</property>
-                <property name="height">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel" id="label4">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="xalign">0</property>
-              </object>
-              <packing>
-                <property name="left_attach">6</property>
-                <property name="top_attach">0</property>
-                <property name="width">1</property>
-                <property name="height">1</property>
-              </packing>
-            </child>
-            <child>
               <object class="GtkVBox" id="vbox0">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
diff --git a/data/ui/filediff.ui b/data/ui/filediff.ui
index 4e43948..1fb7429 100644
--- a/data/ui/filediff.ui
+++ b/data/ui/filediff.ui
@@ -111,7 +111,7 @@
         <signal name="key-press-event" handler="on_key_event" swapped="no"/>
         <signal name="key-release-event" handler="on_key_event" swapped="no"/>
         <child>
-          <object class="GtkGrid" id="grid">
+          <object class="DiffGrid" id="grid">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
             <property name="margin_top">0</property>
diff --git a/meld/diffgrid.py b/meld/diffgrid.py
new file mode 100644
index 0000000..556d0dd
--- /dev/null
+++ b/meld/diffgrid.py
@@ -0,0 +1,312 @@
+# Copyright (C) 2014 Marco Brito <bcaza null net>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from gi.repository import Gtk
+from gi.repository import Gdk
+
+
+class DiffGrid(Gtk.Grid):
+    __gtype_name__ = "DiffGrid"
+
+    def __init__(self):
+        Gtk.Grid.__init__(self)
+        self._in_drag = False
+        self._drag_pos = -1
+        self._drag_handle = None
+        self._handle1 = HandleWindow()
+        self._handle2 = HandleWindow()
+
+    def do_realize(self):
+        Gtk.Grid.do_realize(self)
+        self._handle1.realize(self)
+        self._handle2.realize(self)
+
+    def do_unrealize(self):
+        self._handle1.unrealize()
+        self._handle2.unrealize()
+        Gtk.Grid.do_unrealize(self)
+
+    def do_map(self):
+        Gtk.Grid.do_map(self)
+        drag = self.get_child_at(2, 0)
+        if drag and drag.get_visible():
+            self._handle1.set_visible(True)
+
+        drag = self.get_child_at(4, 0)
+        if drag and drag.get_visible():
+            self._handle2.set_visible(True)
+
+    def do_unmap(self):
+        self._handle1.set_visible(False)
+        self._handle2.set_visible(False)
+        Gtk.Grid.do_unmap(self)
+
+    def _handle_set_prelight(self, window, flag):
+        if hasattr(window, "handle"):
+            window.handle.set_prelight(flag)
+            return True
+        return False
+
+    def do_enter_notify_event(self, event):
+        return self._handle_set_prelight(event.window, True)
+
+    def do_leave_notify_event(self, event):
+        if not self._in_drag:
+            return self._handle_set_prelight(event.window, False)
+        return False
+
+    def do_button_press_event(self, event):
+        if event.button & Gdk.BUTTON_PRIMARY:
+            self._drag_pos = event.x
+            self._in_drag = True
+            return True
+        return False
+
+    def do_button_release_event(self, event):
+        if event.button & Gdk.BUTTON_PRIMARY:
+            self._in_drag = False
+            return True
+        return False
+
+    def do_motion_notify_event(self, event):
+        if event.state & Gdk.ModifierType.BUTTON1_MASK:
+            if hasattr(event.window, "handle"):
+                x, y = event.window.get_position()
+                pos = round(x + event.x - self._drag_pos)
+                event.window.handle.set_position(pos)
+                self._drag_handle = event.window.handle
+                self.queue_resize_no_redraw()
+                return True
+        return False
+
+    def _calculate_positions(self, xmin, xmax, wlink1, wlink2,
+                             wpane1, wpane2, wpane3):
+        wremain = max(0, xmax - xmin - wlink1 - wlink2)
+        pos1 = self._handle1.get_position(wremain, xmin)
+        pos2 = self._handle2.get_position(wremain, xmin + wlink1)
+
+        if not self._drag_handle:
+            npanes = 0
+            if wpane1 > 0:
+                npanes += 1
+            if wpane2 > 0:
+                npanes += 1
+            if wpane3 > 0:
+                npanes += 1
+            wpane = float(wremain) / max(1, npanes)
+            if wpane1 > 0:
+                wpane1 = wpane
+            if wpane2 > 0:
+                wpane2 = wpane
+            if wpane3 > 0:
+                wpane3 = wpane
+
+        xminlink1 = xmin + wpane1
+        xmaxlink2 = xmax - wpane3 - wlink2
+        wlinkpane = wlink1 + wpane2
+
+        if wpane1 == 0:
+            pos1 = xminlink1
+        if wpane3 == 0:
+            pos2 = xmaxlink2
+        if wpane2 == 0:
+            if wpane3 == 0:
+                pos1 = pos2 - wlink2
+            else:
+                pos2 = pos1 + wlink1
+
+        if self._drag_handle == self._handle2:
+            xminlink2 = xminlink1 + wlinkpane
+            pos2 = min(max(xminlink2, pos2), xmaxlink2)
+            xmaxlink1 = pos2 - wlinkpane
+            pos1 = min(max(xminlink1, pos1), xmaxlink1)
+        else:
+            xmaxlink1 = xmaxlink2 - wlinkpane
+            pos1 = min(max(xminlink1, pos1), xmaxlink1)
+            xminlink2 = pos1 + wlinkpane
+            pos2 = min(max(xminlink2, pos2), xmaxlink2)
+
+        self._handle1.set_position(pos1)
+        self._handle2.set_position(pos2)
+        return int(round(pos1)), int(round(pos2))
+
+    def do_size_allocate(self, allocation):
+        self.set_allocation(allocation)
+        wcols, hrows = self._get_min_sizes()
+        yrows = [allocation.y,
+                 allocation.y + hrows[0],
+                 allocation.y + allocation.height]
+
+        wmap1, wpane1, wlink1, wpane2, wlink2, wpane3, wmap2 = wcols
+        xmin = allocation.x + wmap1
+        xmax = allocation.x + allocation.width - wmap2
+        pos1, pos2 = self._calculate_positions(xmin, xmax,
+                                               wlink1, wlink2,
+                                               wpane1, wpane2, wpane3)
+        xmap1 = allocation.x
+        xpane1 = xmin
+        wpane1 = pos1 - xpane1
+        xlink1 = pos1
+        xpane2 = pos1 + wlink1
+        wpane2 = pos2 - xpane2
+        xlink2 = pos2
+        xpane3 = pos2 + wlink2
+        wpane3 = xmax - xpane3
+        xmap2 = xmax
+        self._set_column_allocation(0, xmap1, wmap1, yrows)
+        self._set_column_allocation(1, xpane1, wpane1, yrows)
+        self._set_column_allocation(2, xlink1, wlink1, yrows)
+        self._set_column_allocation(3, xpane2, wpane2, yrows)
+        self._set_column_allocation(4, xlink2, wlink2, yrows)
+        self._set_column_allocation(5, xpane3, wpane3, yrows)
+        self._set_column_allocation(6, xmap2, wmap2, yrows)
+
+        if self.get_realized():
+            mapped = self.get_mapped()
+            ydrag = yrows[0]
+            hdrag = yrows[1] - yrows[0]
+            self._handle1.set_visible(mapped and wlink1 > 0)
+            self._handle1.move_resize(xlink1, ydrag, wlink1, hdrag)
+            self._handle2.set_visible(mapped and wlink2 > 0)
+            self._handle2.move_resize(xlink2, ydrag, wlink2, hdrag)
+
+    def _set_column_allocation(self, col, x, width, rows):
+        for row in range(0, 2):
+            child = self.get_child_at(col, row)
+            if child and child.get_visible():
+                alloc = self.get_allocation()
+                alloc.x = x
+                alloc.y = rows[row]
+                alloc.width = width
+                alloc.height = rows[row+1] - alloc.y
+                child.size_allocate(alloc)
+
+    def _get_min_sizes(self):
+        hrows = [0] * 2
+        wcols = [0] * 7
+        for row in range(0, 2):
+            for col in range(0, 7):
+                child = self.get_child_at(col, row)
+                if child and child.get_visible():
+                    msize, nsize = child.get_preferred_size()
+                    wcols[col] = max(wcols[col], msize.width, nsize.width)
+                    hrows[row] = max(hrows[row], msize.height, nsize.height)
+        return wcols, hrows
+
+    def do_draw(self, context):
+        Gtk.Grid.do_draw(self, context)
+        self._handle1.draw(context)
+        self._handle2.draw(context)
+
+
+class HandleWindow():
+    def __init__(self):
+        self._widget = None
+        self._window = None
+        self._area_x = -1
+        self._area_y = -1
+        self._area_width = 1
+        self._area_height = 1
+        self._prelit = False
+        self._pos = 0.0
+        self._transform = (0, 0)
+
+    def get_position(self, width, xtrans):
+        self._transform = (width, xtrans)
+        return float(self._pos * width) + xtrans
+
+    def set_position(self, pos):
+        width, xtrans = self._transform
+        self._pos = float(pos - xtrans) / width
+
+    def realize(self, widget):
+        attr = Gdk.WindowAttr()
+        attr.window_type = Gdk.WindowType.CHILD
+        attr.x = self._area_x
+        attr.y = self._area_y
+        attr.width = self._area_width
+        attr.height = self._area_height
+        attr.wclass = Gdk.WindowWindowClass.INPUT_OUTPUT
+        attr.event_mask = (widget.get_events() |
+                           Gdk.EventMask.BUTTON_PRESS_MASK |
+                           Gdk.EventMask.BUTTON_RELEASE_MASK |
+                           Gdk.EventMask.ENTER_NOTIFY_MASK |
+                           Gdk.EventMask.LEAVE_NOTIFY_MASK |
+                           Gdk.EventMask.POINTER_MOTION_MASK)
+        attr.cursor = Gdk.Cursor.new_for_display(widget.get_display(),
+                                                 Gdk.CursorType.
+                                                 SB_H_DOUBLE_ARROW)
+        attr_mask = (Gdk.WindowAttributesType.X |
+                     Gdk.WindowAttributesType.Y |
+                     Gdk.WindowAttributesType.CURSOR)
+
+        parent = widget.get_parent_window()
+        self._window = Gdk.Window(parent, attr, attr_mask)
+        self._window.handle = self
+        self._widget = widget
+        self._widget.register_window(self._window)
+
+    def unrealize(self):
+        self._widget.unregister_window(self._window)
+
+    def set_visible(self, visible):
+        if visible:
+            self._window.show()
+        else:
+            self._window.hide()
+
+    def move_resize(self, x, y, width, height):
+        self._window.move_resize(x, y, width, height)
+        self._area_x = x
+        self._area_y = y
+        self._area_width = width
+        self._area_height = height
+
+    def set_prelight(self, flag):
+        self._prelit = flag
+        self._widget.queue_draw_area(self._area_x, self._area_y,
+                                     self._area_width, self._area_height)
+
+    def draw(self, cairocontext):
+        alloc = self._widget.get_allocation()
+        padding = 5
+        x = self._area_x - alloc.x + padding
+        y = self._area_y - alloc.y + padding
+        width = max(0, self._area_width - 2 * padding)
+        height = max(0, self._area_height - 2 * padding)
+
+        if width == 0 or height == 0:
+            return
+
+        stylecontext = self._widget.get_style_context()
+        state = self._widget.get_state_flags()
+        if self._widget.is_focus():
+            state |= Gtk.StateFlags.SELECTED
+        if self._prelit:
+            state |= Gtk.StateFlags.PRELIGHT
+
+        if Gtk.cairo_should_draw_window(cairocontext, self._window):
+            stylecontext.save()
+            stylecontext.set_state(state)
+            stylecontext.add_class(Gtk.STYLE_CLASS_PANE_SEPARATOR)
+            color = stylecontext.get_background_color(state)
+            if color.alpha > 0.0:
+                Gtk.render_handle(stylecontext, cairocontext,
+                                  x, y, width, height)
+            else:
+                xcenter = x + width / 2.0
+                Gtk.render_line(stylecontext, cairocontext,
+                                xcenter, y, xcenter, y + height)
+            stylecontext.restore()
diff --git a/meld/ui/catalog.xml b/meld/ui/catalog.xml
index ddfdf83..0bc2954 100644
--- a/meld/ui/catalog.xml
+++ b/meld/ui/catalog.xml
@@ -3,6 +3,12 @@
     <init-function>glade_python_init</init-function>
 
     <glade-widget-classes>
+        <glade-widget-class title="DiffGrid" name="DiffGrid" generic-name="diffgrid">
+            <properties>
+                <property id="n-rows" default="2" query="False"/>
+                <property id="n-columns" default="7" query="False"/>
+            </properties>
+        </glade-widget-class>
         <glade-widget-class title="DiffMap" name="DiffMap" generic-name="diffmap"/>
         <glade-widget-class title="LinkMap" name="LinkMap" generic-name="linkmap"/>
         <glade-widget-class title="MeldSourceView" name="MeldSourceView" generic-name="meldsourceview"/>
diff --git a/meld/ui/gladesupport.py b/meld/ui/gladesupport.py
index 2827db8..ce77eae 100644
--- a/meld/ui/gladesupport.py
+++ b/meld/ui/gladesupport.py
@@ -1,4 +1,5 @@
 
+from meld import diffgrid
 from meld import diffmap
 from meld import linkmap
 from meld import preferences


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