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