~jtaylor/ubuntu/natty/meld/meld-sru

« back to all changes in this revision

Viewing changes to vcview.py

  • Committer: Bazaar Package Importer
  • Author(s): Ross Burton
  • Date: 2009-06-02 10:00:20 UTC
  • mfrom: (1.2.9 upstream) (2.1.5 squeeze)
  • Revision ID: james.westby@ubuntu.com-20090602100020-jb5wlixbu8xws29a
Tags: 1.3.0-1
* New upstream release (Closes: #528327)
  - Copy/paste behaviour fixes (Closes: #523576)
  - Dotfiles and removals handled in darcs (Closes: #509069)
  - Can handle Mercurial repos (Closes: #428843)
  - Up/Down patch used to skip changes (Closes: #511027)
  - Handle last line when highlighting properly (Closes: #465804)
* Update message and depends for new python-gtksourceview2 package
* Resync makefile.patch
* Remove python-gnome dependency

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
import gtk
20
20
import os
21
21
from gettext import gettext as _
22
 
import re
23
22
 
24
23
import tree
25
24
import misc
68
67
        self.widget.show_all()
69
68
 
70
69
    def run(self):
71
 
        self.previousentry.list.select_item(0)
 
70
        self.previousentry.child.set_editable(False)
 
71
        self.previousentry.set_active(0)
72
72
        self.textview.grab_focus()
73
73
        buf = self.textview.get_buffer()
74
74
        buf.place_cursor( buf.get_start_iter() )
78
78
        if response == gtk.RESPONSE_OK:
79
79
            self.parent._command_on_selected( self.parent.vc.commit_command(msg) )
80
80
        if len(msg.strip()):
81
 
            self.previousentry.prepend_history(1, msg)
 
81
            self.previousentry.prepend_text(msg)
82
82
        self.widget.destroy()
83
83
    def on_previousentry_activate(self, gentry):
84
84
        buf = self.textview.get_buffer()
85
 
        buf.set_text( gentry.gtk_entry().get_text() )
 
85
        buf.set_text( gentry.child.get_text() )
86
86
 
87
87
COL_LOCATION, COL_STATUS, COL_REVISION, COL_TAG, COL_OPTIONS, COL_END = range(tree.COL_END, tree.COL_END+6)
88
88
 
89
89
class VcTreeStore(tree.DiffTreeStore):
90
90
    def __init__(self):
91
 
        types = [type("")] * COL_END
92
 
        types[tree.COL_ICON] = type(tree.pixbuf_file)
93
 
        gtk.TreeStore.__init__(self, *types)
94
 
        self.ntree = 1
95
 
        self._setup_default_styles()
 
91
        tree.DiffTreeStore.__init__(self, 1, COL_END)
96
92
        self.textstyle[tree.STATE_MISSING] = '<span foreground="#000088" strikethrough="true" weight="bold">%s</span>'
97
93
 
98
94
################################################################################
109
105
#
110
106
################################################################################
111
107
class VcView(melddoc.MeldDoc, gnomeglade.Component):
 
108
    # Map action names to VC commands and required arguments list
 
109
    action_vc_cmds_map = {
 
110
                         "VcCompare": ("diff_command", ()),
 
111
                         "VcCommit": ("commit_command", ("",)),
 
112
                         "VcUpdate": ("update_command", ()),
 
113
                         "VcAdd": ("add_command", ()),
 
114
                         "VcAddBinary": ("add_command", ()),
 
115
                         "VcRemove": ("remove_command", ()),
 
116
                         "VcRevert": ("revert_command", ()),
 
117
                         }
112
118
 
113
119
    def __init__(self, prefs):
114
120
        melddoc.MeldDoc.__init__(self, prefs)
134
140
            ("VcShowIgnored", "filter-ignored-24",   _("Ignored"),   None, _("Show ignored files"), self.on_button_filter_toggled, False),
135
141
        )
136
142
 
137
 
        ui_file = paths.share_dir("glade2/vcview-ui.xml")
 
143
        self.ui_file = paths.share_dir("glade2/vcview-ui.xml")
138
144
        self.actiongroup = gtk.ActionGroup('VcviewActions')
139
145
        self.actiongroup.set_translation_domain("meld")
140
146
        self.actiongroup.add_actions(actions)
141
147
        self.actiongroup.add_toggle_actions(toggleactions)
142
 
        self.ui = gtk.UIManager()
143
 
        self.ui.insert_action_group(self.actiongroup, 0)
144
 
        self.ui.add_ui_from_file(ui_file)
145
148
        for action in ("VcCompare", "VcFlatten", "VcShowModified",
146
149
                       "VcShowNormal", "VcShowNonVC", "VcShowIgnored"):
147
150
            self.actiongroup.get_action(action).props.is_important = True
150
153
                       "VcShowIgnored"):
151
154
            button = self.actiongroup.get_action(action)
152
155
            button.props.icon_name = button.props.stock_id
153
 
        self.toolbar = self.ui.get_widget('/VcviewToolbar')
154
 
        self.vcview.pack_start(self.toolbar, False, True, 0)
155
 
        self.vcview.reorder_child(self.toolbar, 0)
156
 
        self.toolbar.set_style( self.prefs.get_toolbar_style() )
157
 
        self.popup_menu = self.ui.get_widget('/VcviewPopup')
158
156
        self.tempdirs = []
159
157
        self.model = VcTreeStore()
160
158
        self.treeview.set_model(self.model)
184
182
        addCol(_("Options"), COL_OPTIONS)
185
183
 
186
184
        class ConsoleStream(object):
187
 
            def __init__(this, textview):
188
 
                this.textview = textview
 
185
            def __init__(self, textview):
 
186
                self.textview = textview
189
187
                b = textview.get_buffer()
190
 
                this.mark = b.create_mark("END", b.get_end_iter(), 0)
191
 
            def write(this, s):
 
188
                self.mark = b.create_mark("END", b.get_end_iter(), 0)
 
189
            def write(self, s):
192
190
                if s:
193
 
                    b = this.textview.get_buffer()
 
191
                    b = self.textview.get_buffer()
194
192
                    b.insert(b.get_end_iter(), s)
195
 
                    this.textview.scroll_mark_onscreen( this.mark )
 
193
                    self.textview.scroll_mark_onscreen( self.mark )
196
194
        self.consolestream = ConsoleStream(self.consoleview)
197
195
        self.location = None
198
196
        self.treeview_column_location.set_visible(self.actiongroup.get_action("VcFlatten").get_active())
199
 
        size = self.fileentry.size_request()[1]
200
 
        self.button_jump.set_size_request(size, size)
201
 
        self.button_jump.hide()
 
197
        self.fileentry.show() #TODO: remove once bug 97503 is fixed
202
198
        if not self.prefs.vc_console_visible:
203
199
            self.on_console_view_toggle(self.console_hide_box)
 
200
        self.vc = None
 
201
        # VC ComboBox
 
202
        self.combobox_vcs = gtk.ComboBox()
 
203
        self.combobox_vcs.lock = True
 
204
        self.combobox_vcs.set_model(gtk.ListStore(str, object))
 
205
        cell = gtk.CellRendererText()
 
206
        self.combobox_vcs.pack_start(cell, False)
 
207
        self.combobox_vcs.add_attribute(cell, 'text', 0)
 
208
        self.combobox_vcs.lock = False
 
209
        self.hbox2.pack_end(self.combobox_vcs, expand=False)
 
210
        self.combobox_vcs.show()
 
211
        self.combobox_vcs.connect("changed", self.on_vc_change)
 
212
 
 
213
    def update_actions_sensitivity(self):
 
214
        """Disable actions that use not implemented VC plugin methods
 
215
        """
 
216
        for action_name, (meth_name, args) in self.action_vc_cmds_map.items():
 
217
            action = self.actiongroup.get_action(action_name)
 
218
            try:
 
219
                getattr(self.vc, meth_name)(*args)
 
220
                action.props.sensitive = True
 
221
            except NotImplementedError:
 
222
                action.props.sensitive = False
 
223
 
 
224
    def choose_vc(self, vcs):
 
225
        """Display VC plugin(s) that can handle the location"""
 
226
        self.combobox_vcs.lock = True
 
227
        self.combobox_vcs.get_model().clear()
 
228
        tooltip_texts = [_("Choose one Version Control"),
 
229
                         _("Only one Version Control in this directory")]
 
230
        for avc in vcs:
 
231
            self.combobox_vcs.get_model().append([avc.NAME, avc])
 
232
        if gtk.pygtk_version >= (2, 12, 0):
 
233
            self.combobox_vcs.set_tooltip_text(tooltip_texts[len(vcs) == 1])
 
234
        self.combobox_vcs.set_sensitive(len(vcs) > 1)
 
235
        self.combobox_vcs.lock = False
 
236
        self.combobox_vcs.set_active(0)
 
237
  
 
238
    def on_vc_change(self, cb):
 
239
        if not cb.lock:
 
240
            self.vc = cb.get_model()[cb.get_active_iter()][1]
 
241
            self._set_location(self.vc.root)
 
242
            self.update_actions_sensitivity()
204
243
 
205
244
    def set_location(self, location):
 
245
        self.choose_vc(vc.get_vcs(os.path.abspath(location or ".")))
 
246
 
 
247
    def _set_location(self, location):
 
248
        self.location = location
206
249
        self.model.clear()
207
 
        self.location = location = os.path.abspath(location or ".")
208
 
        self.fileentry.gtk_entry().set_text(location)
209
 
        self.vc = vc.Vc(location)
 
250
        self.fileentry.set_filename(location)
 
251
        self.fileentry.prepend_history(location)
210
252
        it = self.model.add_entries( None, [location] )
211
253
        self.treeview.grab_focus()
212
254
        self.treeview.get_selection().select_iter(it)
271
313
                self.treeview.expand_row( (0,), 0)
272
314
        self.vc.uncache_inventory()
273
315
 
274
 
    def on_preference_changed(self, key, value):
275
 
        if key == "toolbar_style":
276
 
            self.toolbar.set_style( self.prefs.get_toolbar_style() )
277
 
 
278
316
    def on_fileentry_activate(self, fileentry):
279
 
        path = fileentry.get_full_path(0)
 
317
        path = fileentry.get_full_path()
280
318
        self.set_location(path)
281
319
 
282
320
    def on_quit_event(self):
300
338
            path = self.model.value_path(it, 0)
301
339
            self.run_diff( [path] )
302
340
 
303
 
    def run_diff_iter(self, paths, empty_patch_ok):
 
341
    def run_diff_iter(self, path_list, empty_patch_ok):
304
342
        yield _("[%s] Fetching differences") % self.label_text
305
 
        difffunc = self._command_iter(self.vc.diff_command(), paths, 0).next
 
343
        difffunc = self._command_iter(self.vc.diff_command(), path_list, 0).next
306
344
        diff = None
307
345
        while type(diff) != type(()):
308
346
            diff = difffunc()
314
352
        elif empty_patch_ok:
315
353
            misc.run_dialog( _("No differences found."), parent=self, messagetype=gtk.MESSAGE_INFO)
316
354
        else:
317
 
            for path in paths:
 
355
            for path in path_list:
318
356
                self.emit("create-diff", [path])
319
357
 
320
 
    def run_diff(self, paths, empty_patch_ok=0):
321
 
        self.scheduler.add_task( self.run_diff_iter(paths, empty_patch_ok).next, atfront=1 )
 
358
    def run_diff(self, path_list, empty_patch_ok=0):
 
359
        for path in path_list:
 
360
            self.scheduler.add_task(self.run_diff_iter([path], empty_patch_ok).next, atfront=1)
322
361
 
323
362
    def on_button_press_event(self, text, event):
324
363
        if event.button==3:
356
395
        msg = misc.shelljoin(command)
357
396
        yield "[%s] %s" % (self.label_text, msg.replace("\n", u"\u21b2") )
358
397
        def relpath(pbase, p):
359
 
            assert p.startswith(pbase)
360
 
            kill = len(pbase) and (len(pbase)+1) or 0
 
398
            kill = 0
 
399
            if p.startswith(pbase):
 
400
                kill = len(pbase) and (len(pbase)+1) or 0
361
401
            return p[kill:] or "."
362
402
        if len(files) == 1 and os.path.isdir(files[0]):
363
403
            workdir = self.vc.get_working_directory(files[0])
391
431
        else:
392
432
            misc.run_dialog( _("Select some files first."), parent=self, messagetype=gtk.MESSAGE_INFO)
393
433
 
394
 
    def on_button_update_clicked(self, object):
 
434
    def on_button_update_clicked(self, obj):
395
435
        self._command_on_selected( self.vc.update_command() )
396
 
    def on_button_commit_clicked(self, object):
 
436
    def on_button_commit_clicked(self, obj):
397
437
        dialog = CommitDialog( self )
398
438
        dialog.run()
399
439
 
400
 
    def on_button_add_clicked(self, object):
 
440
    def on_button_add_clicked(self, obj):
401
441
        self._command_on_selected(self.vc.add_command() )
402
 
    def on_button_add_binary_clicked(self, object):
 
442
    def on_button_add_binary_clicked(self, obj):
403
443
        self._command_on_selected(self.vc.add_command(binary=1))
404
 
    def on_button_remove_clicked(self, object):
 
444
    def on_button_remove_clicked(self, obj):
405
445
        self._command_on_selected(self.vc.remove_command())
406
 
    def on_button_revert_clicked(self, object):
 
446
    def on_button_revert_clicked(self, obj):
407
447
        self._command_on_selected(self.vc.revert_command())
408
 
    def on_button_delete_clicked(self, object):
 
448
    def on_button_delete_clicked(self, obj):
409
449
        files = self._get_selected_files()
410
450
        for name in files:
411
451
            try:
412
452
                if os.path.isfile(name):
413
453
                    os.remove(name)
414
454
                elif os.path.isdir(name):
415
 
                    if misc.run_dialog(_("'%s' is a directory.\nRemove recusively?") % os.path.basename(name),
 
455
                    if misc.run_dialog(_("'%s' is a directory.\nRemove recursively?") % os.path.basename(name),
416
456
                            parent = self,
417
457
                            buttonstype=gtk.BUTTONS_OK_CANCEL) == gtk.RESPONSE_OK:
418
458
                        shutil.rmtree(name)
421
461
        workdir = _commonprefix(files)
422
462
        self.refresh_partial(workdir)
423
463
 
424
 
    def on_button_diff_clicked(self, object):
 
464
    def on_button_diff_clicked(self, obj):
425
465
        files = self._get_selected_files()
426
466
        if len(files):
427
467
            self.run_diff(files, empty_patch_ok=1)
428
468
 
429
 
    def on_button_open_clicked(self, object):
 
469
    def on_button_open_clicked(self, obj):
430
470
        self._open_files(self._get_selected_files())
431
471
 
432
472
    def show_patch(self, prefix, patch):
433
 
        if not patch: return
434
 
 
435
473
        tmpdir = tempfile.mkdtemp("-meld")
436
474
        self.tempdirs.append(tmpdir)
437
475
 
438
 
        regex = re.compile(self.vc.PATCH_INDEX_RE, re.M)
439
 
        files = [f.strip() for f in regex.findall(patch)]
440
476
        diffs = []
441
 
        for fname in files:
 
477
        for fname in self.vc.get_patch_files(patch):
442
478
            destfile = os.path.join(tmpdir,fname)
443
479
            destdir = os.path.dirname( destfile )
444
480
 
456
492
            for d in diffs:
457
493
                self.emit("create-diff", d)
458
494
        else:
459
 
            misc.run_dialog( _("Invoking patch failed, you need GNU patch.")+ "\n'%s'"%" ".join(patchcmd), parent=self)
 
495
            import meldapp
 
496
            msg = _("""
 
497
                    Invoking 'patch' failed.
 
498
                    
 
499
                    Maybe you don't have 'GNU patch' installed,
 
500
                    or you use an untested version of %s.
 
501
                    
 
502
                    Please send email bug report to:
 
503
                    meld-list@gnome.org
 
504
                    
 
505
                    Containing the following information:
 
506
                    
 
507
                    - meld version: '%s'
 
508
                    - source control software type: '%s'
 
509
                    - source control software version: 'X.Y.Z'
 
510
                    - the output of '%s somefile.txt'
 
511
                    - patch command: '%s'
 
512
                    """) % (self.vc.NAME,
 
513
                            meldapp.version,
 
514
                            self.vc.NAME,
 
515
                            " ".join(self.vc.diff_command()),
 
516
                            " ".join(patchcmd))
 
517
            msg = '\n'.join([line.strip() for line in msg.split('\n')])
 
518
            misc.run_dialog(msg, parent=self)
460
519
 
461
520
    def refresh(self):
462
521
        self.set_location( self.model.value_path( self.model.get_iter_root(), 0 ) )
473
532
        else: # XXX fixme
474
533
            self.refresh()
475
534
 
476
 
    def on_button_jump_press_event(self, button, event):
477
 
        class MyMenu(gtk.Menu):
478
 
            def __init__(self, parent, where, showup=1):
479
 
                gtk.Menu.__init__(self)
480
 
                self.vcview = parent
481
 
                self.map_id = self.connect("map", lambda item: self.on_map(item,where,showup) )
482
 
            def add_item(self, name, submenu, showup):
483
 
                item = gtk.MenuItem(name)
484
 
                if submenu:
485
 
                    item.set_submenu( MyMenu(self.vcview, submenu, showup ) )
486
 
                self.append( item )
487
 
            def on_map(self, item, where, showup):
488
 
                if showup:
489
 
                    self.add_item("..", os.path.dirname(where), 1 )
490
 
                self.populate( where, self.listdir(where) )
491
 
                self.show_all()
492
 
                self.disconnect(self.map_id)
493
 
                del self.map_id
494
 
            def listdir(self, d):
495
 
                try:
496
 
                    return [p for p in os.listdir(d) if os.path.isdir( os.path.join(d,p))]
497
 
                except OSError:
498
 
                    return []
499
 
            def populate(self, where, children):
500
 
                for child in children:
501
 
                    cc = self.listdir( os.path.join(where, child) )
502
 
                    self.add_item( child, len(cc) and os.path.join(where,child), 0 )
503
 
        menu = MyMenu( self, os.path.abspath(self.location) )
504
 
        menu.popup(None, None, None, event.button, event.time)
505
 
 
506
535
    def _update_item_state(self, it, vcentry, location):
507
536
        e = vcentry
508
537
        self.model.set_state( it, 0, e.state, e.isdir )
509
 
        def set(col, val):
510
 
            self.model.set_value( it, self.model.column_index(col,0), val)
511
 
        set( COL_LOCATION, location )
512
 
        set( COL_STATUS, e.get_status())
513
 
        set( COL_REVISION, e.rev)
514
 
        set( COL_TAG, e.tag)
515
 
        set( COL_OPTIONS, e.options)
 
538
        def setcol(col, val):
 
539
            self.model.set_value(it, self.model.column_index(col, 0), val)
 
540
        setcol(COL_LOCATION, location)
 
541
        setcol(COL_STATUS, e.get_status())
 
542
        setcol(COL_REVISION, e.rev)
 
543
        setcol(COL_TAG, e.tag)
 
544
        setcol(COL_OPTIONS, e.options)
516
545
 
517
546
    def on_file_changed(self, filename):
518
547
        it = self.find_iter_by_name(filename)
519
548
        if it:
520
549
            path = self.model.value_path(it, 0)
521
 
            dirs, files = self.vc.lookup_files( [], [ (os.path.basename(path), path)] )
 
550
            files = self.vc.lookup_files([], [(os.path.basename(path), path)])[1]
522
551
            for e in files:
523
552
                if e.path == path:
524
553
                    prefixlen = 1 + len( self.model.value_path( self.model.get_iter_root(), 0 ) )