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
30
from bzrlib import errors, osutils
31
from bzrlib.trace import mutter
32
from bzrlib.util import bencode
34
from bzrlib.plugins.gtk import _i18n
35
from dialog import error_dialog, question_dialog
36
from errors import show_bzr_error
19
from gi.repository import Gdk
20
from gi.repository import Gtk
21
from gi.repository import GObject
22
from gi.repository import Pango
25
from bzrlib import bencode
27
from bzrlib.util import bencode
33
from bzrlib.plugins.gtk.dialog import question_dialog
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
100
class CommitDialog(gtk.Dialog):
95
_newline_variants_re = re.compile(r'\r\n?')
96
def _sanitize_and_decode_message(utf8_message):
97
"""Turn a utf-8 message into a sanitized Unicode message."""
98
fixed_newline = _newline_variants_re.sub('\n', utf8_message)
99
return fixed_newline.decode('utf-8')
102
class CommitDialog(Gtk.Dialog):
101
103
"""Implementation of Commit."""
103
105
def __init__(self, wt, selected=None, parent=None):
104
gtk.Dialog.__init__(self, title="Commit - Olive",
107
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
106
super(CommitDialog, self).__init__(
107
title="Commit to %s" % wt.basedir, parent=parent, flags=0)
108
self.connect('delete-event', self._on_delete_window)
108
109
self._question_dialog = question_dialog
111
self.set_type_hint(Gdk.WindowTypeHint.NORMAL)
111
114
# TODO: Do something with this value, it is used by Olive
112
115
# It used to set all changes but this one to False
188
197
self._basis_tree.lock_read()
190
199
from diff import iter_changes_to_status
200
saved_file_messages = self._saved_commit_messages_manager.get()[1]
191
201
for (file_id, real_path, change_type, display_path
192
202
) in iter_changes_to_status(self._basis_tree, self._wt):
193
203
if self._selected and real_path != self._selected:
208
default_message = saved_file_messages[file_id]
197
211
item_iter = store.append([
199
213
real_path.encode('UTF-8'),
201
215
display_path.encode('UTF-8'),
203
'', # Initial comment
217
default_message, # Initial comment
205
219
if self._selected and enabled:
206
220
initial_cursor = store.get_path(item_iter)
298
312
self._hpane.set_position(300)
300
314
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)
315
group = Gtk.AccelGroup()
316
group.connect(Gdk.keyval_from_name('N'),
317
Gdk.ModifierType.CONTROL_MASK, 0, self._on_accel_next)
304
318
self.add_accel_group(group)
306
320
# ignore the escape key (avoid closing the window)
307
321
self.connect_object('close', self.emit_stop_by_name, 'close')
309
323
def _construct_left_pane(self):
310
self._left_pane_box = gtk.VBox(homogeneous=False, spacing=5)
324
self._left_pane_box = Gtk.VBox(homogeneous=False, spacing=5)
311
325
self._construct_file_list()
312
326
self._construct_pending_list()
314
self._check_local = gtk.CheckButton(_i18n("_Only commit locally"),
328
self._check_local = Gtk.CheckButton(_i18n("_Only commit locally"),
315
329
use_underline=True)
316
self._left_pane_box.pack_end(self._check_local, False, False)
330
self._left_pane_box.pack_end(self._check_local, False, False, 0)
317
331
self._check_local.set_active(False)
319
333
self._hpane.pack1(self._left_pane_box, resize=False, shrink=False)
338
352
self._hpane.pack2(self._right_pane_table, resize=True, shrink=True)
340
354
def _construct_action_pane(self):
341
self._button_commit = gtk.Button(_i18n("Comm_it"), use_underline=True)
355
self._button_cancel = Gtk.Button(stock=Gtk.STOCK_CANCEL)
356
self._button_cancel.connect('clicked', self._on_cancel_clicked)
357
self._button_cancel.show()
358
self.get_action_area().pack_end(
359
self._button_cancel, True, True, 0)
360
self._button_commit = Gtk.Button(_i18n("Comm_it"), use_underline=True)
342
361
self._button_commit.connect('clicked', self._on_commit_clicked)
343
self._button_commit.set_flags(gtk.CAN_DEFAULT)
362
self._button_commit.set_can_default(True)
344
363
self._button_commit.show()
345
self.action_area.pack_end(self._button_commit)
364
self.get_action_area().pack_end(
365
self._button_commit, True, True, 0)
346
366
self._button_commit.grab_default()
348
368
def _add_to_right_table(self, widget, weight, expanding=False):
363
383
self._right_pane_table_row = end_row
365
385
def _construct_file_list(self):
366
self._files_box = gtk.VBox(homogeneous=False, spacing=0)
367
file_label = gtk.Label(_i18n('Files'))
386
self._files_box = Gtk.VBox(homogeneous=False, spacing=0)
387
file_label = Gtk.Label(label=_i18n('Files'))
368
388
# file_label.show()
369
self._files_box.pack_start(file_label, expand=False)
389
self._files_box.pack_start(file_label, False, True, 0)
371
self._commit_all_files_radio = gtk.RadioButton(
391
self._commit_all_files_radio = Gtk.RadioButton.new_with_label(
372
392
None, _i18n("Commit all changes"))
373
self._files_box.pack_start(self._commit_all_files_radio, expand=False)
393
self._files_box.pack_start(self._commit_all_files_radio, False, True, 0)
374
394
self._commit_all_files_radio.show()
375
395
self._commit_all_files_radio.connect('toggled',
376
396
self._toggle_commit_selection)
377
self._commit_selected_radio = gtk.RadioButton(
397
self._commit_selected_radio = Gtk.RadioButton.new_with_label_from_widget(
378
398
self._commit_all_files_radio, _i18n("Only commit selected changes"))
379
self._files_box.pack_start(self._commit_selected_radio, expand=False)
399
self._files_box.pack_start(self._commit_selected_radio, False, True, 0)
380
400
self._commit_selected_radio.show()
381
401
self._commit_selected_radio.connect('toggled',
382
402
self._toggle_commit_selection)
385
405
self._commit_all_files_radio.set_sensitive(False)
386
406
self._commit_selected_radio.set_sensitive(False)
388
scroller = gtk.ScrolledWindow()
389
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
390
self._treeview_files = gtk.TreeView()
408
scroller = Gtk.ScrolledWindow()
409
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
410
self._treeview_files = Gtk.TreeView()
391
411
self._treeview_files.show()
392
412
scroller.add(self._treeview_files)
393
scroller.set_shadow_type(gtk.SHADOW_IN)
413
scroller.set_shadow_type(Gtk.ShadowType.IN)
395
self._files_box.pack_start(scroller,
396
expand=True, fill=True)
415
self._files_box.pack_start(scroller, True, True, 0)
397
416
self._files_box.show()
398
self._left_pane_box.pack_start(self._files_box)
417
self._left_pane_box.pack_start(self._files_box, True, True, 0)
400
419
# Keep note that all strings stored in a ListStore must be UTF-8
401
420
# strings. GTK does not support directly setting and restoring Unicode
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
422
liststore = Gtk.ListStore(
423
GObject.TYPE_STRING, # [0] file_id
424
GObject.TYPE_STRING, # [1] real path
425
GObject.TYPE_BOOLEAN, # [2] checkbox
426
GObject.TYPE_STRING, # [3] display path
427
GObject.TYPE_STRING, # [4] changes type
428
GObject.TYPE_STRING, # [5] commit message
411
430
self._files_store = liststore
412
431
self._treeview_files.set_model(liststore)
413
crt = gtk.CellRendererToggle()
432
crt = Gtk.CellRendererToggle()
414
433
crt.set_property('activatable', not bool(self._pending))
415
434
crt.connect("toggled", self._toggle_commit, self._files_store)
416
435
if self._pending:
417
436
name = _i18n('Commit*')
419
438
name = _i18n('Commit')
420
commit_col = gtk.TreeViewColumn(name, crt, active=2)
439
commit_col = Gtk.TreeViewColumn(name, crt, active=2)
421
440
commit_col.set_visible(False)
422
441
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))
442
self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Path'),
443
Gtk.CellRendererText(), text=3))
444
self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Type'),
445
Gtk.CellRendererText(), text=4))
427
446
self._treeview_files.connect('cursor-changed',
428
447
self._on_treeview_files_cursor_changed)
444
463
checked_col.set_visible(False)
446
465
checked_col.set_visible(True)
447
renderer = checked_col.get_cell_renderers()[0]
466
renderer = checked_col.get_cells()[0]
448
467
renderer.set_property('activatable', not all_files)
450
469
def _construct_pending_list(self):
451
470
# Pending information defaults to hidden, we put it all in 1 box, so
452
471
# that we can show/hide all of them at once
453
self._pending_box = gtk.VBox()
472
self._pending_box = Gtk.VBox()
454
473
self._pending_box.hide()
456
pending_message = gtk.Label()
475
pending_message = Gtk.Label()
457
476
pending_message.set_markup(
458
477
_i18n('<i>* Cannot select specific files when merging</i>'))
459
self._pending_box.pack_start(pending_message, expand=False, padding=5)
478
self._pending_box.pack_start(pending_message, False, True, 5)
460
479
pending_message.show()
462
pending_label = gtk.Label(_i18n('Pending Revisions'))
463
self._pending_box.pack_start(pending_label, expand=False, padding=0)
481
pending_label = Gtk.Label(label=_i18n('Pending Revisions'))
482
self._pending_box.pack_start(pending_label, False, True, 0)
464
483
pending_label.show()
466
scroller = gtk.ScrolledWindow()
467
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
468
self._treeview_pending = gtk.TreeView()
485
scroller = Gtk.ScrolledWindow()
486
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
487
self._treeview_pending = Gtk.TreeView()
469
488
scroller.add(self._treeview_pending)
470
scroller.set_shadow_type(gtk.SHADOW_IN)
489
scroller.set_shadow_type(Gtk.ShadowType.IN)
472
self._pending_box.pack_start(scroller,
473
expand=True, fill=True, padding=5)
491
self._pending_box.pack_start(scroller, True, True, 5)
474
492
self._treeview_pending.show()
475
self._left_pane_box.pack_start(self._pending_box)
493
self._left_pane_box.pack_start(self._pending_box, True, True, 0)
477
liststore = gtk.ListStore(gobject.TYPE_STRING, # revision_id
478
gobject.TYPE_STRING, # date
479
gobject.TYPE_STRING, # committer
480
gobject.TYPE_STRING, # summary
495
liststore = Gtk.ListStore(GObject.TYPE_STRING, # revision_id
496
GObject.TYPE_STRING, # date
497
GObject.TYPE_STRING, # committer
498
GObject.TYPE_STRING, # summary
482
500
self._pending_store = liststore
483
501
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))
502
self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Date'),
503
Gtk.CellRendererText(), text=1))
504
self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Committer'),
505
Gtk.CellRendererText(), text=2))
506
self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Summary'),
507
Gtk.CellRendererText(), text=3))
491
509
def _construct_diff_view(self):
492
from diff import DiffView
510
from bzrlib.plugins.gtk.diff import DiffView
494
512
# TODO: jam 2007-10-30 The diff label is currently disabled. If we
495
513
# decide that we really don't ever want to display it, we should
496
514
# actually remove it, and other references to it, along with the
497
515
# tests that it is set properly.
498
self._diff_label = gtk.Label(_i18n('Diff for whole tree'))
516
self._diff_label = Gtk.Label(label=_i18n('Diff for whole tree'))
499
517
self._diff_label.set_alignment(0, 0)
500
518
self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
501
519
self._add_to_right_table(self._diff_label, 1, False)
506
524
self._diff_view.show()
508
526
def _construct_file_message(self):
509
scroller = gtk.ScrolledWindow()
510
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
527
scroller = Gtk.ScrolledWindow()
528
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
512
self._file_message_text_view = gtk.TextView()
530
self._file_message_text_view = Gtk.TextView()
513
531
scroller.add(self._file_message_text_view)
514
scroller.set_shadow_type(gtk.SHADOW_IN)
532
scroller.set_shadow_type(Gtk.ShadowType.IN)
517
self._file_message_text_view.modify_font(pango.FontDescription("Monospace"))
518
self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
535
self._file_message_text_view.modify_font(Pango.FontDescription("Monospace"))
536
self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
519
537
self._file_message_text_view.set_accepts_tab(False)
520
538
self._file_message_text_view.show()
522
self._file_message_expander = gtk.Expander(_i18n('File commit message'))
540
self._file_message_expander = Gtk.Expander(
541
label=_i18n('File commit message'))
523
542
self._file_message_expander.set_expanded(True)
524
543
self._file_message_expander.add(scroller)
525
544
self._add_to_right_table(self._file_message_expander, 1, False)
526
545
self._file_message_expander.show()
528
547
def _construct_global_message(self):
529
self._global_message_label = gtk.Label(_i18n('Global Commit Message'))
548
self._global_message_label = Gtk.Label(label=_i18n('Global Commit Message'))
530
549
self._global_message_label.set_markup(
531
550
_i18n('<b>Global Commit Message</b>'))
532
551
self._global_message_label.set_alignment(0, 0)
535
554
# Can we remove the spacing between the label and the box?
536
555
self._global_message_label.show()
538
scroller = gtk.ScrolledWindow()
539
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
557
scroller = Gtk.ScrolledWindow()
558
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
541
self._global_message_text_view = gtk.TextView()
542
self._global_message_text_view.modify_font(pango.FontDescription("Monospace"))
560
self._global_message_text_view = Gtk.TextView()
561
self._set_global_commit_message(self._saved_commit_messages_manager.get()[0])
562
self._global_message_text_view.modify_font(Pango.FontDescription("Monospace"))
543
563
scroller.add(self._global_message_text_view)
544
scroller.set_shadow_type(gtk.SHADOW_IN)
564
scroller.set_shadow_type(Gtk.ShadowType.IN)
546
566
self._add_to_right_table(scroller, 2, True)
547
self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
567
self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
548
568
self._file_message_text_view.set_accepts_tab(False)
549
569
self._global_message_text_view.show()
575
595
# We have either made it to the end of the list, or nothing was
576
596
# selected. Either way, select All Files, and jump to the global
577
597
# commit message.
578
self._treeview_files.set_cursor((0,))
598
self._treeview_files.set_cursor(
599
Gtk.TreePath(path=0), None, False)
579
600
self._global_message_text_view.grab_focus()
581
602
# Set the cursor to this entry, and jump to the per-file commit
583
self._treeview_files.set_cursor(model.get_path(next))
604
self._treeview_files.set_cursor(model.get_path(next), None, False)
584
605
self._file_message_text_view.grab_focus()
586
607
def _save_current_file_message(self):
669
def _on_cancel_clicked(self, button):
670
""" Cancel button clicked handler. """
674
def _on_delete_window(self, source, event):
675
""" Delete window handler. """
678
def _do_cancel(self):
679
"""If requested, saves commit messages when cancelling gcommit; they are re-used by a next gcommit"""
680
mgr = SavedCommitMessagesManager()
681
self._saved_commit_messages_manager = mgr
682
mgr.insert(self._get_global_commit_message(),
683
self._get_specific_files()[1])
684
if mgr.is_not_empty(): # maybe worth saving
685
response = self._question_dialog(
686
_i18n('Commit cancelled'),
687
_i18n('Do you want to save your commit messages ?'),
689
if response == Gtk.ResponseType.NO:
690
# save nothing and destroy old comments if any
691
mgr = SavedCommitMessagesManager()
692
mgr.save(self._wt, self._wt.branch)
693
self.response(Gtk.ResponseType.CANCEL) # close window
646
696
def _on_commit_clicked(self, button):
647
697
""" Commit button clicked handler. """
648
698
self._do_commit()
702
756
specific_files=specific_files,
703
757
revprops=revprops)
704
758
self.committed_revision_id = rev_id
705
self.response(gtk.RESPONSE_OK)
759
# destroy old comments if any
760
SavedCommitMessagesManager().save(self._wt, self._wt.branch)
761
self.response(Gtk.ResponseType.OK)
707
763
def _get_global_commit_message(self):
708
764
buf = self._global_message_text_view.get_buffer()
709
765
start, end = buf.get_bounds()
710
return buf.get_text(start, end).decode('utf-8')
766
text = buf.get_text(start, end, True)
767
return _sanitize_and_decode_message(text)
712
769
def _set_global_commit_message(self, message):
713
770
"""Just a helper for the test suite."""