/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz
0.8.19 by Szilveszter Farkas (Phanatic)
2006-07-21 Szilveszter Farkas <Szilveszter.Farkas@gmail.com>
1
# Copyright (C) 2006 by Szilveszter Farkas (Phanatic) <szilveszter.farkas@gmail.com>
0.8.46 by Szilveszter Farkas (Phanatic)
Modified OliveDialog class interface; huge cleanups.
2
#
0.8.19 by Szilveszter Farkas (Phanatic)
2006-07-21 Szilveszter Farkas <Szilveszter.Farkas@gmail.com>
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
0.8.46 by Szilveszter Farkas (Phanatic)
Modified OliveDialog class interface; huge cleanups.
7
#
0.8.19 by Szilveszter Farkas (Phanatic)
2006-07-21 Szilveszter Farkas <Szilveszter.Farkas@gmail.com>
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
0.8.46 by Szilveszter Farkas (Phanatic)
Modified OliveDialog class interface; huge cleanups.
12
#
0.8.19 by Szilveszter Farkas (Phanatic)
2006-07-21 Szilveszter Farkas <Szilveszter.Farkas@gmail.com>
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
576.1.1 by Jasper Groenewegen
Add parent setting to dialogs and implement in gcommit
17
import re
18
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
19
from gi.repository import Gtk
20
from gi.repository import GObject
21
from gi.repository import Pango
0.8.19 by Szilveszter Farkas (Phanatic)
2006-07-21 Szilveszter Farkas <Szilveszter.Farkas@gmail.com>
22
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
23
from bzrlib import (
24
    errors,
25
    osutils,
26
    trace,
27
    )
643 by Jelmer Vernooij
Fix import of bencode.
28
try:
646 by Jelmer Vernooij
Reorder bencode imports, prefer the new location to prevent deprecation warnings.
29
    from bzrlib import bencode
30
except ImportError:
643 by Jelmer Vernooij
Fix import of bencode.
31
    from bzrlib.util import bencode
0.8.20 by Szilveszter Farkas (Phanatic)
2006-07-24 Szilveszter Farkas <Szilveszter.Farkas@gmail.com>
32
576.1.1 by Jasper Groenewegen
Add parent setting to dialogs and implement in gcommit
33
from bzrlib.plugins.gtk.dialog import question_dialog
34
from bzrlib.plugins.gtk.errors import show_bzr_error
729.1.1 by Jelmer Vernooij
Move i18n support to a separate file, so gettext files aren't loaded unless bzr-gtk is used.
35
from bzrlib.plugins.gtk.i18n import _i18n
93.1.6 by Alexander Belchenko
detecting name of glade file doing in separate module (olive.gladefile)
36
158 by Jelmer Vernooij
If available, use NetworkManager to find out whether a commit should be local or not.
37
try:
38
    import dbus
39
    import dbus.glib
180 by Jelmer Vernooij
Don't obtain handle to network manager until it's actually needed.
40
    have_dbus = True
158 by Jelmer Vernooij
If available, use NetworkManager to find out whether a commit should be local or not.
41
except ImportError:
180 by Jelmer Vernooij
Don't obtain handle to network manager until it's actually needed.
42
    have_dbus = False
158 by Jelmer Vernooij
If available, use NetworkManager to find out whether a commit should be local or not.
43
278.1.4 by John Arbash Meinel
Just playing around.
44
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
45
def pending_revisions(wt):
46
    """Return a list of pending merges or None if there are none of them.
47
48
    Arguably this should be a core function, and
49
    ``bzrlib.status.show_pending_merges`` should be built on top of it.
50
51
    :return: [(rev, [children])]
52
    """
53
    parents = wt.get_parent_ids()
54
    if len(parents) < 2:
55
        return None
56
57
    # The basic pending merge algorithm uses the same algorithm as
58
    # bzrlib.status.show_pending_merges
59
    pending = parents[1:]
60
    branch = wt.branch
61
    last_revision = parents[0]
62
63
    if last_revision is not None:
64
        try:
65
            ignore = set(branch.repository.get_ancestry(last_revision,
66
                                                        topo_sorted=False))
67
        except errors.NoSuchRevision:
68
            # the last revision is a ghost : assume everything is new
69
            # except for it
70
            ignore = set([None, last_revision])
71
    else:
72
        ignore = set([None])
73
74
    pm = []
75
    for merge in pending:
76
        ignore.add(merge)
77
        try:
78
            rev = branch.repository.get_revision(merge)
79
            children = []
80
            pm.append((rev, children))
81
82
            # This does need to be topo sorted, so we search backwards
83
            inner_merges = branch.repository.get_ancestry(merge)
84
            assert inner_merges[0] is None
85
            inner_merges.pop(0)
86
            for mmerge in reversed(inner_merges):
87
                if mmerge in ignore:
88
                    continue
89
                rev = branch.repository.get_revision(mmerge)
90
                children.append(rev)
91
92
                ignore.add(mmerge)
93
        except errors.NoSuchRevision:
94
            print "DEBUG: NoSuchRevision:", merge
95
96
    return pm
97
98
622.1.1 by John Arbash Meinel
Ensure that per-file commit messages and global commit messages get sanitized.
99
_newline_variants_re = re.compile(r'\r\n?')
100
def _sanitize_and_decode_message(utf8_message):
101
    """Turn a utf-8 message into a sanitized Unicode message."""
102
    fixed_newline = _newline_variants_re.sub('\n', utf8_message)
103
    return fixed_newline.decode('utf-8')
104
105
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
106
class CommitDialog(Gtk.Dialog):
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
107
    """Implementation of Commit."""
108
109
    def __init__(self, wt, selected=None, parent=None):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
110
        GObject.GObject.__init__(self, title="Commit to %s" % wt.basedir,
635.2.7 by Vincent Ladeuil
Land Anne Mohsen's patch for bug 215674.
111
                            parent=parent, flags=0,)
112
        self.connect('delete-event', self._on_delete_window)
606 by Vincent Ladeuil
Fix gtk dialogs popping up and asking for input during selftest.
113
        self._question_dialog = question_dialog
635.2.7 by Vincent Ladeuil
Land Anne Mohsen's patch for bug 215674.
114
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
115
        self.set_type_hint(Gdk.WindowTypeHint.NORMAL)
278.1.24 by John Arbash Meinel
Actually show the commit button.
116
278.1.16 by John Arbash Meinel
Implement the file changes list on top of _iter_changes rather than
117
        self._wt = wt
278.1.34 by John Arbash Meinel
Cleanup, we are still ignoring the 'selected' property, and the 'wtpath'
118
        # TODO: Do something with this value, it is used by Olive
119
        #       It used to set all changes but this one to False
278.1.16 by John Arbash Meinel
Implement the file changes list on top of _iter_changes rather than
120
        self._selected = selected
278.1.33 by John Arbash Meinel
Only enable the per-file dialog if 'per_file_commits' is enabled in the config.
121
        self._enable_per_file_commits = True
278.1.43 by John Arbash Meinel
Finish connecting the 'Commit all changes' radio buttons.
122
        self._commit_all_changes = True
278.1.23 by John Arbash Meinel
Beginning to support actual commit.
123
        self.committed_revision_id = None # Nothing has been committed yet
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
124
        self._saved_commit_messages_manager = SavedCommitMessagesManager(self._wt, self._wt.branch)
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
125
278.1.16 by John Arbash Meinel
Implement the file changes list on top of _iter_changes rather than
126
        self.setup_params()
127
        self.construct()
128
        self.fill_in_data()
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
129
130
    def setup_params(self):
131
        """Setup the member variables for state."""
132
        self._basis_tree = self._wt.basis_tree()
278.1.12 by John Arbash Meinel
Delay computing the delta, and clean up some of the diff view names.
133
        self._delta = None
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
134
        self._pending = pending_revisions(self._wt)
135
136
        self._is_checkout = (self._wt.branch.get_bound_location() is not None)
137
278.1.14 by John Arbash Meinel
Tests that we fill out the pending list correctly.
138
    def fill_in_data(self):
139
        # Now that we are built, handle changes to the view based on the state
140
        self._fill_in_pending()
278.1.20 by John Arbash Meinel
We always select the All Files record in the files view,
141
        self._fill_in_diff()
278.1.15 by John Arbash Meinel
Hook up the list of modified files.
142
        self._fill_in_files()
278.1.25 by John Arbash Meinel
Add the 'Only Commit Locally' checkbox, we may want to put it elsewhere, though.
143
        self._fill_in_checkout()
278.1.33 by John Arbash Meinel
Only enable the per-file dialog if 'per_file_commits' is enabled in the config.
144
        self._fill_in_per_file_info()
278.1.14 by John Arbash Meinel
Tests that we fill out the pending list correctly.
145
146
    def _fill_in_pending(self):
147
        if not self._pending:
148
            self._pending_box.hide()
149
            return
150
151
        # TODO: We'd really prefer this to be a nested list
152
        for rev, children in self._pending:
153
            rev_info = self._rev_to_pending_info(rev)
278.1.15 by John Arbash Meinel
Hook up the list of modified files.
154
            self._pending_store.append([
278.1.14 by John Arbash Meinel
Tests that we fill out the pending list correctly.
155
                rev_info['revision_id'],
156
                rev_info['date'],
157
                rev_info['committer'],
158
                rev_info['summary'],
159
                ])
160
            for child in children:
161
                rev_info = self._rev_to_pending_info(child)
278.1.15 by John Arbash Meinel
Hook up the list of modified files.
162
                self._pending_store.append([
278.1.14 by John Arbash Meinel
Tests that we fill out the pending list correctly.
163
                    rev_info['revision_id'],
164
                    rev_info['date'],
165
                    rev_info['committer'],
166
                    rev_info['summary'],
167
                    ])
168
        self._pending_box.show()
169
278.1.15 by John Arbash Meinel
Hook up the list of modified files.
170
    def _fill_in_files(self):
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
171
        # We should really use add a progress bar of some kind.
278.1.15 by John Arbash Meinel
Hook up the list of modified files.
172
        # While we fill in the view, hide the store
173
        store = self._files_store
174
        self._treeview_files.set_model(None)
175
475.1.2 by Vincent Ladeuil
Fix bug #187283 fix replacing _() by _i18n().
176
        added = _i18n('added')
177
        removed = _i18n('removed')
178
        renamed = _i18n('renamed')
179
        renamed_and_modified = _i18n('renamed and modified')
180
        modified = _i18n('modified')
181
        kind_changed = _i18n('kind changed')
278.1.15 by John Arbash Meinel
Hook up the list of modified files.
182
278.1.16 by John Arbash Meinel
Implement the file changes list on top of _iter_changes rather than
183
        # The store holds:
278.1.21 by John Arbash Meinel
Start tracking the per-file commit messages.
184
        # [file_id, real path, checkbox, display path, changes type, message]
450 by Aaron Bentley
Update to use new Tree.iter_changes
185
        # iter_changes returns:
278.1.16 by John Arbash Meinel
Implement the file changes list on top of _iter_changes rather than
186
        # (file_id, (path_in_source, path_in_target),
187
        #  changed_content, versioned, parent, name, kind,
188
        #  executable)
189
278.1.35 by John Arbash Meinel
Make use of the 'selected' parameter to CommitDialog.
190
        all_enabled = (self._selected is None)
278.1.20 by John Arbash Meinel
We always select the All Files record in the files view,
191
        # The first entry is always the 'whole tree'
278.1.35 by John Arbash Meinel
Make use of the 'selected' parameter to CommitDialog.
192
        all_iter = store.append([None, None, all_enabled, 'All Files', '', ''])
193
        initial_cursor = store.get_path(all_iter)
278.1.16 by John Arbash Meinel
Implement the file changes list on top of _iter_changes rather than
194
        # should we pass specific_files?
195
        self._wt.lock_read()
196
        self._basis_tree.lock_read()
197
        try:
450 by Aaron Bentley
Update to use new Tree.iter_changes
198
            from diff import iter_changes_to_status
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
199
            saved_file_messages = self._saved_commit_messages_manager.get()[1]
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
200
            for (file_id, real_path, change_type, display_path
450 by Aaron Bentley
Update to use new Tree.iter_changes
201
                ) in iter_changes_to_status(self._basis_tree, self._wt):
278.1.35 by John Arbash Meinel
Make use of the 'selected' parameter to CommitDialog.
202
                if self._selected and real_path != self._selected:
203
                    enabled = False
204
                else:
205
                    enabled = True
635.2.7 by Vincent Ladeuil
Land Anne Mohsen's patch for bug 215674.
206
                try:
207
                    default_message = saved_file_messages[file_id]
208
                except KeyError:
209
                    default_message = ''
278.1.35 by John Arbash Meinel
Make use of the 'selected' parameter to CommitDialog.
210
                item_iter = store.append([
211
                    file_id,
212
                    real_path.encode('UTF-8'),
213
                    enabled,
214
                    display_path.encode('UTF-8'),
215
                    change_type,
635.2.7 by Vincent Ladeuil
Land Anne Mohsen's patch for bug 215674.
216
                    default_message, # Initial comment
278.1.35 by John Arbash Meinel
Make use of the 'selected' parameter to CommitDialog.
217
                    ])
218
                if self._selected and enabled:
219
                    initial_cursor = store.get_path(item_iter)
278.1.16 by John Arbash Meinel
Implement the file changes list on top of _iter_changes rather than
220
        finally:
221
            self._basis_tree.unlock()
222
            self._wt.unlock()
278.1.15 by John Arbash Meinel
Hook up the list of modified files.
223
224
        self._treeview_files.set_model(store)
278.1.21 by John Arbash Meinel
Start tracking the per-file commit messages.
225
        self._last_selected_file = None
278.1.32 by John Arbash Meinel
Add the Ctrl+n accelerator to jump through the commit messages.
226
        # This sets the cursor, which causes the expander to close, which
227
        # causes the _file_message_text_view to never get realized. So we have
228
        # to give it a little kick, or it warns when we try to grab the focus
278.1.35 by John Arbash Meinel
Make use of the 'selected' parameter to CommitDialog.
229
        self._treeview_files.set_cursor(initial_cursor)
278.1.15 by John Arbash Meinel
Hook up the list of modified files.
230
278.1.32 by John Arbash Meinel
Add the Ctrl+n accelerator to jump through the commit messages.
231
        def _realize_file_message_tree_view(*args):
232
            self._file_message_text_view.realize()
233
        self.connect_after('realize', _realize_file_message_tree_view)
234
278.1.17 by John Arbash Meinel
Add a * reference for why you can't change the commit selection.
235
    def _fill_in_diff(self):
236
        self._diff_view.set_trees(self._wt, self._basis_tree)
237
278.1.25 by John Arbash Meinel
Add the 'Only Commit Locally' checkbox, we may want to put it elsewhere, though.
238
    def _fill_in_checkout(self):
239
        if not self._is_checkout:
240
            self._check_local.hide()
241
            return
242
        if have_dbus:
243
            bus = dbus.SystemBus()
445 by Szilveszter Farkas (Phanatic)
Fix a traceback if NetworkManager is not available (#199513).
244
            try:
245
                proxy_obj = bus.get_object('org.freedesktop.NetworkManager',
246
                                           '/org/freedesktop/NetworkManager')
446 by Szilveszter Farkas (Phanatic)
Hot fix. Sorry dudes.
247
            except dbus.DBusException:
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
248
                trace.mutter("networkmanager not available.")
445 by Szilveszter Farkas (Phanatic)
Fix a traceback if NetworkManager is not available (#199513).
249
                self._check_local.show()
250
                return
251
            
278.1.25 by John Arbash Meinel
Add the 'Only Commit Locally' checkbox, we may want to put it elsewhere, though.
252
            dbus_iface = dbus.Interface(proxy_obj,
253
                                        'org.freedesktop.NetworkManager')
254
            try:
255
                # 3 is the enum value for STATE_CONNECTED
256
                self._check_local.set_active(dbus_iface.state() != 3)
257
            except dbus.DBusException, e:
258
                # Silently drop errors. While DBus may be
259
                # available, NetworkManager doesn't necessarily have to be
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
260
                trace.mutter("unable to get networkmanager state: %r" % e)
278.1.25 by John Arbash Meinel
Add the 'Only Commit Locally' checkbox, we may want to put it elsewhere, though.
261
        self._check_local.show()
262
278.1.33 by John Arbash Meinel
Only enable the per-file dialog if 'per_file_commits' is enabled in the config.
263
    def _fill_in_per_file_info(self):
264
        config = self._wt.branch.get_config()
265
        enable_per_file_commits = config.get_user_option('per_file_commits')
266
        if (enable_per_file_commits is None
267
            or enable_per_file_commits.lower()
268
                not in ('y', 'yes', 'on', 'enable', '1', 't', 'true')):
269
            self._enable_per_file_commits = False
270
        else:
271
            self._enable_per_file_commits = True
272
        if not self._enable_per_file_commits:
273
            self._file_message_expander.hide()
475.1.2 by Vincent Ladeuil
Fix bug #187283 fix replacing _() by _i18n().
274
            self._global_message_label.set_markup(_i18n('<b>Commit Message</b>'))
278.1.33 by John Arbash Meinel
Only enable the per-file dialog if 'per_file_commits' is enabled in the config.
275
278.1.12 by John Arbash Meinel
Delay computing the delta, and clean up some of the diff view names.
276
    def _compute_delta(self):
277
        self._delta = self._wt.changes_from(self._basis_tree)
278
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
279
    def construct(self):
280
        """Build up the dialog widgets."""
281
        # The primary pane which splits it into left and right (adjustable)
282
        # sections.
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
283
        self._hpane = Gtk.HPaned()
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
284
285
        self._construct_left_pane()
286
        self._construct_right_pane()
278.1.23 by John Arbash Meinel
Beginning to support actual commit.
287
        self._construct_action_pane()
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
288
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
289
        self.vbox.pack_start(self._hpane, True, True, 0)
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
290
        self._hpane.show()
291
        self.set_focus(self._global_message_text_view)
292
278.1.32 by John Arbash Meinel
Add the Ctrl+n accelerator to jump through the commit messages.
293
        self._construct_accelerators()
294
        self._set_sizes()
295
296
    def _set_sizes(self):
278.1.17 by John Arbash Meinel
Add a * reference for why you can't change the commit selection.
297
        # This seems like a reasonable default, we might like it to
298
        # be a bit wider, so that by default we can fit an 80-line diff in the
299
        # diff window.
300
        # Alternatively, we should be saving the last position/size rather than
301
        # setting it to a fixed value every time we start up.
302
        screen = self.get_screen()
303
        monitor = 0 # We would like it to be the monitor we are going to
304
                    # display on, but I don't know how to figure that out
305
                    # Only really useful for freaks like me that run dual
306
                    # monitor, with different sizes on the monitors
307
        monitor_rect = screen.get_monitor_geometry(monitor)
308
        width = int(monitor_rect.width * 0.66)
309
        height = int(monitor_rect.height * 0.66)
310
        self.set_default_size(width, height)
278.1.16 by John Arbash Meinel
Implement the file changes list on top of _iter_changes rather than
311
        self._hpane.set_position(300)
312
278.1.32 by John Arbash Meinel
Add the Ctrl+n accelerator to jump through the commit messages.
313
    def _construct_accelerators(self):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
314
        group = Gtk.AccelGroup()
315
        group.connect_group(Gdk.keyval_from_name('N'),
316
                            Gdk.EventMask.CONTROL_MASK, 0, self._on_accel_next)
278.1.32 by John Arbash Meinel
Add the Ctrl+n accelerator to jump through the commit messages.
317
        self.add_accel_group(group)
318
500.2.1 by Scott Scriven
Made 'gcommit' ignore the escape key ('close' signal).
319
        # ignore the escape key (avoid closing the window)
500.2.2 by Scott Scriven
Simpler/cleaner way to ignore the 'close' signal.
320
        self.connect_object('close', self.emit_stop_by_name, 'close')
500.2.1 by Scott Scriven
Made 'gcommit' ignore the escape key ('close' signal).
321
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
322
    def _construct_left_pane(self):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
323
        self._left_pane_box = Gtk.VBox(homogeneous=False, spacing=5)
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
324
        self._construct_file_list()
325
        self._construct_pending_list()
326
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
327
        self._check_local = Gtk.CheckButton(_i18n("_Only commit locally"),
278.1.25 by John Arbash Meinel
Add the 'Only Commit Locally' checkbox, we may want to put it elsewhere, though.
328
                                            use_underline=True)
329
        self._left_pane_box.pack_end(self._check_local, False, False)
330
        self._check_local.set_active(False)
331
278.1.15 by John Arbash Meinel
Hook up the list of modified files.
332
        self._hpane.pack1(self._left_pane_box, resize=False, shrink=False)
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
333
        self._left_pane_box.show()
334
335
    def _construct_right_pane(self):
336
        # TODO: I really want to make it so the diff view gets more space than
337
        # the global commit message, and the per-file commit message gets even
338
        # less. When I did it with wxGlade, I set it to 4 for diff, 2 for
339
        # commit, and 1 for file commit, and it looked good. But I don't seem
340
        # to have a way to do that with the gtk boxes... :( (Which is extra
341
        # weird since wx uses gtk on Linux...)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
342
        self._right_pane_table = Gtk.Table(rows=10, columns=1, homogeneous=False)
278.1.11 by John Arbash Meinel
Worked out the rest of the spacing.
343
        self._right_pane_table.set_row_spacings(5)
344
        self._right_pane_table.set_col_spacings(5)
278.1.10 by John Arbash Meinel
To get the space weighting I wanted, I turned to a Table.
345
        self._right_pane_table_row = 0
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
346
        self._construct_diff_view()
347
        self._construct_file_message()
348
        self._construct_global_message()
349
278.1.10 by John Arbash Meinel
To get the space weighting I wanted, I turned to a Table.
350
        self._right_pane_table.show()
351
        self._hpane.pack2(self._right_pane_table, resize=True, shrink=True)
352
278.1.23 by John Arbash Meinel
Beginning to support actual commit.
353
    def _construct_action_pane(self):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
354
        self._button_cancel = Gtk.Button(stock=Gtk.STOCK_CANCEL)
635.2.7 by Vincent Ladeuil
Land Anne Mohsen's patch for bug 215674.
355
        self._button_cancel.connect('clicked', self._on_cancel_clicked)
356
        self._button_cancel.show()
357
        self.action_area.pack_end(self._button_cancel)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
358
        self._button_commit = Gtk.Button(_i18n("Comm_it"), use_underline=True)
278.1.23 by John Arbash Meinel
Beginning to support actual commit.
359
        self._button_commit.connect('clicked', self._on_commit_clicked)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
360
        self._button_commit.set_can_default(True)
278.1.24 by John Arbash Meinel
Actually show the commit button.
361
        self._button_commit.show()
278.1.23 by John Arbash Meinel
Beginning to support actual commit.
362
        self.action_area.pack_end(self._button_commit)
278.1.24 by John Arbash Meinel
Actually show the commit button.
363
        self._button_commit.grab_default()
278.1.23 by John Arbash Meinel
Beginning to support actual commit.
364
278.1.10 by John Arbash Meinel
To get the space weighting I wanted, I turned to a Table.
365
    def _add_to_right_table(self, widget, weight, expanding=False):
366
        """Add another widget to the table
367
368
        :param widget: The object to add
369
        :param weight: How many rows does this widget get to request
370
        :param expanding: Should expand|fill|shrink be set?
371
        """
372
        end_row = self._right_pane_table_row + weight
373
        options = 0
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
374
        expand_opts = Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK
278.1.10 by John Arbash Meinel
To get the space weighting I wanted, I turned to a Table.
375
        if expanding:
376
            options = expand_opts
377
        self._right_pane_table.attach(widget, 0, 1,
378
                                      self._right_pane_table_row, end_row,
379
                                      xoptions=expand_opts, yoptions=options)
380
        self._right_pane_table_row = end_row
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
381
382
    def _construct_file_list(self):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
383
        self._files_box = Gtk.VBox(homogeneous=False, spacing=0)
384
        file_label = Gtk.Label(label=_i18n('Files'))
278.1.41 by John Arbash Meinel
For the moment, just hide the section headings Files and Diff for *.
385
        # file_label.show()
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
386
        self._files_box.pack_start(file_label, False, True, 0)
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
387
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
388
        self._commit_all_files_radio = Gtk.RadioButton(
475.1.2 by Vincent Ladeuil
Fix bug #187283 fix replacing _() by _i18n().
389
            None, _i18n("Commit all changes"))
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
390
        self._files_box.pack_start(self._commit_all_files_radio, False, True, 0)
278.1.42 by John Arbash Meinel
start playing with using a radial box, rather than an entry in the list
391
        self._commit_all_files_radio.show()
278.1.43 by John Arbash Meinel
Finish connecting the 'Commit all changes' radio buttons.
392
        self._commit_all_files_radio.connect('toggled',
393
            self._toggle_commit_selection)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
394
        self._commit_selected_radio = Gtk.RadioButton(
475.1.2 by Vincent Ladeuil
Fix bug #187283 fix replacing _() by _i18n().
395
            self._commit_all_files_radio, _i18n("Only commit selected changes"))
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
396
        self._files_box.pack_start(self._commit_selected_radio, False, True, 0)
278.1.42 by John Arbash Meinel
start playing with using a radial box, rather than an entry in the list
397
        self._commit_selected_radio.show()
278.1.43 by John Arbash Meinel
Finish connecting the 'Commit all changes' radio buttons.
398
        self._commit_selected_radio.connect('toggled',
399
            self._toggle_commit_selection)
400
        if self._pending:
475.1.2 by Vincent Ladeuil
Fix bug #187283 fix replacing _() by _i18n().
401
            self._commit_all_files_radio.set_label(_i18n('Commit all changes*'))
278.1.43 by John Arbash Meinel
Finish connecting the 'Commit all changes' radio buttons.
402
            self._commit_all_files_radio.set_sensitive(False)
403
            self._commit_selected_radio.set_sensitive(False)
278.1.42 by John Arbash Meinel
start playing with using a radial box, rather than an entry in the list
404
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
405
        scroller = Gtk.ScrolledWindow()
406
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
407
        self._treeview_files = Gtk.TreeView()
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
408
        self._treeview_files.show()
409
        scroller.add(self._treeview_files)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
410
        scroller.set_shadow_type(Gtk.ShadowType.IN)
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
411
        scroller.show()
412
        self._files_box.pack_start(scroller,
413
                                   expand=True, fill=True)
414
        self._files_box.show()
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
415
        self._left_pane_box.pack_start(self._files_box, True, True, 0)
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
416
278.1.31 by John Arbash Meinel
We can make bencode work again by a simple decode/encode step.
417
        # Keep note that all strings stored in a ListStore must be UTF-8
418
        # strings. GTK does not support directly setting and restoring Unicode
419
        # objects.
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
420
        liststore = Gtk.ListStore(
421
            GObject.TYPE_STRING,  # [0] file_id
422
            GObject.TYPE_STRING,  # [1] real path
423
            GObject.TYPE_BOOLEAN, # [2] checkbox
424
            GObject.TYPE_STRING,  # [3] display path
425
            GObject.TYPE_STRING,  # [4] changes type
426
            GObject.TYPE_STRING,  # [5] commit message
278.1.15 by John Arbash Meinel
Hook up the list of modified files.
427
            )
428
        self._files_store = liststore
429
        self._treeview_files.set_model(liststore)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
430
        crt = Gtk.CellRendererToggle()
278.1.39 by John Arbash Meinel
To disable a checkbox it is set_property('activatable', False),
431
        crt.set_property('activatable', not bool(self._pending))
278.1.15 by John Arbash Meinel
Hook up the list of modified files.
432
        crt.connect("toggled", self._toggle_commit, self._files_store)
278.1.17 by John Arbash Meinel
Add a * reference for why you can't change the commit selection.
433
        if self._pending:
475.1.2 by Vincent Ladeuil
Fix bug #187283 fix replacing _() by _i18n().
434
            name = _i18n('Commit*')
278.1.17 by John Arbash Meinel
Add a * reference for why you can't change the commit selection.
435
        else:
475.1.2 by Vincent Ladeuil
Fix bug #187283 fix replacing _() by _i18n().
436
            name = _i18n('Commit')
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
437
        commit_col = Gtk.TreeViewColumn(name, crt, active=2)
278.1.43 by John Arbash Meinel
Finish connecting the 'Commit all changes' radio buttons.
438
        commit_col.set_visible(False)
439
        self._treeview_files.append_column(commit_col)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
440
        self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Path'),
441
                                           Gtk.CellRendererText(), text=3))
442
        self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Type'),
443
                                           Gtk.CellRendererText(), text=4))
278.1.17 by John Arbash Meinel
Add a * reference for why you can't change the commit selection.
444
        self._treeview_files.connect('cursor-changed',
445
                                     self._on_treeview_files_cursor_changed)
278.1.15 by John Arbash Meinel
Hook up the list of modified files.
446
447
    def _toggle_commit(self, cell, path, model):
278.1.20 by John Arbash Meinel
We always select the All Files record in the files view,
448
        if model[path][0] is None: # No file_id means 'All Files'
449
            new_val = not model[path][2]
450
            for node in model:
451
                node[2] = new_val
452
        else:
453
            model[path][2] = not model[path][2]
278.1.15 by John Arbash Meinel
Hook up the list of modified files.
454
278.1.43 by John Arbash Meinel
Finish connecting the 'Commit all changes' radio buttons.
455
    def _toggle_commit_selection(self, button):
456
        all_files = self._commit_all_files_radio.get_active()
457
        if self._commit_all_changes != all_files:
458
            checked_col = self._treeview_files.get_column(0)
459
            self._commit_all_changes = all_files
460
            if all_files:
461
                checked_col.set_visible(False)
462
            else:
463
                checked_col.set_visible(True)
464
            renderer = checked_col.get_cell_renderers()[0]
465
            renderer.set_property('activatable', not all_files)
466
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
467
    def _construct_pending_list(self):
468
        # Pending information defaults to hidden, we put it all in 1 box, so
469
        # that we can show/hide all of them at once
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
470
        self._pending_box = Gtk.VBox()
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
471
        self._pending_box.hide()
472
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
473
        pending_message = Gtk.Label()
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
474
        pending_message.set_markup(
475.1.2 by Vincent Ladeuil
Fix bug #187283 fix replacing _() by _i18n().
475
            _i18n('<i>* Cannot select specific files when merging</i>'))
278.1.11 by John Arbash Meinel
Worked out the rest of the spacing.
476
        self._pending_box.pack_start(pending_message, expand=False, padding=5)
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
477
        pending_message.show()
478
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
479
        pending_label = Gtk.Label(label=_i18n('Pending Revisions'))
278.1.11 by John Arbash Meinel
Worked out the rest of the spacing.
480
        self._pending_box.pack_start(pending_label, expand=False, padding=0)
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
481
        pending_label.show()
482
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
483
        scroller = Gtk.ScrolledWindow()
484
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
485
        self._treeview_pending = Gtk.TreeView()
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
486
        scroller.add(self._treeview_pending)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
487
        scroller.set_shadow_type(Gtk.ShadowType.IN)
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
488
        scroller.show()
489
        self._pending_box.pack_start(scroller,
278.1.11 by John Arbash Meinel
Worked out the rest of the spacing.
490
                                     expand=True, fill=True, padding=5)
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
491
        self._treeview_pending.show()
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
492
        self._left_pane_box.pack_start(self._pending_box, True, True, 0)
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
493
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
494
        liststore = Gtk.ListStore(GObject.TYPE_STRING, # revision_id
495
                                  GObject.TYPE_STRING, # date
496
                                  GObject.TYPE_STRING, # committer
497
                                  GObject.TYPE_STRING, # summary
278.1.14 by John Arbash Meinel
Tests that we fill out the pending list correctly.
498
                                 )
278.1.15 by John Arbash Meinel
Hook up the list of modified files.
499
        self._pending_store = liststore
278.1.14 by John Arbash Meinel
Tests that we fill out the pending list correctly.
500
        self._treeview_pending.set_model(liststore)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
501
        self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Date'),
502
                                             Gtk.CellRendererText(), text=1))
503
        self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Committer'),
504
                                             Gtk.CellRendererText(), text=2))
505
        self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Summary'),
506
                                             Gtk.CellRendererText(), text=3))
278.1.14 by John Arbash Meinel
Tests that we fill out the pending list correctly.
507
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
508
    def _construct_diff_view(self):
729.1.1 by Jelmer Vernooij
Move i18n support to a separate file, so gettext files aren't loaded unless bzr-gtk is used.
509
        from bzrlib.plugins.gtk.diff import DiffView
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
510
278.1.41 by John Arbash Meinel
For the moment, just hide the section headings Files and Diff for *.
511
        # TODO: jam 2007-10-30 The diff label is currently disabled. If we
512
        #       decide that we really don't ever want to display it, we should
513
        #       actually remove it, and other references to it, along with the
514
        #       tests that it is set properly.
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
515
        self._diff_label = Gtk.Label(label=_i18n('Diff for whole tree'))
278.1.11 by John Arbash Meinel
Worked out the rest of the spacing.
516
        self._diff_label.set_alignment(0, 0)
517
        self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
278.1.10 by John Arbash Meinel
To get the space weighting I wanted, I turned to a Table.
518
        self._add_to_right_table(self._diff_label, 1, False)
278.1.41 by John Arbash Meinel
For the moment, just hide the section headings Files and Diff for *.
519
        # self._diff_label.show()
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
520
278.1.12 by John Arbash Meinel
Delay computing the delta, and clean up some of the diff view names.
521
        self._diff_view = DiffView()
278.1.10 by John Arbash Meinel
To get the space weighting I wanted, I turned to a Table.
522
        self._add_to_right_table(self._diff_view, 4, True)
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
523
        self._diff_view.show()
524
525
    def _construct_file_message(self):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
526
        scroller = Gtk.ScrolledWindow()
527
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
278.1.9 by John Arbash Meinel
Move all text entry boxes into a ScrolledWindow, so that they don't change size constantly.
528
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
529
        self._file_message_text_view = Gtk.TextView()
278.1.9 by John Arbash Meinel
Move all text entry boxes into a ScrolledWindow, so that they don't change size constantly.
530
        scroller.add(self._file_message_text_view)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
531
        scroller.set_shadow_type(Gtk.ShadowType.IN)
278.1.9 by John Arbash Meinel
Move all text entry boxes into a ScrolledWindow, so that they don't change size constantly.
532
        scroller.show()
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
533
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
534
        self._file_message_text_view.modify_font(Pango.FontDescription("Monospace"))
535
        self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
536
        self._file_message_text_view.set_accepts_tab(False)
537
        self._file_message_text_view.show()
538
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
539
        self._file_message_expander = Gtk.Expander(_i18n('File commit message'))
278.1.32 by John Arbash Meinel
Add the Ctrl+n accelerator to jump through the commit messages.
540
        self._file_message_expander.set_expanded(True)
541
        self._file_message_expander.add(scroller)
278.1.10 by John Arbash Meinel
To get the space weighting I wanted, I turned to a Table.
542
        self._add_to_right_table(self._file_message_expander, 1, False)
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
543
        self._file_message_expander.show()
544
545
    def _construct_global_message(self):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
546
        self._global_message_label = Gtk.Label(label=_i18n('Global Commit Message'))
475.1.2 by Vincent Ladeuil
Fix bug #187283 fix replacing _() by _i18n().
547
        self._global_message_label.set_markup(
548
            _i18n('<b>Global Commit Message</b>'))
278.1.11 by John Arbash Meinel
Worked out the rest of the spacing.
549
        self._global_message_label.set_alignment(0, 0)
550
        self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
278.1.10 by John Arbash Meinel
To get the space weighting I wanted, I turned to a Table.
551
        self._add_to_right_table(self._global_message_label, 1, False)
278.1.11 by John Arbash Meinel
Worked out the rest of the spacing.
552
        # Can we remove the spacing between the label and the box?
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
553
        self._global_message_label.show()
554
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
555
        scroller = Gtk.ScrolledWindow()
556
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
278.1.9 by John Arbash Meinel
Move all text entry boxes into a ScrolledWindow, so that they don't change size constantly.
557
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
558
        self._global_message_text_view = Gtk.TextView()
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
559
        self._set_global_commit_message(self._saved_commit_messages_manager.get()[0])
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
560
        self._global_message_text_view.modify_font(Pango.FontDescription("Monospace"))
278.1.9 by John Arbash Meinel
Move all text entry boxes into a ScrolledWindow, so that they don't change size constantly.
561
        scroller.add(self._global_message_text_view)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
562
        scroller.set_shadow_type(Gtk.ShadowType.IN)
278.1.9 by John Arbash Meinel
Move all text entry boxes into a ScrolledWindow, so that they don't change size constantly.
563
        scroller.show()
278.1.10 by John Arbash Meinel
To get the space weighting I wanted, I turned to a Table.
564
        self._add_to_right_table(scroller, 2, True)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
565
        self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
566
        self._file_message_text_view.set_accepts_tab(False)
567
        self._global_message_text_view.show()
568
278.1.17 by John Arbash Meinel
Add a * reference for why you can't change the commit selection.
569
    def _on_treeview_files_cursor_changed(self, treeview):
278.1.18 by John Arbash Meinel
Start checking the diff view is correct.
570
        treeselection = treeview.get_selection()
278.1.21 by John Arbash Meinel
Start tracking the per-file commit messages.
571
        (model, selection) = treeselection.get_selected()
278.1.17 by John Arbash Meinel
Add a * reference for why you can't change the commit selection.
572
278.1.21 by John Arbash Meinel
Start tracking the per-file commit messages.
573
        if selection is not None:
574
            path, display_path = model.get(selection, 1, 3)
475.1.2 by Vincent Ladeuil
Fix bug #187283 fix replacing _() by _i18n().
575
            self._diff_label.set_text(_i18n('Diff for ') + display_path)
278.1.20 by John Arbash Meinel
We always select the All Files record in the files view,
576
            if path is None:
577
                self._diff_view.show_diff(None)
578
            else:
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
579
                self._diff_view.show_diff([path.decode('UTF-8')])
278.1.21 by John Arbash Meinel
Start tracking the per-file commit messages.
580
            self._update_per_file_info(selection)
581
278.1.32 by John Arbash Meinel
Add the Ctrl+n accelerator to jump through the commit messages.
582
    def _on_accel_next(self, accel_group, window, keyval, modifier):
583
        # We don't really care about any of the parameters, because we know
584
        # where this message came from
585
        tree_selection = self._treeview_files.get_selection()
586
        (model, selection) = tree_selection.get_selected()
587
        if selection is None:
588
            next = None
589
        else:
590
            next = model.iter_next(selection)
591
592
        if next is None:
593
            # We have either made it to the end of the list, or nothing was
594
            # selected. Either way, select All Files, and jump to the global
595
            # commit message.
596
            self._treeview_files.set_cursor((0,))
597
            self._global_message_text_view.grab_focus()
598
        else:
599
            # Set the cursor to this entry, and jump to the per-file commit
600
            # message
601
            self._treeview_files.set_cursor(model.get_path(next))
602
            self._file_message_text_view.grab_focus()
603
278.1.21 by John Arbash Meinel
Start tracking the per-file commit messages.
604
    def _save_current_file_message(self):
605
        if self._last_selected_file is None:
606
            return # Nothing to save
607
        text_buffer = self._file_message_text_view.get_buffer()
608
        cur_text = text_buffer.get_text(text_buffer.get_start_iter(),
609
                                        text_buffer.get_end_iter())
610
        last_selected = self._files_store.get_iter(self._last_selected_file)
611
        self._files_store.set_value(last_selected, 5, cur_text)
612
613
    def _update_per_file_info(self, selection):
614
        # The node is changing, so cache the current message
278.1.33 by John Arbash Meinel
Only enable the per-file dialog if 'per_file_commits' is enabled in the config.
615
        if not self._enable_per_file_commits:
616
            return
617
278.1.21 by John Arbash Meinel
Start tracking the per-file commit messages.
618
        self._save_current_file_message()
619
        text_buffer = self._file_message_text_view.get_buffer()
620
        file_id, display_path, message = self._files_store.get(selection, 0, 3, 5)
621
        if file_id is None: # Whole tree
475.1.2 by Vincent Ladeuil
Fix bug #187283 fix replacing _() by _i18n().
622
            self._file_message_expander.set_label(_i18n('File commit message'))
278.1.21 by John Arbash Meinel
Start tracking the per-file commit messages.
623
            self._file_message_expander.set_expanded(False)
624
            self._file_message_expander.set_sensitive(False)
625
            text_buffer.set_text('')
626
            self._last_selected_file = None
627
        else:
475.1.2 by Vincent Ladeuil
Fix bug #187283 fix replacing _() by _i18n().
628
            self._file_message_expander.set_label(_i18n('Commit message for ')
278.1.21 by John Arbash Meinel
Start tracking the per-file commit messages.
629
                                                  + display_path)
630
            self._file_message_expander.set_expanded(True)
631
            self._file_message_expander.set_sensitive(True)
632
            text_buffer.set_text(message)
633
            self._last_selected_file = self._files_store.get_path(selection)
278.1.17 by John Arbash Meinel
Add a * reference for why you can't change the commit selection.
634
278.1.27 by John Arbash Meinel
Add the ability to commit just specific files.
635
    def _get_specific_files(self):
278.1.31 by John Arbash Meinel
We can make bencode work again by a simple decode/encode step.
636
        """Return the list of selected paths, and file info.
637
638
        :return: ([unicode paths], [{utf-8 file info}]
639
        """
278.1.27 by John Arbash Meinel
Add the ability to commit just specific files.
640
        self._save_current_file_message()
641
        files = []
642
        records = iter(self._files_store)
643
        rec = records.next() # Skip the All Files record
644
        assert rec[0] is None, "Are we skipping the wrong record?"
645
646
        file_info = []
647
        for record in records:
278.1.43 by John Arbash Meinel
Finish connecting the 'Commit all changes' radio buttons.
648
            if self._commit_all_changes or record[2]:# [2] checkbox
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
649
                file_id = record[0] # [0] file_id
278.1.31 by John Arbash Meinel
We can make bencode work again by a simple decode/encode step.
650
                path = record[1]    # [1] real path
622.1.1 by John Arbash Meinel
Ensure that per-file commit messages and global commit messages get sanitized.
651
                # [5] commit message
652
                file_message = _sanitize_and_decode_message(record[5])
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
653
                files.append(path.decode('UTF-8'))
278.1.33 by John Arbash Meinel
Only enable the per-file dialog if 'per_file_commits' is enabled in the config.
654
                if self._enable_per_file_commits and file_message:
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
655
                    # All of this needs to be utf-8 information
622.1.1 by John Arbash Meinel
Ensure that per-file commit messages and global commit messages get sanitized.
656
                    file_message = file_message.encode('UTF-8')
278.1.27 by John Arbash Meinel
Add the ability to commit just specific files.
657
                    file_info.append({'path':path, 'file_id':file_id,
658
                                     'message':file_message})
659
        file_info.sort(key=lambda x:(x['path'], x['file_id']))
278.1.33 by John Arbash Meinel
Only enable the per-file dialog if 'per_file_commits' is enabled in the config.
660
        if self._enable_per_file_commits:
661
            return files, file_info
662
        else:
663
            return files, []
278.1.27 by John Arbash Meinel
Add the ability to commit just specific files.
664
278.1.23 by John Arbash Meinel
Beginning to support actual commit.
665
    @show_bzr_error
635.2.7 by Vincent Ladeuil
Land Anne Mohsen's patch for bug 215674.
666
    def _on_cancel_clicked(self, button):
667
        """ Cancel button clicked handler. """
668
        self._do_cancel()
669
670
    @show_bzr_error
671
    def _on_delete_window(self, source, event):
672
        """ Delete window handler. """
673
        self._do_cancel()
674
675
    def _do_cancel(self):
676
        """If requested, saves commit messages when cancelling gcommit; they are re-used by a next gcommit"""
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
677
        mgr = SavedCommitMessagesManager()
678
        self._saved_commit_messages_manager = mgr
679
        mgr.insert(self._get_global_commit_message(),
680
                   self._get_specific_files()[1])
681
        if mgr.is_not_empty(): # maybe worth saving
682
            response = self._question_dialog(
683
                _i18n('Commit cancelled'),
684
                _i18n('Do you want to save your commit messages ?'),
685
                parent=self)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
686
            if response == Gtk.ResponseType.NO:
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
687
                 # save nothing and destroy old comments if any
688
                mgr = SavedCommitMessagesManager()
689
        mgr.save(self._wt, self._wt.branch)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
690
        self.response(Gtk.ResponseType.CANCEL) # close window
635.2.7 by Vincent Ladeuil
Land Anne Mohsen's patch for bug 215674.
691
692
    @show_bzr_error
278.1.23 by John Arbash Meinel
Beginning to support actual commit.
693
    def _on_commit_clicked(self, button):
694
        """ Commit button clicked handler. """
695
        self._do_commit()
696
697
    def _do_commit(self):
278.1.25 by John Arbash Meinel
Add the 'Only Commit Locally' checkbox, we may want to put it elsewhere, though.
698
        message = self._get_global_commit_message()
278.1.23 by John Arbash Meinel
Beginning to support actual commit.
699
700
        if message == '':
606 by Vincent Ladeuil
Fix gtk dialogs popping up and asking for input during selftest.
701
            response = self._question_dialog(
475.1.2 by Vincent Ladeuil
Fix bug #187283 fix replacing _() by _i18n().
702
                _i18n('Commit with an empty message?'),
576.1.1 by Jasper Groenewegen
Add parent setting to dialogs and implement in gcommit
703
                _i18n('You can describe your commit intent in the message.'),
704
                parent=self)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
705
            if response == Gtk.ResponseType.NO:
278.1.23 by John Arbash Meinel
Beginning to support actual commit.
706
                # Kindly give focus to message area
707
                self._global_message_text_view.grab_focus()
708
                return
709
278.1.27 by John Arbash Meinel
Add the ability to commit just specific files.
710
        specific_files, file_info = self._get_specific_files()
278.1.28 by John Arbash Meinel
Ensure that we can set per-file messages even during a merge.
711
        if self._pending:
712
            specific_files = None
278.1.23 by John Arbash Meinel
Beginning to support actual commit.
713
278.1.25 by John Arbash Meinel
Add the 'Only Commit Locally' checkbox, we may want to put it elsewhere, though.
714
        local = self._check_local.get_active()
278.1.23 by John Arbash Meinel
Beginning to support actual commit.
715
278.1.26 by John Arbash Meinel
Handle pointless commits and trees with unknown files.
716
        # All we care about is if there is a single unknown, so if this loop is
717
        # entered, then there are unknown files.
718
        # TODO: jam 20071002 It seems like this should cancel the dialog
719
        #       entirely, since there isn't a way for them to add the unknown
720
        #       files at this point.
721
        for path in self._wt.unknowns():
606 by Vincent Ladeuil
Fix gtk dialogs popping up and asking for input during selftest.
722
            response = self._question_dialog(
475.1.2 by Vincent Ladeuil
Fix bug #187283 fix replacing _() by _i18n().
723
                _i18n("Commit with unknowns?"),
576.1.1 by Jasper Groenewegen
Add parent setting to dialogs and implement in gcommit
724
                _i18n("Unknown files exist in the working tree. Commit anyway?"),
725
                parent=self)
726
                # Doesn't set a parent for the dialog..
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
727
            if response == Gtk.ResponseType.NO:
278.1.26 by John Arbash Meinel
Handle pointless commits and trees with unknown files.
728
                return
729
            break
278.1.23 by John Arbash Meinel
Beginning to support actual commit.
730
278.1.25 by John Arbash Meinel
Add the 'Only Commit Locally' checkbox, we may want to put it elsewhere, though.
731
        rev_id = None
278.1.27 by John Arbash Meinel
Add the ability to commit just specific files.
732
        revprops = {}
733
        if file_info:
278.1.31 by John Arbash Meinel
We can make bencode work again by a simple decode/encode step.
734
            revprops['file-info'] = bencode.bencode(file_info).decode('UTF-8')
278.1.23 by John Arbash Meinel
Beginning to support actual commit.
735
        try:
736
            rev_id = self._wt.commit(message,
737
                       allow_pointless=False,
738
                       strict=False,
739
                       local=local,
278.1.27 by John Arbash Meinel
Add the ability to commit just specific files.
740
                       specific_files=specific_files,
741
                       revprops=revprops)
278.1.23 by John Arbash Meinel
Beginning to support actual commit.
742
        except errors.PointlessCommit:
606 by Vincent Ladeuil
Fix gtk dialogs popping up and asking for input during selftest.
743
            response = self._question_dialog(
475.1.2 by Vincent Ladeuil
Fix bug #187283 fix replacing _() by _i18n().
744
                _i18n('Commit with no changes?'),
745
                _i18n('There are no changes in the working tree.'
576.1.1 by Jasper Groenewegen
Add parent setting to dialogs and implement in gcommit
746
                      ' Do you want to commit anyway?'),
747
                parent=self)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
748
            if response == Gtk.ResponseType.YES:
278.1.23 by John Arbash Meinel
Beginning to support actual commit.
749
                rev_id = self._wt.commit(message,
750
                               allow_pointless=True,
751
                               strict=False,
752
                               local=local,
278.1.27 by John Arbash Meinel
Add the ability to commit just specific files.
753
                               specific_files=specific_files,
754
                               revprops=revprops)
278.1.23 by John Arbash Meinel
Beginning to support actual commit.
755
        self.committed_revision_id = rev_id
635.2.7 by Vincent Ladeuil
Land Anne Mohsen's patch for bug 215674.
756
        # destroy old comments if any
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
757
        SavedCommitMessagesManager().save(self._wt, self._wt.branch)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
758
        self.response(Gtk.ResponseType.OK)
278.1.23 by John Arbash Meinel
Beginning to support actual commit.
759
278.1.25 by John Arbash Meinel
Add the 'Only Commit Locally' checkbox, we may want to put it elsewhere, though.
760
    def _get_global_commit_message(self):
761
        buf = self._global_message_text_view.get_buffer()
762
        start, end = buf.get_bounds()
622.1.1 by John Arbash Meinel
Ensure that per-file commit messages and global commit messages get sanitized.
763
        text = buf.get_text(start, end)
764
        return _sanitize_and_decode_message(text)
278.1.25 by John Arbash Meinel
Add the 'Only Commit Locally' checkbox, we may want to put it elsewhere, though.
765
766
    def _set_global_commit_message(self, message):
767
        """Just a helper for the test suite."""
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
768
        if isinstance(message, unicode):
769
            message = message.encode('UTF-8')
278.1.25 by John Arbash Meinel
Add the 'Only Commit Locally' checkbox, we may want to put it elsewhere, though.
770
        self._global_message_text_view.get_buffer().set_text(message)
771
278.1.27 by John Arbash Meinel
Add the ability to commit just specific files.
772
    def _set_file_commit_message(self, message):
773
        """Helper for the test suite."""
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
774
        if isinstance(message, unicode):
775
            message = message.encode('UTF-8')
278.1.27 by John Arbash Meinel
Add the ability to commit just specific files.
776
        self._file_message_text_view.get_buffer().set_text(message)
777
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
778
    @staticmethod
779
    def _rev_to_pending_info(rev):
780
        """Get the information from a pending merge."""
126.1.1 by Szilveszter Farkas (Phanatic)
New Commit dialog implementation (no more Glade).
781
        from bzrlib.osutils import format_date
278.1.5 by John Arbash Meinel
Starting to flesh out the dialog with actual windows.
782
        rev_dict = {}
783
        rev_dict['committer'] = re.sub('<.*@.*>', '', rev.committer).strip(' ')
784
        rev_dict['summary'] = rev.get_summary()
785
        rev_dict['date'] = format_date(rev.timestamp,
786
                                       rev.timezone or 0,
787
                                       'original', date_fmt="%Y-%m-%d",
788
                                       show_offset=False)
789
        rev_dict['revision_id'] = rev.revision_id
790
        return rev_dict
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
791
792
793
class SavedCommitMessagesManager:
635.2.13 by Vincent Ladeuil
Add NEWS entry and do some cleanup.
794
    """Save glogal and per-file commit messages.
795
796
    Saves global commit message and utf-8 file_id->message dictionary
797
    of per-file commit messages on disk. Re-reads them later for re-using.
798
    """
799
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
800
    def __init__(self, tree=None, branch=None):
801
        """If branch is None, builds empty messages, otherwise reads them
802
        from branch's disk storage. 'tree' argument is for the future."""
803
        if branch is None:
804
            self.global_message = u''
805
            self.file_messages = {}
806
        else:
694 by Jelmer Vernooij
Avoid using private functions to get branch config.
807
            config = branch.get_config()
635.2.13 by Vincent Ladeuil
Add NEWS entry and do some cleanup.
808
            self.global_message = config.get_user_option(
809
                'gtk_global_commit_message')
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
810
            if self.global_message is None:
811
                self.global_message = u''
812
            file_messages = config.get_user_option('gtk_file_commit_messages')
813
            if file_messages: # unicode and B-encoded:
635.2.13 by Vincent Ladeuil
Add NEWS entry and do some cleanup.
814
                self.file_messages = bencode.bdecode(
815
                    file_messages.encode('UTF-8'))
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
816
            else:
817
                self.file_messages = {}
635.2.13 by Vincent Ladeuil
Add NEWS entry and do some cleanup.
818
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
819
    def get(self):
820
        return self.global_message, self.file_messages
635.2.13 by Vincent Ladeuil
Add NEWS entry and do some cleanup.
821
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
822
    def is_not_empty(self):
823
        return bool(self.global_message or self.file_messages)
635.2.13 by Vincent Ladeuil
Add NEWS entry and do some cleanup.
824
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
825
    def insert(self, global_message, file_info):
826
        """Formats per-file commit messages (list of dictionaries, one per file)
827
        into one utf-8 file_id->message dictionary and merges this with
828
        previously existing dictionary. Merges global commit message too."""
829
        file_messages = {}
830
        for fi in file_info:
831
            file_message = fi['message']
832
            if file_message:
833
                file_messages[fi['file_id']] = file_message # utf-8 strings
834
        for k,v in file_messages.iteritems():
835
            try:
836
                self.file_messages[k] = v + '\n******\n' + self.file_messages[k]
837
            except KeyError:
838
                self.file_messages[k] = v
839
        if self.global_message:
635.2.13 by Vincent Ladeuil
Add NEWS entry and do some cleanup.
840
            self.global_message = global_message + '\n******\n' \
841
                + self.global_message
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
842
        else:
843
            self.global_message = global_message
635.2.13 by Vincent Ladeuil
Add NEWS entry and do some cleanup.
844
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
845
    def save(self, tree, branch):
846
        # We store in branch's config, which can be a problem if two gcommit
847
        # are done in two checkouts of one single branch (comments overwrite
848
        # each other). Ideally should be in working tree. But uncommit does
849
        # not always have a working tree, though it always has a branch.
850
        # 'tree' argument is for the future
851
        config = branch.get_config()
852
        # should it be named "gtk_" or some more neutral name ("gui_" ?) to
853
        # be compatible with qbzr in the future?
854
        config.set_user_option('gtk_global_commit_message', self.global_message)
635.2.13 by Vincent Ladeuil
Add NEWS entry and do some cleanup.
855
        # bencode() does not know unicode objects but set_user_option()
856
        # requires one:
857
        config.set_user_option(
858
            'gtk_file_commit_messages',
859
            bencode.bencode(self.file_messages).decode('UTF-8'))
635.2.12 by Vincent Ladeuil
Implement commit message saving without modifying bzrlib.
860
861
862
def save_commit_messages(local, master, old_revno, old_revid,
863
                         new_revno, new_revid):
864
    b = local
865
    if b is None:
866
        b = master
867
    mgr = SavedCommitMessagesManager(None, b)
868
    revid_iterator = b.repository.iter_reverse_revision_history(old_revid)
869
    cur_revno = old_revno
870
    new_revision_id = old_revid
871
    graph = b.repository.get_graph()
872
    for rev_id in revid_iterator:
873
        if cur_revno == new_revno:
874
            break
875
        cur_revno -= 1
876
        rev = b.repository.get_revision(rev_id)
877
        file_info = rev.properties.get('file-info', None)
878
        if file_info is None:
879
            file_info = {}
880
        else:
881
            file_info = bencode.bdecode(file_info.encode('UTF-8'))
882
        global_message = osutils.safe_unicode(rev.message)
883
        # Concatenate comment of the uncommitted revision
884
        mgr.insert(global_message, file_info)
885
886
        parents = graph.get_parent_map([rev_id]).get(rev_id, None)
887
        if not parents:
888
            continue
889
    mgr.save(None, b)