1 """This module contains the FullScreenWindow class."""
3 __all__ = ["FullScreenWindow"]
9 from kofoto.gkofoto.imageview import ImageView
10 from kofoto.gkofoto.environment import env
12 class FullScreenWindow(gtk.Window):
13 """A fullscreen window widget."""
15 def __init__(self, image_versions, current_index=0):
20 image_versions -- A list of ImageVersion instance to display.
21 current_index -- Where to start in image_versions.
24 self.__gobject_init__() # TODO: Use gtk.Window.__init__ in PyGTK 2.8.
26 self._last_allocated_size = None
27 self._image_versions = image_versions
28 self._current_index = current_index
29 self._latest_handle = None
30 self._latest_size = (0, 0)
31 self._selected_category = None
32 self._last_selected_category = None
33 self._show_image_categories = True
34 self._show_category_keys_info = False
35 self._keysym_to_category_map = {}
37 bg_color = gtk.gdk.color_parse("#000000")
38 fg_color = gtk.gdk.color_parse("#999999")
40 eventbox = gtk.EventBox()
44 # Add the image widget.
45 self._image_view = ImageView()
46 self._image_view.set_error_pixbuf(
47 gtk.gdk.pixbuf_new_from_file(env.unknownImageIconFileName))
48 self._image_view.modify_bg(gtk.STATE_NORMAL, bg_color)
49 vbox.pack_start(self._image_view)
51 # Add end screen label.
54 "No more images.\nPress escape to get back to GKofoto.")
55 label.set_justify(gtk.JUSTIFY_CENTER)
56 label.modify_fg(gtk.STATE_NORMAL, fg_color)
57 vbox.pack_start(label)
58 self._end_screen_label = label
60 # Add image categories label.
62 label.modify_fg(gtk.STATE_NORMAL, fg_color)
63 vbox.pack_start(label, False)
64 self._image_categories_label = label
66 # Add category keys info label.
68 label.modify_fg(gtk.STATE_NORMAL, fg_color)
69 vbox.pack_start(label, False)
70 label.set_text(u"Assigned keys:")
71 self._category_keys_info_label = label
73 # Add key assignment form.
74 self._key_assignment_hbox = gtk.HBox()
75 self._key_assignment_hbox.set_spacing(5)
76 vbox.pack_start(self._key_assignment_hbox, False)
77 label = gtk.Label("Assign last entered category to key (a-z):")
78 label.modify_fg(gtk.STATE_NORMAL, fg_color)
79 self._key_assignment_hbox.pack_start(label, False)
80 self._key_assignment_entry = gtk.Entry()
81 self._key_assignment_entry.connect(
82 "activate", self._key_assignment_entry_activate_cb)
83 self._key_assignment_hbox.pack_start(self._key_assignment_entry, False)
85 # Add categorization form.
86 self._categorization_hbox = gtk.HBox()
87 self._categorization_hbox.set_spacing(5)
88 vbox.pack_start(self._categorization_hbox, False)
89 label = gtk.Label("Category:")
90 label.modify_fg(gtk.STATE_NORMAL, fg_color)
91 self._categorization_hbox.pack_start(label, False)
92 self._category_entry = gtk.Entry()
93 self._category_entry.connect(
94 "activate", self._category_entry_activate_cb)
95 self._category_entry.connect(
96 "changed", self._category_entry_changed_cb)
97 self._categorization_hbox.pack_start(self._category_entry, False)
98 self._category_indicator_image = gtk.Image()
99 self._category_indicator_image.set_from_stock(
100 gtk.STOCK_CANCEL, gtk.ICON_SIZE_MENU)
101 self._categorization_hbox.pack_start(self._category_indicator_image, False)
102 self._matching_category_label = gtk.Label()
103 self._matching_category_label.set_line_wrap(True)
104 self._matching_category_label.modify_fg(gtk.STATE_NORMAL, fg_color)
105 self._categorization_hbox.pack_start(self._matching_category_label, True, True)
107 self.modify_bg(gtk.STATE_NORMAL, bg_color)
108 eventbox.modify_bg(gtk.STATE_NORMAL, bg_color)
111 self.set_default_size(400, 400)
113 self.connect_after("map-event", self._after_map_event_cb)
114 self.connect_after("size-allocate", self._after_size_allocate_cb)
115 eventbox.connect("button-press-event", self._button_press_event_cb)
116 self.connect("key-press-event", self._key_press_event_cb)
119 """Destroy the widget."""
121 self._maybe_cancel_load()
122 gtk.Window.destroy(self)
124 # ----------------------------------------
126 def _add_or_remove_image_category(self, category):
127 image = self._image_versions[self._current_index].getImage()
128 if category in image.getCategories():
129 image.removeCategory(category)
131 image.addCategory(category)
132 self._update_image_categories_label()
134 def _after_map_event_cb(self, *unused):
137 def _after_size_allocate_cb(self, widget, rect):
138 allocated_size = (rect.width, rect.height)
139 if allocated_size == self._last_allocated_size:
141 self._last_allocated_size = allocated_size
142 self._goto(self._current_index)
144 def _button_press_event_cb(self, widget, event):
145 iv = self._image_view
146 if event.button == 1 and iv.get_zoom_mode() == iv.ZoomMode.BestFit:
147 self._goto(self._current_index + 1)
149 def _category_entry_activate_cb(self, widget):
150 if self._selected_category is not None:
151 self._add_or_remove_image_category(self._selected_category)
152 self._categorization_hbox.hide_all()
153 self._last_selected_category = self._selected_category
154 self._category_entry.set_text("")
155 # self._selected_category is set to None implicitly by set_text.
157 def _category_entry_changed_cb(self, widget):
158 text = self._category_entry.get_text().decode("utf-8")
159 regexp = re.compile(".*%s.*" % re.escape(text.lower()))
161 categories = list(env.shelf.getMatchingCategories(regexp))
165 for category in categories:
166 if category.getTag().lower() == text.lower() \
167 or category.getDescription().lower() == text.lower():
168 exact_match = category
170 if len(categories) == 1 or exact_match is not None:
171 image_stock_id = gtk.STOCK_OK
172 if len(categories) == 1:
173 self._selected_category = categories[0]
175 self._selected_category = exact_match
177 self._image_versions[self._current_index].getImage()
178 image_categories = current_image.getCategories()
179 category_set = self._selected_category in image_categories
180 self._matching_category_label.set_markup(
181 u"Press enter to <b>%s</b> category <b>%s</b> [<b>%s</b>]" % (
182 ["set", "unset"][category_set],
183 self._selected_category.getDescription(),
184 self._selected_category.getTag()))
186 image_stock_id = gtk.STOCK_CANCEL
187 self._selected_category = None
188 self._matching_category_label.set_text("No matching category")
189 self._category_indicator_image.set_from_stock(
190 image_stock_id, gtk.ICON_SIZE_MENU)
192 def _display_end_screen(self):
193 self._maybe_cancel_load()
195 self._image_view.hide()
196 self._end_screen_label.show()
197 self._image_categories_label.hide()
198 self._category_keys_info_label.hide()
199 self._key_assignment_hbox.hide_all()
200 self._categorization_hbox.hide_all()
202 def _display_image(self):
203 self._image_view.set_image(self._get_image_async_cb)
204 self._update_image_categories_label()
206 self._image_view.show()
207 self._end_screen_label.hide()
208 if self._show_image_categories:
209 self._image_categories_label.show()
211 self._image_categories_label.hide()
212 if self._show_category_keys_info:
213 self._category_keys_info_label.show()
215 self._category_keys_info_label.hide()
216 self._key_assignment_hbox.hide_all()
217 self._categorization_hbox.hide_all()
219 def _get_image_async_cb(self, size):
220 path = self._image_versions[self._current_index].getLocation()
221 self._maybe_cancel_load()
222 self._latest_handle = env.pixbufLoader.load(
225 self._image_view.set_from_pixbuf,
226 self._image_view.set_error)
227 if size != self._latest_size:
228 self._unload(self._latest_size)
230 self._latest_size = size
232 def _goto(self, new_index):
234 self._current_index = -1
235 self._display_end_screen()
236 elif new_index >= len(self._image_versions):
237 self._current_index = len(self._image_versions)
238 self._display_end_screen()
240 self._current_index = new_index
241 self._display_image()
243 def _hide_cursor(self):
244 pixmap = gtk.gdk.Pixmap(None, 1, 1, 1)
245 color = gtk.gdk.Color()
246 invisible_cursor = gtk.gdk.Cursor(pixmap, pixmap, color, color, 0, 0)
247 self.window.set_cursor(invisible_cursor)
249 def _is_valid_index(self, index):
250 return 0 <= index < len(self._image_versions)
252 def _key_assignment_entry_activate_cb(self, widget):
253 key = self._key_assignment_entry.get_text()
254 if (self._last_selected_category is None
256 or key not in string.lowercase):
259 keysym = getattr(gtk.keysyms, key)
260 self._keysym_to_category_map[keysym] = self._last_selected_category
261 self._key_assignment_hbox.hide_all()
262 self._key_assignment_entry.set_text("")
263 self._update_key_assignment_info()
265 def _key_press_event_cb(self, unused, event):
266 # GIMP: 1 --> 100%, C-S-e --> fit
267 # EOG: [1,C-0,C-1] --> 100%
268 # f-spot: [0,1,C-0,C-1] --> fit
271 CTRL = gtk.gdk.CONTROL_MASK
272 e = (event.keyval, event.state & CTRL)
274 if self._key_assignment_hbox.props.visible:
276 # Showing key assignment form -- disable bindings except escape.
278 if e in [(k.Escape, 0), (k.a, CTRL)]:
279 self._toggle_key_assignment_form()
284 if self._categorization_hbox.props.visible:
286 # Showing categorization form -- disable bindings except escape.
288 if e in [(k.Escape, 0), (k.t, CTRL)]:
289 self._toggle_categorization_form()
297 if not (event.state & CTRL) and event.keyval in self._keysym_to_category_map:
298 category = self._keysym_to_category_map[event.keyval]
299 self._add_or_remove_image_category(category)
301 if e in [(k.space, 0), (k.Right, 0), (k.Down, 0), (k.Page_Down, 0)]:
302 self._goto(self._current_index + 1)
304 if e in [(k.BackSpace, 0), (k.Left, 0), (k.Up, 0), (k.Page_Up, 0)]:
305 self._goto(self._current_index - 1)
311 self._goto(len(self._image_versions) - 1)
313 if e == (k.Escape, 0):
317 self._image_view.zoom_in()
319 if e == (k.minus, 0):
320 self._image_view.zoom_out()
323 self._image_view.zoom_to_actual()
325 if e in [(k.equal, 0), (k._0, 0)]:
326 self._image_view.zoom_to_fit()
329 self._toggle_key_assignment_form()
332 self._toggle_image_categories()
335 self._toggle_category_keys_info()
338 self._toggle_categorization_form()
342 def _maybe_cancel_load(self):
343 if self._latest_handle is None:
346 env.pixbufLoader.cancel_load(self._latest_handle)
347 self._latest_handle = None
349 def _preload(self, size):
350 self._preload_or_unload(size, True)
352 def _preload_or_unload(self, size, preload):
353 index = self._current_index
354 for x in [index + 2, index - 1, index + 1]:
355 if self._is_valid_index(x):
356 location = self._image_versions[x].getLocation()
358 env.pixbufLoader.preload(location, size)
360 env.pixbufLoader.unload(location, size)
362 def _toggle_categorization_form(self):
363 if not self._image_view.props.visible:
364 # Displaying end screen.
366 if self._categorization_hbox.props.visible:
367 self._categorization_hbox.hide_all()
369 self._categorization_hbox.show_all()
370 self._category_entry.grab_focus()
372 def _toggle_category_keys_info(self):
373 self._show_category_keys_info = not self._show_category_keys_info
374 if self._show_category_keys_info:
375 self._category_keys_info_label.show()
377 self._category_keys_info_label.hide()
379 def _toggle_image_categories(self):
380 self._show_image_categories = not self._show_image_categories
381 if self._show_image_categories:
382 self._image_categories_label.show()
384 self._image_categories_label.hide()
386 def _toggle_key_assignment_form(self):
387 if not self._image_view.props.visible:
388 # Displaying end screen.
390 if self._key_assignment_hbox.props.visible:
391 self._key_assignment_hbox.hide_all()
393 self._key_assignment_hbox.show_all()
394 self._key_assignment_entry.grab_focus()
396 def _unload(self, size):
397 self._preload_or_unload(size, False)
399 def _update_image_categories_label(self):
400 image = self._image_versions[self._current_index].getImage()
401 categories = image.getCategories()
402 texts = sorted(x.getTag() for x in categories)
403 markup = u" | ".join(u"<b>%s</b>" % x for x in texts)
404 self._image_categories_label.set_markup(markup)
406 def _update_key_assignment_info(self):
408 u"<b>%s</b>: <b>%s</b>" % (chr(k), v.getTag())
409 for (k, v) in self._keysym_to_category_map.items())
410 self._category_keys_info_label.set_markup(u"Assigned keys:\n" + text)
412 gobject.type_register(FullScreenWindow) # TODO: Not needed in PyGTK 2.8.