/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: Curtis Hovey
  • Date: 2012-03-20 12:39:08 UTC
  • mfrom: (776.3.23 gpush-do-push)
  • Revision ID: sinzui.is@verizon.net-20120320123908-lclvaiasu0e50odg
Merged faster push.

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