/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: 2011-12-20 16:47:38 UTC
  • Revision ID: jelmer@canonical.com-20111220164738-l6tgnbttkxmq6877
Cope with some strings being unicode when returned by some versions of gtk.

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
    trace,
 
29
    )
 
30
from bzrlib.plugins.gtk.dialog import question_dialog
 
31
from bzrlib.plugins.gtk.errors import show_bzr_error
 
32
from bzrlib.plugins.gtk.i18n import _i18n
 
33
from bzrlib.plugins.gtk.commitmsgs import SavedCommitMessagesManager
37
34
 
38
35
try:
39
36
    import dbus
62
59
    last_revision = parents[0]
63
60
 
64
61
    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])
 
62
        graph = branch.repository.get_graph()
 
63
        ignore = set([r for r,ps in graph.iter_ancestry([last_revision])])
72
64
    else:
73
 
        ignore = set([None])
 
65
        ignore = set([])
74
66
 
75
67
    pm = []
76
68
    for merge in pending:
97
89
    return pm
98
90
 
99
91
 
100
 
class CommitDialog(gtk.Dialog):
 
92
_newline_variants_re = re.compile(r'\r\n?')
 
93
def _sanitize_and_decode_message(utf8_message):
 
94
    """Turn a utf-8 message into a sanitized Unicode message."""
 
95
    fixed_newline = _newline_variants_re.sub('\n', utf8_message)
 
96
    return osutils.safe_unicode(fixed_newline)
 
97
 
 
98
 
 
99
class CommitDialog(Gtk.Dialog):
101
100
    """Implementation of Commit."""
102
101
 
103
102
    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))
 
103
        super(CommitDialog, self).__init__(
 
104
            title="Commit to %s" % wt.basedir, parent=parent, flags=0)
 
105
        self.connect('delete-event', self._on_delete_window)
108
106
        self._question_dialog = question_dialog
109
107
 
 
108
        self.set_type_hint(Gdk.WindowTypeHint.NORMAL)
 
109
 
110
110
        self._wt = wt
111
111
        # TODO: Do something with this value, it is used by Olive
112
112
        #       It used to set all changes but this one to False
114
114
        self._enable_per_file_commits = True
115
115
        self._commit_all_changes = True
116
116
        self.committed_revision_id = None # Nothing has been committed yet
 
117
        self._saved_commit_messages_manager = SavedCommitMessagesManager(
 
118
            self._wt, self._wt.branch)
117
119
 
118
120
        self.setup_params()
119
121
        self.construct()
123
125
        """Setup the member variables for state."""
124
126
        self._basis_tree = self._wt.basis_tree()
125
127
        self._delta = None
126
 
        self._pending = pending_revisions(self._wt)
 
128
        self._wt.lock_read()
 
129
        try:
 
130
            self._pending = pending_revisions(self._wt)
 
131
        finally:
 
132
            self._wt.unlock()
127
133
 
128
134
        self._is_checkout = (self._wt.branch.get_bound_location() is not None)
129
135
 
181
187
 
182
188
        all_enabled = (self._selected is None)
183
189
        # The first entry is always the 'whole tree'
184
 
        all_iter = store.append([None, None, all_enabled, 'All Files', '', ''])
 
190
        all_iter = store.append(["", "", all_enabled, 'All Files', '', ''])
185
191
        initial_cursor = store.get_path(all_iter)
186
192
        # should we pass specific_files?
187
193
        self._wt.lock_read()
188
194
        self._basis_tree.lock_read()
189
195
        try:
190
196
            from diff import iter_changes_to_status
 
197
            saved_file_messages = self._saved_commit_messages_manager.get()[1]
191
198
            for (file_id, real_path, change_type, display_path
192
199
                ) in iter_changes_to_status(self._basis_tree, self._wt):
193
200
                if self._selected and real_path != self._selected:
194
201
                    enabled = False
195
202
                else:
196
203
                    enabled = True
 
204
                try:
 
205
                    default_message = saved_file_messages[file_id]
 
206
                except KeyError:
 
207
                    default_message = ''
197
208
                item_iter = store.append([
198
209
                    file_id,
199
210
                    real_path.encode('UTF-8'),
200
211
                    enabled,
201
212
                    display_path.encode('UTF-8'),
202
213
                    change_type,
203
 
                    '', # Initial comment
 
214
                    default_message, # Initial comment
204
215
                    ])
205
216
                if self._selected and enabled:
206
217
                    initial_cursor = store.get_path(item_iter)
213
224
        # This sets the cursor, which causes the expander to close, which
214
225
        # causes the _file_message_text_view to never get realized. So we have
215
226
        # to give it a little kick, or it warns when we try to grab the focus
216
 
        self._treeview_files.set_cursor(initial_cursor)
 
227
        self._treeview_files.set_cursor(initial_cursor, None, False)
217
228
 
218
229
        def _realize_file_message_tree_view(*args):
219
230
            self._file_message_text_view.realize()
232
243
                proxy_obj = bus.get_object('org.freedesktop.NetworkManager',
233
244
                                           '/org/freedesktop/NetworkManager')
234
245
            except dbus.DBusException:
235
 
                mutter("networkmanager not available.")
 
246
                trace.mutter("networkmanager not available.")
236
247
                self._check_local.show()
237
248
                return
238
249
            
244
255
            except dbus.DBusException, e:
245
256
                # Silently drop errors. While DBus may be
246
257
                # available, NetworkManager doesn't necessarily have to be
247
 
                mutter("unable to get networkmanager state: %r" % e)
 
258
                trace.mutter("unable to get networkmanager state: %r" % e)
248
259
        self._check_local.show()
249
260
 
250
261
    def _fill_in_per_file_info(self):
267
278
        """Build up the dialog widgets."""
268
279
        # The primary pane which splits it into left and right (adjustable)
269
280
        # sections.
270
 
        self._hpane = gtk.HPaned()
 
281
        self._hpane = Gtk.HPaned()
271
282
 
272
283
        self._construct_left_pane()
273
284
        self._construct_right_pane()
274
285
        self._construct_action_pane()
275
286
 
276
 
        self.vbox.pack_start(self._hpane)
 
287
        self.get_content_area().pack_start(self._hpane, True, True, 0)
277
288
        self._hpane.show()
278
289
        self.set_focus(self._global_message_text_view)
279
290
 
298
309
        self._hpane.set_position(300)
299
310
 
300
311
    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)
 
312
        group = Gtk.AccelGroup()
 
313
        group.connect(Gdk.keyval_from_name('N'),
 
314
                      Gdk.ModifierType.CONTROL_MASK, 0, self._on_accel_next)
304
315
        self.add_accel_group(group)
305
316
 
306
317
        # ignore the escape key (avoid closing the window)
307
318
        self.connect_object('close', self.emit_stop_by_name, 'close')
308
319
 
309
320
    def _construct_left_pane(self):
310
 
        self._left_pane_box = gtk.VBox(homogeneous=False, spacing=5)
 
321
        self._left_pane_box = Gtk.VBox(homogeneous=False, spacing=5)
311
322
        self._construct_file_list()
312
323
        self._construct_pending_list()
313
324
 
314
 
        self._check_local = gtk.CheckButton(_i18n("_Only commit locally"),
 
325
        self._check_local = Gtk.CheckButton(_i18n("_Only commit locally"),
315
326
                                            use_underline=True)
316
 
        self._left_pane_box.pack_end(self._check_local, False, False)
 
327
        self._left_pane_box.pack_end(self._check_local, False, False, 0)
317
328
        self._check_local.set_active(False)
318
329
 
319
330
        self._hpane.pack1(self._left_pane_box, resize=False, shrink=False)
326
337
        # commit, and 1 for file commit, and it looked good. But I don't seem
327
338
        # to have a way to do that with the gtk boxes... :( (Which is extra
328
339
        # weird since wx uses gtk on Linux...)
329
 
        self._right_pane_table = gtk.Table(rows=10, columns=1, homogeneous=False)
 
340
        self._right_pane_table = Gtk.Table(rows=10, columns=1, homogeneous=False)
330
341
        self._right_pane_table.set_row_spacings(5)
331
342
        self._right_pane_table.set_col_spacings(5)
332
343
        self._right_pane_table_row = 0
338
349
        self._hpane.pack2(self._right_pane_table, resize=True, shrink=True)
339
350
 
340
351
    def _construct_action_pane(self):
341
 
        self._button_commit = gtk.Button(_i18n("Comm_it"), use_underline=True)
 
352
        self._button_cancel = Gtk.Button(stock=Gtk.STOCK_CANCEL)
 
353
        self._button_cancel.connect('clicked', self._on_cancel_clicked)
 
354
        self._button_cancel.show()
 
355
        self.get_action_area().pack_end(
 
356
            self._button_cancel, True, True, 0)
 
357
        self._button_commit = Gtk.Button(_i18n("Comm_it"), use_underline=True)
342
358
        self._button_commit.connect('clicked', self._on_commit_clicked)
343
 
        self._button_commit.set_flags(gtk.CAN_DEFAULT)
 
359
        self._button_commit.set_can_default(True)
344
360
        self._button_commit.show()
345
 
        self.action_area.pack_end(self._button_commit)
 
361
        self.get_action_area().pack_end(
 
362
            self._button_commit, True, True, 0)
346
363
        self._button_commit.grab_default()
347
364
 
348
365
    def _add_to_right_table(self, widget, weight, expanding=False):
354
371
        """
355
372
        end_row = self._right_pane_table_row + weight
356
373
        options = 0
357
 
        expand_opts = gtk.EXPAND | gtk.FILL | gtk.SHRINK
 
374
        expand_opts = Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK
358
375
        if expanding:
359
376
            options = expand_opts
360
377
        self._right_pane_table.attach(widget, 0, 1,
363
380
        self._right_pane_table_row = end_row
364
381
 
365
382
    def _construct_file_list(self):
366
 
        self._files_box = gtk.VBox(homogeneous=False, spacing=0)
367
 
        file_label = gtk.Label(_i18n('Files'))
 
383
        self._files_box = Gtk.VBox(homogeneous=False, spacing=0)
 
384
        file_label = Gtk.Label(label=_i18n('Files'))
368
385
        # file_label.show()
369
 
        self._files_box.pack_start(file_label, expand=False)
 
386
        self._files_box.pack_start(file_label, False, True, 0)
370
387
 
371
 
        self._commit_all_files_radio = gtk.RadioButton(
 
388
        self._commit_all_files_radio = Gtk.RadioButton.new_with_label(
372
389
            None, _i18n("Commit all changes"))
373
 
        self._files_box.pack_start(self._commit_all_files_radio, expand=False)
 
390
        self._files_box.pack_start(self._commit_all_files_radio, False, True, 0)
374
391
        self._commit_all_files_radio.show()
375
392
        self._commit_all_files_radio.connect('toggled',
376
393
            self._toggle_commit_selection)
377
 
        self._commit_selected_radio = gtk.RadioButton(
 
394
        self._commit_selected_radio = Gtk.RadioButton.new_with_label_from_widget(
378
395
            self._commit_all_files_radio, _i18n("Only commit selected changes"))
379
 
        self._files_box.pack_start(self._commit_selected_radio, expand=False)
 
396
        self._files_box.pack_start(self._commit_selected_radio, False, True, 0)
380
397
        self._commit_selected_radio.show()
381
398
        self._commit_selected_radio.connect('toggled',
382
399
            self._toggle_commit_selection)
385
402
            self._commit_all_files_radio.set_sensitive(False)
386
403
            self._commit_selected_radio.set_sensitive(False)
387
404
 
388
 
        scroller = gtk.ScrolledWindow()
389
 
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
390
 
        self._treeview_files = gtk.TreeView()
 
405
        scroller = Gtk.ScrolledWindow()
 
406
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
 
407
        self._treeview_files = Gtk.TreeView()
391
408
        self._treeview_files.show()
392
409
        scroller.add(self._treeview_files)
393
 
        scroller.set_shadow_type(gtk.SHADOW_IN)
 
410
        scroller.set_shadow_type(Gtk.ShadowType.IN)
394
411
        scroller.show()
395
 
        self._files_box.pack_start(scroller,
396
 
                                   expand=True, fill=True)
 
412
        self._files_box.pack_start(scroller, True, True, 0)
397
413
        self._files_box.show()
398
 
        self._left_pane_box.pack_start(self._files_box)
 
414
        self._left_pane_box.pack_start(self._files_box, True, True, 0)
399
415
 
400
416
        # Keep note that all strings stored in a ListStore must be UTF-8
401
417
        # strings. GTK does not support directly setting and restoring Unicode
402
418
        # 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
 
419
        liststore = Gtk.ListStore(
 
420
            GObject.TYPE_STRING,  # [0] file_id
 
421
            GObject.TYPE_STRING,  # [1] real path
 
422
            GObject.TYPE_BOOLEAN, # [2] checkbox
 
423
            GObject.TYPE_STRING,  # [3] display path
 
424
            GObject.TYPE_STRING,  # [4] changes type
 
425
            GObject.TYPE_STRING,  # [5] commit message
410
426
            )
411
427
        self._files_store = liststore
412
428
        self._treeview_files.set_model(liststore)
413
 
        crt = gtk.CellRendererToggle()
 
429
        crt = Gtk.CellRendererToggle()
414
430
        crt.set_property('activatable', not bool(self._pending))
415
431
        crt.connect("toggled", self._toggle_commit, self._files_store)
416
432
        if self._pending:
417
433
            name = _i18n('Commit*')
418
434
        else:
419
435
            name = _i18n('Commit')
420
 
        commit_col = gtk.TreeViewColumn(name, crt, active=2)
 
436
        commit_col = Gtk.TreeViewColumn(name, crt, active=2)
421
437
        commit_col.set_visible(False)
422
438
        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))
 
439
        self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Path'),
 
440
                                           Gtk.CellRendererText(), text=3))
 
441
        self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Type'),
 
442
                                           Gtk.CellRendererText(), text=4))
427
443
        self._treeview_files.connect('cursor-changed',
428
444
                                     self._on_treeview_files_cursor_changed)
429
445
 
430
446
    def _toggle_commit(self, cell, path, model):
431
 
        if model[path][0] is None: # No file_id means 'All Files'
 
447
        if model[path][0] == "": # No file_id means 'All Files'
432
448
            new_val = not model[path][2]
433
449
            for node in model:
434
450
                node[2] = new_val
444
460
                checked_col.set_visible(False)
445
461
            else:
446
462
                checked_col.set_visible(True)
447
 
            renderer = checked_col.get_cell_renderers()[0]
 
463
            renderer = checked_col.get_cells()[0]
448
464
            renderer.set_property('activatable', not all_files)
449
465
 
450
466
    def _construct_pending_list(self):
451
467
        # Pending information defaults to hidden, we put it all in 1 box, so
452
468
        # that we can show/hide all of them at once
453
 
        self._pending_box = gtk.VBox()
 
469
        self._pending_box = Gtk.VBox()
454
470
        self._pending_box.hide()
455
471
 
456
 
        pending_message = gtk.Label()
 
472
        pending_message = Gtk.Label()
457
473
        pending_message.set_markup(
458
474
            _i18n('<i>* Cannot select specific files when merging</i>'))
459
 
        self._pending_box.pack_start(pending_message, expand=False, padding=5)
 
475
        self._pending_box.pack_start(pending_message, False, True, 5)
460
476
        pending_message.show()
461
477
 
462
 
        pending_label = gtk.Label(_i18n('Pending Revisions'))
463
 
        self._pending_box.pack_start(pending_label, expand=False, padding=0)
 
478
        pending_label = Gtk.Label(label=_i18n('Pending Revisions'))
 
479
        self._pending_box.pack_start(pending_label, False, True, 0)
464
480
        pending_label.show()
465
481
 
466
 
        scroller = gtk.ScrolledWindow()
467
 
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
468
 
        self._treeview_pending = gtk.TreeView()
 
482
        scroller = Gtk.ScrolledWindow()
 
483
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
 
484
        self._treeview_pending = Gtk.TreeView()
469
485
        scroller.add(self._treeview_pending)
470
 
        scroller.set_shadow_type(gtk.SHADOW_IN)
 
486
        scroller.set_shadow_type(Gtk.ShadowType.IN)
471
487
        scroller.show()
472
 
        self._pending_box.pack_start(scroller,
473
 
                                     expand=True, fill=True, padding=5)
 
488
        self._pending_box.pack_start(scroller, True, True, 5)
474
489
        self._treeview_pending.show()
475
 
        self._left_pane_box.pack_start(self._pending_box)
 
490
        self._left_pane_box.pack_start(self._pending_box, True, True, 0)
476
491
 
477
 
        liststore = gtk.ListStore(gobject.TYPE_STRING, # revision_id
478
 
                                  gobject.TYPE_STRING, # date
479
 
                                  gobject.TYPE_STRING, # committer
480
 
                                  gobject.TYPE_STRING, # summary
 
492
        liststore = Gtk.ListStore(GObject.TYPE_STRING, # revision_id
 
493
                                  GObject.TYPE_STRING, # date
 
494
                                  GObject.TYPE_STRING, # committer
 
495
                                  GObject.TYPE_STRING, # summary
481
496
                                 )
482
497
        self._pending_store = liststore
483
498
        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))
 
499
        self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Date'),
 
500
                                             Gtk.CellRendererText(), text=1))
 
501
        self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Committer'),
 
502
                                             Gtk.CellRendererText(), text=2))
 
503
        self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Summary'),
 
504
                                             Gtk.CellRendererText(), text=3))
490
505
 
491
506
    def _construct_diff_view(self):
492
 
        from diff import DiffView
 
507
        from bzrlib.plugins.gtk.diff import DiffView
493
508
 
494
509
        # TODO: jam 2007-10-30 The diff label is currently disabled. If we
495
510
        #       decide that we really don't ever want to display it, we should
496
511
        #       actually remove it, and other references to it, along with the
497
512
        #       tests that it is set properly.
498
 
        self._diff_label = gtk.Label(_i18n('Diff for whole tree'))
 
513
        self._diff_label = Gtk.Label(label=_i18n('Diff for whole tree'))
499
514
        self._diff_label.set_alignment(0, 0)
500
515
        self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
501
516
        self._add_to_right_table(self._diff_label, 1, False)
506
521
        self._diff_view.show()
507
522
 
508
523
    def _construct_file_message(self):
509
 
        scroller = gtk.ScrolledWindow()
510
 
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
524
        scroller = Gtk.ScrolledWindow()
 
525
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
511
526
 
512
 
        self._file_message_text_view = gtk.TextView()
 
527
        self._file_message_text_view = Gtk.TextView()
513
528
        scroller.add(self._file_message_text_view)
514
 
        scroller.set_shadow_type(gtk.SHADOW_IN)
 
529
        scroller.set_shadow_type(Gtk.ShadowType.IN)
515
530
        scroller.show()
516
531
 
517
 
        self._file_message_text_view.modify_font(pango.FontDescription("Monospace"))
518
 
        self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
 
532
        self._file_message_text_view.modify_font(Pango.FontDescription("Monospace"))
 
533
        self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
519
534
        self._file_message_text_view.set_accepts_tab(False)
520
535
        self._file_message_text_view.show()
521
536
 
522
 
        self._file_message_expander = gtk.Expander(_i18n('File commit message'))
 
537
        self._file_message_expander = Gtk.Expander(
 
538
            label=_i18n('File commit message'))
523
539
        self._file_message_expander.set_expanded(True)
524
540
        self._file_message_expander.add(scroller)
525
541
        self._add_to_right_table(self._file_message_expander, 1, False)
526
542
        self._file_message_expander.show()
527
543
 
528
544
    def _construct_global_message(self):
529
 
        self._global_message_label = gtk.Label(_i18n('Global Commit Message'))
 
545
        self._global_message_label = Gtk.Label(label=_i18n('Global Commit Message'))
530
546
        self._global_message_label.set_markup(
531
547
            _i18n('<b>Global Commit Message</b>'))
532
548
        self._global_message_label.set_alignment(0, 0)
535
551
        # Can we remove the spacing between the label and the box?
536
552
        self._global_message_label.show()
537
553
 
538
 
        scroller = gtk.ScrolledWindow()
539
 
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
554
        scroller = Gtk.ScrolledWindow()
 
555
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
540
556
 
541
 
        self._global_message_text_view = gtk.TextView()
542
 
        self._global_message_text_view.modify_font(pango.FontDescription("Monospace"))
 
557
        self._global_message_text_view = Gtk.TextView()
 
558
        self._set_global_commit_message(self._saved_commit_messages_manager.get()[0])
 
559
        self._global_message_text_view.modify_font(Pango.FontDescription("Monospace"))
543
560
        scroller.add(self._global_message_text_view)
544
 
        scroller.set_shadow_type(gtk.SHADOW_IN)
 
561
        scroller.set_shadow_type(Gtk.ShadowType.IN)
545
562
        scroller.show()
546
563
        self._add_to_right_table(scroller, 2, True)
547
 
        self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
 
564
        self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
548
565
        self._file_message_text_view.set_accepts_tab(False)
549
566
        self._global_message_text_view.show()
550
567
 
555
572
        if selection is not None:
556
573
            path, display_path = model.get(selection, 1, 3)
557
574
            self._diff_label.set_text(_i18n('Diff for ') + display_path)
558
 
            if path is None:
 
575
            if path == "":
559
576
                self._diff_view.show_diff(None)
560
577
            else:
561
 
                self._diff_view.show_diff([path.decode('UTF-8')])
 
578
                self._diff_view.show_diff([osutils.safe_unicode(path)])
562
579
            self._update_per_file_info(selection)
563
580
 
564
581
    def _on_accel_next(self, accel_group, window, keyval, modifier):
575
592
            # We have either made it to the end of the list, or nothing was
576
593
            # selected. Either way, select All Files, and jump to the global
577
594
            # commit message.
578
 
            self._treeview_files.set_cursor((0,))
 
595
            self._treeview_files.set_cursor(
 
596
                Gtk.TreePath(path=0), "", False)
579
597
            self._global_message_text_view.grab_focus()
580
598
        else:
581
599
            # Set the cursor to this entry, and jump to the per-file commit
582
600
            # message
583
 
            self._treeview_files.set_cursor(model.get_path(next))
 
601
            self._treeview_files.set_cursor(model.get_path(next), None, False)
584
602
            self._file_message_text_view.grab_focus()
585
603
 
586
604
    def _save_current_file_message(self):
588
606
            return # Nothing to save
589
607
        text_buffer = self._file_message_text_view.get_buffer()
590
608
        cur_text = text_buffer.get_text(text_buffer.get_start_iter(),
591
 
                                        text_buffer.get_end_iter())
 
609
                                        text_buffer.get_end_iter(), True)
592
610
        last_selected = self._files_store.get_iter(self._last_selected_file)
593
611
        self._files_store.set_value(last_selected, 5, cur_text)
594
612
 
600
618
        self._save_current_file_message()
601
619
        text_buffer = self._file_message_text_view.get_buffer()
602
620
        file_id, display_path, message = self._files_store.get(selection, 0, 3, 5)
603
 
        if file_id is None: # Whole tree
 
621
        if file_id == "": # Whole tree
604
622
            self._file_message_expander.set_label(_i18n('File commit message'))
605
623
            self._file_message_expander.set_expanded(False)
606
624
            self._file_message_expander.set_sensitive(False)
623
641
        files = []
624
642
        records = iter(self._files_store)
625
643
        rec = records.next() # Skip the All Files record
626
 
        assert rec[0] is None, "Are we skipping the wrong record?"
 
644
        assert rec[0] == "", "Are we skipping the wrong record?"
627
645
 
628
646
        file_info = []
629
647
        for record in records:
630
648
            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
 
649
                file_id = osutils.safe_utf8(record[0]) # [0] file_id
 
650
                path = osutils.safe_utf8(record[1])    # [1] real path
 
651
                # [5] commit message
 
652
                file_message = _sanitize_and_decode_message(record[5])
634
653
                files.append(path.decode('UTF-8'))
635
654
                if self._enable_per_file_commits and file_message:
636
655
                    # All of this needs to be utf-8 information
 
656
                    file_message = file_message.encode('UTF-8')
637
657
                    file_info.append({'path':path, 'file_id':file_id,
638
658
                                     'message':file_message})
639
659
        file_info.sort(key=lambda x:(x['path'], x['file_id']))
643
663
            return files, []
644
664
 
645
665
    @show_bzr_error
 
666
    def _on_cancel_clicked(self, button):
 
667
        """ Cancel button clicked handler. """
 
668
        self._do_cancel()
 
669
 
 
670
    @show_bzr_error
 
671
    def _on_delete_window(self, source, event):
 
672
        """ Delete window handler. """
 
673
        self._do_cancel()
 
674
 
 
675
    def _do_cancel(self):
 
676
        """If requested, saves commit messages when cancelling gcommit; they are re-used by a next gcommit"""
 
677
        mgr = SavedCommitMessagesManager()
 
678
        self._saved_commit_messages_manager = mgr
 
679
        mgr.insert(self._get_global_commit_message(),
 
680
                   self._get_specific_files()[1])
 
681
        if mgr.is_not_empty(): # maybe worth saving
 
682
            response = self._question_dialog(
 
683
                _i18n('Commit cancelled'),
 
684
                _i18n('Do you want to save your commit messages ?'),
 
685
                parent=self)
 
686
            if response == Gtk.ResponseType.NO:
 
687
                 # save nothing and destroy old comments if any
 
688
                mgr = SavedCommitMessagesManager()
 
689
        mgr.save(self._wt, self._wt.branch)
 
690
        self.response(Gtk.ResponseType.CANCEL) # close window
 
691
 
 
692
    @show_bzr_error
646
693
    def _on_commit_clicked(self, button):
647
694
        """ Commit button clicked handler. """
648
695
        self._do_commit()
653
700
        if message == '':
654
701
            response = self._question_dialog(
655
702
                _i18n('Commit with an empty message?'),
656
 
                _i18n('You can describe your commit intent in the message.'))
657
 
            if response == gtk.RESPONSE_NO:
 
703
                _i18n('You can describe your commit intent in the message.'),
 
704
                parent=self)
 
705
            if response == Gtk.ResponseType.NO:
658
706
                # Kindly give focus to message area
659
707
                self._global_message_text_view.grab_focus()
660
708
                return
673
721
        for path in self._wt.unknowns():
674
722
            response = self._question_dialog(
675
723
                _i18n("Commit with unknowns?"),
676
 
                _i18n("Unknown files exist in the working tree. Commit anyway?"))
677
 
            if response == gtk.RESPONSE_NO:
 
724
                _i18n("Unknown files exist in the working tree. Commit anyway?"),
 
725
                parent=self)
 
726
                # Doesn't set a parent for the dialog..
 
727
            if response == Gtk.ResponseType.NO:
678
728
                return
679
729
            break
680
730
 
693
743
            response = self._question_dialog(
694
744
                _i18n('Commit with no changes?'),
695
745
                _i18n('There are no changes in the working tree.'
696
 
                      ' Do you want to commit anyway?'))
697
 
            if response == gtk.RESPONSE_YES:
 
746
                      ' Do you want to commit anyway?'),
 
747
                parent=self)
 
748
            if response == Gtk.ResponseType.YES:
698
749
                rev_id = self._wt.commit(message,
699
750
                               allow_pointless=True,
700
751
                               strict=False,
702
753
                               specific_files=specific_files,
703
754
                               revprops=revprops)
704
755
        self.committed_revision_id = rev_id
705
 
        self.response(gtk.RESPONSE_OK)
 
756
        # destroy old comments if any
 
757
        SavedCommitMessagesManager().save(self._wt, self._wt.branch)
 
758
        self.response(Gtk.ResponseType.OK)
706
759
 
707
760
    def _get_global_commit_message(self):
708
761
        buf = self._global_message_text_view.get_buffer()
709
762
        start, end = buf.get_bounds()
710
 
        return buf.get_text(start, end).decode('utf-8')
 
763
        text = buf.get_text(start, end, True)
 
764
        return _sanitize_and_decode_message(text)
711
765
 
712
766
    def _set_global_commit_message(self, message):
713
767
        """Just a helper for the test suite."""
734
788
                                       show_offset=False)
735
789
        rev_dict['revision_id'] = rev.revision_id
736
790
        return rev_dict
 
791