/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: 2012-07-09 15:23:26 UTC
  • mto: This revision was merged to the branch mainline in revision 794.
  • Revision ID: jelmer@samba.org-20120709152326-dzxb8zoz0btull7n
Remove bzr-notify.

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