[meld] Rework patch creation dialog



commit 46cf62c17cc3338e21c3c5c3684fbee78e3d370c
Author: Kai Willadsen <kai willadsen gmail com>
Date:   Sat Oct 9 08:56:51 2010 +1000

    Rework patch creation dialog
    
    This commit overhauls Meld's create-a-patch dialog. New features include
    being able to select which panes are used to create the patch and being
    able to create reverse patches.
    
    The corresponding action was also moved from the popup menu of a
    FileDiff to the File menu to improve discoverability.

 data/ui/filediff-ui.xml |    7 +-
 data/ui/filediff.ui     |  115 -----------------------
 data/ui/meldapp-ui.xml  |    2 +
 data/ui/patch-dialog.ui |  234 +++++++++++++++++++++++++++++++++++++++++++++++
 meld/filediff.py        |  130 ++++++++++++++++++++------
 5 files changed, 341 insertions(+), 147 deletions(-)
---
diff --git a/data/ui/filediff-ui.xml b/data/ui/filediff-ui.xml
index cedb010..1f37e15 100644
--- a/data/ui/filediff-ui.xml
+++ b/data/ui/filediff-ui.xml
@@ -8,6 +8,11 @@
   </toolbar>
 
   <menubar name="Menubar">
+    <menu action="FileMenu">
+      <placeholder name="FileActionsPlaceholder">
+        <menuitem action="MakePatch"/>
+      </placeholder>
+    </menu>
     <menu action="ChangesMenu">
       <placeholder name="ChangesActions">
         <menuitem action="PrevConflict"/>
@@ -30,8 +35,6 @@
     <menuitem action="Save" />
     <menuitem action="SaveAs" />
     <separator/>
-    <menuitem action="CreatePatch" />
-    <separator/>
     <menuitem action="Cut" />
     <menuitem action="Copy" />
     <menuitem action="Paste" />
diff --git a/data/ui/filediff.ui b/data/ui/filediff.ui
index a73ea61..7f0737c 100644
--- a/data/ui/filediff.ui
+++ b/data/ui/filediff.ui
@@ -451,119 +451,4 @@ Which ones would you like to save?</property>
       <action-widget response="-5">button_ok</action-widget>
     </action-widgets>
   </object>
-  <object class="GtkDialog" id="patchdialog">
-    <property name="visible">True</property>
-    <property name="title" translatable="yes">Create Patch</property>
-    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
-    <property name="skip_taskbar_hint">True</property>
-    <property name="skip_pager_hint">True</property>
-    <property name="has_separator">False</property>
-    <child internal-child="vbox">
-      <object class="GtkVBox" id="dialog-vbox3">
-        <property name="visible">True</property>
-        <child>
-          <object class="GtkScrolledWindow" id="scrolledwindow3">
-            <property name="width_request">600</property>
-            <property name="height_request">400</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="shadow_type">GTK_SHADOW_IN</property>
-            <child>
-              <object class="MeldSourceView" id="textview">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="accepts_tab">False</property>
-              </object>
-            </child>
-          </object>
-          <packing>
-            <property name="padding">6</property>
-            <property name="position">1</property>
-          </packing>
-        </child>
-        <child internal-child="action_area">
-          <object class="GtkHButtonBox" id="dialog-action_area3">
-            <property name="visible">True</property>
-            <property name="layout_style">GTK_BUTTONBOX_END</property>
-            <child>
-              <object class="GtkButton" id="button6">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="can_default">True</property>
-                <child>
-                  <object class="GtkAlignment" id="alignment1">
-                    <property name="visible">True</property>
-                    <property name="xscale">0</property>
-                    <property name="yscale">0</property>
-                    <child>
-                      <object class="GtkHBox" id="hbox2">
-                        <property name="visible">True</property>
-                        <property name="spacing">2</property>
-                        <child>
-                          <object class="GtkImage" id="image24">
-                            <property name="visible">True</property>
-                            <property name="stock">gtk-copy</property>
-                          </object>
-                          <packing>
-                            <property name="expand">False</property>
-                            <property name="fill">False</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkLabel" id="label9">
-                            <property name="visible">True</property>
-                            <property name="label" translatable="yes">Copy to Clipboard</property>
-                            <property name="use_underline">True</property>
-                          </object>
-                          <packing>
-                            <property name="expand">False</property>
-                            <property name="fill">False</property>
-                            <property name="position">1</property>
-                          </packing>
-                        </child>
-                      </object>
-                    </child>
-                  </object>
-                </child>
-              </object>
-            </child>
-            <child>
-              <object class="GtkButton" id="button7">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="can_default">True</property>
-                <property name="label">gtk-save-as</property>
-                <property name="use_stock">True</property>
-              </object>
-              <packing>
-                <property name="position">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="button9">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="can_default">True</property>
-                <property name="label">gtk-cancel</property>
-                <property name="use_stock">True</property>
-                <accelerator key="Escape" modifiers="" signal="activate"/>
-              </object>
-              <packing>
-                <property name="position">2</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="pack_type">GTK_PACK_END</property>
-          </packing>
-        </child>
-      </object>
-    </child>
-    <action-widgets>
-      <action-widget response="1">button6</action-widget>
-      <action-widget response="0">button7</action-widget>
-      <action-widget response="-6">button9</action-widget>
-    </action-widgets>
-  </object>
 </interface>
diff --git a/data/ui/meldapp-ui.xml b/data/ui/meldapp-ui.xml
index c5497e9..95bdf52 100644
--- a/data/ui/meldapp-ui.xml
+++ b/data/ui/meldapp-ui.xml
@@ -5,6 +5,8 @@
       <menuitem action="Save" />
       <menuitem action="SaveAs" />
       <separator/>
+      <placeholder name="FileActionsPlaceholder" />
+      <separator/>
       <menuitem action="Close" />
       <menuitem action="Quit" />
     </menu>
diff --git a/data/ui/patch-dialog.ui b/data/ui/patch-dialog.ui
new file mode 100644
index 0000000..0f2fcb6
--- /dev/null
+++ b/data/ui/patch-dialog.ui
@@ -0,0 +1,234 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy toplevel-contextual -->
+  <object class="GtkDialog" id="patchdialog">
+    <property name="visible">True</property>
+    <property name="border_width">12</property>
+    <property name="title" translatable="yes">Create Patch</property>
+    <property name="type_hint">dialog</property>
+    <property name="skip_taskbar_hint">True</property>
+    <property name="skip_pager_hint">True</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="dialog-vbox3">
+        <property name="visible">True</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkLabel" id="label1">
+            <property name="visible">True</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">Create a patch</property>
+            <attributes>
+              <attribute name="weight" value="bold"/>
+            </attributes>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkHBox" id="hbox1">
+            <property name="visible">True</property>
+            <child>
+              <object class="GtkLabel" id="label2">
+                <property name="visible">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="padding">12</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkVBox" id="vbox1">
+                <property name="visible">True</property>
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkLabel" id="label3">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Use differences between:</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkHBox" id="hbox2">
+                    <property name="visible">True</property>
+                    <child>
+                      <object class="GtkLabel" id="label4">
+                        <property name="visible">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="padding">12</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkVBox" id="vbox2">
+                        <property name="visible">True</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkRadioButton" id="left_radiobutton">
+                            <property name="label" translatable="yes">Left and middle panes</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">False</property>
+                            <property name="use_action_appearance">False</property>
+                            <property name="active">True</property>
+                            <property name="draw_indicator">True</property>
+                            <signal name="toggled" handler="on_buffer_selection_changed" swapped="no"/>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkRadioButton" id="right_radiobutton">
+                            <property name="label" translatable="yes">Middle and right panes</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">False</property>
+                            <property name="use_action_appearance">False</property>
+                            <property name="active">True</property>
+                            <property name="draw_indicator">True</property>
+                            <property name="group">left_radiobutton</property>
+                            <signal name="toggled" handler="on_buffer_selection_changed" swapped="no"/>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkCheckButton" id="reverse_checkbutton">
+                    <property name="label" translatable="yes">_Reverse patch direction</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="use_action_appearance">False</property>
+                    <property name="use_underline">True</property>
+                    <property name="draw_indicator">True</property>
+                    <signal name="toggled" handler="on_reverse_checkbutton_toggled" swapped="no"/>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkScrolledWindow" id="scrolledwindow">
+                    <property name="width_request">600</property>
+                    <property name="height_request">400</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hscrollbar_policy">automatic</property>
+                    <property name="vscrollbar_policy">automatic</property>
+                    <property name="shadow_type">in</property>
+                    <child>
+                      <object class="MeldSourceView" id="textview">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="accepts_tab">False</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="padding">6</property>
+                    <property name="position">3</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <object class="GtkHButtonBox" id="dialog-action_area">
+            <property name="visible">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="button1">
+                <property name="label" translatable="yes">Copy to Clipboard</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_action_appearance">False</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button9">
+                <property name="label">gtk-cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="receives_default">False</property>
+                <property name="use_stock">True</property>
+                <accelerator key="Escape" signal="activate"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button7">
+                <property name="label">gtk-save-as</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="receives_default">False</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="1">button1</action-widget>
+      <action-widget response="0">button7</action-widget>
+      <action-widget response="-6">button9</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/meld/filediff.py b/meld/filediff.py
index 8d0d280..a0b1afb 100644
--- a/meld/filediff.py
+++ b/meld/filediff.py
@@ -109,6 +109,101 @@ class BufferLines(object):
         return self.buf.get_line_count()
 
 
+class PatchDialog(gnomeglade.Component):
+
+    def __init__(self, filediff):
+        ui_file = paths.ui_dir("patch-dialog.ui")
+        gnomeglade.Component.__init__(self, ui_file, "patchdialog")
+
+        self.widget.set_transient_for(filediff.widget.get_toplevel())
+        self.prefs = filediff.prefs
+        self.prefs.notify_add(self.on_preference_changed)
+        self.filediff = filediff
+
+        buf = srcviewer.GtkTextBuffer()
+        self.textview.set_buffer(buf)
+        srcviewer.set_highlighting_enabled_from_mimetype(buf, "text/x-diff", True)
+        fontdesc = pango.FontDescription(self.prefs.get_current_font())
+        self.textview.modify_font(fontdesc)
+        self.textview.set_editable(False)
+
+        self.index_map = {self.left_radiobutton: (0, 1),
+                          self.right_radiobutton: (1, 2)}
+        self.left_patch = True
+        self.reverse_patch = self.reverse_checkbutton.get_active()
+
+        if self.filediff.num_panes < 3:
+            self.label3.hide()
+            self.hbox2.hide()
+
+    def on_preference_changed(self, key, value):
+        if key == "use_custom_font" or key == "custom_font":
+            fontdesc = pango.FontDescription(self.prefs.get_current_font())
+            self.textview.modify_font(fontdesc)
+
+    def on_buffer_selection_changed(self, radiobutton):
+        if not radiobutton.get_active():
+            return
+        self.left_patch = radiobutton == self.left_radiobutton
+        self.update_patch()
+
+    def on_reverse_checkbutton_toggled(self, checkbutton):
+        self.reverse_patch = checkbutton.get_active()
+        self.update_patch()
+
+    def update_patch(self):
+        indices = (0, 1)
+        if not self.left_patch:
+            indices = (1, 2)
+        if self.reverse_patch:
+            indices = (indices[1], indices[0])
+
+        texts = []
+        for b in self.filediff.textbuffer:
+            start, end = b.get_bounds()
+            text = b.get_text(start, end, False)
+            lines = text.splitlines(True)
+            texts.append(lines)
+
+        names = [self.filediff._get_pane_label(i) for i in range(3)]
+        prefix = os.path.commonprefix(names)
+        names = [n[prefix.rfind("/") + 1:] for n in names]
+
+        buf = self.textview.get_buffer()
+        text0, text1 = texts[indices[0]], texts[indices[1]]
+        name0, name1 = names[indices[0]], names[indices[1]]
+        diff_text = "".join(difflib.unified_diff(text0, text1, name0, name1))
+        buf.set_text(diff_text)
+
+    def run(self):
+        self.update_patch()
+
+        while 1:
+            result = self.widget.run()
+            if result < 0:
+                break
+
+            buf = self.textview.get_buffer()
+            txt = buf.get_text(buf.get_start_iter(), buf.get_end_iter(), False)
+
+            # Copy patch to clipboard
+            if result == 1:
+                clip = gtk.clipboard_get()
+                clip.set_text(txt)
+                clip.store()
+                break
+            # Save patch as a file
+            else:
+                # FIXME: These filediff methods are actually general utility.
+                filename = self.filediff._get_filename_for_saving(
+                                                         _("Save Patch As..."))
+                if filename:
+                    self.filediff._save_text_to_filename(filename, txt)
+                    break
+
+        self.widget.hide()
+
+
 ################################################################################
 #
 # FileDiff
@@ -223,7 +318,7 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
 
         actions = (
             ("FileOpen",          gtk.STOCK_OPEN,       None,            None, _("Open selected"), self.on_open_activate),
-            ("CreatePatch",       None,                 _("Create Patch"),  None, _("Create a patch"), self.make_patch),
+            ("MakePatch", None, _("Format as patch..."), None, _("Create a patch using differences between files"), self.make_patch),
             ("PrevConflict", None, _("Previous conflict"), "<Ctrl>I", _("Go to the previous conflict"), lambda x: self.on_next_conflict(gtk.gdk.SCROLL_UP)),
             ("NextConflict", None, _("Next conflict"), "<Ctrl>K", _("Go to the next conflict"), lambda x: self.on_next_conflict(gtk.gdk.SCROLL_DOWN)),
             ("PushLeft",  gtk.STOCK_GO_BACK,    _("Push to left"),    "<Alt>Left", _("Push current change to the left"), lambda x: self.push_change(-1)),
@@ -1100,35 +1195,8 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
             return melddoc.RESULT_ERROR
 
     def make_patch(self, *extra):
-        fontdesc = pango.FontDescription(self.prefs.get_current_font())
-        dialog = gnomeglade.Component(paths.ui_dir("filediff.ui"), "patchdialog")
-        dialog.widget.set_transient_for( self.widget.get_toplevel() )
-        texts = [b.get_text(*b.get_bounds()).split("\n") for b in self.textbuffer]
-        texts[0] = [l+"\n" for l in texts[0]]
-        texts[1] = [l+"\n" for l in texts[1]]
-        names = [self._get_pane_label(i) for i in range(2)]
-        prefix = os.path.commonprefix( names )
-        names = [n[prefix.rfind("/") + 1:] for n in names]
-        dialog.textview.set_buffer(srcviewer.GtkTextBuffer())
-        dialog.textview.modify_font(fontdesc)
-        buf = dialog.textview.get_buffer()
-        lines = []
-        for line in difflib.unified_diff(texts[0], texts[1], names[0], names[1]):
-            buf.insert( buf.get_end_iter(), line )
-            lines.append(line)
-        srcviewer.set_highlighting_enabled_from_mimetype(buf, "text/x-diff", True)
-        result = dialog.widget.run()
-        dialog.widget.destroy()
-        if result >= 0:
-            txt = buf.get_text(buf.get_start_iter(), buf.get_end_iter(), 0)
-            if result == 1: # copy
-                clip = gtk.clipboard_get()
-                clip.set_text(txt)
-                clip.store()
-            else:# save as
-                filename = self._get_filename_for_saving( _("Save patch as...") )
-                if filename:
-                    self._save_text_to_filename(filename, txt)
+        dialog = PatchDialog(self)
+        dialog.run()
 
     def set_buffer_writable(self, buf, yesno):
         pane = self.textbuffer.index(buf)
@@ -1273,6 +1341,8 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
             tohide += self.linkmap[n-1:] + self.diffmap[n:]
             map( lambda x: x.hide(), tohide )
 
+            self.actiongroup.get_action("MakePatch").set_sensitive(n > 1)
+
             def chunk_change_fn(i):
                 return lambda: self.linediffer.single_changes(i)
             for (w, i) in zip(self.diffmap, (0, self.num_panes - 1)):



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