3
3
This module contains code for handling the 'merging' of duplicate
6
import gtk, os.path, time
6
import gtk, pango, os.path, time
7
7
import gourmet.recipeIdentifier
8
from gourmet.gtk_extras import ratingWidget, mnemonic_manager
8
from gourmet.gtk_extras import ratingWidget, mnemonic_manager, dialog_extras
9
9
import gourmet.recipeIdentifier
10
10
import gourmet.convert as convert
11
11
import gourmet.gglobals as gglobals
12
import gourmet.recipeManager
12
13
from gettext import gettext as _
15
19
current_path = os.path.split(os.path.join(os.getcwd(),__file__))[0]
23
def time_to_text (val):
27
# within 18 hours, return in form 4 hours 23 minutes ago or some such
28
if curtime - val < 18 * 60 * 60:
29
return _("%s ago")%convert.seconds_to_timestring(curtime-val,round_at=1)
30
tupl=time.localtime(val)
31
if curtime - val < 7 * 24 * 60 * 60:
32
return time.strftime('%A %T',tupl)
34
return time.strftime('%D %T',tupl)
19
37
class ConflictError (ValueError):
20
38
def __init__ (self, conflicts):
21
39
self.conflicts = conflicts
37
def __init__ (self, rd, in_recipes=None, on_close_callback=None):
55
def __init__ (self, rd=None, in_recipes=None, on_close_callback=None):
59
self.rd = gourmet.recipeManager.get_recipe_manager()
39
60
self.in_recipes = in_recipes
40
61
self.on_close_callback = on_close_callback
41
62
self.to_merge = [] # Queue of recipes to be merged...
62
84
'recipeDiffScrolledWindow',
63
85
'duplicateRecipeTreeView',
64
86
'mergeAllButton','mergeSelectedButton', # buttons on list-dups page (minus close button)
65
'applyMergeButton','cancelMergeButton', # buttons on merge-recs page
66
'searchTypeCombo','includeDeletedRecipesCheckButton','notebook'
87
'applyMergeButton','closeMergeButton','cancelMergeButton', # buttons on merge-recs page
88
'searchTypeCombo','includeDeletedRecipesCheckButton','notebook',
68
91
setattr(self,w,self.glade.get_widget(w))
69
92
self.setup_treeview()
74
97
self.duplicateRecipeTreeView.append_column(col)
75
98
self.duplicateRecipeTreeView.insert_column_with_data_func(
77
'Last Modified', # title
79
self.time_cell_data_func, # function
82
self.duplicateRecipeTreeView.append_column(col)
100
'Last Modified', # title
102
self.time_cell_data_func, # function
83
105
col = gtk.TreeViewColumn('Duplicates',renderer,text=4)
84
106
self.duplicateRecipeTreeView.append_column(col)
85
107
self.duplicateRecipeTreeView.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
88
110
"""Display time in treeview cell.
90
112
val = model.get_value(titer,data_col)
92
# within 18 hours, return in form 4 hours 23 minutes ago or some such
94
cell.set_property('text',_('Unknown'))
96
if curtime - val < 18 * 60 * 60:
97
cell.set_property('text',
98
_("%s ago")%convert.seconds_to_timestring(curtime-val,round_at=1))
100
tupl=time.localtime(val)
101
if curtime - val < 7 * 24 * 60 * 60:
102
cell.set_property('text',
103
time.strftime('%A %T',tupl))
106
cell.set_property('text',
107
time.strftime('%D %T',tupl))
113
cell.set_property('text',time_to_text(val))
110
115
def populate_tree (self):
111
116
"""Populate treeview with duplicate recipes.
147
152
def merge_next_recipe (self, ):
148
153
if self.to_merge:
149
154
self.current_dup_index = self.to_merge.pop(0)
155
self.mergeInfoLabel.set_text(
156
'Merging recipe %(index)s of %(total)s'%{
157
'index':self.total_to_merge - len(self.to_merge),
158
'total':self.total_to_merge
150
160
duplicate_recipes = self.dups[self.current_dup_index]
151
161
#self.idt = IngDiffTable(self.rd,duplicate_recipes[0],duplicate_recipes[1])
152
162
self.current_recs = [self.rd.get_rec(i) for i in duplicate_recipes]
163
last_modified = {'last_modified':[r.last_modified for r in self.current_recs]}
153
164
self.current_diff_data = gourmet.recipeIdentifier.diff_recipes(self.rd,self.current_recs)
154
self.diff_table = DiffTable(self.current_diff_data,self.current_recs[0],parent=self.recipeDiffScrolledWindow)
165
last_modified.update(self.current_diff_data)
166
self.diff_table = DiffTable(last_modified,self.current_recs[0],parent=self.recipeDiffScrolledWindow)
155
167
self.diff_table.add_ingblocks(self.rd, self.current_recs)
156
168
if not self.diff_table.idiffs and not self.current_diff_data:
157
169
# If there are no differences, just merge the recipes...
201
213
for d in dup_indices:
202
214
if d not in self.to_merge:
203
215
self.to_merge.append(d)
216
self.total_to_merge = len(self.to_merge)
204
217
self.merge_next_recipe()
206
219
def merge_all (self, *args):
207
220
"""Merge all rows currently in treeview.
209
#print 'CALL: merge_all'
210
self.to_merge = range(len(self.dups))
222
self.total_to_merge = len(self.dups)
223
self.to_merge = range(self.total_to_merge)
211
224
self.merge_next_recipe()
226
def offer_auto_merge (self, *args):
228
option =dialog_extras.getOption(
229
label=_('Auto-Merge recipes'),
231
(_('Always use newest recipe'),NEWER),
232
(_('Always use oldest recipe'),OLDER),
233
# The following would be nice to add eventually...
234
#_('Always use longer field'),
235
#_('Ignore differences in ingredient keys')
240
self.do_auto_merge(NEWER)
241
except dialog_extras.UserCancelledError:
244
def do_auto_merge (self, mode):
245
if self.recipeDiffScrolledWindow.get_child():
246
self.recipeDiffScrolledWindow.remove(self.recipeDiffScrolledWindow.get_child())
249
l.set_markup('<u>Automatically merged recipes</u>')
250
vb.pack_start(l,expand=False,fill=False); vb.show_all()
251
self.recipeDiffScrolledWindow.add_with_viewport(vb)
252
def do_auto_merge ():
253
kept = self.auto_merge_current_rec(mode)
254
label = gtk.Label('%s'%kept.title)
255
vb.pack_start(label,expand=False,fill=False); label.show()
256
self.cancelMergeButton.hide()
257
self.applyMergeButton.hide()
258
self.closeMergeButton.set_sensitive(False)
261
self.mergeInfoLabel.set_text(
262
'Automatically merging recipe %(index)s of %(total)s'%{
263
'index':self.total_to_merge - len(self.to_merge),
264
'total':self.total_to_merge
266
self.current_dup_index = self.to_merge.pop(0)
267
duplicate_recipes = self.dups[self.current_dup_index]
268
self.current_recs = [self.rd.get_rec(i) for i in duplicate_recipes]
270
while gtk.events_pending(): gtk.main_iteration()
271
self.mergeInfoLabel.set_text('Automatically merged %s recipes'%self.total_to_merge)
272
self.closeMergeButton.set_sensitive(True)
274
def auto_merge_current_rec (self, mode):
275
def compare_recs (r1, r2):
276
result = cmp(r1.last_modified,r2.last_modified)
277
if mode==NEWER: return result
279
self.current_recs.sort(compare_recs)
280
keeper = self.current_recs[0]
281
tossers = self.current_recs[1:]
282
for to_toss in tossers:
283
self.rd.delete_rec(to_toss)
213
286
def cancel_merge (self, *args):
214
287
self.merge_next_recipe()
215
288
if not self.to_merge:
286
359
recipe_object is a recipe object representing one of our duplicate
287
360
recs, from which we can grab attributes that are not different.
362
dont_choose is a list of attributes whose differences are
363
displayed, but where no choice is offered (such as modification
364
time for the recipe).
290
def __init__ (self, diff_dic, recipe_object=None, parent=None):
367
def __init__ (self, diff_dic, recipe_object=None, parent=None,
292
370
self.diff_dic = diff_dic
293
371
gtk.Table.__init__(self)
296
374
self.set_row_spacings(6)
298
376
self.max_cols = 1
299
for attr,name,typ in gglobals.REC_ATTRS \
377
for attr,name,typ in [('last_modified','Last Modified',None)] + gglobals.REC_ATTRS \
300
378
+ [('image','Image',None)] \
301
379
+ [(attr,gglobals.TEXT_ATTR_DIC[attr],None) for attr in gglobals.DEFAULT_TEXT_ATTR_ORDER]:
302
380
if diff_dic.has_key(attr):