Use the new gtk.Tooltip API instead of deprecated gtk.Tooltips API
[joel/kofoto.git] / src / packages / kofoto / gkofoto / imageversionslist.py
1 import os
2 import subprocess
3 import gtk
4 from kofoto.alternative import Alternative
5 from kofoto.shelf import ImageVersionType
6 from kofoto.gkofoto.environment import env
7 from kofoto.gkofoto.menuhandler import MenuGroup
8 from kofoto.gkofoto.imageversionsdialog import ImageVersionsDialog
9 from kofoto.gkofoto.duplicateandopenimagedialog import DuplicateAndOpenImageDialog
10 from kofoto.gkofoto.fullscreenwindow import FullScreenWindow
11
12 _imageVersionTypeToStringMap = {
13     ImageVersionType.Important: "Important",
14     ImageVersionType.Original: "Original",
15     ImageVersionType.Other: "Other",
16 }
17
18 _rotationDirection = Alternative("Left", "Right")
19
20 class ImageVersionsList(gtk.ScrolledWindow):
21     def __init__(self, singleObjectView):
22         gtk.ScrolledWindow.__init__(self)
23         self.__menuGroup = None
24         self.__imageWidgetToImageVersion = None
25         self.__imageWidgetList = None
26         self.__selectedImageWidgets = None
27         self.__singleObjectView = singleObjectView
28         self.__vbox = gtk.VBox()
29         self.__vbox.set_border_width(5)
30         self.__vbox.set_spacing(10)
31         self.__vbox.show()
32         self.add_with_viewport(self.__vbox)
33         self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
34         self.__image = None
35         self.__recentlySelectedImageWidget = None
36         self.connect("focus-in-event", self.__focusInEventHandler_cb)
37         self.connect("focus-out-event", self.__focusOutEventHandler_cb)
38         self.__contextMenu = self.__createContextMenu()
39         self.clear()
40
41         callbacks = [
42             ("menubarViewImageVersion", self.__view_cb),
43             ("menubarViewImageVersionsFullScreen", self.__view_full_screen_cb),
44             ("menubarCopyImageVersionLocations", self.__copyImageLocation_cb),
45             ("menubarOpenImageVersions", self.__open_cb),
46             ("menubarDuplicateAndOpenImageVersion", self.__duplicateAndOpen_cb),
47             ("menubarRotateImageVersionLeft", self.__rotateLeft_cb),
48             ("menubarRotateImageVersionRight", self.__rotateRight_cb),
49             ("menubarSplitToIndependentImages", self.__split_cb),
50             ("menubarDestroyImageVersion", self.__destroy_cb),
51             ("menubarEditImageVersionProperties", self.__editProperties_cb),
52             ]
53         for widgetName, callback in callbacks:
54             env.widgets[widgetName].connect("activate", callback)
55
56     def clear(self):
57         for widget in self.__vbox.get_children():
58             self.__vbox.remove(widget)
59         self.__imageWidgetList = []
60         self.__imageWidgetToImageVersion = {}
61         self.__selectedImageWidgets = set()
62         self.__updateMenus()
63
64     def loadImage(self, image):
65         self.clear()
66         self.__image = image
67         for iv in image.getImageVersions():
68             vbox = gtk.VBox()
69             vbox.set_border_width(3)
70             self.__vbox.pack_start(vbox, expand=False, fill=False)
71             thumbnail = gtk.Image()
72             try:
73                 thumbnailLocation = env.imageCache.get(iv, 128, 128)[0]
74                 thumbnail.set_from_file(thumbnailLocation)
75             except (IOError, OSError):
76                 thumbnail.set_from_pixbuf(env.unknownImageIconPixbuf)
77             alignment = gtk.Alignment(0.5, 0.5, 0.5, 0.5)
78             alignment.add(thumbnail)
79             alignment.set_padding(5, 5, 5, 5)
80             eventbox = gtk.EventBox()
81             eventbox.add(alignment)
82             eventbox.connect(
83                 "button-press-event", self.__mouseButtonPressed_cb)
84             eventbox.connect_after(
85                 "expose-event", self.__imageWidgetExposed_cb)
86             tooltipText = "Location: " + iv.getLocation()
87             if iv.getComment():
88                 tooltipText += "\nComment: " + iv.getComment()
89             eventbox.set_tooltip_text(tooltipText)
90             vbox.add(eventbox)
91             if iv.isPrimary():
92                 vbox.add(gtk.Label("Primary"))
93             vbox.add(gtk.Label(_imageVersionTypeToStringMap[iv.getType()]))
94             self.__imageWidgetList.append(eventbox)
95             self.__imageWidgetToImageVersion[eventbox] = iv
96         self.__vbox.show_all()
97
98     def reload(self):
99         self.loadImage(self.__image)
100
101     def __createContextMenu(self):
102         menu = gtk.Menu()
103         menugroup = MenuGroup()
104         menugroup.addMenuItem(
105             "View",
106             self.__view_cb)
107         menugroup.addMenuItem(
108             "View in full screen mode",
109             self.__view_full_screen_cb)
110         menugroup.addMenuItem(
111             "Copy image version location(s)",
112             self.__copyImageLocation_cb)
113         menugroup.addStockImageMenuItem(
114             "Open image version(s) in external program...",
115             gtk.STOCK_OPEN,
116             self.__open_cb)
117         menugroup.addStockImageMenuItem(
118             "Duplicate and open image version(s) in external program...",
119             gtk.STOCK_OPEN,
120             self.__duplicateAndOpen_cb)
121         menugroup.addImageMenuItem(
122             "Rotate left",
123             os.path.join(env.iconDir, "rotateleft.png"),
124             self.__rotateLeft_cb)
125         menugroup.addImageMenuItem(
126             "Rotate right",
127             os.path.join(env.iconDir, "rotateright.png"),
128             self.__rotateRight_cb)
129         menugroup.addSeparator()
130         menugroup.addMenuItem(
131             "Split to independent image(s)",
132             self.__split_cb)
133         menugroup.addSeparator()
134         menugroup.addStockImageMenuItem(
135             "Destroy...",
136             gtk.STOCK_DELETE,
137             self.__destroy_cb)
138         menugroup.addStockImageMenuItem(
139             "Edit properties...",
140             gtk.STOCK_PROPERTIES,
141             self.__editProperties_cb)
142         for item in menugroup:
143             menu.add(item)
144         self.__menuGroup = menugroup
145         return menu
146
147     def __updateMenus(self):
148         zeroSelected = len(self.__selectedImageWidgets) == 0
149         oneSelected = len(self.__selectedImageWidgets) == 1
150         allSelected = (
151             len(self.__selectedImageWidgets) == len(self.__imageWidgetList))
152
153         env.widgets["menubarViewImageVersion"].set_sensitive(
154             oneSelected)
155         env.widgets["menubarViewImageVersionsFullScreen"].set_sensitive(
156             not zeroSelected)
157         env.widgets["menubarCopyImageVersionLocations"].set_sensitive(
158             not zeroSelected)
159         env.widgets["menubarOpenImageVersions"].set_sensitive(
160             not zeroSelected)
161         env.widgets["menubarDuplicateAndOpenImageVersion"].set_sensitive(
162             oneSelected)
163         env.widgets["menubarRotateImageVersionLeft"].set_sensitive(
164             not zeroSelected)
165         env.widgets["menubarRotateImageVersionRight"].set_sensitive(
166             not zeroSelected)
167         env.widgets["menubarSplitToIndependentImages"].set_sensitive(
168             not zeroSelected and not allSelected)
169         env.widgets["menubarDestroyImageVersion"].set_sensitive(
170             not zeroSelected)
171         env.widgets["menubarEditImageVersionProperties"].set_sensitive(
172             not zeroSelected)
173
174         if zeroSelected:
175             self.__menuGroup.disable()
176         else:
177             self.__menuGroup.enable()
178             if not oneSelected:
179                 self.__menuGroup["View"].set_sensitive(False)
180                 self.__menuGroup[
181                     ("Duplicate and open image version(s) in external"
182                      " program...")
183                     ].set_sensitive(False)
184             if allSelected:
185                 self.__menuGroup["Split to independent image(s)"].set_sensitive(False)
186
187     def __mouseButtonPressed_cb(self, widget, event):
188         def selectWidget(widget):
189             widget.set_state(gtk.STATE_SELECTED)
190             self.__selectedImageWidgets.add(widget)
191         def unselectWidget(widget):
192             widget.set_state(gtk.STATE_NORMAL)
193             self.__selectedImageWidgets.remove(widget)
194         def selectOnlyThis():
195             for x in list(self.__selectedImageWidgets):
196                 unselectWidget(x)
197             selectWidget(widget)
198         def selectThisToo():
199             selectWidget(widget)
200         def flipThis():
201             if widget in self.__selectedImageWidgets:
202                 unselectWidget(widget)
203             else:
204                 selectWidget(widget)
205         def extendSelection():
206             otherIndex = self.__imageWidgetList.index(
207                 self.__recentlySelectedImageWidget)
208             thisIndex = self.__imageWidgetList.index(widget)
209             for x in xrange(
210                 min(otherIndex, thisIndex), max(otherIndex, thisIndex) + 1):
211                 selectWidget(self.__imageWidgetList[x])
212
213         self.grab_focus()
214         if event.button == 1:
215             if event.type == gtk.gdk.BUTTON_PRESS:
216                 if event.state & gtk.gdk.CONTROL_MASK:
217                     flipThis()
218                 elif event.state & gtk.gdk.SHIFT_MASK:
219                     extendSelection()
220                 else:
221                     selectOnlyThis()
222             elif event.type == gtk.gdk._2BUTTON_PRESS:
223                 if (event.state & (
224                         gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK) == 0):
225                     self.__view_cb()
226         elif event.button == 3:
227             if widget in self.__selectedImageWidgets:
228                 selectThisToo()
229             else:
230                 selectOnlyThis()
231             self.__contextMenu.popup(
232                 None, None, None, event.button, event.time)
233         if self.__recentlySelectedImageWidget:
234             self.__recentlySelectedImageWidget.queue_draw()
235         self.__recentlySelectedImageWidget = widget
236         self.__updateMenus()
237
238     def __imageWidgetExposed_cb(self, widget, event):
239         if widget == self.__recentlySelectedImageWidget:
240             state = gtk.STATE_SELECTED
241             allocation = widget.get_allocation()
242             widget.style.paint_focus(
243                 widget.window, state, None, widget, "",
244                 2, 2, allocation.width - 4, allocation.height - 4)
245         else:
246             state = gtk.STATE_NORMAL
247
248     def __view_cb(self, *args):
249         assert len(self.__selectedImageWidgets) == 1
250         widget = list(self.__selectedImageWidgets)[0]
251         imageVersion = self.__imageWidgetToImageVersion[widget]
252         location = imageVersion.getLocation()
253         self.__singleObjectView._loadImageAtLocation(location, False)
254
255     def __view_full_screen_cb(self, *args):
256         assert len(self.__selectedImageWidgets) > 0
257         widgets = self.__getSelectedImageVersionsInOrder()
258         if len(widgets) > 1:
259             imageVersions = [
260                 self.__imageWidgetToImageVersion[x] for x in widgets]
261             window = FullScreenWindow(imageVersions)
262         else:
263             imageVersions = [
264                 self.__imageWidgetToImageVersion[x]
265                 for x in self.__imageWidgetList]
266             index = self.__imageWidgetList.index(widgets[0])
267             window = FullScreenWindow(imageVersions, index)
268         window.show_all()
269
270     def __copyImageLocation_cb(self, widget, param):
271         assert len(self.__selectedImageWidgets) > 0
272         clipboard = gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD)
273         primary = gtk.clipboard_get(gtk.gdk.SELECTION_PRIMARY)
274         location = "\n".join(
275             [self.__imageWidgetToImageVersion[x].getLocation()
276              for x in self.__getSelectedImageVersionsInOrder()])
277         clipboard.set_text(location)
278         primary.set_text(location)
279
280     def __open_cb(self, *args):
281         assert len(self.__selectedImageWidgets) > 0
282         locations = [
283             self.__imageWidgetToImageVersion[x].getLocation()
284             for x in self.__getSelectedImageVersionsInOrder()]
285         command = env.openCommand % {"locations": " ".join(locations)}
286         try:
287             result = subprocess.call(command, shell=True)
288         except OSError:
289             result = 1
290         if result != 0:
291             dialog = gtk.MessageDialog(
292                 type=gtk.MESSAGE_ERROR,
293                 buttons=gtk.BUTTONS_OK,
294                 message_format="Failed to execute command: \"%s\"" % command)
295             dialog.run()
296             dialog.destroy()
297
298     def __duplicateAndOpen_cb(self, *args):
299         assert len(self.__selectedImageWidgets) == 1
300         imageWidget = list(self.__selectedImageWidgets)[0]
301         imageVersion = self.__imageWidgetToImageVersion[imageWidget]
302         dialog = DuplicateAndOpenImageDialog()
303         dialog.run(imageVersion)
304
305     def __split_cb(self, *args):
306         assert len(self.__selectedImageWidgets) > 0
307         assert len(self.__selectedImageWidgets) < len(self.__imageWidgetList)
308         for widget in self.__selectedImageWidgets:
309             imageVersion = self.__imageWidgetToImageVersion[widget]
310             image = env.shelf.createImage()
311             imageVersion.setImage(image)
312             for key, value in self.__image.getAttributeMap().iteritems():
313                 image.setAttribute(key, value)
314             for category in  self.__image.getCategories():
315                 image.addCategory(category)
316         self.__singleObjectView.reload()
317
318     def __rotateLeft_cb(self, *args):
319         assert len(self.__selectedImageWidgets) > 0
320         self.__rotate(_rotationDirection.Left)
321
322     def __rotateRight_cb(self, *args):
323         assert len(self.__selectedImageWidgets) > 0
324         self.__rotate(_rotationDirection.Right)
325
326     def __destroy_cb(self, *args):
327         assert len(self.__selectedImageWidgets) > 0
328         widgets = gtk.glade.XML(env.gladeFile, "destroyImageVersionsDialog")
329         dialog = widgets.get_widget("destroyImageVersionsDialog")
330         result = dialog.run()
331         if result == gtk.RESPONSE_OK:
332             checkbutton = widgets.get_widget("deleteImageFilesCheckbutton")
333             deleteFiles = checkbutton.get_active()
334             for widget in self.__selectedImageWidgets:
335                 imageVersion = self.__imageWidgetToImageVersion[widget]
336                 if deleteFiles:
337                     try:
338                         os.remove(imageVersion.getLocation())
339                         # TODO: Delete from image cache too?
340                     except OSError:
341                         pass
342                 env.shelf.deleteImageVersion(imageVersion.getId())
343             self.__singleObjectView.reload()
344         dialog.destroy()
345
346     def __editProperties_cb(self, *args):
347         assert len(self.__selectedImageWidgets) > 0
348         dialog = ImageVersionsDialog(self.__singleObjectView._objectCollection)
349         dialog.runViewImageVersions(self.__image)
350         self.__singleObjectView.reload()
351
352     def __focusInEventHandler_cb(self, widget, event):
353         for x in self.__selectedImageWidgets:
354             x.set_state(gtk.STATE_SELECTED)
355
356     def __focusOutEventHandler_cb(self, widget, event):
357         for x in self.__selectedImageWidgets:
358             x.set_state(gtk.STATE_ACTIVE)
359
360     def __rotate(self, rotationDirection):
361         for widget in self.__selectedImageWidgets:
362             imageVersion = self.__imageWidgetToImageVersion[widget]
363             if rotationDirection == _rotationDirection.Left:
364                 rotateCommand = env.rotateLeftCommand
365             elif rotationDirection == _rotationDirection.Right:
366                 rotateCommand = env.rotateRightCommand
367             else:
368                 # Can't happen.
369                 assert False
370             location = imageVersion.getLocation()
371             env.pixbufLoader.unload_all(location)
372             command = rotateCommand % {"location": location}
373             try:
374                 result = subprocess.call(command, shell=True)
375             except OSError:
376                 result = 1
377             if result != 0:
378                 dialog = gtk.MessageDialog(
379                     type=gtk.MESSAGE_ERROR,
380                     buttons=gtk.BUTTONS_OK,
381                     message_format=
382                         "Failed to execute command: \"%s\"" % command)
383                 dialog.run()
384                 dialog.destroy()
385             else:
386                 imageVersion.contentChanged()
387         self.__singleObjectView.reload()
388
389     def __getSelectedImageVersionsInOrder(self):
390         return [
391             x
392             for x in self.__imageWidgetList
393             if x in self.__selectedImageWidgets]