class _ComboEntryPopup(PopupWindow): FRAME_PADDING = (0, 0, 0, 0) gsignal('text-selected', str) def __init__(self, comboentry): self._comboentry = comboentry super(_ComboEntryPopup, self).__init__(comboentry) # Number of visible rows in the popup window, sensible # default value from other toolkits self._visible_rows = 10 self._initial_text = None self._filter_model = None def get_main_widget(self): vbox = gtk.VBox() vbox.show() self._sw = gtk.ScrolledWindow() self._sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER) vbox.pack_start(self._sw) self._sw.show() self._model = gtk.ListStore(str) self._treeview = gtk.TreeView(self._model) self._treeview.set_enable_search(False) self._treeview.connect('motion-notify-event', self._on_treeview__motion_notify_event) self._treeview.connect('button-release-event', self._on_treeview__button_release_event) self._treeview.add_events(gdk.BUTTON_PRESS_MASK) self._selection = self._treeview.get_selection() self._selection.set_mode(gtk.SELECTION_BROWSE) self._renderer = ComboDetailsCellRenderer() self._treeview.append_column( gtk.TreeViewColumn('Foo', self._renderer, label=0, data=1)) self._treeview.set_headers_visible(False) self._sw.add(self._treeview) self._treeview.show() self._label = gtk.Label() return vbox def get_widget_for_popup(self): return self._comboentry.entry def popup(self, text=None, filter=False): """ Shows the list of options. And optionally selects an item :param text: text to select :param filter: filter the list of options. A filter_model must be set using :class:`set_model`() """ self.GRAB_WINDOW = not filter self.GRAB_ADD = not filter treeview = self._treeview if filter and self._filter_model: model = self._filter_model else: model = self._model if not len(model): return treeview.set_model(model) treeview.set_hover_expand(True) selection = treeview.get_selection() selection.unselect_all() if text: for row in model: if text in row: selection.select_iter(row.iter) treeview.scroll_to_cell(row.path, use_align=True, row_align=0.5) treeview.set_cursor(row.path) break popped = super(_ComboEntryPopup, self).popup() if not popped: return False treeview.set_size_request(-1, -1) if not filter: # Grab window self.grab_focus() if not self._treeview.has_focus(): self._treeview.grab_focus() return True def set_label_text(self, text): if text is None: text = '' self._label.hide() else: self._label.show() self._label.set_text(text) def set_model(self, model): if isinstance(model, gtk.TreeModelFilter): self._filter_model = model model = model.get_model() self._treeview.set_model(model) self._model = model def confirm(self): model, treeiter = self._selection.get_selected() if treeiter: self.emit('text-selected', model[treeiter][0]) def handle_key_press_event(self, event): if event.keyval == keysyms.Tab: self.popdown() # XXX: private member of comboentry self._comboentry._button.grab_focus() return True return False # Callbacks def _on_treeview__motion_notify_event(self, treeview, event): retval = treeview.get_path_at_pos(int(event.x), int(event.y)) if not retval: return path, column, x, y = retval self._selection.select_path(path) self._treeview.set_cursor(path) def _on_treeview__button_release_event(self, treeview, event): retval = treeview.get_path_at_pos(int(event.x), int(event.y)) if not retval: return path, column, x, y = retval model = treeview.get_model() self.emit('text-selected', model[path][0]) def get_size(self, allocation, monitor): treeview = self._treeview treeview.realize() rows = len(self._treeview.get_model()) if rows > self._visible_rows: rows = self._visible_rows self._sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS) else: self._sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER) cell_height = treeview.get_cell_area(0, treeview.get_column(0)).height height = cell_height * rows # Use half of the available screen space max_height = monitor.height / 2 if height > max_height: height = int(max_height) elif height < 0: height = 0 return allocation.width, height def get_selected_iter(self): model, treeiter = self._selection.get_selected() # if the model currently being used is a TreeModelFilter, convert # the iter to be a TreeModel iter (witch is what the user expects) if isinstance(model, gtk.TreeModelFilter) and treeiter: treeiter = model.convert_iter_to_child_iter(treeiter) return treeiter def set_selected_iter(self, treeiter): """ Selects an item in the comboentry given a treeiter :param treeiter: the tree iter to select """ model = self._treeview.get_model() # Since the user passed a TreeModel iter, if the model currently # being used is a TreeModelFilter, convert it to be a TreeModelFilter # iter if isinstance(model, gtk.TreeModelFilter): # See #3099 for an explanation why this is needed and a # testcase tmodel = model.get_model() if tmodel.iter_is_valid(treeiter): # revert back to the unfiltered model so we can select # the right object self._treeview.set_model(tmodel) self._selection = self._treeview.get_selection() else: treeiter = model.convert_child_iter_to_iter(treeiter) self._selection.select_iter(treeiter) def set_details_callback(self, callable): self._renderer.set_details_callback(callable)
class _ComboEntryPopup(gtk.Window): gsignal('text-selected', str) def __init__(self, comboentry): gtk.Window.__init__(self, gtk.WINDOW_POPUP) self.add_events(gdk.BUTTON_PRESS_MASK) self.connect('key-press-event', self._on__key_press_event) self.connect('button-press-event', self._on__button_press_event) self._comboentry = comboentry # Number of visible rows in the popup window, sensible # default value from other toolkits self._visible_rows = 10 self._initial_text = None self._popping_up = False self._filter_model = None frame = gtk.Frame() frame.set_shadow_type(gtk.SHADOW_ETCHED_OUT) self.add(frame) frame.show() vbox = gtk.VBox() frame.add(vbox) vbox.show() self._sw = gtk.ScrolledWindow() self._sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER) vbox.pack_start(self._sw) self._sw.show() self._model = gtk.ListStore(str) self._treeview = gtk.TreeView(self._model) self._treeview.set_enable_search(False) self._treeview.connect('motion-notify-event', self._on_treeview__motion_notify_event) self._treeview.connect('button-release-event', self._on_treeview__button_release_event) self._treeview.add_events(gdk.BUTTON_PRESS_MASK) self._selection = self._treeview.get_selection() self._selection.set_mode(gtk.SELECTION_BROWSE) self._renderer = ComboDetailsCellRenderer() self._treeview.append_column( gtk.TreeViewColumn('Foo', self._renderer, label=0, data=1)) self._treeview.set_headers_visible(False) self._sw.add(self._treeview) self._treeview.show() self._label = gtk.Label() vbox.pack_start(self._label, False, False) self.set_resizable(False) self.set_screen(comboentry.get_screen()) def popup(self, text=None, filter=False): """ Shows the list of options. And optionally selects an item @param text: text to select @param filter: filter the list of options. A filter_model must be set using L{set_model}() """ combo = self._comboentry if not (combo.flags() & gtk.REALIZED): return treeview = self._treeview if filter and self._filter_model: model = self._filter_model else: model = self._model if not len(model): return treeview.set_model(model) toplevel = combo.get_toplevel() if isinstance(toplevel, gtk.Window) and toplevel.group: toplevel.group.add_window(self) # width is meant for the popup window # height is meant for the treeview, since it calculates using # the height of the cells on the rows x, y, width, height = self._get_position() self.set_size_request(width, -1) treeview.set_size_request(-1, height) self.move(x, y) self.show() treeview.set_hover_expand(True) selection = treeview.get_selection() selection.unselect_all() if text: for row in model: if text in row: selection.select_iter(row.iter) treeview.scroll_to_cell(row.path, use_align=True, row_align=0.5) treeview.set_cursor(row.path) break self._popping_up = True if filter: # do not grab if its a completion return # Grab window self.grab_focus() if not (self._treeview.flags() & gtk.HAS_FOCUS): self._treeview.grab_focus() if not self._popup_grab_window(): self.hide() return self.grab_add() def popdown(self): combo = self._comboentry if not (combo.flags() & gtk.REALIZED): return self.grab_remove() self.hide() def set_label_text(self, text): if text is None: text = '' self._label.hide() else: self._label.show() self._label.set_text(text) def set_model(self, model): if isinstance(model, gtk.TreeModelFilter): self._filter_model = model model = model.get_model() self._treeview.set_model(model) self._model = model # Callbacks def _on__key_press_event(self, window, event): """ Mimics Combobox behavior Escape or Alt+Up: Close Enter, Return or Space: Select """ keyval = event.keyval state = event.state & gtk.accelerator_get_default_mod_mask() if (keyval == keysyms.Escape or ((keyval == keysyms.Up or keyval == keysyms.KP_Up) and state == gdk.MOD1_MASK)): self.popdown() return True elif keyval == keysyms.Tab: self.popdown() # XXX: private member of comboentry self._comboentry._button.grab_focus() return True elif (keyval == keysyms.Return or keyval == keysyms.space or keyval == keysyms.KP_Enter or keyval == keysyms.KP_Space): model, treeiter = self._selection.get_selected() self.emit('text-selected', model[treeiter][0]) return True return False def _on__button_press_event(self, window, event): # If we're clicking outside of the window # close the popup if (event.window != self.window or (tuple(self.allocation.intersect( gdk.Rectangle(x=int(event.x), y=int(event.y), width=1, height=1)))) == (0, 0, 0, 0)): self.popdown() def _on_treeview__motion_notify_event(self, treeview, event): retval = treeview.get_path_at_pos(int(event.x), int(event.y)) if not retval: return path, column, x, y = retval self._selection.select_path(path) self._treeview.set_cursor(path) def _on_treeview__button_release_event(self, treeview, event): retval = treeview.get_path_at_pos(int(event.x), int(event.y)) if not retval: return path, column, x, y = retval model = treeview.get_model() self.emit('text-selected', model[path][0]) def _popup_grab_window(self): activate_time = 0L if gdk.pointer_grab(self.window, True, (gdk.BUTTON_PRESS_MASK | gdk.BUTTON_RELEASE_MASK | gdk.POINTER_MOTION_MASK), None, None, activate_time) == 0: if gdk.keyboard_grab(self.window, True, activate_time) == 0: return True else: self.window.get_display().pointer_ungrab(activate_time); return False return False def _get_position(self): treeview = self._treeview treeview.realize() sample = self._comboentry # We need to fetch the coordinates of the entry window # since comboentry itself does not have a window x, y = sample.entry.window.get_origin() width = sample.allocation.width hpolicy = vpolicy = gtk.POLICY_NEVER self._sw.set_policy(hpolicy, vpolicy) pwidth = self.size_request()[0] if pwidth > width: self._sw.set_policy(gtk.POLICY_ALWAYS, vpolicy) pwidth, pheight = self.size_request() rows = len(self._treeview.get_model()) if rows > self._visible_rows: rows = self._visible_rows self._sw.set_policy(hpolicy, gtk.POLICY_ALWAYS) cell_height = treeview.get_cell_area(0, treeview.get_column(0)).height height = cell_height * rows screen = self._comboentry.get_screen() monitor_num = screen.get_monitor_at_window(sample.entry.window) monitor = screen.get_monitor_geometry(monitor_num) if x < monitor.x: x = monitor.x elif x + width > monitor.x + monitor.width: x = monitor.x + monitor.width - width if y + sample.entry.allocation.height + height <= monitor.y + monitor.height: y += sample.entry.allocation.height elif y - height >= monitor.y: y -= height elif (monitor.y + monitor.height - (y + sample.entry.allocation.height) > y - monitor.y): y += sample.entry.allocation.height height = monitor.y + monitor.height - y else : height = y - monitor.y y = monitor.y # Use half of the available screen space max_height = monitor.height / 2 if height > max_height: height = int(max_height) elif height < 0: height = 0 return x, y, width, height def get_selected_iter(self): model, treeiter = self._selection.get_selected() # if the model currently being used is a TreeModelFilter, convert # the iter to be a TreeModel iter (witch is what the user expects) if isinstance(model, gtk.TreeModelFilter) and treeiter: treeiter = model.convert_iter_to_child_iter(treeiter) return treeiter def set_selected_iter(self, treeiter): """ Selects an item in the comboentry given a treeiter @param treeiter: the tree iter to select """ model = self._treeview.get_model() # Since the user passed a TreeModel iter, if the model currently # being used is a TreeModelFilter, convert it to be a TreeModelFilter # iter if isinstance(model, gtk.TreeModelFilter): # See #3099 for an explanation why this is needed and a # testcase tmodel = model.get_model() if tmodel.iter_is_valid(treeiter): # revert back to the unfiltered model so we can select # the right object self._treeview.set_model(tmodel) self._selection = self._treeview.get_selection() else: treeiter = model.convert_child_iter_to_iter(treeiter) self._selection.select_iter(treeiter) def set_details_callback(self, callable): self._renderer.set_details_callback(callable)