1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 from draw import draw_pixbuf, draw_vlinear
24 from keymap import get_keyevent_name
25 from skin_config import skin_config
26 from theme import ui_theme
27 import gobject
28 import gtk
29 from utils import (get_match_parent, cairo_state, get_event_coords,
30 is_in_rect, is_left_button, is_double_click,
31 is_single_click, get_window_shadow_size)
32
34 '''
35 Icon view.
36
37 @undocumented: expose_icon_view
38 @undocumented: motion_icon_view
39 @undocumented: icon_view_get_event_index
40 @undocumented: button_press_icon_view
41 @undocumented: button_release_icon_view
42 @undocumented: leave_icon_view
43 @undocumented: key_press_icon_view
44 @undocumented: key_release_icon_view
45 @undocumented: update_redraw_request_list
46 @undocumented: redraw_item
47 @undocumented: get_offset_coordinate
48 '''
49
50 __gsignals__ = {
51 "lost-focus-item" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
52 "motion-notify-item" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, int, int)),
53 "highlight-item" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
54 "normal-item" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
55 "button-press-item" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, int, int)),
56 "button-release-item" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, int, int)),
57 "single-click-item" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, int, int)),
58 "double-click-item" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, int, int)),
59 }
60
61 - def __init__(self, padding_x=0, padding_y=0):
62 '''
63 Initialize IconView class.
64
65 @param padding_x: Horizontal padding value.
66 @param padding_y: Vertical padding value.
67 '''
68
69 gtk.DrawingArea.__init__(self)
70 self.padding_x = padding_x
71 self.padding_y = padding_y
72 self.add_events(gtk.gdk.ALL_EVENTS_MASK)
73 self.set_can_focus(True)
74 self.items = []
75 self.focus_index = None
76 self.highlight_item = None
77 self.double_click_item = None
78 self.single_click_item = None
79
80
81 self.connect("realize", lambda w: self.grab_focus())
82 self.connect("expose-event", self.expose_icon_view)
83 self.connect("motion-notify-event", self.motion_icon_view)
84 self.connect("button-press-event", self.button_press_icon_view)
85 self.connect("button-release-event", self.button_release_icon_view)
86 self.connect("leave-notify-event", self.leave_icon_view)
87 self.connect("key-press-event", self.key_press_icon_view)
88
89
90 self.connect("lost-focus-item", lambda view, item: item.icon_item_lost_focus())
91 self.connect("motion-notify-item", lambda view, item, x, y: item.icon_item_motion_notify(x, y))
92 self.connect("highlight-item", lambda view, item: item.icon_item_highlight())
93 self.connect("normal-item", lambda view, item: item.icon_item_normal())
94 self.connect("button-press-item", lambda view, item, x, y: item.icon_item_button_press(x, y))
95 self.connect("button-release-item", lambda view, item, x, y: item.icon_item_button_release(x, y))
96 self.connect("single-click-item", lambda view, item, x, y: item.icon_item_single_click(x, y))
97 self.connect("double-click-item", lambda view, item, x, y: item.icon_item_double_click(x, y))
98
99
100 self.redraw_request_list = []
101 self.redraw_delay = 100
102 gtk.timeout_add(self.redraw_delay, self.update_redraw_request_list)
103
104 self.keymap = {
105 "Home" : self.select_first_item,
106 "End" : self.select_last_item,
107 "Return" : self.return_item,
108 "Up" : self.select_up_item,
109 "Down" : self.select_down_item,
110 "Left" : self.select_left_item,
111 "Right" : self.select_right_item,
112 "Page_Up" : self.scroll_page_up,
113 "Page_Down" : self.scroll_page_down,
114 }
115
129
131 '''
132 Select last item.
133 '''
134 if len(self.items) > 0:
135 self.clear_focus_item()
136 self.focus_index = len(self.items) - 1
137
138 self.emit("motion-notify-item", self.items[self.focus_index], 0, 0)
139
140
141 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment()
142 vadjust.set_value(vadjust.get_upper() - vadjust.get_page_size())
143
145 '''
146 Do return action.
147
148 This function will emit `double-click-item` signal.
149 '''
150 if self.focus_index != None:
151 self.emit("double-click-item", self.items[self.focus_index], 0, 0)
152
154 '''
155 Select up row item.
156 '''
157 if len(self.items) > 0:
158 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment()
159
160 if self.focus_index == None:
161 self.focus_index = 0
162 self.emit("motion-notify-item", self.items[self.focus_index], 0, 0)
163
164
165 vadjust.set_value(vadjust.get_lower())
166 else:
167 item_width, item_height = self.items[0].get_width(), self.items[0].get_height()
168 scrolled_window = get_match_parent(self, ["ScrolledWindow"])
169 columns = int((scrolled_window.allocation.width - self.padding_x * 2) / item_width)
170
171 if self.focus_index - columns >= 0:
172 self.emit("lost-focus-item", self.items[self.focus_index])
173 self.focus_index -= columns
174 self.emit("motion-notify-item", self.items[self.focus_index], 0, 0)
175
176
177 row = int(self.focus_index / columns)
178 if vadjust.get_value() - self.padding_y > row * item_height:
179 vadjust.set_value(vadjust.get_lower() + row * item_height + self.padding_y)
180 elif vadjust.get_value() - self.padding_y == row * item_height:
181 vadjust.set_value(vadjust.get_lower())
182
184 '''
185 Select next row item.
186 '''
187 if len(self.items) > 0:
188 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment()
189 if self.focus_index == None:
190 self.focus_index = 0
191 self.emit("motion-notify-item", self.items[self.focus_index], 0, 0)
192
193
194 vadjust.set_value(vadjust.get_lower())
195 else:
196 item_width, item_height = self.items[0].get_width(), self.items[0].get_height()
197 scrolled_window = get_match_parent(self, ["ScrolledWindow"])
198 columns = int((scrolled_window.allocation.width - self.padding_x * 2) / item_width)
199
200 if self.focus_index + columns <= len(self.items) - 1:
201 self.emit("lost-focus-item", self.items[self.focus_index])
202 self.focus_index += columns
203 self.emit("motion-notify-item", self.items[self.focus_index], 0, 0)
204
205
206 row = int(self.focus_index / columns)
207 if vadjust.get_value() + vadjust.get_page_size() - self.padding_y < (row + 1) * item_height:
208 vadjust.set_value(vadjust.get_lower() + (row + 1) * item_height - vadjust.get_page_size() + self.padding_y)
209 elif vadjust.get_value() + vadjust.get_page_size() - self.padding_y == (row + 1) * item_height:
210 vadjust.set_value(vadjust.get_upper() - vadjust.get_page_size())
211
213 '''
214 Select left item.
215 '''
216 if len(self.items) > 0:
217 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment()
218 if self.focus_index == None:
219 self.focus_index = 0
220 self.emit("motion-notify-item", self.items[self.focus_index], 0, 0)
221
222
223 vadjust.set_value(vadjust.get_lower())
224 else:
225 item_width, item_height = self.items[0].get_width(), self.items[0].get_height()
226 scrolled_window = get_match_parent(self, ["ScrolledWindow"])
227 columns = int((scrolled_window.allocation.width - self.padding_x * 2) / item_width)
228 row = int(self.focus_index / columns)
229 min_index = row * columns
230
231 if self.focus_index - 1 >= min_index:
232 self.emit("lost-focus-item", self.items[self.focus_index])
233 self.focus_index -= 1
234 self.emit("motion-notify-item", self.items[self.focus_index], 0, 0)
235
237 '''
238 Select right item.
239 '''
240 if len(self.items) > 0:
241 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment()
242 if self.focus_index == None:
243 self.focus_index = 0
244 self.emit("motion-notify-item", self.items[self.focus_index], 0, 0)
245
246
247 vadjust.set_value(vadjust.get_lower())
248 else:
249 item_width, item_height = self.items[0].get_width(), self.items[0].get_height()
250 scrolled_window = get_match_parent(self, ["ScrolledWindow"])
251 columns = int((scrolled_window.allocation.width - self.padding_x * 2) / item_width)
252 row = int(self.focus_index / columns)
253 max_index = min((row + 1) * columns - 1, len(self.items) - 1)
254
255 if self.focus_index + 1 <= max_index:
256 self.emit("lost-focus-item", self.items[self.focus_index])
257 self.focus_index += 1
258 self.emit("motion-notify-item", self.items[self.focus_index], 0, 0)
259
261 '''
262 Scroll page up of iconview.
263 '''
264 if len(self.items) > 0:
265 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment()
266 if self.focus_index == None:
267 self.focus_index = 0
268 self.emit("motion-notify-item", self.items[self.focus_index], 0, 0)
269
270
271 vadjust.set_value(vadjust.get_lower())
272 else:
273 item_width, item_height = self.items[0].get_width(), self.items[0].get_height()
274 scrolled_window = get_match_parent(self, ["ScrolledWindow"])
275 columns = int((scrolled_window.allocation.width - self.padding_x * 2) / item_width)
276 column = int(self.focus_index % columns)
277 if (vadjust.get_value() - vadjust.get_lower()) % item_height == 0:
278 row = int((vadjust.get_value() - vadjust.get_lower() - self.padding_y) / item_height) - 1
279 else:
280 row = int((vadjust.get_value() - vadjust.get_lower() - self.padding_y) / item_height)
281 if row * columns + column >= 0:
282 self.emit("lost-focus-item", self.items[self.focus_index])
283 self.focus_index = row * columns + column
284 self.emit("motion-notify-item", self.items[self.focus_index], 0, 0)
285 else:
286 self.emit("lost-focus-item", self.items[self.focus_index])
287 self.focus_index = column
288 self.emit("motion-notify-item", self.items[self.focus_index], 0, 0)
289
290 vadjust.set_value(max(0, vadjust.get_value() - vadjust.get_page_size() + self.padding_y))
291
293 '''
294 Scroll page down of iconview.
295 '''
296 if len(self.items):
297 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment()
298 if self.focus_index == None:
299 self.focus_index = len(self.items) - 1
300 self.emit("motion-notify-item", self.items[self.focus_index], 0, 0)
301
302
303 vadjust.set_value(vadjust.get_upper() - vadjust.get_page_size())
304 else:
305 item_width, item_height = self.items[0].get_width(), self.items[0].get_height()
306 scrolled_window = get_match_parent(self, ["ScrolledWindow"])
307 columns = int((scrolled_window.allocation.width - self.padding_x * 2) / item_width)
308 column = int(self.focus_index % columns)
309 if (vadjust.get_value() - vadjust.get_lower() + vadjust.get_page_size()) % item_height == 0:
310 row = int((vadjust.get_value() - vadjust.get_lower() + vadjust.get_page_size() - self.padding_y) / item_height) - 1
311 else:
312 row = int((vadjust.get_value() - vadjust.get_lower() + vadjust.get_page_size() - self.padding_y) / item_height)
313 if row * columns + column <= len(self.items) - 1:
314 self.emit("lost-focus-item", self.items[self.focus_index])
315 self.focus_index = row * columns + column
316 self.emit("motion-notify-item", self.items[self.focus_index], 0, 0)
317 else:
318 self.emit("lost-focus-item", self.items[self.focus_index])
319 self.focus_index = (row - 1) * columns + column
320 self.emit("motion-notify-item", self.items[self.focus_index], 0, 0)
321
322 vadjust.set_value(min(vadjust.get_upper() - vadjust.get_page_size(),
323 vadjust.get_value() + vadjust.get_page_size() - self.padding_y))
324
325 - def add_items(self, items, insert_pos=None):
326 '''
327 Add items to iconview.
328
329 @param items: A list of item that follow the rule of IconItem.
330 @param insert_pos: Insert position, default is None to insert new item at B{end} position.
331 '''
332 if insert_pos == None:
333 self.items += items
334 else:
335 self.items = self.items[0:insert_pos] + items + self.items[insert_pos::]
336
337 for item in items:
338 item.connect("redraw_request", self.redraw_item)
339
340 self.queue_draw()
341
343 '''
344 Delete items.
345
346 @param items: Items need to remove.
347 '''
348 match_item = False
349 for item in items:
350 if item in self.items:
351 self.items.remove(item)
352 match_item = True
353
354 if match_item:
355 self.queue_draw()
356
358 '''
359 Clear all items.
360 '''
361 self.items = []
362 self.queue_draw()
363
365 '''
366 Draw mask interface.
367
368 @param cr: Cairo context.
369 @param x: X coordiante of draw area.
370 @param y: Y coordiante of draw area.
371 @param w: Width of draw area.
372 @param h: Height of draw area.
373 '''
374 draw_vlinear(cr, x, y, w, h,
375 ui_theme.get_shadow_color("linear_background").get_color_info())
376
378 '''
379 Internal callback for `expose-event` signal.
380 '''
381
382 self.update_vadjustment()
383
384
385 cr = widget.window.cairo_create()
386 rect = widget.allocation
387
388
389 (offset_x, offset_y, viewport) = self.get_offset_coordinate(widget)
390
391
392 with cairo_state(cr):
393 scrolled_window = get_match_parent(self, ["ScrolledWindow"])
394 cr.translate(-scrolled_window.allocation.x, -scrolled_window.allocation.y)
395 cr.rectangle(offset_x, offset_y,
396 scrolled_window.allocation.x + scrolled_window.allocation.width,
397 scrolled_window.allocation.y + scrolled_window.allocation.height)
398 cr.clip()
399
400 (shadow_x, shadow_y) = get_window_shadow_size(self.get_toplevel())
401 skin_config.render_background(cr, self, offset_x + shadow_x, offset_y + shadow_y)
402
403
404 self.draw_mask(cr, offset_x, offset_y, viewport.allocation.width, viewport.allocation.height)
405
406
407 if len(self.items) > 0:
408 with cairo_state(cr):
409
410 cr.rectangle(offset_x, offset_y,
411 viewport.allocation.width,
412 viewport.allocation.height)
413 cr.clip()
414
415
416 item_width, item_height = self.items[0].get_width(), self.items[0].get_height()
417 scrolled_window = get_match_parent(self, ["ScrolledWindow"])
418 columns = int((scrolled_window.allocation.width - self.padding_x * 2) / item_width)
419
420
421 start_y = offset_y - self.padding_y
422 start_row = max(int(start_y / item_height), 0)
423 start_index = start_row * columns
424
425 end_y = offset_y - self.padding_y + viewport.allocation.height
426 if end_y % item_height == 0:
427 end_row = end_y / item_height - 1
428 else:
429 end_row = end_y / item_height
430 end_index = min((end_row + 1) * columns, len(self.items))
431
432 for (index, item) in enumerate(self.items[start_index:end_index]):
433 row = int((start_index + index) / columns)
434 column = (start_index + index) % columns
435 render_x = rect.x + self.padding_x + column * item_width
436 render_y = rect.y + self.padding_y + row * item_height
437
438 with cairo_state(cr):
439
440 cr.rectangle(render_x, render_y, item_width, item_height)
441 cr.clip()
442
443 item.render(cr, gtk.gdk.Rectangle(render_x, render_y, item_width, item_height))
444
446 '''
447 Clear item's focus status.
448 '''
449 if self.focus_index != None:
450 if 0 <= self.focus_index < len(self.items):
451 self.emit("lost-focus-item", self.items[self.focus_index])
452 self.focus_index = None
453
455 '''
456 Internal callback for `motion-notify-event` signal.
457 '''
458 if len(self.items) > 0:
459 index_info = self.icon_view_get_event_index(event)
460 if index_info == None:
461 self.clear_focus_item()
462 else:
463 (row_index, column_index, item_index, offset_x, offset_y) = index_info
464
465
466 if self.focus_index != item_index:
467 self.clear_focus_item()
468
469 self.focus_index = item_index
470
471 self.emit("motion-notify-item", self.items[self.focus_index], offset_x - self.padding_x, offset_y - self.padding_y)
472
474 '''
475 Internal function to get item index at event coordinate..
476 '''
477 if len(self.items) > 0:
478 (event_x, event_y) = get_event_coords(event)
479 item_width, item_height = self.items[0].get_width(), self.items[0].get_height()
480 scrolled_window = get_match_parent(self, ["ScrolledWindow"])
481 columns = int((scrolled_window.allocation.width - self.padding_x * 2) / item_width)
482 if len(self.items) % columns == 0:
483 rows = int(len(self.items) / columns)
484 else:
485 rows = int(len(self.items) / columns) + 1
486
487 if event_x > columns * item_width:
488 return None
489 elif event_y > rows * item_height:
490 return None
491 else:
492
493 if event_x % item_width == 0:
494 column_index = max(event_x / item_width - 1, 0)
495 else:
496 column_index = min(event_x / item_width, columns - 1)
497
498 if event_y % item_height == 0:
499 row_index = max(event_y / item_height - 1, 0)
500 else:
501 row_index = min(event_y / item_height, rows - 1)
502
503 item_index = row_index * columns + column_index
504 if item_index > len(self.items) - 1:
505 return None
506 else:
507 return (row_index, column_index, item_index,
508 event_x - column_index * item_width,
509 event_y - row_index * item_height)
510
541
543 '''
544 Set highlight status with given item.
545
546 @param item: Item need highlight.
547 '''
548 self.highlight_item = item
549 self.emit("highlight-item", self.highlight_item)
550
552 '''
553 Clear all highlight status.
554 '''
555 if self.highlight_item != None:
556 self.emit("normal-item", self.highlight_item)
557 self.highlight_item = None
558
577
588
590 '''
591 Internal callback for `key-press-event` signal.
592 '''
593 key_name = get_keyevent_name(event)
594 if self.keymap.has_key(key_name):
595 self.keymap[key_name]()
596
597 return True
598
600 '''
601 Internal callback for `key-release-event` signal.
602 '''
603 pass
604
606 '''
607 Internal function to update redraw request list.
608 '''
609
610 if len(self.redraw_request_list) > 0:
611
612 (offset_x, offset_y, viewport) = self.get_offset_coordinate(self)
613
614
615 item_width, item_height = self.items[0].get_width(), self.items[0].get_height()
616 scrolled_window = get_match_parent(self, ["ScrolledWindow"])
617 columns = int((scrolled_window.allocation.width - self.padding_x * 2) / item_width)
618
619 start_y = offset_y - self.padding_y
620 start_row = max(int(start_y / item_height), 0)
621 start_index = start_row * columns
622
623 end_y = offset_y - self.padding_y + viewport.allocation.height
624 if end_y % item_height == 0:
625 end_row = end_y / item_height - 1
626 else:
627 end_row = end_y / item_height
628 end_index = min((end_row + 1) * columns, len(self.items))
629
630
631 for item in self.redraw_request_list:
632 if item in self.items[start_index:end_index]:
633 self.queue_draw()
634 break
635
636
637 self.redraw_request_list = []
638
639 return True
640
642 '''
643 Internal function to redraw item.
644 '''
645 self.redraw_request_list.append(list_item)
646
648 '''
649 Internal function to get offset coordinate.
650 '''
651
652 rect = widget.allocation
653
654
655 viewport = get_match_parent(widget, "Viewport")
656 if viewport:
657 coordinate = widget.translate_coordinates(viewport, rect.x, rect.y)
658 if len(coordinate) == 2:
659 (offset_x, offset_y) = coordinate
660 return (-offset_x, -offset_y, viewport)
661 else:
662 return (0, 0, viewport)
663 else:
664 return (0, 0, viewport)
665
692
693 gobject.type_register(IconView)
694
696 '''
697 Icon item.
698 '''
699
700 __gsignals__ = {
701 "redraw-request" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
702 }
703
705 '''
706 Initialize ItemIcon class.
707
708 @param pixbuf: Icon pixbuf.
709 '''
710 gobject.GObject.__init__(self)
711 self.pixbuf = pixbuf
712 self.padding_x = 21
713 self.padding_y = 21
714 self.hover_flag = False
715 self.highlight_flag = False
716
718 '''
719 Emit `redraw-request` signal.
720
721 This is IconView interface, you should implement it.
722 '''
723 self.emit("redraw-request")
724
726 '''
727 Get item width.
728
729 This is IconView interface, you should implement it.
730 '''
731 return self.pixbuf.get_width() + self.padding_x * 2
732
734 '''
735 Get item height.
736
737 This is IconView interface, you should implement it.
738 '''
739 return self.pixbuf.get_height() + self.padding_y * 2
740
742 '''
743 Render item.
744
745 This is IconView interface, you should implement it.
746 '''
747
748 border_size = 4
749
750 if self.hover_flag:
751 cr.set_source_rgb(1, 0, 0)
752 elif self.highlight_flag:
753 cr.set_source_rgb(0, 1, 0)
754 else:
755 cr.set_source_rgb(1, 1, 1)
756 cr.rectangle(
757 rect.x + (rect.width - self.pixbuf.get_width()) / 2 - border_size,
758 rect.y + (rect.height - self.pixbuf.get_height()) / 2 - border_size,
759 self.pixbuf.get_width() + border_size * 2,
760 self.pixbuf.get_height() + border_size * 2)
761 cr.fill()
762
763
764 draw_pixbuf(
765 cr,
766 self.pixbuf,
767 rect.x + self.padding_x,
768 rect.y + self.padding_y)
769
771 '''
772 Handle `motion-notify-event` signal.
773
774 This is IconView interface, you should implement it.
775 '''
776 self.hover_flag = True
777
778 self.emit_redraw_request()
779
781 '''
782 Lost focus.
783
784 This is IconView interface, you should implement it.
785 '''
786 self.hover_flag = False
787
788 self.emit_redraw_request()
789
791 '''
792 Highlight item.
793
794 This is IconView interface, you should implement it.
795 '''
796 self.highlight_flag = True
797
798 self.emit_redraw_request()
799
801 '''
802 Set item with normal status.
803
804 This is IconView interface, you should implement it.
805 '''
806 self.highlight_flag = False
807
808 self.emit_redraw_request()
809
817
825
827 '''
828 Handle single click event.
829
830 This is IconView interface, you should implement it.
831 '''
832 pass
833
835 '''
836 Handle double click event.
837
838 This is IconView interface, you should implement it.
839 '''
840 pass
841
842 gobject.type_register(IconItem)
843