[meld] Split out DiffMap functionality into new widget



commit 48b455adfc3ee40ec7a274f96eea53a59064a6db
Author: Kai Willadsen <kai willadsen gmail com>
Date:   Sun Mar 14 19:19:05 2010 +1000

    Split out DiffMap functionality into new widget
    
    The DiffMap widget (the bars showing a coloured summary of changes at
    the left and right of the main file comparison window) has always been
    a purely custom creation based on gtk.DrawingArea. This commit breaks it
    out into a separate file and makes it a DrawingArea subclass.
    
    This also changes the drawing done, syncing the colours used with the
    LinkMap and TextView, and allowing thinner lines for insert chunks. In
    addition, our style-dependent calculations should now be more robust for
    unusual themes.

 data/ui/filediff.glade |   14 +---
 meld/diffmap.py        |  151 ++++++++++++++++++++++++++++++++++++++++++++++++
 meld/filediff.py       |   53 ++---------------
 3 files changed, 161 insertions(+), 57 deletions(-)
---
diff --git a/data/ui/filediff.glade b/data/ui/filediff.glade
index 9c5f7f5..91b455d 100644
--- a/data/ui/filediff.glade
+++ b/data/ui/filediff.glade
@@ -216,12 +216,9 @@
               </packing>
             </child>
             <child>
-              <widget class="GtkDrawingArea" id="diffmap1">
-                <property name="width_request">20</property>
+              <widget class="Custom" id="diffmap1">
                 <property name="visible">True</property>
-                <property name="events">GDK_BUTTON_PRESS_MASK</property>
-                <signal name="expose_event" handler="on_diffmap_expose_event"/>
-                <signal name="button_press_event" handler="on_diffmap_button_press_event"/>
+                <property name="creation_function">meld.diffmap.create_diffmap</property>
               </widget>
               <packing>
                 <property name="left_attach">6</property>
@@ -256,12 +253,9 @@
               </packing>
             </child>
             <child>
-              <widget class="GtkDrawingArea" id="diffmap0">
-                <property name="width_request">20</property>
+              <widget class="Custom" id="diffmap0">
                 <property name="visible">True</property>
-                <property name="events">GDK_BUTTON_PRESS_MASK</property>
-                <signal name="expose_event" handler="on_diffmap_expose_event"/>
-                <signal name="button_press_event" handler="on_diffmap_button_press_event"/>
+                <property name="creation_function">meld.diffmap.create_diffmap</property>
               </widget>
               <packing>
                 <property name="top_attach">1</property>
diff --git a/meld/diffmap.py b/meld/diffmap.py
new file mode 100644
index 0000000..3b31b60
--- /dev/null
+++ b/meld/diffmap.py
@@ -0,0 +1,151 @@
+### Copyright (C) 2002-2009 Stephen Kennedy <stevek gnome org>
+### Copyright (C) 2009-2010 Kai Willadsen <kai willadsen gmail com>
+
+### 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, write to the Free Software
+### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import gobject
+import gtk
+
+
+class DiffMap(gtk.DrawingArea):
+
+    __gtype_name__ = "DiffMap"
+
+    __gsignals__ = {
+        'expose-event': 'override',
+        'button-press-event': 'override',
+        'size-request': 'override',
+    }
+
+    def __init__(self):
+        gtk.DrawingArea.__init__(self)
+        self.add_events(gtk.gdk.BUTTON_PRESS_MASK)
+        self._scrolladj = None
+        self._difffunc = lambda: None
+        self._handlers = []
+        self._y_offset = 0
+        self._h_offset = 0
+        self._scroll_y = 0
+        self._scroll_height = 0
+        self._num_lines = 0
+
+    def setup(self, scrollbar, textbuffer, change_chunk_fn):
+        for (o, h) in self._handlers:
+            o.disconnect(h)
+
+        self._scrolladj = scrollbar.get_adjustment()
+        self.on_scrollbar_style_set(scrollbar, None)
+        self.on_scrollbar_size_allocate(scrollbar, scrollbar.allocation)
+        self.on_textbuffer_changed(textbuffer)
+        scroll_style_hid = scrollbar.connect("style-set",
+                                             self.on_scrollbar_style_set)
+        scroll_size_hid = scrollbar.connect("size-allocate",
+                                            self.on_scrollbar_size_allocate)
+        buffer_changed_hid = textbuffer.connect("changed",
+                                                self.on_textbuffer_changed)
+        self._handlers = [(scrollbar, scroll_style_hid),
+                          (scrollbar, scroll_size_hid),
+                          (textbuffer, buffer_changed_hid)]
+        self._difffunc = change_chunk_fn
+        self.queue_draw()
+
+    def on_scrollbar_style_set(self, scrollbar, previous_style):
+        stepper_size = scrollbar.style_get_property("stepper-size")
+        steppers = [scrollbar.style_get_property(x) for x in
+                    ("has-backward-stepper", "has-secondary-forward-stepper",
+                     "has-secondary-backward-stepper", "has-forward-stepper")]
+        stepper_spacing = scrollbar.style_get_property("stepper-spacing")
+
+        offset = stepper_size * steppers[0:2].count(True)
+        shorter = stepper_size * steppers.count(True)
+        if steppers[0] or steppers[1]:
+            offset += stepper_spacing
+            shorter += stepper_spacing
+        if steppers[2] or steppers[3]:
+            shorter += stepper_spacing
+        self._y_offset = offset
+        self._h_offset = shorter
+        self.queue_draw()
+
+    def on_scrollbar_size_allocate(self, scrollbar, allocation):
+        self._scroll_y = allocation.y
+        self._scroll_height = allocation.height
+        self.queue_draw()
+
+    def on_textbuffer_changed(self, textbuffer):
+        num_lines = textbuffer.get_line_count()
+        if num_lines != self._num_lines:
+            self._num_lines = num_lines
+            self.queue_draw()
+
+    def do_expose_event(self, event):
+        scale = float(self._scroll_height - self._h_offset) / self._num_lines
+        y_start = self._scroll_y - self.allocation.y + self._y_offset
+
+        context = self.window.cairo_create()
+        context.translate(0, y_start)
+        context.set_line_width(1)
+        ctab = {"conflict": (1.0, 0.75294117647058822, 0.79607843137254897),
+                "insert": (0.75686274509803919, 1.0, 0.75686274509803919),
+                "replace": (0.8666666666666667, 0.93333333333333335, 1.0),
+                "delete": (0.75686274509803919, 1.0, 0.75686274509803919)}
+        darken = lambda color: [x * 0.8 for x in color]
+
+        xpad = self.style_get_property('x-padding')
+        x0 = xpad
+        x1 = self.allocation.width - 2 * xpad
+        for c in self._difffunc():
+            color = ctab[c[0]]
+            y0 = round(scale * c[1]) - 0.5
+            y1 = round(scale * c[2]) - 0.5
+            context.set_source_rgb(*color)
+            context.rectangle(x0, y0, x1, int(y1 - y0))
+            context.fill_preserve()
+            context.set_source_rgb(*darken(color))
+            context.stroke()
+
+    def do_button_press_event(self, event):
+        if event.button == 1:
+            y_start = self.allocation.y - self._scroll_y - self._y_offset
+            total_height = self._scroll_height - self._h_offset
+            fraction = (event.y + y_start) / total_height
+
+            adj = self._scrolladj
+            val = fraction * adj.upper - adj.page_size / 2
+            upper = adj.upper - adj.page_size
+            adj.set_value(max(min(upper, val), adj.lower))
+            return True
+        return False
+
+    def do_size_request(self, request):
+        request.width = self.style_get_property('width')
+
+gtk.widget_class_install_style_property(DiffMap,
+                                        ('width', float,
+                                         'Width',
+                                         'Width of the bar',
+                                         0.0, gobject.G_MAXFLOAT, 20,
+                                         gobject.PARAM_READABLE))
+gtk.widget_class_install_style_property(DiffMap,
+                                        ('x-padding', float,
+                                         'Width-wise padding',
+                                         'Padding to be left between left and '
+                                         'right edges and change blocks',
+                                         0.0, gobject.G_MAXFLOAT, 2.5,
+                                         gobject.PARAM_READABLE))
+
+
+def create_diffmap(str1, str2, int1, int2):
+    return DiffMap()
diff --git a/meld/filediff.py b/meld/filediff.py
index 1f875ab..3eb4ead 100644
--- a/meld/filediff.py
+++ b/meld/filediff.py
@@ -33,7 +33,6 @@ from ui import gnomeglade
 import misc
 import melddoc
 import paths
-import cairo
 
 from util.sourceviewer import srcviewer
 
@@ -1142,52 +1141,6 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
                 lm.window.invalidate_rect(None, True)
                 lm.window.process_updates(True)
 
-        #
-        # scrollbar drawing
-        #
-    def on_diffmap__expose_event(self, area, event):
-        def rect(ctx, color, y0,y1, xpad=2.5,width=area.get_allocation().width):
-            ctx.set_source(color)
-            context.rectangle(xpad, y0, width-2*xpad, max(2, y1-y0))
-            ctx.fill_preserve()
-            ctx.set_source_rgba(0, 0, 0, 1.0)
-            ctx.stroke()
-
-        diffmapindex = self.diffmap.index(area)
-        textindex = (0, self.num_panes-1)[diffmapindex]
-        scroll = self.scrolledwindow[textindex].get_vscrollbar()
-        stepper_size = scroll.style_get_property("stepper-size")
-
-        context = area.window.cairo_create() # setup cairo
-        context.translate( 0, stepper_size )
-        scale = float(scroll.get_allocation().height - 2*stepper_size) / self.textbuffer[textindex].get_line_count()
-
-        context.set_line_width(0.5)
-        solid_green = cairo.SolidPattern(.5, 1, .5, 0.25)
-        solid_red = cairo.SolidPattern(1, .5, .5, 0.75)
-        solid_blue = cairo.SolidPattern(.5, 1, 1, 0.25)
-        ctab = {"conflict":solid_red,
-                "insert":solid_green,
-                "replace":solid_blue,
-                "delete":solid_green}
-        for c in self.linediffer.single_changes(textindex):
-            assert c[0] != "equal"
-            rect(context, ctab[c[0]], scale*c[1], scale*c[2])
-
-    def on_diffmap_button_press_event(self, area, event):
-        if event.button == 1:
-            textindex = (0, self.num_panes-1)[self.diffmap.index(area)]
-            scroll = self.scrolledwindow[textindex].get_vscrollbar()
-            stepper_size = scroll.style_get_property("stepper-size")
-            alloc = scroll.get_allocation()
-            fraction = (event.y - (stepper_size + alloc.y) + area.get_allocation().y ) / (alloc.height - 2*stepper_size)
-            adj = self.scrolledwindow[textindex].get_vadjustment()
-            val = fraction * adj.upper - adj.page_size/2
-            upper = adj.upper - adj.page_size
-            adj.set_value( max( min(upper, val), 0) )
-            return 1
-        return 0
-
     def set_num_panes(self, n):
         if n != self.num_panes and n in (1,2,3):
             self.num_panes = n
@@ -1201,6 +1154,12 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
             tohide += self.linkmap[n-1:] + self.diffmap[n:]
             map( lambda x: x.hide(), tohide )
 
+            def chunk_change_fn(i):
+                return lambda: self.linediffer.single_changes(i)
+            for (w, i) in zip(self.diffmap, (0, self.num_panes - 1)):
+                scroll = self.scrolledwindow[i].get_vscrollbar()
+                w.setup(scroll, self.textbuffer[i], chunk_change_fn(i))
+
             for i in range(self.num_panes):
                 if self.bufferdata[i].modified:
                     self.statusimage[i].show()



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