def __init__(self, entry): if not isinstance(entry, gtk.Entry): raise TypeError("entry must be a gtk.Entry") self._constructed = False self._pixbuf = None self._pixw = 1 self._pixh = 1 self._text_area = None self._text_area_pos = (0, 0) self._icon_win = None self._entry = entry self._tooltip = Tooltip(self) self._locked = False entry.connect('enter-notify-event', self._on_entry__enter_notify_event) entry.connect('leave-notify-event', self._on_entry__leave_notify_event) entry.connect('notify::xalign', self._on_entry__notify_xalign) self._update_position()
class IconEntry(object): """ Helper object for rendering an icon in a GtkEntry """ def __init__(self, entry): if not isinstance(entry, gtk.Entry): raise TypeError("entry must be a gtk.Entry") self._constructed = False self._pixbuf = None self._pixw = 1 self._pixh = 1 self._text_area = None self._text_area_pos = (0, 0) self._icon_win = None self._entry = entry self._tooltip = Tooltip(self) self._locked = False entry.connect('enter-notify-event', self._on_entry__enter_notify_event) entry.connect('leave-notify-event', self._on_entry__leave_notify_event) entry.connect('notify::xalign', self._on_entry__notify_xalign) self._update_position() def _on_entry__notify_xalign(self, entry, pspec): self._update_position() def _on_entry__enter_notify_event(self, entry, event): icon_win = self.get_icon_window() if event.window != icon_win: return self._tooltip.display(entry) def _on_entry__leave_notify_event(self, entry, event): if event.window != self.get_icon_window(): return self._tooltip.hide() def set_tooltip(self, text): self._tooltip.set_text(text) def get_icon_window(self): return self._icon_win def set_pixbuf(self, pixbuf): """ Sets a pixbuf or None to reset. @param pixbuf: a gdk.Pixbuf or None """ entry = self._entry if not isinstance(entry.get_toplevel(), gtk.Window): # For widgets in SlaveViews, wait until they're attached # to something visible, then set the pixbuf entry.connect_object('realize', self.set_pixbuf, pixbuf) return if pixbuf: if not isinstance(pixbuf, gdk.Pixbuf): raise TypeError("pixbuf must be a GdkPixbuf") else: # Turning of the icon should also restore the background entry.modify_base(gtk.STATE_NORMAL, None) if not self._pixbuf: return self._pixbuf = pixbuf if pixbuf: self._pixw = pixbuf.get_width() self._pixh = pixbuf.get_height() else: self._pixw = self._pixh = 0 win = self._icon_win if not win: self.construct() win = self._icon_win self.resize_windows() # XXX: Why? if win: if not pixbuf: win.hide() else: win.show() self._recompute() entry.queue_draw() def construct(self): if self._constructed: return entry = self._entry if not entry.flags() & gtk.REALIZED: entry.realize() # Hack: Save a reference to the text area, now when its created self._text_area = entry.window.get_children()[0] self._text_area_pos = self._text_area.get_position() # PyGTK should allow default values for most of the values here. win = gtk.gdk.Window(entry.window, self._pixw, self._pixh, gtk.gdk.WINDOW_CHILD, (gtk.gdk.ENTER_NOTIFY_MASK | gtk.gdk.LEAVE_NOTIFY_MASK), gtk.gdk.INPUT_OUTPUT, 'icon window', 0, 0, entry.get_visual(), entry.get_colormap(), gtk.gdk.Cursor(entry.get_display(), gdk.LEFT_PTR), '', '', True) self._icon_win = win win.set_user_data(entry) win.set_background(entry.style.base[entry.state]) self._constructed = True def deconstruct(self): if self._icon_win: # This is broken on PyGTK 2.6.x try: self._icon_win.set_user_data(None) except: pass # Destroy not needed, called by the GC. self._icon_win = None def update_background(self, color): if self._locked: return if not self._icon_win: return self._entry.modify_base(gtk.STATE_NORMAL, color) self.draw_pixbuf() def get_background(self): return self._entry.style.base[gtk.STATE_NORMAL] def resize_windows(self): if not self._pixbuf: return icony = iconx = 4 # Make space for the icon, both windows winw = self._entry.window.get_size()[0] textw, texth = self._text_area.get_size() textw = winw - self._pixw - (iconx + icony) if self._pos == gtk.POS_LEFT: textx, texty = self._text_area_pos textx += iconx + self._pixw # FIXME: Why is this needed. Focus padding? # The text jumps without this textw -= 2 self._text_area.move_resize(textx, texty, textw, texth) self._recompute() elif self._pos == gtk.POS_RIGHT: self._text_area.resize(textw, texth) iconx += textw icon_win = self._icon_win # XXX: Why? if not icon_win: return # If the size of the window is large enough, resize and move it # Otherwise just move it to the right side of the entry if icon_win.get_size() != (self._pixw, self._pixh): icon_win.move_resize(iconx, icony, self._pixw, self._pixh) else: icon_win.move(iconx, icony) def draw_pixbuf(self): if not self._pixbuf: return win = self._icon_win # XXX: Why? if not win: return # Draw background first color = self._entry.style.base_gc[self._entry.state] win.draw_rectangle(color, True, 0, 0, self._pixw, self._pixh) # If sensitive draw the icon, regardless of the window emitting the # event since makes it a bit smoother on resize if self._entry.flags() & gtk.SENSITIVE: win.draw_pixbuf(None, self._pixbuf, 0, 0, 0, 0, self._pixw, self._pixh) def _update_position(self): if self._entry.get_property('xalign') > 0.5: self._pos = gtk.POS_LEFT else: self._pos = gtk.POS_RIGHT def _recompute(self): # Protect against re-entrancy when inserting text, happens in DateEntry if self._locked: return self._locked = True # Hack: This triggers a .recompute() which is private visibility = self._entry.get_visibility() self._entry.set_visibility(not visibility) self._entry.set_visibility(visibility) # Another option would be to call insert_text, however it # emits the signal ::changed which is not desirable. #self._entry.insert_text('') self._locked = False