1 import gtk, gobject
2
3 from traylib import *
4 from traylib.icon_config import IconConfig
5 from traylib.pixbuf_helper import *
6
7 _targets = [("text/uri-list", 0, TARGET_URI_LIST),
8 ("text/x-moz-url", 0, TARGET_MOZ_URL)]
9
10
11 MAX_SIZE = 128
12 """
13 A pixbuf larger than this size (either in height or width) will be scaled
14 down in order to speed up later scaling.
15 """
16
17
18
19
20 ZOOM_ACTION_NONE = 0
21 ZOOM_ACTION_SHOW = 1
22 ZOOM_ACTION_HIDE = 2
23 ZOOM_ACTION_DESTROY = 3
24
25
26 -class Icon(gtk.EventBox, object):
27
29 """
30 Creates a new C{Icon}.
31
32 @param config: The L{IconConfig} controlling the configuration of this
33 C{Icon}.
34 """
35 assert isinstance(config, IconConfig)
36
37 object.__init__(self)
38 gtk.EventBox.__init__(self)
39 self.add_events(gtk.gdk.POINTER_MOTION_MASK)
40
41 self.__config = config
42 config.add_configurable(self)
43
44
45 self.__image = gtk.Image()
46 self.__image.show()
47 self.add(self.__image)
48 self.__canvas = None
49 self.__pixbuf = None
50 self.__pixbuf_current = None
51
52
53 self.__blink_event = 0
54
55
56 self.__menu = None
57 self.__mouse_over = False
58
59
60 self.__target_size = config.size
61 self.__current_size = 1
62 self.__zoom_factor = 1.0
63 self.__zoom_action = ZOOM_ACTION_NONE
64 self.__zoom_event = 0
65
66
67 self.__has_arrow = False
68 self.__arrow_target_alpha = 0
69 self.__arrow_current_alpha = 0
70
71
72 self.__emblem_orig = None
73 self.__emblem_scaled = None
74 self.__emblem_target_alpha = 0
75 self.__emblem_current_alpha = 0
76
77 self.__update_max_size()
78 self.__update_size_request()
79
80
81 self.__tooltip = ''
82
83 self.connect("enter-notify-event", self.__enter_notify_event)
84 self.connect("motion-notify-event", self.__motion)
85 self.connect("leave-notify-event", self.__leave_notify_event)
86 self.connect("button-press-event", self.__button_press_event)
87 self.connect("button-release-event", self.__button_release_event)
88 self.connect("scroll-event", self.__scroll_event)
89
90
91
92 self.__is_drop_target = False
93 self.drag_dest_set(gtk.DEST_DEFAULT_HIGHLIGHT, _targets,
94 gtk.gdk.ACTION_DEFAULT)
95 self.connect("drag-motion", self.__drag_motion)
96 self.connect("drag-leave", self.__drag_leave)
97 self.connect("drag-data-received", self.__drag_data_received)
98 self.connect("drag-drop", self.__drag_drop)
99 self.__spring_open_event = 0
100
101
102 self.drag_source_set(gtk.gdk.BUTTON1_MASK, [], 0)
103 self.connect("drag-begin", self.__drag_begin)
104 self.connect("drag-end", self.__drag_end)
105 self.__is_dragged = False
106
107 self.connect("destroy", self.__destroy)
108
109 assert self.__config == config
110 assert self.__config.has_configurable(self)
111 assert self.__image
112 assert self.__pixbuf == None
113 assert self.__pixbuf_current == None
114 assert self.__zoom_factor == 1.0
115 assert self.__canvas == None
116 assert self.__has_arrow == False
117 assert self.__menu == None
118 assert self.__mouse_over == False
119 assert self.__blink_event == 0
120 assert self.__target_size == self.__config.size
121 assert self.__current_size == 1
122 assert self.__zoom_event == 0
123 assert self.__zoom_action == ZOOM_ACTION_NONE
124 assert self.__arrow_target_alpha == 0
125 assert self.__arrow_current_alpha == 0
126 assert self.__emblem_orig == None
127 assert self.__emblem_scaled == None
128 assert self.__emblem_target_alpha == 0
129 assert self.__emblem_current_alpha == 0
130 assert self.__spring_open_event == 0
131 assert self.__is_drop_target == False
132 assert self.__is_dragged == False
133
135 """
136 Makes the C{Icon} blink or stops it from blinking.
137
138 @param blinking: If True, makes the C{Icon} blink, if False stops it
139 from blinking.
140 @param time: The time between two blink states (in ms).
141 """
142 blink_state = gtk.STATE_NORMAL
143 def blink():
144 running = (self.__blink_event != 0)
145 if not running or blink_state == gtk.STATE_SELECTED:
146 blink_state = gtk.STATE_NORMAL
147 else:
148 blink_state = gtk.STATE_SELECTED
149 self.set_state(blink_state)
150 return running
151 if blinking:
152 self.__blink_event = gobject.timeout_add(time, blink)
153 else:
154 self.__blink_event = 0
155
157 """Updates the emblem by calling L{make_emblem()}"""
158 old_emblem = self.__emblem_orig
159 self.__emblem_orig = self.make_emblem()
160 assert (self.__emblem_orig == None
161 or isinstance(self.__emblem_orig, gtk.gdk.Pixbuf))
162 if self.__emblem_orig:
163 self.__emblem_scaled = scale_pixbuf_to_size(self.__emblem_orig,
164 self.__max_size/3,
165 scale_up = False)
166
167 self.__update_emblem_target_alpha()
168 self._refresh(self.__emblem_orig != old_emblem)
169
170 assert (self.__emblem_orig == self.__emblem_scaled == None
171 or self.__emblem_orig and self.__emblem_scaled)
172
174 """Updates the icon by calling L{make_icon()}"""
175 old_pixbuf = self.__pixbuf
176 self.__pixbuf = self.make_icon()
177 assert (self.__pixbuf == None
178 or isinstance(self.__pixbuf, gtk.gdk.Pixbuf))
179 if (self.__pixbuf and (self.__pixbuf.get_width() >= MAX_SIZE
180 or self.__pixbuf.get_height() >= MAX_SIZE)):
181 self.__pixbuf = scale_pixbuf_to_size(self.__pixbuf, MAX_SIZE, False)
182 if old_pixbuf != self.__pixbuf:
183 self.__pixbuf_current = None
184 self._refresh(self.__pixbuf != old_pixbuf)
185
187 """Updates whether URIs can be dropped on the C{Icon}."""
188 self.__is_drop_target = self.make_is_drop_target()
189
191 """Updates the arrow by calling L{make_has_arrow()}"""
192 old_has_arrow = self.__has_arrow
193 self.__has_arrow = self.make_has_arrow()
194 self.__update_arrow_target_alpha()
195 self._refresh()
196
203
205 """Updates the visibility by calling L{make_visibility()}"""
206 if not self.__config.hidden and self.make_visibility():
207 self.show()
208 else:
209 self.hide()
210
212 """Updates the zoom factor by calling L{make_zoom_factor()}."""
213 old_zoom_factor = self.__zoom_factor
214 self.__zoom_factor = max(0.0, min(self.make_zoom_factor(), 1.5))
215 assert isinstance(self.__zoom_factor, float)
216 if old_zoom_factor != self.__zoom_factor:
217 self._refresh()
218
220 if self.__zoom_action in (ZOOM_ACTION_HIDE, ZOOM_ACTION_DESTROY):
221 return
222 if self.__has_arrow:
223 self.__arrow_target_alpha = 255
224 else:
225 self.__arrow_target_alpha = 0
226
228 if (self.__zoom_action == ZOOM_ACTION_NONE
229 or self.__emblem_scaled and self.__emblem_current_alpha > 0):
230 width = self.__max_size
231 height = self.__max_size
232 else:
233 if self.__config.edge in (0, TOP, BOTTOM):
234 width = min(int(self.__current_size * 1.5), self.__max_size)
235 height = self.__max_size
236 else:
237 width = self.__max_size
238 height = min(int(self.__current_size * 1.5), self.__max_size)
239 if (self.__canvas
240 and self.__canvas.get_width() == width
241 and self.__canvas.get_height() == height):
242 return
243 self.__canvas = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB,
244 True,
245 8,
246 width,
247 height)
248
250 if self.__zoom_action in (ZOOM_ACTION_HIDE, ZOOM_ACTION_DESTROY):
251 return
252 if self.__emblem_orig:
253 self.__emblem_target_alpha = 196
254 else:
255 self.__emblem_target_alpha = 0
256
258 self.__max_size = int(self.__config.size*1.5)
259
261 if event:
262 px = event.x
263 py = event.y
264 else:
265 px, py = self.get_pointer()
266 i, i, w, h, i = self.window.get_geometry()
267 self.__mouse_over = (py >= 0 and py < h and px >= 0 and px < w)
268 self.update_zoom_factor()
269
271 if self.__zoom_action != ZOOM_ACTION_NONE:
272 return
273 if self.__config.vertical:
274 self.set_size_request(-1, self.__max_size)
275 else:
276 self.set_size_request(self.__max_size, -1)
277
279 """
280 Refreshes the C{Icon}.
281
282 @param force: If True, forces refresh even if the icon has the right
283 size.
284 """
285 if not self.__pixbuf:
286 return
287 if not int(self.get_property('visible')):
288 return
289
290 effects = self.__config.effects
291
292 if self.__zoom_action not in (ZOOM_ACTION_HIDE, ZOOM_ACTION_DESTROY):
293 self.__target_size = max(1, min(int(self.__config.size
294 * self.__zoom_factor),
295 self.__max_size - 2))
296 if (not force
297 and self.__current_size
298 == self.__target_size
299 and self.__arrow_current_alpha
300 == self.__arrow_target_alpha
301 and self.__emblem_current_alpha
302 == self.__emblem_target_alpha):
303 return
304
305 if self.__zoom_event != 0:
306 return
307
308 if effects:
309 if self.__refresh():
310 self.__zoom_event = gobject.timeout_add(6, self.__refresh)
311 else:
312 self.__arrow_current_alpha = self.__arrow_target_alpha
313 self.__emblem_current_alpha = self.__emblem_target_alpha
314 self.__current_size = self.__target_size
315 self.__pixbuf_current = None
316 while self.__refresh():
317 pass
318
320 if not self.__pixbuf:
321 return False
322
323 edge = self.__config.edge
324
325 if (not self.__pixbuf_current
326 or self.__current_size != self.__target_size):
327 self.__pixbuf_current = scale_pixbuf_to_size(self.__pixbuf,
328 self.__current_size)
329 self.__update_canvas()
330 self.__canvas.fill(0x000000)
331 canvas_width = self.__canvas.get_width()
332 canvas_height = self.__canvas.get_height()
333 width = self.__pixbuf_current.get_width()
334 height = self.__pixbuf_current.get_height()
335 x = int(round(float(canvas_width)/2.0)
336 - round(float(width)/2.0))
337 y = int(round(float(canvas_height)/2.0)
338 - round(float(height)/2.0))
339 self.__pixbuf_current.composite(self.__canvas,
340 x, y,
341 width, height,
342 x, y,
343 1.0, 1.0,
344 gtk.gdk.INTERP_TILES,
345 255)
346 if self.__emblem_current_alpha > 0:
347 width = self.__max_size/3
348 height = width
349 self.__emblem_scaled.composite(self.__canvas, 0, 0,
350 width, height,
351 0, 0,
352 1.0, 1.0, gtk.gdk.INTERP_TILES,
353 self.__emblem_current_alpha)
354 if self.__arrow_current_alpha > 0:
355 arrow = self.__config.arrow
356 width = arrow.get_width()
357 height = arrow.get_height()
358 x = 0
359 y = 0
360 if edge in (0, TOP, BOTTOM):
361 x = canvas_width/2 - width/2
362 if edge == TOP:
363 y = canvas_height - height
364 if edge in (LEFT, RIGHT):
365 y = canvas_height/2 - height/2
366 if edge == LEFT:
367 x = canvas_width - width
368
369 arrow.composite(self.__canvas, x, y,
370 width, height,
371 x, y,
372 1.0, 1.0,
373 gtk.gdk.INTERP_TILES,
374 self.__arrow_current_alpha)
375 self.__image.set_from_pixbuf(self.__canvas)
376
377 if (self.__current_size == self.__target_size
378 and self.__arrow_current_alpha == self.__arrow_target_alpha
379 and self.__emblem_current_alpha == self.__emblem_target_alpha):
380 if self.__zoom_action == ZOOM_ACTION_HIDE:
381 if (self.__arrow_current_alpha > 0
382 or self.__emblem_current_alpha > 0):
383 self.__arrow_target_alpha = 0
384 self.__emblem_target_alpha = 0
385 return True
386 else:
387 if self.__current_size > 1:
388 self.__target_size = 1
389 return True
390 else:
391 gtk.EventBox.hide(self)
392 if self.__zoom_action == ZOOM_ACTION_DESTROY:
393 if (self.__arrow_current_alpha > 0
394 or self.__emblem_current_alpha > 0):
395 self.__arrow_target_alpha = 0
396 self.__emblem_target_alpha = 0
397 return True
398 else:
399 if self.__current_size > 1:
400 self.__target_size = 1
401 return True
402 else:
403 gtk.EventBox.destroy(self)
404 zoom_action = self.__zoom_action
405 self.__zoom_action = ZOOM_ACTION_NONE
406 if zoom_action == ZOOM_ACTION_SHOW:
407 self.__update_size_request()
408 self.__zoom_event = 0
409 return False
410
411 if self.__current_size > self.__target_size:
412 self.__current_size -= 1
413 elif self.__current_size < self.__target_size:
414 self.__current_size += 1
415 if self.__arrow_current_alpha > self.__arrow_target_alpha:
416 self.__arrow_current_alpha = max(self.__arrow_target_alpha,
417 self.__arrow_current_alpha - 5)
418 elif self.__arrow_current_alpha < self.__arrow_target_alpha:
419 self.__arrow_current_alpha = min(self.__arrow_target_alpha,
420 self.__arrow_current_alpha + 5)
421 if self.__emblem_current_alpha > self.__emblem_target_alpha:
422 self.__emblem_current_alpha = max(self.__emblem_target_alpha,
423 self.__emblem_current_alpha - 5)
424 elif self.__emblem_current_alpha < self.__emblem_target_alpha:
425 self.__emblem_current_alpha = min(self.__emblem_target_alpha,
426 self.__emblem_current_alpha + 5)
427 return True
428
429
430
431
433 """Zooms out the C{Icon} before destroying it."""
434 if not int(self.get_property('visible')):
435 gtk.EventBox.destroy(self)
436 return
437 self.set_size_request(-1, -1)
438 self.__zoom_action = ZOOM_ACTION_DESTROY
439 self._refresh()
440
442 """Zooms out the C{Icon} before hiding it."""
443 if not int(self.get_property('visible')):
444 return
445 self.set_size_request(-1, -1)
446 self.__zoom_action = ZOOM_ACTION_HIDE
447 self._refresh()
448
450 """Zooms in the C{Icon} after showing it."""
451 self.__zoom_action = ZOOM_ACTION_SHOW
452 self.__update_arrow_target_alpha()
453 self.__update_emblem_target_alpha()
454 if not int(self.get_property('visible')):
455 self.set_size_request(-1, -1)
456 self.__current_size = 1
457 self.__arrow_current_alpha = 0
458 self.__emblem_current_alpha = 0
459 gtk.EventBox.show(self)
460 self._refresh()
461
462
463
464
466 """Updates the edge the C{Icon} is at."""
467 self._refresh(True)
468
470 """Updates the effects of the C{Icon}."""
471 self._refresh(True)
472
474 """Updates the C{Icon}'s size."""
475 self.__update_max_size()
476 self.__update_size_request()
477 self.update_icon()
478 self.update_emblem()
479
482
483
484
485
489
491 if data.data == None:
492 context.drop_finish(False, time)
493 return
494 if self == context.get_source_widget():
495 return
496 uri_list = []
497 if info == TARGET_MOZ_URL:
498 uri_list = [data.data.decode('utf-16').encode('utf-8').split('\n')[0]]
499 elif info == TARGET_URI_LIST:
500 uri_list = data.get_uris()
501 self.uris_dropped(uri_list, context.action)
502 context.drop_finish(True, time)
503
504 - def __drag_drop(self, widget, context, data, info, time):
505 """Callback for the 'drag-drop' signal."""
506 if not self.is_drop_target:
507 return False
508 target = widget.drag_dest_find_target(context, _targets)
509 widget.drag_get_data(context, target, time)
510 return True
511
513 if self.__spring_open_event == 0:
514 return
515 gobject.source_remove(self.__spring_open_event)
516 self.__spring_open_event = 0
517
519 if self.__spring_open_event == 0:
520 self.__spring_open_event = gobject.timeout_add(1000,
521 self.spring_open, time)
522 if self.is_drop_target:
523 action = context.suggested_action
524 else:
525 action = 0
526 context.drag_status(action, time)
527 return True
528
530 assert widget == self
531 self.__is_dragged = True
532 context.set_icon_pixbuf(self.icon, 0,0)
533
538
554
556 assert button in (1,3)
557 if button == 1:
558 menu = self.get_menu_left()
559 elif button == 3:
560 menu = self.get_menu_right()
561 if menu:
562 def menu_deactivate(menu):
563 self.__menu = None
564 self.__update_mouse_over()
565 menu.connect("deactivate", menu_deactivate)
566 menu.show_all()
567 menu.popup(None, None, self.__config.pos_func,
568 button,
569 time)
570 self.__menu = menu
571 self.update_zoom_factor()
572
584
586 if event.mode != gtk.gdk.CROSSING_NORMAL:
587 return False
588 self.__update_mouse_over(event)
589 return False
590
592 self.__update_mouse_over(event)
593 return False
594
596 self.__update_mouse_over(event)
597 return False
598
607
608
609
610
612 """
613 Override this to determine the menu that pops up when right-clicking the
614 C{Icon}.
615 @return: The menu that pops up when right-clicking the C{Icon}.
616 """
617 return None
618
620 """
621 Override this to determine the menu that pops up when left-clicking the
622 C{Icon}. (In case the C{click()} method returned C{False}.)
623 @return: The menu that pops up when left-clicking the C{Icon}.
624 """
625 return None
626
627 - def click(self, time = 0L):
628 """
629 Override this to determine the action when left-clicking the C{Icon}.
630 If an action was performed, return C{True}, else return C{False}.
631
632 @param time: The time of the click event.
633 """
634 return False
635
637 """
638 Override this to determine the action when the mouse wheel is scrolled
639 up.
640
641 @param time: The time of the scroll event.
642 """
643 return False
644
646 """
647 Override this to determine the action when the mouse wheel is scrolled
648 down.
649
650 @param time: The time of the scroll event.
651 """
652 return False
653
654 - def uris_dropped(self, uris, action = gtk.gdk.ACTION_COPY):
655 """
656 Override this to react to URIs being dropped on the C{Icon}.
657
658 @param uris: A list of URIs.
659 @param action: One of C{gtk.gdk.ACTION_COPY}, C{gtk.gdk.ACTION_MOVE} or
660 C{gtk.gdk.ACTION_LINK}.
661 """
662 pass
663
665 """
666 Override this to determine the action when the mouse pointer stays on an
667 icon some time while dragging.
668
669 @return: C{True} if C{spring_open()} should be called again in a second.
670 """
671 return False
672
674 """
675 Override this to determine the emblem to be shown in the upper left
676 corner.
677
678 @return: The new emblem.
679 """
680 return None
681
683 """
684 Override this to determine whether the C{Icon} has an arrow or not.
685
686 @return: C{True} if the C{Icon} should have an arrow.
687 """
688 return False
689
691 """
692 Override this to determine the C{gtk.gdk.Pixbuf} the C{Icon} should have.
693
694 @return: The new pixbuf.
695 """
696 return None
697
699 """
700 Override this to determine whether URIs can be dropped on the C{Icon} or
701 not.
702
703 @return: C{True} if URIs may be dropped on the C{Icon}.
704 """
705 return False
706
714
716 """
717 Override this to determine the visibility.
718
719 @return: C{True} if the C{Icon} should be visible.
720 """
721 return True
722
724 """
725 Extend this to determine the zoom factor.
726
727 @return: The new zoom factor.
728 """
729 if self.__menu:
730 return 1.5
731 if self.__config.effects and self.__mouse_over:
732 px, py = self.get_pointer()
733 hsize = float(self.__max_size)/2.0
734 fract_x = (hsize - (1.0/hsize)*(px-hsize)**2) / hsize
735 fract_y = (hsize - (1.0/hsize)*(py-hsize)**2) / hsize
736 edge = self.__config.edge
737 if edge == TOP and py < hsize or edge == BOTTOM and py > hsize:
738 fract = fract_x
739 elif edge == LEFT and px < hsize or edge == RIGHT and px > hsize:
740 fract = fract_y
741 else:
742 fract = fract_x * fract_y
743 fract = max(0.0, fract)
744 return 1.0 + fract/2.0
745 return 1.0
746
747 icon_config = property(lambda self : self.__config)
748 """
749 The C{Icon}'s configuration.
750 """
751
752 icon = property(lambda self : self.__pixbuf)
753 """
754 The pixbuf of the C{Icon}.
755 """
756
757 size = property(lambda self : self.__config.size)
758 """
759 The size of the C{Icon}.
760 """
761
762 tooltip = property(lambda self : self.__tooltip)
763 """
764 The tooltip of the C{Icon}.
765 """
766
767 has_arrow = property(lambda self : self.__has_arrow)
768 """
769 True if the C{Icon} has an arrow.
770 """
771
772 is_drop_target = property(lambda self : self.__is_drop_target)
773 """
774 C{True} if uris can be dropped on the C{Icon}.
775 """
776