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