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