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