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