2
# Copyright 2009 Martin Owens
4
# This program is free software: you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program. If not, see <http://www.gnu.org/licenses/>
17
"""App for looking at changes and confirming them."""
19
# Import standard python libs
25
# Various required variables and locations
26
from GroundControl.gtkviews import (
30
from GroundControl.bugs import BugSelection
31
from bzrlib.plugins.gtk.diff import DiffView
34
'added', # (path, id, kind)
35
'removed', # (path, id, kind)
36
'renamed', # (oldpath, newpath, id, kind, text_modified, meta_mod)
37
'kind_changed', # (path, id, old_kind, new_kind)
38
'modified', # (path, id, kind, text_modified, meta_modified)
39
# 'unmodifed', # (path, id, kind)
40
'unversioned' # (path, None, kind)
44
'removed' : _('Deleted'),
45
'renamed' : _('Renamed'),
46
'kind_changed' : _('Changed'),
47
'modified' : _('Edited'),
48
'unversioned' : _('New'),
51
change_icons = IconManager('chicons')
54
class CommitWindow(Window):
55
"""Select a project to import."""
58
def load(self, *args, **kwargs):
59
"""Load a commit window"""
60
self.branch = kwargs.pop('branch')
61
self.project = kwargs.pop('project')
62
self.fixing = kwargs.pop('fixing', None)
63
self.wtr = self.branch.get_workingtree()
64
self.prevt = self.branch.get_oldtree()
72
super(CommitWindow, self).load(*args, **kwargs)
73
self.diffview = DiffView()
74
#self.diffview.set_trees(self.prevt, self.wtr)
75
self.diffview.set_trees(self.wtr, self.prevt)
76
self.widget('viewchanges').add(self.diffview)
77
self.slist = ChangesView(self.widget('changes'),
78
selected=self.selected)
79
all_changes = self.branch.get_changes()
80
for atype in change_types:
81
changes = getattr(all_changes, atype)
83
for change in changes:
84
self.add_change(atype, change)
85
# Remove any empty directories that aren't changing
86
for iter in self.dirs.values():
87
if not self.slist.get_item(iter).changes:
88
self.slist.remove_item(target_iter=iter)
89
# Hide the ignore new files feature if we have no new files.
91
self.widget('ignorenew').hide()
92
self.widget('ignorelabel').hide()
93
# Auto add the bug we're fixing as expected
95
self.tie_bug(self.fixing)
96
# If we ever add a feature to enable a user
97
# To select what files should be commited, Then
98
# disable it here since fixing bugs is incompatable
102
'tie_bug' : self.get_bug,
105
def add_change(self, atype, change):
106
"""Return an added tree item node"""
107
# Make our change item object for the list
108
item = ChangeItem(self.branch, atype, change)
109
parent = self.get_folder(item.stem)
110
# We want to track if we have any added files
111
if atype == 'unversioned':
112
# Don't add items that are going to be ignored anyway
113
if self.wtr.is_ignored(item.path):
116
# Add item to visual tree view list
117
iter1 = self.slist.add_item( item, parent )
119
self.dirs[item.stem] = iter1
121
self.slist.expand_item(parent)
122
if atype == 'unversioned' and item.isdir:
123
# Also add in any sub files
124
self.add_sub_files( item.stem )
126
def get_folder(self, path):
127
"""Returns a folder tree item node"""
130
fdir = os.path.dirname(path)
131
full_path = os.path.join(self.branch.path, fdir)
132
if fdir and os.path.isdir(full_path):
133
if not self.dirs.has_key(fdir):
134
parent = self.get_folder(fdir)
135
item = ChangeFolder(full_path)
136
self.dirs[fdir] = self.slist.add_item( item, parent )
137
return self.dirs.get(fdir, None)
139
def add_sub_files(self, dir):
140
"""Add the subfiles of a directory"""
141
path = self.branch.path
142
full_path = os.path.join(path, dir)
143
for res in os.walk(full_path):
144
for sfile in (res[1]+res[2]):
145
new_path = os.path.join(res[0], sfile).replace(path, '')
146
self.add_change('added', (new_path, None, None))
149
"""Return true if inputs are correct."""
150
return len(self.widget('comments').get_text()) > 2
152
def get_bug(self, widget=None):
153
"""Load up the bug gui and let someone tie a bug to this report"""
156
project = str(self.project.pid)
157
BugSelection(project=project,
158
callback=self.tie_bug,
162
def tie_bug(self, bug=None, project=None):
163
"""Add the bug to our fixes list"""
164
if type(bug) not in (str, unicode):
165
bug = "lp:%s" % str(bug.id)
168
self.fixes.append(bug)
169
# This button will allow us to remove the fixes.
173
bugb.connect('clicked', self.remove_bug)
174
self.widget('fixeslist').pack_start(bugb)
175
self.widget('fixeslist').reorder_child(bugb, 0)
177
def selected(self, item):
178
"""Show in the diff any items which we want"""
179
if not item.isdir and item.type == 'modified':
180
logging.debug("Showing diff for %s" % item.stem)
181
self.diffview.show_diff([item.stem])
182
self.widget("viewchanges").set_position(300)
183
self.window.resize(800, 600)
188
def remove_bug(self, widget):
189
"""It doesn't fix this bug at all!"""
190
bug = widget.get_label()
191
self.fixes.remove(bug)
195
"""Do out commit here"""
197
'message' : self.widget('comments').get_text(),
198
'ignore' : self.widget('ignorenew').get_active(),
199
'branch' : self.branch,
200
'fixes' : self.fixes,
205
class CommitChanges(GtkApp):
206
"""Graphical application for presenting the user with commit changes"""
207
gtkfile = 'commit-branch.glade'
208
windows = [ CommitWindow ]
211
class NodeItem(object):
212
"""Wrap all items in the review"""
213
def __init__(self, path, atype=None):
218
self.name = os.path.basename(self.path)
219
self.isdir = os.path.isdir(self.path)
222
"""Return the change type label"""
223
return "<i>%s</i>" % self.name
226
"""Icon of the modeification"""
228
base = change_icons.get_icon('file').copy()
230
base = change_icons.get_icon('folder').copy()
231
overlay = change_icons.get_icon(self.type).copy()
232
overlay.composite( base, 0, 0, base.props.width, base.props.height,
233
0, 0, 1.0, 1.0, gtk.gdk.INTERP_BILINEAR, 128 )
238
class ChangeItem(NodeItem):
239
"""Wraps any change item from TreeDelta"""
240
def __init__(self, branch, atype, changes):
242
self.selected = False
243
if atype == 'renamed':
244
self.oldname = changes.pop(0)
245
self.stem = changes[0]
246
path = os.path.join(self.branch.path, self.stem)
247
super(ChangeItem, self).__init__(path, atype)
248
self.changes = changes
249
# Force the correct type for deleted elements.
250
if changes[2] == 'directory':
253
def set_selected(self, shouldi=True):
254
"""Choose to confirm the changes of this file"""
255
self.selected = shouldi
258
"""Return the simple label for this item"""
259
if self.type == 'renamed':
260
return "%s => %s" % (self.oldname, self.name)
265
class ChangeFolder(NodeItem):
266
"""Wraps a change type"""
267
def has_changes(self):
268
self.changes = 'folder'
271
class ChangesView(TreeView):
272
"""Configures the view for changes."""
274
def add_item(self, item, parent=None):
276
folder = self.get_item(parent)
277
if not folder.changes:
279
return super(ChangesView, self).add_item(item, parent)
282
"""Setup a treeview for showing services"""
283
def text_cell_func(cell_layout, renderer, model, item_iter):
284
"""Render the text of the services tree view"""
285
item = model.get_value(item_iter, 0)
286
renderer.set_property("markup", item.label())
288
def type_cell_func(cell_layout, renderer, model, item_iter):
289
"""Render the kind of change"""
290
item = model.get_value(item_iter, 0)
292
renderer.set_property("markup", change_label[item.type])
294
renderer.set_property("markup", '')
296
def icon_cell_func(column, cell, model, item_iter):
297
"""Reender the icons of the services tree view"""
298
item = model.get_value(item_iter, 0)
299
cell.set_property("pixbuf", item.icon())
300
cell.set_property("visible", True)
302
svlist = super(ChangesView, self).setup()
303
column = gtk.TreeViewColumn((_("List of Changes")))
304
column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
305
#column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
306
column.set_expand(True)
308
renderer_icon = gtk.CellRendererPixbuf()
309
renderer_icon.set_property("ypad", 2)
310
renderer_icon.set_property("xpad", 2)
311
column.pack_start(renderer_icon, False)
312
column.set_cell_data_func(renderer_icon, icon_cell_func)
314
renderer = gtk.CellRendererText()
315
column.pack_start(renderer, True)
316
column.set_cell_data_func(renderer, text_cell_func)
317
# Add the required coluns to the treeview
318
svlist.append_column(column)
320
column2 = gtk.TreeViewColumn(("Kind"))
321
column2.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
322
renderer = gtk.CellRendererText()
323
column2.pack_start(renderer, True)
324
column2.set_cell_data_func(renderer, type_cell_func)
325
# Add the required coluns to the treeview
326
svlist.append_column(column2)
329
class RevertWindow(Window):
330
"""Show a window that asks the user to confirm the revert."""
333
def load(self, branch, *args, **kwargs):
334
"""Load a revert confirmation window"""
336
super(RevertWindow, self).load(*args, **kwargs)
339
"""Return the branch to a sucess response"""
340
return { 'branch' : self.branch }
342
class RevertChanges(GtkApp):
343
"""An application to show some help to the user."""
344
gtkfile = 'revert-branch.glade'
345
windows = [ RevertWindow ]