/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz

« back to all changes in this revision

Viewing changes to commit.py

  • Committer: Jelmer Vernooij
  • Date: 2008-06-29 15:47:30 UTC
  • mto: This revision was merged to the branch mainline in revision 519.
  • Revision ID: jelmer@samba.org-20080629154730-xfsotoxwkiytf0ph
Pass graph object rather than full repository to linegraph.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
 
17
try:
 
18
    import pygtk
 
19
    pygtk.require("2.0")
 
20
except:
 
21
    pass
 
22
 
 
23
import gtk
 
24
import gobject
 
25
import pango
 
26
 
 
27
import os.path
17
28
import re
18
29
 
19
 
from gi.repository import Gdk
20
 
from gi.repository import Gtk
21
 
from gi.repository import GObject
22
 
from gi.repository import Pango
 
30
from bzrlib import errors, osutils
 
31
from bzrlib.trace import mutter
 
32
from bzrlib.util import bencode
23
33
 
24
 
from bzrlib import (
25
 
    bencode,
26
 
    errors,
27
 
    osutils,
28
 
    revision as _mod_revision,
29
 
    trace,
30
 
    tsort,
31
 
    )
32
 
from bzrlib.plugins.gtk.dialog import question_dialog
33
 
from bzrlib.plugins.gtk.errors import show_bzr_error
34
 
from bzrlib.plugins.gtk.i18n import _i18n
35
 
from bzrlib.plugins.gtk.commitmsgs import SavedCommitMessagesManager
 
34
from bzrlib.plugins.gtk import _i18n
 
35
from dialog import error_dialog, question_dialog
 
36
from errors import show_bzr_error
36
37
 
37
38
try:
38
39
    import dbus
42
43
    have_dbus = False
43
44
 
44
45
 
45
 
def _get_sorted_revisions(tip_revision, revision_ids, parent_map):
46
 
    """Get an iterator which will return the revisions in merge sorted order.
47
 
 
48
 
    This will build up a list of all nodes, such that only nodes in the list
49
 
    are referenced. It then uses MergeSorter to return them in 'merge-sorted'
50
 
    order.
51
 
 
52
 
    :param revision_ids: A set of revision_ids
53
 
    :param parent_map: The parent information for each node. Revisions which
54
 
        are considered ghosts should not be present in the map.
55
 
    :return: iterator from MergeSorter.iter_topo_order()
56
 
    """
57
 
    # MergeSorter requires that all nodes be present in the graph, so get rid
58
 
    # of any references pointing outside of this graph.
59
 
    parent_graph = {}
60
 
    for revision_id in revision_ids:
61
 
        if revision_id not in parent_map: # ghost
62
 
            parent_graph[revision_id] = []
63
 
        else:
64
 
            # Only include parents which are in this sub-graph
65
 
            parent_graph[revision_id] = [p for p in parent_map[revision_id]
66
 
                                            if p in revision_ids]
67
 
    sorter = tsort.MergeSorter(parent_graph, tip_revision)
68
 
    return sorter.iter_topo_order()
69
 
 
70
 
 
71
46
def pending_revisions(wt):
72
47
    """Return a list of pending merges or None if there are none of them.
73
48
 
78
53
    """
79
54
    parents = wt.get_parent_ids()
80
55
    if len(parents) < 2:
81
 
        return
 
56
        return None
82
57
 
83
58
    # The basic pending merge algorithm uses the same algorithm as
84
59
    # bzrlib.status.show_pending_merges
86
61
    branch = wt.branch
87
62
    last_revision = parents[0]
88
63
 
89
 
    graph = branch.repository.get_graph()
90
 
    other_revisions = [last_revision]
 
64
    if last_revision is not None:
 
65
        try:
 
66
            ignore = set(branch.repository.get_ancestry(last_revision,
 
67
                                                        topo_sorted=False))
 
68
        except errors.NoSuchRevision:
 
69
            # the last revision is a ghost : assume everything is new
 
70
            # except for it
 
71
            ignore = set([None, last_revision])
 
72
    else:
 
73
        ignore = set([None])
91
74
 
92
75
    pm = []
93
76
    for merge in pending:
94
 
        try:
95
 
            merge_rev = branch.repository.get_revision(merge)
96
 
        except errors.NoSuchRevision:
97
 
            # If we are missing a revision, just print out the revision id
98
 
            trace.mutter("ghost: %r", merge)
99
 
            other_revisions.append(merge)
100
 
            continue
101
 
 
102
 
        # Find all of the revisions in the merge source, which are not in the
103
 
        # last committed revision.
104
 
        merge_extra = graph.find_unique_ancestors(merge, other_revisions)
105
 
        other_revisions.append(merge)
106
 
        merge_extra.discard(_mod_revision.NULL_REVISION)
107
 
 
108
 
        # Get a handle to all of the revisions we will need
109
 
        try:
110
 
            revisions = dict((rev.revision_id, rev) for rev in
111
 
                             branch.repository.get_revisions(merge_extra))
112
 
        except errors.NoSuchRevision:
113
 
            # One of the sub nodes is a ghost, check each one
114
 
            revisions = {}
115
 
            for revision_id in merge_extra:
116
 
                try:
117
 
                    rev = branch.repository.get_revisions([revision_id])[0]
118
 
                except errors.NoSuchRevision:
119
 
                    revisions[revision_id] = None
120
 
                else:
121
 
                    revisions[revision_id] = rev
122
 
 
123
 
         # Display the revisions brought in by this merge.
124
 
        rev_id_iterator = _get_sorted_revisions(merge, merge_extra,
125
 
                            branch.repository.get_parent_map(merge_extra))
126
 
        # Skip the first node
127
 
        num, first, depth, eom = rev_id_iterator.next()
128
 
        if first != merge:
129
 
            raise AssertionError('Somehow we misunderstood how'
130
 
                ' iter_topo_order works %s != %s' % (first, merge))
131
 
        children = []
132
 
        for num, sub_merge, depth, eom in rev_id_iterator:
133
 
            rev = revisions[sub_merge]
134
 
            if rev is None:
135
 
                trace.warning("ghost: %r", sub_merge)
136
 
                continue
137
 
            children.append(rev)
138
 
        yield (merge_rev, children)
139
 
 
140
 
 
141
 
_newline_variants_re = re.compile(r'\r\n?')
142
 
def _sanitize_and_decode_message(utf8_message):
143
 
    """Turn a utf-8 message into a sanitized Unicode message."""
144
 
    fixed_newline = _newline_variants_re.sub('\n', utf8_message)
145
 
    return osutils.safe_unicode(fixed_newline)
146
 
 
147
 
 
148
 
class CommitDialog(Gtk.Dialog):
 
77
        ignore.add(merge)
 
78
        try:
 
79
            rev = branch.repository.get_revision(merge)
 
80
            children = []
 
81
            pm.append((rev, children))
 
82
 
 
83
            # This does need to be topo sorted, so we search backwards
 
84
            inner_merges = branch.repository.get_ancestry(merge)
 
85
            assert inner_merges[0] is None
 
86
            inner_merges.pop(0)
 
87
            for mmerge in reversed(inner_merges):
 
88
                if mmerge in ignore:
 
89
                    continue
 
90
                rev = branch.repository.get_revision(mmerge)
 
91
                children.append(rev)
 
92
 
 
93
                ignore.add(mmerge)
 
94
        except errors.NoSuchRevision:
 
95
            print "DEBUG: NoSuchRevision:", merge
 
96
 
 
97
    return pm
 
98
 
 
99
 
 
100
class CommitDialog(gtk.Dialog):
149
101
    """Implementation of Commit."""
150
102
 
151
103
    def __init__(self, wt, selected=None, parent=None):
152
 
        super(CommitDialog, self).__init__(
153
 
            title="Commit to %s" % wt.basedir, parent=parent, flags=0)
154
 
        self.connect('delete-event', self._on_delete_window)
 
104
        gtk.Dialog.__init__(self, title="Commit - Olive",
 
105
                                  parent=parent,
 
106
                                  flags=0,
 
107
                                  buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
155
108
        self._question_dialog = question_dialog
156
109
 
157
 
        self.set_type_hint(Gdk.WindowTypeHint.NORMAL)
158
 
 
159
110
        self._wt = wt
160
111
        # TODO: Do something with this value, it is used by Olive
161
112
        #       It used to set all changes but this one to False
163
114
        self._enable_per_file_commits = True
164
115
        self._commit_all_changes = True
165
116
        self.committed_revision_id = None # Nothing has been committed yet
166
 
        self._last_selected_file = None
167
 
        self._saved_commit_messages_manager = SavedCommitMessagesManager(
168
 
            self._wt, self._wt.branch)
169
117
 
170
118
        self.setup_params()
171
119
        self.construct()
175
123
        """Setup the member variables for state."""
176
124
        self._basis_tree = self._wt.basis_tree()
177
125
        self._delta = None
178
 
        self._wt.lock_read()
179
 
        try:
180
 
            self._pending = list(pending_revisions(self._wt))
181
 
        finally:
182
 
            self._wt.unlock()
 
126
        self._pending = pending_revisions(self._wt)
183
127
 
184
128
        self._is_checkout = (self._wt.branch.get_bound_location() is not None)
185
129
 
237
181
 
238
182
        all_enabled = (self._selected is None)
239
183
        # The first entry is always the 'whole tree'
240
 
        all_iter = store.append(["", "", all_enabled, 'All Files', '', ''])
 
184
        all_iter = store.append([None, None, all_enabled, 'All Files', '', ''])
241
185
        initial_cursor = store.get_path(all_iter)
242
186
        # should we pass specific_files?
243
187
        self._wt.lock_read()
244
188
        self._basis_tree.lock_read()
245
189
        try:
246
190
            from diff import iter_changes_to_status
247
 
            saved_file_messages = self._saved_commit_messages_manager.get()[1]
248
191
            for (file_id, real_path, change_type, display_path
249
192
                ) in iter_changes_to_status(self._basis_tree, self._wt):
250
193
                if self._selected and real_path != self._selected:
251
194
                    enabled = False
252
195
                else:
253
196
                    enabled = True
254
 
                try:
255
 
                    default_message = saved_file_messages[file_id]
256
 
                except KeyError:
257
 
                    default_message = ''
258
197
                item_iter = store.append([
259
198
                    file_id,
260
199
                    real_path.encode('UTF-8'),
261
200
                    enabled,
262
201
                    display_path.encode('UTF-8'),
263
202
                    change_type,
264
 
                    default_message, # Initial comment
 
203
                    '', # Initial comment
265
204
                    ])
266
205
                if self._selected and enabled:
267
206
                    initial_cursor = store.get_path(item_iter)
274
213
        # This sets the cursor, which causes the expander to close, which
275
214
        # causes the _file_message_text_view to never get realized. So we have
276
215
        # to give it a little kick, or it warns when we try to grab the focus
277
 
        self._treeview_files.set_cursor(initial_cursor, None, False)
 
216
        self._treeview_files.set_cursor(initial_cursor)
278
217
 
279
218
        def _realize_file_message_tree_view(*args):
280
219
            self._file_message_text_view.realize()
293
232
                proxy_obj = bus.get_object('org.freedesktop.NetworkManager',
294
233
                                           '/org/freedesktop/NetworkManager')
295
234
            except dbus.DBusException:
296
 
                trace.mutter("networkmanager not available.")
 
235
                mutter("networkmanager not available.")
297
236
                self._check_local.show()
298
237
                return
299
 
 
 
238
            
300
239
            dbus_iface = dbus.Interface(proxy_obj,
301
240
                                        'org.freedesktop.NetworkManager')
302
241
            try:
305
244
            except dbus.DBusException, e:
306
245
                # Silently drop errors. While DBus may be
307
246
                # available, NetworkManager doesn't necessarily have to be
308
 
                trace.mutter("unable to get networkmanager state: %r" % e)
 
247
                mutter("unable to get networkmanager state: %r" % e)
309
248
        self._check_local.show()
310
249
 
311
250
    def _fill_in_per_file_info(self):
328
267
        """Build up the dialog widgets."""
329
268
        # The primary pane which splits it into left and right (adjustable)
330
269
        # sections.
331
 
        self._hpane = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
 
270
        self._hpane = gtk.HPaned()
332
271
 
333
272
        self._construct_left_pane()
334
273
        self._construct_right_pane()
335
274
        self._construct_action_pane()
336
275
 
337
 
        self.get_content_area().pack_start(self._hpane, True, True, 0)
 
276
        self.vbox.pack_start(self._hpane)
338
277
        self._hpane.show()
339
278
        self.set_focus(self._global_message_text_view)
340
279
 
359
298
        self._hpane.set_position(300)
360
299
 
361
300
    def _construct_accelerators(self):
362
 
        group = Gtk.AccelGroup()
363
 
        group.connect(Gdk.keyval_from_name('N'),
364
 
                      Gdk.ModifierType.CONTROL_MASK, 0, self._on_accel_next)
 
301
        group = gtk.AccelGroup()
 
302
        group.connect_group(gtk.gdk.keyval_from_name('N'),
 
303
                            gtk.gdk.CONTROL_MASK, 0, self._on_accel_next)
365
304
        self.add_accel_group(group)
366
305
 
367
306
        # ignore the escape key (avoid closing the window)
368
307
        self.connect_object('close', self.emit_stop_by_name, 'close')
369
308
 
370
309
    def _construct_left_pane(self):
371
 
        self._left_pane_box = Gtk.VBox(homogeneous=False, spacing=5)
 
310
        self._left_pane_box = gtk.VBox(homogeneous=False, spacing=5)
372
311
        self._construct_file_list()
373
312
        self._construct_pending_list()
374
313
 
375
 
        self._check_local = Gtk.CheckButton(_i18n("_Only commit locally"),
 
314
        self._check_local = gtk.CheckButton(_i18n("_Only commit locally"),
376
315
                                            use_underline=True)
377
 
        self._left_pane_box.pack_end(self._check_local, False, False, 0)
 
316
        self._left_pane_box.pack_end(self._check_local, False, False)
378
317
        self._check_local.set_active(False)
379
318
 
380
319
        self._hpane.pack1(self._left_pane_box, resize=False, shrink=False)
387
326
        # commit, and 1 for file commit, and it looked good. But I don't seem
388
327
        # to have a way to do that with the gtk boxes... :( (Which is extra
389
328
        # weird since wx uses gtk on Linux...)
390
 
        self._right_pane_table = Gtk.Table(rows=10, columns=1, homogeneous=False)
 
329
        self._right_pane_table = gtk.Table(rows=10, columns=1, homogeneous=False)
391
330
        self._right_pane_table.set_row_spacings(5)
392
331
        self._right_pane_table.set_col_spacings(5)
393
332
        self._right_pane_table_row = 0
399
338
        self._hpane.pack2(self._right_pane_table, resize=True, shrink=True)
400
339
 
401
340
    def _construct_action_pane(self):
402
 
        self._button_cancel = Gtk.Button(stock=Gtk.STOCK_CANCEL)
403
 
        self._button_cancel.connect('clicked', self._on_cancel_clicked)
404
 
        self._button_cancel.show()
405
 
        self.get_action_area().pack_end(
406
 
            self._button_cancel, True, True, 0)
407
 
        self._button_commit = Gtk.Button(_i18n("Comm_it"), use_underline=True)
 
341
        self._button_commit = gtk.Button(_i18n("Comm_it"), use_underline=True)
408
342
        self._button_commit.connect('clicked', self._on_commit_clicked)
409
 
        self._button_commit.set_can_default(True)
 
343
        self._button_commit.set_flags(gtk.CAN_DEFAULT)
410
344
        self._button_commit.show()
411
 
        self.get_action_area().pack_end(
412
 
            self._button_commit, True, True, 0)
 
345
        self.action_area.pack_end(self._button_commit)
413
346
        self._button_commit.grab_default()
414
347
 
415
348
    def _add_to_right_table(self, widget, weight, expanding=False):
421
354
        """
422
355
        end_row = self._right_pane_table_row + weight
423
356
        options = 0
424
 
        expand_opts = Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK
 
357
        expand_opts = gtk.EXPAND | gtk.FILL | gtk.SHRINK
425
358
        if expanding:
426
359
            options = expand_opts
427
360
        self._right_pane_table.attach(widget, 0, 1,
430
363
        self._right_pane_table_row = end_row
431
364
 
432
365
    def _construct_file_list(self):
433
 
        self._files_box = Gtk.VBox(homogeneous=False, spacing=0)
434
 
        file_label = Gtk.Label(label=_i18n('Files'))
 
366
        self._files_box = gtk.VBox(homogeneous=False, spacing=0)
 
367
        file_label = gtk.Label(_i18n('Files'))
435
368
        # file_label.show()
436
 
        self._files_box.pack_start(file_label, False, True, 0)
 
369
        self._files_box.pack_start(file_label, expand=False)
437
370
 
438
 
        self._commit_all_files_radio = Gtk.RadioButton.new_with_label(
 
371
        self._commit_all_files_radio = gtk.RadioButton(
439
372
            None, _i18n("Commit all changes"))
440
 
        self._files_box.pack_start(self._commit_all_files_radio, False, True, 0)
 
373
        self._files_box.pack_start(self._commit_all_files_radio, expand=False)
441
374
        self._commit_all_files_radio.show()
442
375
        self._commit_all_files_radio.connect('toggled',
443
376
            self._toggle_commit_selection)
444
 
        self._commit_selected_radio = Gtk.RadioButton.new_with_label_from_widget(
 
377
        self._commit_selected_radio = gtk.RadioButton(
445
378
            self._commit_all_files_radio, _i18n("Only commit selected changes"))
446
 
        self._files_box.pack_start(self._commit_selected_radio, False, True, 0)
 
379
        self._files_box.pack_start(self._commit_selected_radio, expand=False)
447
380
        self._commit_selected_radio.show()
448
381
        self._commit_selected_radio.connect('toggled',
449
382
            self._toggle_commit_selection)
452
385
            self._commit_all_files_radio.set_sensitive(False)
453
386
            self._commit_selected_radio.set_sensitive(False)
454
387
 
455
 
        scroller = Gtk.ScrolledWindow()
456
 
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
457
 
        self._treeview_files = Gtk.TreeView()
 
388
        scroller = gtk.ScrolledWindow()
 
389
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
390
        self._treeview_files = gtk.TreeView()
458
391
        self._treeview_files.show()
459
392
        scroller.add(self._treeview_files)
460
 
        scroller.set_shadow_type(Gtk.ShadowType.IN)
 
393
        scroller.set_shadow_type(gtk.SHADOW_IN)
461
394
        scroller.show()
462
 
        self._files_box.pack_start(scroller, True, True, 0)
 
395
        self._files_box.pack_start(scroller,
 
396
                                   expand=True, fill=True)
463
397
        self._files_box.show()
464
 
        self._left_pane_box.pack_start(self._files_box, True, True, 0)
 
398
        self._left_pane_box.pack_start(self._files_box)
465
399
 
466
400
        # Keep note that all strings stored in a ListStore must be UTF-8
467
401
        # strings. GTK does not support directly setting and restoring Unicode
468
402
        # objects.
469
 
        liststore = Gtk.ListStore(
470
 
            GObject.TYPE_STRING,  # [0] file_id
471
 
            GObject.TYPE_STRING,  # [1] real path
472
 
            GObject.TYPE_BOOLEAN, # [2] checkbox
473
 
            GObject.TYPE_STRING,  # [3] display path
474
 
            GObject.TYPE_STRING,  # [4] changes type
475
 
            GObject.TYPE_STRING,  # [5] commit message
 
403
        liststore = gtk.ListStore(
 
404
            gobject.TYPE_STRING,  # [0] file_id
 
405
            gobject.TYPE_STRING,  # [1] real path
 
406
            gobject.TYPE_BOOLEAN, # [2] checkbox
 
407
            gobject.TYPE_STRING,  # [3] display path
 
408
            gobject.TYPE_STRING,  # [4] changes type
 
409
            gobject.TYPE_STRING,  # [5] commit message
476
410
            )
477
411
        self._files_store = liststore
478
412
        self._treeview_files.set_model(liststore)
479
 
        crt = Gtk.CellRendererToggle()
 
413
        crt = gtk.CellRendererToggle()
480
414
        crt.set_property('activatable', not bool(self._pending))
481
415
        crt.connect("toggled", self._toggle_commit, self._files_store)
482
416
        if self._pending:
483
417
            name = _i18n('Commit*')
484
418
        else:
485
419
            name = _i18n('Commit')
486
 
        commit_col = Gtk.TreeViewColumn(name, crt, active=2)
 
420
        commit_col = gtk.TreeViewColumn(name, crt, active=2)
487
421
        commit_col.set_visible(False)
488
422
        self._treeview_files.append_column(commit_col)
489
 
        self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Path'),
490
 
                                           Gtk.CellRendererText(), text=3))
491
 
        self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Type'),
492
 
                                           Gtk.CellRendererText(), text=4))
 
423
        self._treeview_files.append_column(gtk.TreeViewColumn(_i18n('Path'),
 
424
                                           gtk.CellRendererText(), text=3))
 
425
        self._treeview_files.append_column(gtk.TreeViewColumn(_i18n('Type'),
 
426
                                           gtk.CellRendererText(), text=4))
493
427
        self._treeview_files.connect('cursor-changed',
494
428
                                     self._on_treeview_files_cursor_changed)
495
429
 
496
430
    def _toggle_commit(self, cell, path, model):
497
 
        if model[path][0] == "": # No file_id means 'All Files'
 
431
        if model[path][0] is None: # No file_id means 'All Files'
498
432
            new_val = not model[path][2]
499
433
            for node in model:
500
434
                node[2] = new_val
510
444
                checked_col.set_visible(False)
511
445
            else:
512
446
                checked_col.set_visible(True)
513
 
            renderer = checked_col.get_cells()[0]
 
447
            renderer = checked_col.get_cell_renderers()[0]
514
448
            renderer.set_property('activatable', not all_files)
515
449
 
516
450
    def _construct_pending_list(self):
517
451
        # Pending information defaults to hidden, we put it all in 1 box, so
518
452
        # that we can show/hide all of them at once
519
 
        self._pending_box = Gtk.VBox()
 
453
        self._pending_box = gtk.VBox()
520
454
        self._pending_box.hide()
521
455
 
522
 
        pending_message = Gtk.Label()
 
456
        pending_message = gtk.Label()
523
457
        pending_message.set_markup(
524
458
            _i18n('<i>* Cannot select specific files when merging</i>'))
525
 
        self._pending_box.pack_start(pending_message, False, True, 5)
 
459
        self._pending_box.pack_start(pending_message, expand=False, padding=5)
526
460
        pending_message.show()
527
461
 
528
 
        pending_label = Gtk.Label(label=_i18n('Pending Revisions'))
529
 
        self._pending_box.pack_start(pending_label, False, True, 0)
 
462
        pending_label = gtk.Label(_i18n('Pending Revisions'))
 
463
        self._pending_box.pack_start(pending_label, expand=False, padding=0)
530
464
        pending_label.show()
531
465
 
532
 
        scroller = Gtk.ScrolledWindow()
533
 
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
534
 
        self._treeview_pending = Gtk.TreeView()
 
466
        scroller = gtk.ScrolledWindow()
 
467
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
468
        self._treeview_pending = gtk.TreeView()
535
469
        scroller.add(self._treeview_pending)
536
 
        scroller.set_shadow_type(Gtk.ShadowType.IN)
 
470
        scroller.set_shadow_type(gtk.SHADOW_IN)
537
471
        scroller.show()
538
 
        self._pending_box.pack_start(scroller, True, True, 5)
 
472
        self._pending_box.pack_start(scroller,
 
473
                                     expand=True, fill=True, padding=5)
539
474
        self._treeview_pending.show()
540
 
        self._left_pane_box.pack_start(self._pending_box, True, True, 0)
 
475
        self._left_pane_box.pack_start(self._pending_box)
541
476
 
542
 
        liststore = Gtk.ListStore(GObject.TYPE_STRING, # revision_id
543
 
                                  GObject.TYPE_STRING, # date
544
 
                                  GObject.TYPE_STRING, # committer
545
 
                                  GObject.TYPE_STRING, # summary
 
477
        liststore = gtk.ListStore(gobject.TYPE_STRING, # revision_id
 
478
                                  gobject.TYPE_STRING, # date
 
479
                                  gobject.TYPE_STRING, # committer
 
480
                                  gobject.TYPE_STRING, # summary
546
481
                                 )
547
482
        self._pending_store = liststore
548
483
        self._treeview_pending.set_model(liststore)
549
 
        self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Date'),
550
 
                                             Gtk.CellRendererText(), text=1))
551
 
        self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Committer'),
552
 
                                             Gtk.CellRendererText(), text=2))
553
 
        self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Summary'),
554
 
                                             Gtk.CellRendererText(), text=3))
 
484
        self._treeview_pending.append_column(gtk.TreeViewColumn(_i18n('Date'),
 
485
                                             gtk.CellRendererText(), text=1))
 
486
        self._treeview_pending.append_column(gtk.TreeViewColumn(_i18n('Committer'),
 
487
                                             gtk.CellRendererText(), text=2))
 
488
        self._treeview_pending.append_column(gtk.TreeViewColumn(_i18n('Summary'),
 
489
                                             gtk.CellRendererText(), text=3))
555
490
 
556
491
    def _construct_diff_view(self):
557
 
        from bzrlib.plugins.gtk.diff import DiffView
 
492
        from diff import DiffView
558
493
 
559
494
        # TODO: jam 2007-10-30 The diff label is currently disabled. If we
560
495
        #       decide that we really don't ever want to display it, we should
561
496
        #       actually remove it, and other references to it, along with the
562
497
        #       tests that it is set properly.
563
 
        self._diff_label = Gtk.Label(label=_i18n('Diff for whole tree'))
 
498
        self._diff_label = gtk.Label(_i18n('Diff for whole tree'))
564
499
        self._diff_label.set_alignment(0, 0)
565
500
        self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
566
501
        self._add_to_right_table(self._diff_label, 1, False)
571
506
        self._diff_view.show()
572
507
 
573
508
    def _construct_file_message(self):
574
 
        scroller = Gtk.ScrolledWindow()
575
 
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
 
509
        scroller = gtk.ScrolledWindow()
 
510
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
576
511
 
577
 
        self._file_message_text_view = Gtk.TextView()
 
512
        self._file_message_text_view = gtk.TextView()
578
513
        scroller.add(self._file_message_text_view)
579
 
        scroller.set_shadow_type(Gtk.ShadowType.IN)
 
514
        scroller.set_shadow_type(gtk.SHADOW_IN)
580
515
        scroller.show()
581
516
 
582
 
        self._file_message_text_view.modify_font(Pango.FontDescription("Monospace"))
583
 
        self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
 
517
        self._file_message_text_view.modify_font(pango.FontDescription("Monospace"))
 
518
        self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
584
519
        self._file_message_text_view.set_accepts_tab(False)
585
520
        self._file_message_text_view.show()
586
521
 
587
 
        self._file_message_expander = Gtk.Expander(
588
 
            label=_i18n('File commit message'))
 
522
        self._file_message_expander = gtk.Expander(_i18n('File commit message'))
589
523
        self._file_message_expander.set_expanded(True)
590
524
        self._file_message_expander.add(scroller)
591
525
        self._add_to_right_table(self._file_message_expander, 1, False)
592
526
        self._file_message_expander.show()
593
527
 
594
528
    def _construct_global_message(self):
595
 
        self._global_message_label = Gtk.Label(label=_i18n('Global Commit Message'))
 
529
        self._global_message_label = gtk.Label(_i18n('Global Commit Message'))
596
530
        self._global_message_label.set_markup(
597
531
            _i18n('<b>Global Commit Message</b>'))
598
532
        self._global_message_label.set_alignment(0, 0)
601
535
        # Can we remove the spacing between the label and the box?
602
536
        self._global_message_label.show()
603
537
 
604
 
        scroller = Gtk.ScrolledWindow()
605
 
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
 
538
        scroller = gtk.ScrolledWindow()
 
539
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
606
540
 
607
 
        self._global_message_text_view = Gtk.TextView()
608
 
        self._set_global_commit_message(self._saved_commit_messages_manager.get()[0])
609
 
        self._global_message_text_view.modify_font(Pango.FontDescription("Monospace"))
 
541
        self._global_message_text_view = gtk.TextView()
 
542
        self._global_message_text_view.modify_font(pango.FontDescription("Monospace"))
610
543
        scroller.add(self._global_message_text_view)
611
 
        scroller.set_shadow_type(Gtk.ShadowType.IN)
 
544
        scroller.set_shadow_type(gtk.SHADOW_IN)
612
545
        scroller.show()
613
546
        self._add_to_right_table(scroller, 2, True)
614
 
        self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
 
547
        self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
615
548
        self._file_message_text_view.set_accepts_tab(False)
616
549
        self._global_message_text_view.show()
617
550
 
618
551
    def _on_treeview_files_cursor_changed(self, treeview):
619
552
        treeselection = treeview.get_selection()
620
 
        if treeselection is None:
621
 
            # The treeview was probably destroyed as the dialog closes.
622
 
            return
623
553
        (model, selection) = treeselection.get_selected()
624
554
 
625
555
        if selection is not None:
626
556
            path, display_path = model.get(selection, 1, 3)
627
557
            self._diff_label.set_text(_i18n('Diff for ') + display_path)
628
 
            if path == "":
 
558
            if path is None:
629
559
                self._diff_view.show_diff(None)
630
560
            else:
631
 
                self._diff_view.show_diff([osutils.safe_unicode(path)])
 
561
                self._diff_view.show_diff([path.decode('UTF-8')])
632
562
            self._update_per_file_info(selection)
633
563
 
634
564
    def _on_accel_next(self, accel_group, window, keyval, modifier):
645
575
            # We have either made it to the end of the list, or nothing was
646
576
            # selected. Either way, select All Files, and jump to the global
647
577
            # commit message.
648
 
            self._treeview_files.set_cursor(
649
 
                Gtk.TreePath(path=0), "", False)
 
578
            self._treeview_files.set_cursor((0,))
650
579
            self._global_message_text_view.grab_focus()
651
580
        else:
652
581
            # Set the cursor to this entry, and jump to the per-file commit
653
582
            # message
654
 
            self._treeview_files.set_cursor(model.get_path(next), None, False)
 
583
            self._treeview_files.set_cursor(model.get_path(next))
655
584
            self._file_message_text_view.grab_focus()
656
585
 
657
586
    def _save_current_file_message(self):
659
588
            return # Nothing to save
660
589
        text_buffer = self._file_message_text_view.get_buffer()
661
590
        cur_text = text_buffer.get_text(text_buffer.get_start_iter(),
662
 
                                        text_buffer.get_end_iter(), True)
 
591
                                        text_buffer.get_end_iter())
663
592
        last_selected = self._files_store.get_iter(self._last_selected_file)
664
593
        self._files_store.set_value(last_selected, 5, cur_text)
665
594
 
671
600
        self._save_current_file_message()
672
601
        text_buffer = self._file_message_text_view.get_buffer()
673
602
        file_id, display_path, message = self._files_store.get(selection, 0, 3, 5)
674
 
        if file_id == "": # Whole tree
 
603
        if file_id is None: # Whole tree
675
604
            self._file_message_expander.set_label(_i18n('File commit message'))
676
605
            self._file_message_expander.set_expanded(False)
677
606
            self._file_message_expander.set_sensitive(False)
694
623
        files = []
695
624
        records = iter(self._files_store)
696
625
        rec = records.next() # Skip the All Files record
697
 
        assert rec[0] == "", "Are we skipping the wrong record?"
 
626
        assert rec[0] is None, "Are we skipping the wrong record?"
698
627
 
699
628
        file_info = []
700
629
        for record in records:
701
630
            if self._commit_all_changes or record[2]:# [2] checkbox
702
 
                file_id = osutils.safe_utf8(record[0]) # [0] file_id
703
 
                path = osutils.safe_utf8(record[1])    # [1] real path
704
 
                # [5] commit message
705
 
                file_message = _sanitize_and_decode_message(record[5])
 
631
                file_id = record[0] # [0] file_id
 
632
                path = record[1]    # [1] real path
 
633
                file_message = record[5] # [5] commit message
706
634
                files.append(path.decode('UTF-8'))
707
635
                if self._enable_per_file_commits and file_message:
708
636
                    # All of this needs to be utf-8 information
709
 
                    file_message = file_message.encode('UTF-8')
710
637
                    file_info.append({'path':path, 'file_id':file_id,
711
638
                                     'message':file_message})
712
639
        file_info.sort(key=lambda x:(x['path'], x['file_id']))
716
643
            return files, []
717
644
 
718
645
    @show_bzr_error
719
 
    def _on_cancel_clicked(self, button):
720
 
        """ Cancel button clicked handler. """
721
 
        self._do_cancel()
722
 
 
723
 
    @show_bzr_error
724
 
    def _on_delete_window(self, source, event):
725
 
        """ Delete window handler. """
726
 
        self._do_cancel()
727
 
 
728
 
    def _do_cancel(self):
729
 
        """If requested, saves commit messages when cancelling gcommit; they are re-used by a next gcommit"""
730
 
        mgr = SavedCommitMessagesManager()
731
 
        self._saved_commit_messages_manager = mgr
732
 
        mgr.insert(self._get_global_commit_message(),
733
 
                   self._get_specific_files()[1])
734
 
        if mgr.is_not_empty(): # maybe worth saving
735
 
            response = self._question_dialog(
736
 
                _i18n('Commit cancelled'),
737
 
                _i18n('Do you want to save your commit messages ?'),
738
 
                parent=self)
739
 
            if response == Gtk.ResponseType.NO:
740
 
                 # save nothing and destroy old comments if any
741
 
                mgr = SavedCommitMessagesManager()
742
 
        mgr.save(self._wt, self._wt.branch)
743
 
        self.response(Gtk.ResponseType.CANCEL) # close window
744
 
 
745
 
    @show_bzr_error
746
646
    def _on_commit_clicked(self, button):
747
647
        """ Commit button clicked handler. """
748
648
        self._do_commit()
753
653
        if message == '':
754
654
            response = self._question_dialog(
755
655
                _i18n('Commit with an empty message?'),
756
 
                _i18n('You can describe your commit intent in the message.'),
757
 
                parent=self)
758
 
            if response == Gtk.ResponseType.NO:
 
656
                _i18n('You can describe your commit intent in the message.'))
 
657
            if response == gtk.RESPONSE_NO:
759
658
                # Kindly give focus to message area
760
659
                self._global_message_text_view.grab_focus()
761
660
                return
774
673
        for path in self._wt.unknowns():
775
674
            response = self._question_dialog(
776
675
                _i18n("Commit with unknowns?"),
777
 
                _i18n("Unknown files exist in the working tree. Commit anyway?"),
778
 
                parent=self)
779
 
                # Doesn't set a parent for the dialog..
780
 
            if response == Gtk.ResponseType.NO:
 
676
                _i18n("Unknown files exist in the working tree. Commit anyway?"))
 
677
            if response == gtk.RESPONSE_NO:
781
678
                return
782
679
            break
783
680
 
796
693
            response = self._question_dialog(
797
694
                _i18n('Commit with no changes?'),
798
695
                _i18n('There are no changes in the working tree.'
799
 
                      ' Do you want to commit anyway?'),
800
 
                parent=self)
801
 
            if response == Gtk.ResponseType.YES:
 
696
                      ' Do you want to commit anyway?'))
 
697
            if response == gtk.RESPONSE_YES:
802
698
                rev_id = self._wt.commit(message,
803
699
                               allow_pointless=True,
804
700
                               strict=False,
806
702
                               specific_files=specific_files,
807
703
                               revprops=revprops)
808
704
        self.committed_revision_id = rev_id
809
 
        # destroy old comments if any
810
 
        SavedCommitMessagesManager().save(self._wt, self._wt.branch)
811
 
        self.response(Gtk.ResponseType.OK)
 
705
        self.response(gtk.RESPONSE_OK)
812
706
 
813
707
    def _get_global_commit_message(self):
814
708
        buf = self._global_message_text_view.get_buffer()
815
709
        start, end = buf.get_bounds()
816
 
        text = buf.get_text(start, end, True)
817
 
        return _sanitize_and_decode_message(text)
 
710
        return buf.get_text(start, end).decode('utf-8')
818
711
 
819
712
    def _set_global_commit_message(self, message):
820
713
        """Just a helper for the test suite."""
841
734
                                       show_offset=False)
842
735
        rev_dict['revision_id'] = rev.revision_id
843
736
        return rev_dict
844