1 """
2 Holds classes used for resolving conflicts.
3
4 Copyright: John Stowers, 2006
5 License: GPLv2
6 """
7 import traceback
8 import time
9 import gobject
10 import gtk, gtk.gdk
11 import pango
12 import logging
13 log = logging.getLogger("gtkui.ConflictResolver")
14
15 import conduit
16 import conduit.dataproviders.DataProvider as DataProvider
17 import conduit.Vfs as Vfs
18 import conduit.Conflict as Conflict
19
20 from gettext import gettext as _
21
22
23 CONFLICT_IDX = 0
24 DIRECTION_IDX = 1
25
28 self.sourceWrapper = sourceWrapper
29 self.sinkWrapper = sinkWrapper
30
32 if is_source:
33 return self.sourceWrapper.name
34 else:
35 return self.sinkWrapper.name
36
38 if is_source:
39 return self.sourceWrapper.get_icon()
40 else:
41 return self.sinkWrapper.get_icon()
42
44 """
45 Manages a gtk.TreeView which is used for asking the user what they
46 wish to do in the case of a conflict
47 """
49 self.model = gtk.TreeStore( gobject.TYPE_PYOBJECT,
50 gobject.TYPE_INT
51 )
52
53 self.partnerships = {}
54 self.numConflicts = 0
55
56 self.view = gtk.TreeView( self.model )
57 self._build_view()
58
59
60
61 self.expander = widgets.get_widget("conflictExpander")
62 self.expander.connect("activate", self.on_expand)
63 self.vpane = widgets.get_widget("vpaned1")
64 self.expander.set_sensitive(False)
65 self.fullscreenButton = widgets.get_widget("conflictFullscreenButton")
66 self.fullscreenButton.connect("toggled", self.on_fullscreen_toggled)
67 self.conflictScrolledWindow = widgets.get_widget("conflictExpanderVBox")
68 widgets.get_widget("conflictScrolledWindow").add(self.view)
69
70 self.standalone = gtk.Window()
71 self.standalone.set_title("Conflicts")
72 self.standalone.set_transient_for(widgets.get_widget("MainWindow"))
73 self.standalone.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
74 self.standalone.set_destroy_with_parent(True)
75 self.standalone.set_default_size(-1, 200)
76
77
78 self.standalone.connect("delete-event", self.on_standalone_closed)
79
80 widgets.get_widget("conflictCancelButton").connect("clicked", self.on_cancel_conflicts)
81 widgets.get_widget("conflictResolveButton").connect("clicked", self.on_resolve_conflicts)
82
83 self.compareButton = widgets.get_widget("conflictCompareButton")
84 self.compareButton.connect("clicked", self.on_compare_conflicts)
85 self.compareButton.set_sensitive(False)
86
88
89
90
91 column0 = gtk.TreeViewColumn(_("Source"))
92
93 sourceIconRenderer = gtk.CellRendererPixbuf()
94 sourceNameRenderer = gtk.CellRendererText()
95 sourceNameRenderer.set_property('ellipsize', pango.ELLIPSIZE_END)
96 column0.pack_start(sourceIconRenderer, False)
97 column0.pack_start(sourceNameRenderer, True)
98
99 column0.set_property("expand", True)
100 column0.set_cell_data_func(sourceNameRenderer, self._name_data_func, True)
101 column0.set_cell_data_func(sourceIconRenderer, self._icon_data_func, True)
102
103
104 confRenderer = ConflictCellRenderer()
105 column1 = gtk.TreeViewColumn(_("Resolution"), confRenderer)
106 column1.set_cell_data_func(confRenderer, self._direction_data_func, DIRECTION_IDX)
107 column1.set_property("expand", False)
108
109
110 column2 = gtk.TreeViewColumn(_("Sink"))
111
112 sinkIconRenderer = gtk.CellRendererPixbuf()
113 sinkNameRenderer = gtk.CellRendererText()
114 sinkNameRenderer.set_property('ellipsize', pango.ELLIPSIZE_END)
115 column2.pack_start(sinkIconRenderer, False)
116 column2.pack_start(sinkNameRenderer, True)
117
118 column2.set_property("expand", True)
119 column2.set_cell_data_func(sinkNameRenderer, self._name_data_func, False)
120 column2.set_cell_data_func(sinkIconRenderer, self._icon_data_func, False)
121
122 for c in [column0,column1,column2]:
123 self.view.append_column( c )
124
125
126 self.view.set_property("enable-search", False)
127 self.view.get_selection().connect("changed", self.on_selection_changed)
128
129 - def _name_data_func(self, column, cell_renderer, tree_model, rowref, is_source):
130 conflict = tree_model.get_value(rowref, CONFLICT_IDX)
131 text = conflict.get_snippet(is_source)
132 cell_renderer.set_property("text", text)
133
134 - def _icon_data_func(self, column, cell_renderer, tree_model, rowref, is_source):
135 conflict = tree_model.get_value(rowref, CONFLICT_IDX)
136 icon = conflict.get_icon(is_source)
137 cell_renderer.set_property("pixbuf", icon)
138
140 direction = tree_model.get_value(rowref, user_data)
141 if tree_model.iter_depth(rowref) == 0:
142 cell_renderer.set_property('visible', False)
143 cell_renderer.set_property('mode', gtk.CELL_RENDERER_MODE_INERT)
144 else:
145 cell_renderer.set_property('visible', True)
146 cell_renderer.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
147 cell_renderer.set_direction(direction)
148
150 self.expander.set_label(_("Conflicts (%s)") % self.numConflicts)
151 self.standalone.set_title(_("Conflicts (%s)") % self.numConflicts)
152
154
155 if len(self.model) == 0:
156 self.expander.set_sensitive(True)
157
158 self.numConflicts += 1
159 source,sink = conflict.get_partnership()
160 if (source,sink) not in self.partnerships:
161
162 header = ConflictHeader(source, sink)
163 rowref = self.model.append(None, (header, Conflict.CONFLICT_ASK))
164 self.partnerships[(source,sink)] = (rowref,conflict)
165
166 rowref = self.partnerships[(source,sink)][0]
167 self.model.append(rowref, (conflict, Conflict.CONFLICT_ASK))
168
169
170
171
172
176
178
179
180 if self.fullscreenButton.get_active():
181 self.expander.set_expanded(False)
182 self.fullscreenButton.set_image(gtk.image_new_from_icon_name("gtk-leave-fullscreen", gtk.ICON_SIZE_MENU))
183 self.conflictScrolledWindow.reparent(self.standalone)
184 self.standalone.show()
185 self.expander.set_sensitive(False)
186 else:
187 self.fullscreenButton.set_image(gtk.image_new_from_icon_name("gtk-fullscreen", gtk.ICON_SIZE_MENU))
188 self.conflictScrolledWindow.reparent(self.expander)
189 self.standalone.hide()
190 self.expander.set_sensitive(True)
191
196
198
199 resolved = []
200
201 def _resolve_func(model, path, rowref):
202
203 if model.iter_depth(rowref) == 0:
204 return
205
206 direction = model[path][DIRECTION_IDX]
207 conflict = model[path][CONFLICT_IDX]
208
209 if conflict.resolve(direction):
210 resolved.append(rowref)
211
212 self.model.foreach(_resolve_func)
213 for r in resolved:
214 self.model.remove(r)
215
216
217 empty = []
218 for source,sink in self.partnerships:
219 rowref = self.partnerships[(source,sink)][0]
220 numChildren = self.model.iter_n_children(rowref)
221 if numChildren == 0:
222 sink.module.set_status(DataProvider.STATUS_DONE_SYNC_OK)
223 empty.append( (rowref, source, sink) )
224 else:
225 sink.module.set_status(DataProvider.STATUS_DONE_SYNC_CONFLICT)
226
227
228 for rowref, source, sink in empty:
229 self.model.remove(rowref)
230 try:
231 del(self.partnerships[(source,sink)])
232 except KeyError: pass
233
239
245
247 """
248 Makes the compare button active only if an open_URI for the data
249 has been set and its not a header row.
250 FIXME: In future could convert to text to allow user to compare that way
251 """
252 model, rowref = treeSelection.get_selected()
253
254 if rowref == None:
255 self.compareButton.set_sensitive(False)
256 else:
257 conflict = model.get_value(rowref, CONFLICT_IDX)
258 if model.iter_depth(rowref) == 0:
259 self.compareButton.set_sensitive(False)
260
261 elif conflict.sourceData.get_open_URI() != None and conflict.sinkData.get_open_URI() != None:
262 self.compareButton.set_sensitive(True)
263 else:
264 self.compareButton.set_sensitive(False)
265
267 """
268 An unfortunately neccessary wrapper around a CellRenderPixbuf because
269 said renderer is not activatable
270 """
272 gtk.GenericCellRenderer.__init__(self)
273 self.image = None
274
276 return ( 0,0,
277 16,16
278 )
279
280 - def on_render(self, window, widget, background_area, cell_area, expose_area, flags):
281 if self.image != None:
282 middle_x = (cell_area.width - 16) / 2
283 middle_y = (cell_area.height - 16) / 2
284 self.image.render_to_drawable_alpha(window,
285 0, 0,
286 middle_x + cell_area.x,
287 middle_y + cell_area.y,
288 -1, -1,
289 0, 0,
290 gtk.gdk.RGB_DITHER_NONE,
291 0, 0
292 )
293
294
295
296
297
298
299
300
301
302 return True
303
305 if direction == Conflict.CONFLICT_COPY_SINK_TO_SOURCE:
306 self.image = gtk.icon_theme_get_default().load_icon("conduit-conflict-left",16,0)
307 elif direction == Conflict.CONFLICT_COPY_SOURCE_TO_SINK:
308 self.image = gtk.icon_theme_get_default().load_icon("conduit-conflict-right",16,0)
309 elif direction == Conflict.CONFLICT_SKIP:
310 self.image = gtk.icon_theme_get_default().load_icon("conduit-conflict-skip",16,0)
311 elif direction == Conflict.CONFLICT_DELETE:
312 self.image = gtk.icon_theme_get_default().load_icon("conduit-conflict-delete",16,0)
313 elif direction == Conflict.CONFLICT_ASK:
314 self.image = gtk.icon_theme_get_default().load_icon("conduit-conflict-ask",16,0)
315 else:
316 self.image = None
317
318 - def on_activate(self, event, widget, path, background_area, cell_area, flags):
319 model = widget.get_model()
320 conflict = model[path][CONFLICT_IDX]
321
322
323 try:
324 curIdx = list(conflict.choices).index(model[path][DIRECTION_IDX])
325 except ValueError:
326
327
328 curIdx = 0
329
330 if curIdx == len(conflict.choices) - 1:
331 model[path][DIRECTION_IDX] = conflict.choices[0]
332 else:
333 model[path][DIRECTION_IDX] = conflict.choices[curIdx+1]
334
335 return True
336