From 1596475db23e66d0d54fb927113a4850b95efc9d Mon Sep 17 00:00:00 2001 From: bendikro Date: Fri, 17 May 2013 19:06:00 +0100 Subject: GTKUI: New path chooser to handle remote paths and store favorite paths --- deluge/ui/gtkui/path_combo_chooser.py | 1543 +++++++++++++++++++++++++++++++++ 1 file changed, 1543 insertions(+) create mode 100755 deluge/ui/gtkui/path_combo_chooser.py (limited to 'deluge/ui/gtkui/path_combo_chooser.py') diff --git a/deluge/ui/gtkui/path_combo_chooser.py b/deluge/ui/gtkui/path_combo_chooser.py new file mode 100755 index 000000000..27a716f85 --- /dev/null +++ b/deluge/ui/gtkui/path_combo_chooser.py @@ -0,0 +1,1543 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# path_combo_chooser.py +# +# Copyright (C) 2013 Bro +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. +# + +import os + +import gobject +import gtk +from gtk import gdk, keysyms + +from deluge.path_chooser_common import get_resource, get_completion_paths + +def is_ascii_value(keyval, ascii_key): + try: + # Set show/hide hidden files + if chr(keyval) == ascii_key: + return True + except ValueError: + # Not in ascii range + pass + return False + +def key_is_up(keyval): + return keyval == keysyms.Up or keyval == keysyms.KP_Up + +def key_is_down(keyval): + return keyval == keysyms.Down or keyval == keysyms.KP_Down + +def key_is_up_or_down(keyval): + return key_is_up(keyval) or key_is_down(keyval) + +def key_is_pgup_or_pgdown(keyval): + return keyval == keysyms.Page_Down or keyval == keysyms.Page_Up + +def key_is_enter(keyval): + return keyval == keysyms.Return or keyval == keysyms.KP_Enter + +def path_without_trailing_path_sep(path): + while path.endswith("/") or path.endswith("\\"): + if path == "/": + return path + path = path[0:-1] + return path + +class ValueList(object): + + def get_values_count(self): + return len(self.tree_store) + + def get_values(self): + """ + Returns the values in the list. + """ + values = [] + for row in self.tree_store: + values.append(row[0]) + return values + + def add_values(self, paths, append=True, scroll_to_row=False, + clear=False, emit_signal=False): + """ + Add paths to the liststore + + :param paths: the paths to add + :type paths: list + :param append: if the values should be appended or inserted + :type append: boolean + :param scroll_to_row: if the treeview should scroll to the new row + :type scroll_to_row: boolean + + """ + if clear: + self.tree_store.clear() + + for path in paths: + path = path_without_trailing_path_sep(path) + if append: + tree_iter = self.tree_store.append([path]) + else: + tree_iter = self.tree_store.insert(0, [path]) + + if scroll_to_row: + self.treeview.grab_focus() + tree_path = self.tree_store.get_path(tree_iter) + # Scroll to path + self.handle_list_scroll(path=tree_path) + + if emit_signal: + self.emit("list-value-added", paths) + self.emit("list-values-changed", self.get_values()) + + def set_values(self, paths, scroll_to_row=False, preserve_selection=True): + """ + Add paths to the liststore + + :param paths: the paths to add + :type paths: list + :param scroll_to_row: if the treeview should scroll to the new row + :type scroll_to_row: boolean + + """ + if not (type(paths) is list or type(paths) is tuple): + return + sel = None + if preserve_selection: + sel = self.get_selection_path() + self.add_values(paths, scroll_to_row=scroll_to_row, clear=True) + if sel: + self.treeview.get_selection().select_path(sel) + + def get_selection_path(self): + """Returns the (first) selected path from a treeview""" + tree_selection = self.treeview.get_selection() + model, tree_paths = tree_selection.get_selected_rows() + if len(tree_paths) > 0: + return tree_paths[0] + return None + + def get_selected_value(self): + path = self.get_selection_path() + if path: + return self.tree_store[path][0] + return None + + def remove_selected_path(self): + path = self.get_selection_path() + if path: + path_value = self.tree_store[path][0] + del self.tree_store[path] + index = path[0] + # The last row was deleted + if index == len(self.tree_store): + index -= 1 + if index >= 0: + path = (index, ) + self.treeview.set_cursor(path) + self.set_path_selected(path) + self.emit("list-value-removed", path_value) + self.emit("list-values-changed", self.get_values()) + + def set_selected_value(self, value, select_first=False): + """ + Select the row of the list with value + + :param value: the value to be selected + :type value: str + :param select_first: if the first item should be selected if the value if not found. + :type select_first: boolean + + """ + for i, row in enumerate(self.tree_store): + if row[0] == value: + self.treeview.set_cursor((i)) + return + # The value was not found + if select_first: + self.treeview.set_cursor((0,)) + else: + self.treeview.get_selection().unselect_all() + + def set_path_selected(self, path): + self.treeview.get_selection().select_path(path) + + def on_value_list_treeview_key_press_event(self, widget, event): + """ + Mimics Combobox behavior + + Escape or Alt+Up: Close + Enter or Return : Select + """ + keyval = event.keyval + state = event.state & gtk.accelerator_get_default_mod_mask() + + if keyval == keysyms.Escape or\ + (key_is_up(keyval) and + state == gdk.MOD1_MASK): # ALT Key + self.popdown() + return True + # Set entry value to the selected row + elif key_is_enter(keyval): + path = self.get_selection_path() + if path: + self.set_entry_value(path, popdown=True) + return True + return False + + def on_treeview_mouse_button_press_event(self, treeview, event, double_click=False): + """ + When left clicking twice, the row value is set for the text entry + and the popup is closed. + + """ + # This is left click + if event.button != 3: + # Double clicked a row, set this as the entry value + # and close the popup + if (double_click and event.type == gtk.gdk._2BUTTON_PRESS) or\ + (not double_click and event.type == gtk.gdk.BUTTON_PRESS): + path = self.get_selection_path() + if path: + self.set_entry_value(path, popdown=True) + return True + return False + + def handle_list_scroll(self, next=None, path=None, set_entry=False, swap=False, scroll_window=False): + """ + Handles changes to the row selection. + + :param next: the direction to change selection. None means no change. True means down + and False means up. + :type next: boolean/None + :param path: the current path. If None, the currently selected path is used. + :type path: tuple + :param set_entry: if the new value should be set in the text entry. + :type set_entry: boolean + :param swap: if the old and new value should be swapped + :type swap: boolean + + """ + if scroll_window: + adjustment = self.completion_scrolled_window.get_vadjustment() + + visible_rows_height = self.get_values_count() + if visible_rows_height > self.max_visible_rows: + visible_rows_height = self.max_visible_rows + + visible_rows_height *= self.row_height + value = adjustment.get_value() + + # Max adjustment value + max_value = adjustment.get_upper() - visible_rows_height + # Set adjustment increment to 3 times the row height + adjustment.set_step_increment(self.row_height * 3) + + if next: + # If number of values is less than max rows, no scroll + if self.get_values_count() < self.max_visible_rows: + return + value += adjustment.get_step_increment() + if value > max_value: + value = max_value + else: + value -= adjustment.get_step_increment() + if value < 0: + value = 0 + adjustment.set_value(value) + return + + if path is None: + path = self.get_selection_path() + if not path: + # These options require a selected path + if set_entry or swap: + return + # This is a regular scroll, not setting value in entry or swapping rows, + # so we find a path value anyways + path = (0, ) + cursor = self.treeview.get_cursor() + if cursor is not None and cursor[0] is not None: + path = cursor[0] + else: + # Since cursor is none, we won't advance the index + next = None + + # If next is None, we won't change the selection + if not next is None: + # We move the selection either one up or down. + # If we reach end of list, we wrap + index = path[0] if path else 0 + index = index + 1 if next else index - 1 + if index >= len(self.tree_store): + index = 0 + elif index < 0: + index = len(self.tree_store) - 1 + + # We have the index for the new path + new_path = (index) + if swap: + p1 = self.tree_store[path][0] + p2 = self.tree_store[new_path][0] + self.tree_store.swap(self.tree_store.get_iter(path), + self.tree_store.get_iter(new_path)) + self.emit("list-values-reordered", [p1, p2]) + self.emit("list-values-changed", self.get_values()) + path = new_path + + self.treeview.set_cursor(path) + self.treeview.get_selection().select_path(path) + if set_entry: + self.set_entry_value(path) + +class StoredValuesList(ValueList): + + def __init__(self): + self.tree_store = self.builder.get_object("stored_values_tree_store") + self.tree_column = self.builder.get_object("stored_values_treeview_column") + self.rendererText = self.builder.get_object("stored_values_cellrenderertext") + + # Add signal handlers + self.signal_handlers["on_stored_values_treeview_mouse_button_press_event"] = \ + self.on_treeview_mouse_button_press_event + + self.signal_handlers["on_stored_values_treeview_key_press_event"] = \ + self.on_stored_values_treeview_key_press_event + self.signal_handlers["on_stored_values_treeview_key_release_event"] = \ + self.on_stored_values_treeview_key_release_event + + self.signal_handlers["on_cellrenderertext_edited"] = self.on_cellrenderertext_edited + + def on_cellrenderertext_edited(self, cellrenderertext, path, new_text): + """ + Callback on the 'edited' signal. + + Sets the new text in the path and disables editing on the renderer. + """ + new_text = path_without_trailing_path_sep(new_text) + self.tree_store[path][0] = new_text + self.rendererText.set_property('editable', False) + + def on_edit_path(self, path, column): + """ + Starts editing on the provided path + + :param path: the paths to edit + :type path: tuple + :param column: the column to edit + :type column: gtk.TreeViewColumn + + """ + self.rendererText.set_property('editable', True) + self.treeview.grab_focus() + self.treeview.set_cursor(path, focus_column=column, start_editing=True) + + def on_treeview_mouse_button_press_event(self, treeview, event): + """ + Shows popup on selected row when right clicking + When left clicking twice, the row value is set for the text entry + and the popup is closed. + + """ + # This is left click + if event.button != 3: + super(StoredValuesList, self).on_treeview_mouse_button_press_event(treeview, event, double_click=True) + return False + + # This is right click, create popup menu for this row + x = int(event.x) + y = int(event.y) + time = event.time + pthinfo = treeview.get_path_at_pos(x, y) + if pthinfo is not None: + path, col, cellx, celly = pthinfo + treeview.grab_focus() + treeview.set_cursor(path, col, 0) + + self.path_list_popup = gtk.Menu() + menuitem_edit = gtk.MenuItem("Edit path") + self.path_list_popup.append(menuitem_edit) + menuitem_remove = gtk.MenuItem("Remove path") + self.path_list_popup.append(menuitem_remove) + + def on_edit_clicked(widget, path): + self.on_edit_path(path, self.tree_column) + def on_remove_clicked(widget, path): + self.remove_selected_path() + + menuitem_edit.connect("activate", on_edit_clicked, path) + menuitem_remove.connect("activate", on_remove_clicked, path) + self.path_list_popup.popup(None, None, None, event.button, time, data=path) + self.path_list_popup.show_all() + + def remove_selected_path(self): + ValueList.remove_selected_path(self) + # Resize popup + PathChooserPopup.popup(self) + + + def on_stored_values_treeview_key_press_event(self, widget, event): + super(StoredValuesList, self).on_value_list_treeview_key_press_event(widget, event) + # Prevent the default event handler to move the cursor in the list + if key_is_up_or_down(event.keyval): + return True + + def on_stored_values_treeview_key_release_event(self, widget, event): + """ + Mimics Combobox behavior + + Escape or Alt+Up: Close + Enter or Return : Select + + """ + keyval = event.keyval + state = event.state & gtk.accelerator_get_default_mod_mask() + ctrl = event.state & gtk.gdk.CONTROL_MASK + + # Edit selected row + if (keyval in [keysyms.Left, keysyms.Right, keysyms.space]): + path = self.get_selection_path() + if path: + self.on_edit_path(path, self.tree_column) + elif key_is_up_or_down(keyval): + # Swap the row value + if event.state & gtk.gdk.CONTROL_MASK: + self.handle_list_scroll(next=key_is_down(keyval), + swap=True) + else: + self.handle_list_scroll(next=key_is_down(keyval)) + elif key_is_pgup_or_pgdown(event.keyval): + # The cursor has been changed by the default key-press-event handler + # so set the path of the cursor selected + self.set_path_selected(self.treeview.get_cursor()[0]) + elif ctrl: + # Handle key bindings for manipulating the list + # Remove the selected entry + if is_ascii_value(keyval, 'r'): + self.remove_selected_path() + return True + # Add current value to saved list + elif is_ascii_value(keyval, 's'): + super(PathChooserComboBox, self).add_current_value_to_saved_list() + return True + # Edit selected value + elif is_ascii_value(keyval, 'e'): + self.edit_selected_path() + return True + +class CompletionList(ValueList): + + def __init__(self): + self.tree_store = self.builder.get_object("completion_tree_store") + self.tree_column = self.builder.get_object("completion_treeview_column") + self.rendererText = self.builder.get_object("completion_cellrenderertext") + self.completion_scrolled_window = self.builder.get_object("completion_scrolled_window") + self.signal_handlers["on_completion_treeview_key_press_event"] = \ + self.on_completion_treeview_key_press_event + self.signal_handlers["on_completion_treeview_motion_notify_event"] = \ + self.on_completion_treeview_motion_notify_event + + # Add super class signal handler + self.signal_handlers["on_completion_treeview_mouse_button_press_event"] = \ + super(CompletionList, self).on_treeview_mouse_button_press_event + + def reduce_values(self, prefix): + """ + Reduce the values in the liststore to those starting with the prefix. + + :param prefix: the prefix to be matched + :type paths: string + + """ + values = self.get_values() + matching_values = [] + for v in values: + if v.startswith(prefix): + matching_values.append(v) + self.add_values(matching_values, clear=True) + + def on_completion_treeview_key_press_event(self, widget, event): + ret = super(CompletionList, self).on_value_list_treeview_key_press_event(widget, event) + if ret: + return ret + keyval = event.keyval + ctrl = event.state & gtk.gdk.CONTROL_MASK + if key_is_up_or_down(keyval): + self.handle_list_scroll(next=key_is_down(keyval)) + return True + elif ctrl: + # Set show/hide hidden files + if is_ascii_value(keyval, 'h'): + self.path_entry.set_show_hidden_files(not self.path_entry.get_show_hidden_files(), do_completion=True) + return True + + def on_completion_treeview_motion_notify_event(self, widget, event): + if event.is_hint: + x, y, state = event.window.get_pointer() + else: + x = event.x + y = event.y + state = event.state + + path = self.treeview.get_path_at_pos(int(x), int(y)) + if path: + self.handle_list_scroll(path=path[0], next=None) + +class PathChooserPopup(object): + """ + + This creates the popop window for the ComboEntry + + """ + def __init__(self, min_visible_rows, max_visible_rows, popup_alignment_widget): + self.min_visible_rows = min_visible_rows + # Maximum number of rows to display without scrolling + self.set_max_popup_rows(max_visible_rows) + self.popup_window.realize() + self.alignment_widget = popup_alignment_widget + + def popup(self): + """ + Makes the popup visible. + + """ + # Entry is not yet visible + if not (self.path_entry.flags() & gtk.REALIZED): + return + if not self.is_popped_up(): + self.set_window_position_and_size() + + def popdown(self): + if not self.is_popped_up(): + return + if not (self.path_entry.flags() & gtk.REALIZED): + return + self.popup_window.grab_remove() + self.popup_window.hide_all() + + def is_popped_up(self): + """ + Return True if the window is popped up. + """ + return bool(self.popup_window.flags() & gtk.MAPPED) + + def set_window_position_and_size(self): + if len(self.tree_store) < self.min_visible_rows: + return False + x, y, width, height = self.get_position() + self.popup_window.set_size_request(width, height) + self.popup_window.resize(width, height) + self.popup_window.move(x, y) + self.popup_window.show_all() + + def get_position(self): + """ + Returns the size of the popup window and the coordinates on the screen. + + """ + self.popup_buttonbox = self.builder.get_object("buttonbox") + + # Necessary for the first call, to make treeview.size_request give sensible values + #self.popup_window.realize() + self.treeview.realize() + + # We start with the coordinates of the parent window + x, y = self.path_entry.window.get_origin() + + # Add the position of the alignment_widget relative to the parent window. + x += self.alignment_widget.allocation.x + y += self.alignment_widget.allocation.y + + height_extra = 8 + + height = self.popup_window.size_request()[1] + width = self.popup_window.size_request()[0] + + treeview_height = self.treeview.size_request()[1] + treeview_width = self.treeview.size_request()[0] + + if treeview_height > height: + height = treeview_height + height_extra + + butonbox_height = max(self.popup_buttonbox.size_request()[1], self.popup_buttonbox.allocation.height) + butonbox_width = max(self.popup_buttonbox.size_request()[0], self.popup_buttonbox.allocation.width) + + if treeview_height > butonbox_height and treeview_height < height : + height = treeview_height + height_extra + + # After removing an element from the tree store, self.treeview.size_request()[0] + # returns -1 for some reason, so the requested width cannot be used until the treeview + # has been displayed once. + if treeview_width != -1: + width = treeview_width + butonbox_width + # The list is empty, so ignore initial popup width request + # Will be set to the minimum width next + elif len(self.tree_store) == 0: + width = 0 + + # Minimum width is the width of the path entry + width of buttonbox +# if width < self.alignment_widget.allocation.width + butonbox_width: +# width = self.alignment_widget.allocation.width + butonbox_width + + if width < self.alignment_widget.allocation.width: + width = self.alignment_widget.allocation.width + + # 10 is extra spacing + content_width = self.treeview.size_request()[0] + butonbox_width + 10 + + # If self.max_visible_rows is -1, not restriction is set + if len(self.tree_store) > 0 and self.max_visible_rows > 0: + # The height for one row in the list + self.row_height = self.treeview.size_request()[1] / len(self.tree_store) + # Adjust the height according to the max number of rows + max_height = self.row_height * self.max_visible_rows + # Restrict height to max_visible_rows + if max_height + height_extra < height: + height = max_height + height += height_extra + # Increase width because of vertical scrollbar + content_width += 15 + + # Minimum height is the height of the button box + if height < butonbox_height + height_extra: + height = butonbox_height + height_extra + + if content_width > width: + width = content_width + + screen = self.path_entry.get_screen() + monitor_num = screen.get_monitor_at_window(self.path_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 + + # Set the position + if y + self.path_entry.allocation.height + height <= monitor.y + monitor.height: + y += self.path_entry.allocation.height + # Not enough space downwards on the screen + elif y - height >= monitor.y: + y -= height + elif (monitor.y + monitor.height - (y + self.path_entry.allocation.height) > + y - monitor.y): + y += self.path_entry.allocation.height + height = monitor.y + monitor.height - y + else: + height = y - monitor.y + y = monitor.y + + return x, y, width, height + + def popup_grab_window(self): + activate_time = 0L + if gdk.pointer_grab(self.popup_window.window, True, + (gdk.BUTTON_PRESS_MASK | + gdk.BUTTON_RELEASE_MASK | + gdk.POINTER_MOTION_MASK), + None, None, activate_time) == 0: + if gdk.keyboard_grab(self.popup_window.window, True, activate_time) == 0: + return True + else: + self.popup_window.window.get_display().pointer_ungrab(activate_time); + return False + return False + + def set_entry_value(self, path, popdown=False): + """ + + Sets the text of the entry to the value in path + """ + self.path_entry.set_text(self.tree_store[path][0], set_file_chooser_folder=True) + if popdown: + self.popdown() + + def set_max_popup_rows(self, rows): + try: + int(rows) + except: + self.max_visible_rows = 20 + return + self.max_visible_rows = rows + + def get_max_popup_rows(self): + return self.max_visible_rows + +################################################### +# Callbacks +################################################### + + def on_popup_window_button_press_event(self, window, event): + # If we're clicking outside of the window close the popup + hide = False + # Also if the intersection of self and the event is empty, hide + # the path_list + if (tuple(self.popup_window.allocation.intersect( + gdk.Rectangle(x=int(event.x), y=int(event.y), + width=1, height=1))) == (0, 0, 0, 0)): + hide = True + # Toplevel is the window that received the event, and parent is the + # path_list window. If they are not the same, means the popup should + # be hidden. This is necessary for when the event happens on another + # widget + toplevel = event.window.get_toplevel() + parent = self.popup_window.window + + if toplevel != parent: + hide = True + if hide: + self.popdown() + + +class StoredValuesPopup(StoredValuesList, PathChooserPopup): + """ + + The stored values popup + + """ + def __init__(self, builder, path_entry, max_visible_rows, popup_alignment_widget): + self.builder = builder + self.treeview = self.builder.get_object("stored_values_treeview") + self.popup_window = self.builder.get_object("stored_values_popup_window") + self.popup_buttonbox = self.builder.get_object("buttonbox") + self.button_default = self.builder.get_object("button_default") + self.path_entry = path_entry + self.text_entry = path_entry.text_entry + + self.signal_handlers = {} + PathChooserPopup.__init__(self, 0, max_visible_rows, popup_alignment_widget) + StoredValuesList.__init__(self) + + # Add signal handlers + self.signal_handlers["on_buttonbox_key_press_event"] = \ + self.on_buttonbox_key_press_event + self.signal_handlers["on_stored_values_treeview_scroll_event"] = self.on_scroll_event + self.signal_handlers["on_button_toggle_dropdown_scroll_event"] = self.on_scroll_event + self.signal_handlers["on_entry_text_scroll_event"] = self.on_scroll_event + self.signal_handlers["on_stored_values_popup_window_focus_out_event"] = \ + self.on_stored_values_popup_window_focus_out_event + # For when clicking outside the popup + self.signal_handlers["on_stored_values_popup_window_button_press_event"] = \ + self.on_popup_window_button_press_event + + # Buttons for manipulating the list + self.signal_handlers["on_button_add_clicked"] = self.on_button_add_clicked + self.signal_handlers["on_button_edit_clicked"] = self.on_button_edit_clicked + self.signal_handlers["on_button_remove_clicked"] = self.on_button_remove_clicked + self.signal_handlers["on_button_up_clicked"] = self.on_button_up_clicked + self.signal_handlers["on_button_down_clicked"] = self.on_button_down_clicked + self.signal_handlers["on_button_default_clicked"] = self.on_button_default_clicked + self.signal_handlers["on_button_properties_clicked"] = self.path_entry._on_button_properties_clicked + + def popup(self): + """ + Makes the popup visible. + + """ + # Calling super popup + PathChooserPopup.popup(self) + self.popup_window.grab_focus() + + if not (self.treeview.flags() & gtk.HAS_FOCUS): + self.treeview.grab_focus() + if not self.popup_grab_window(): + self.popup_window.hide() + return + + self.popup_window.grab_add() + # Set value selected if it exists + self.set_selected_value(path_without_trailing_path_sep(self.path_entry.get_text())) + +################################################### +# Callbacks +################################################### + + def on_stored_values_popup_window_focus_out_event(self, entry, event): + """ + Popup sometimes loses the focus to the text entry, e.g. when right click + shows a popup menu on a row. This regains the focus. + """ + self.popup_grab_window() + return True + + def on_scroll_event(self, widget, event): + """ + Handles scroll events from text entry, toggle button and treeview + + """ + swap = event.state & gtk.gdk.CONTROL_MASK + self.handle_list_scroll(next=event.direction == gdk.SCROLL_DOWN, + set_entry=widget != self.treeview, swap=swap) + return True + + def on_buttonbox_key_press_event(self, widget, event): + """ + Handles when Escape or ALT+arrow up is pressed when focus + is on any of the buttons in the popup + """ + keyval = event.keyval + state = event.state & gtk.accelerator_get_default_mod_mask() + if (keyval == keysyms.Escape or + (key_is_up(keyval) and + state == gdk.MOD1_MASK)): + self.popdown() + return True + return False + +# -------------------------------------------------- +# Funcs and callbacks on the buttons to manipulate the list +# -------------------------------------------------- + def add_current_value_to_saved_list(self): + text = self.path_entry.get_text() + text = path_without_trailing_path_sep(text) + values = self.get_values() + if text in values: + # Make the matching value selected + self.set_selected_value(text) + self.handle_list_scroll() + return True + self.add_values([text], scroll_to_row=True, append=False, emit_signal=True) + + def edit_selected_path(self): + path = self.get_selection_path() + if path: + self.on_edit_path(path, self.tree_column) + + def on_button_add_clicked(self, widget): + self.add_current_value_to_saved_list() + self.popup() + + def on_button_edit_clicked(self, widget): + self.edit_selected_path() + + def on_button_remove_clicked(self, widget): + self.remove_selected_path() + return True + + def on_button_up_clicked(self, widget): + self.handle_list_scroll(next=False, swap=True) + + def on_button_down_clicked(self, widget): + self.handle_list_scroll(next=True, swap=True) + + def on_button_default_clicked(self, widget): + if self.default_text: + self.set_text(self.default_text) + +class PathCompletionPopup(CompletionList, PathChooserPopup): + """ + + The auto completion popup + + """ + def __init__(self, builder, path_entry, max_visible_rows): + self.builder = builder + self.treeview = self.builder.get_object("completion_treeview") + self.popup_window = self.builder.get_object("completion_popup_window") + self.path_entry = path_entry + self.text_entry = path_entry.text_entry + self.show_hidden_files = False + + self.signal_handlers = {} + PathChooserPopup.__init__(self, 1, max_visible_rows, self.text_entry) + CompletionList.__init__(self) + + # Add signal handlers + self.signal_handlers["on_completion_treeview_scroll_event"] = self.on_scroll_event + self.signal_handlers["on_completion_popup_window_focus_out_event"] = \ + self.on_completion_popup_window_focus_out_event + + # For when clicking outside the popup + self.signal_handlers["on_completion_popup_window_button_press_event"] = \ + self.on_popup_window_button_press_event + + def popup(self): + """ + Makes the popup visible. + + """ + PathChooserPopup.popup(self) + self.popup_window.grab_focus() + + if not (self.treeview.flags() & gtk.HAS_FOCUS): + self.treeview.grab_focus() + + if not self.popup_grab_window(): + self.popup_window.hide() + return + + self.popup_window.grab_add() + self.text_entry.grab_focus() + self.text_entry.set_position(len(self.path_entry.text_entry.get_text())) + +################################################### +# Callbacks +################################################### + + def on_completion_popup_window_focus_out_event(self, entry, event): + """ + Popup sometimes loses the focus to the text entry, e.g. when right click + shows a popup menu on a row. This regains the focus. + """ + self.popup_grab_window() + return True + + def on_scroll_event(self, widget, event): + """ + Handles scroll events from the treeview + + """ + x, y, state = event.window.get_pointer() + self.handle_list_scroll(next=event.direction == gdk.SCROLL_DOWN, + set_entry=widget != self.treeview, scroll_window=True) + path = self.treeview.get_path_at_pos(int(x), int(y)) + if path: + self.handle_list_scroll(path=path[0], next=None) + return True + +class PathAutoCompleter(object): + + def __init__(self, builder, path_entry, max_visible_rows): + self.completion_popup = PathCompletionPopup(builder, path_entry, max_visible_rows) + self.path_entry = path_entry + self.dirs_cache = {} + self.use_popup = False + self.auto_complete_enabled = True + self.signal_handlers = self.completion_popup.signal_handlers + + self.signal_handlers["on_completion_popup_window_key_press_event"] = \ + self.on_completion_popup_window_key_press_event + self.signal_handlers["on_entry_text_delete_text"] = \ + self.on_entry_text_delete_text + self.signal_handlers["on_entry_text_insert_text"] = \ + self.on_entry_text_insert_text + + self.accelerator_string = gtk.accelerator_name(keysyms.Tab, 0) + + def on_entry_text_insert_text(self, entry, new_text, new_text_length, position): + if (self.path_entry.flags() & gtk.REALIZED): + cur_text = self.path_entry.get_text() + pos = entry.get_position() + new_complete_text = cur_text[:pos] + new_text + cur_text[pos:] + # Remove all values from the list that do not start with new_complete_text + self.completion_popup.reduce_values(new_complete_text) + if self.completion_popup.is_popped_up(): + self.completion_popup.set_window_position_and_size() + + def on_entry_text_delete_text(self, entry, start, end): + """ + Remove the popup when characters are removed + + """ + if self.completion_popup.is_popped_up(): + self.completion_popup.popdown() + + def set_use_popup(self, use): + self.use_popup = use + + def on_completion_popup_window_key_press_event(self, entry, event): + """ + """ + # If on_completion_treeview_key_press_event handles the event, do nothing + ret = self.completion_popup.on_completion_treeview_key_press_event(entry, event) + if ret: + return ret + keyval = event.keyval + state = event.state & gtk.accelerator_get_default_mod_mask() + + if self.is_auto_completion_accelerator(keyval, state)\ + and self.auto_complete_enabled: + values_count = self.completion_popup.get_values_count() + self.do_completion() + if values_count == 1: + self.completion_popup.popdown() + else: + #shift = event.state & gtk.gdk.SHIFT_MASK + #self.completion_popup.handle_list_scroll(next=False if shift else True) + self.completion_popup.handle_list_scroll(next=True) + return True + self.path_entry.text_entry.emit("key-press-event", event) + + def is_auto_completion_accelerator(self, keyval, state): + return gtk.accelerator_name(keyval, state.numerator) == self.accelerator_string + + def do_completion(self): + value = self.path_entry.get_text() + self.path_entry.text_entry.set_position(len(value)) + paths = self._start_completion(value, hidden_files=self.completion_popup.show_hidden_files) + + def _start_completion(self, value, hidden_files): + completion_paths = get_completion_paths(value, hidden_files) + self._end_completion(value, completion_paths) + + def _end_completion(self, value, paths): + common_prefix = os.path.commonprefix(paths) + if len(common_prefix) > len(value): + self.path_entry.set_text(common_prefix, set_file_chooser_folder=True) + + self.path_entry.text_entry.set_position(len(self.path_entry.get_text())) + self.completion_popup.set_values(paths, preserve_selection=True) + if self.use_popup and len(paths) > 1: + self.completion_popup.popup() + elif self.completion_popup.is_popped_up(): + self.completion_popup.popdown() + +class PathChooserComboBox(gtk.HBox, StoredValuesPopup, gobject.GObject): + + __gsignals__ = { + "list-value-added": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object, )), + "list-value-removed": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object, )), + "list-values-reordered": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object, )), + "list-values-changed": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object, )), + "auto-complete-enabled-toggled": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object, )), + "show-filechooser-toggled": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object, )), + "show-path-entry-toggled": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object, )), + "show-folder-name-on-button": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object, )), + "show-hidden-files-toggled": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object, )), + "accelerator-set": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object, )), + "max-rows-changed": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object, )), + } + + def __init__(self, max_visible_rows=20, auto_complete=True, use_completer_popup=True): + gtk.HBox.__init__(self) + gobject.GObject.__init__(self) + self._stored_values_popping_down = False + self.filechooser_visible = True + self.filechooser_enabled = True + self.path_entry_visible = True + self.properties_enabled = True + self.show_folder_name_on_button = False + self.setting_accelerator_key = False + self.builder = gtk.Builder() + self.popup_buttonbox = self.builder.get_object("buttonbox") + self.builder.add_from_file(get_resource("path_combo_chooser.ui")) + self.button_toggle = self.builder.get_object("button_toggle_dropdown") + self.text_entry = self.builder.get_object("entry_text") + self.open_filechooser_dialog_button = self.builder.get_object("button_open_dialog") + self.filechooser_button = self.open_filechooser_dialog_button + self.filechooserdialog = self.builder.get_object("filechooserdialog") + self.folder_name_label = self.builder.get_object("folder_name_label") + self.default_text = None + self.button_properties = self.builder.get_object("button_properties") + self.combo_hbox = self.builder.get_object("entry_combobox_hbox") + # Change the parent of the hbox from the glade Window to this hbox. + self.combo_hbox.reparent(self) + StoredValuesPopup.__init__(self, self.builder, self, max_visible_rows, self.combo_hbox) + self.tooltips = gtk.Tooltips() + + self.auto_completer = PathAutoCompleter(self.builder, self, max_visible_rows) + self.auto_completer.set_use_popup(use_completer_popup) + self.auto_completer.auto_complete_enabled = auto_complete + self._setup_config_dialog() + + signal_handlers = { + "on_button_toggle_dropdown_toggled": self._on_button_toggle_dropdown_toggled, + 'on_entry_text_key_press_event': self._on_entry_text_key_press_event, + 'on_stored_values_popup_window_hide': self._on_stored_values_popup_window_hide, + "on_button_toggle_dropdown_button_press_event": self._on_button_toggle_dropdown_button_press_event, + "on_entry_combobox_hbox_realize": self._on_entry_combobox_hbox_realize, + "on_button_open_dialog_clicked": self._on_button_open_dialog_clicked, + "on_entry_text_focus_out_event": self._on_entry_text_focus_out_event, + } + signal_handlers.update(self.signal_handlers) + signal_handlers.update(self.auto_completer.signal_handlers) + signal_handlers.update(self.config_dialog_signal_handlers) + self.builder.connect_signals(signal_handlers) + + def get_text(self): + """ + Get the current text in the Entry + """ + return self.text_entry.get_text() + + def set_text(self, text, set_file_chooser_folder=True, cursor_end=True, default_text=False): + """ + Set the text for the entry. + + """ + self.text_entry.set_text(text) + self.text_entry.select_region(0, 0) + self.text_entry.set_position(len(text) if cursor_end else 0) + self.set_selected_value(text, select_first=True) + self.tooltips.set_tip(self.combo_hbox, text) + if default_text: + self.default_text = text + self.tooltips.set_tip(self.button_default, "Restore the default value in the text entry:\n%s" % self.default_text) + self.button_default.set_sensitive(True) + # Set text for the filechooser dialog button + if not self.path_entry_visible: + # Show entire path + self.folder_name_label.set_text(text) + else: + if self.show_folder_name_on_button: + text = path_without_trailing_path_sep(text) + if not text is "/" and os.path.basename(text): + text = os.path.basename(text) + else: + text = "" + self.folder_name_label.set_text(text) + + def set_sensitive(self, sensitive): + """ + Set the path chooser widgets sensitive + + :param sensitive: if the widget should be sensitive + :type sensitive: bool + + """ + self.text_entry.set_sensitive(sensitive) + self.filechooser_button.set_sensitive(sensitive) + self.button_toggle.set_sensitive(sensitive) + + def get_accelerator_string(self): + return self.auto_completer.accelerator_string + + def set_accelerator_string(self, accelerator): + """ + Set the accelerator string to trigger auto-completion + """ + if accelerator is None: + return + try: + # Verify that the accelerator can be parsed + keyval, mask = gtk.accelerator_parse(self.auto_completer.accelerator_string) + self.auto_completer.accelerator_string = accelerator + except TypeError, e: + raise TypeError("TypeError when setting accelerator string: %s" % str(e)) + + def get_auto_complete_enabled(self): + return self.auto_completer.auto_complete_enabled + + def set_auto_complete_enabled(self, enable): + if not type(enable) is bool: + return + self.auto_completer.auto_complete_enabled = enable + + def get_show_folder_name_on_button(self): + return self.show_folder_name_on_button + + def set_show_folder_name_on_button(self, show): + if not type(show) is bool: + return + self.show_folder_name_on_button = show + self._set_path_entry_filechooser_widths() + + def get_filechooser_button_enabled(self): + return self.filechooser_enabled + + def set_filechooser_button_enabled(self, enable): + """ + Enable/disable the filechooser button. + + By setting filechooser disabled, in will not be possible + to change the settings in the properties. + """ + if not type(enable) is bool: + return + self.filechooser_enabled = enable + if not enable: + self.set_filechooser_button_visible(False, update=False) + + def get_filechooser_button_visible(self): + return self.filechooser_visible + + def set_filechooser_button_visible(self, visible, update=True): + """ + Set file chooser button entry visible + """ + if not type(visible) is bool: + return + if update: + self.filechooser_visible = visible + if visible and not self.filechooser_enabled: + return + if visible: + self.filechooser_button.show() + else: + self.filechooser_button.hide() + # Update width properties + self._set_path_entry_filechooser_widths() + + def get_path_entry_visible(self): + return self.path_entry_visible + + def set_path_entry_visible(self, visible): + """ + Set the path entry visible + """ + if not type(visible) is bool: + return + self.path_entry_visible = visible + if visible: + self.text_entry.show() + else: + self.text_entry.hide() + self._set_path_entry_filechooser_widths() + + def get_show_hidden_files(self): + return self.auto_completer.completion_popup.show_hidden_files + + def set_show_hidden_files(self, show, do_completion=False, emit_event=False): + """ + Enable/disable showing hidden files on path completion + """ + if not type(show) is bool: + return + self.auto_completer.completion_popup.show_hidden_files = show + if do_completion: + self.auto_completer.do_completion() + if emit_event: + self.emit("show-hidden-files-toggled", show) + + def set_enable_properties(self, enable): + """ + Enable/disable the config properties + """ + if not type(enable) is bool: + return + self.properties_enabled = enable + if self.properties_enabled: + self.popup_buttonbox.add(self.button_properties) + else: + self.popup_buttonbox.remove(self.button_properties) + + def set_auto_completer_func(self, func): + """ + Set the function to be called when the auto completion + accelerator is triggered. + """ + self.auto_completer._start_completion = func + + def complete(self, value, paths): + """ + Perform the auto completion with the provided paths + """ + self.auto_completer._end_completion(value, paths) + +###################################### +## Callbacks and internal functions +###################################### + + def _on_entry_text_focus_out_event(self, widget, event): + self.set_text(self.get_text()) + + def _set_path_entry_filechooser_widths(self): + if self.path_entry_visible: + self.combo_hbox.set_child_packing(self.filechooser_button, 0, 0, 0, gtk.PACK_START) + width, height = self.folder_name_label.get_size_request() + width = 120 + if not self.show_folder_name_on_button: + width = 0 + self.folder_name_label.set_size_request(width, height) + self.combo_hbox.set_child_packing(self.filechooser_button, 0, 0, 0, gtk.PACK_START) + else: + self.combo_hbox.set_child_packing(self.filechooser_button, 1, 1, 0, gtk.PACK_START) + self.folder_name_label.set_size_request(-1, -1) + # Update text on the button label + self.set_text(self.get_text()) + + def _on_entry_combobox_hbox_realize(self, widget): + """ Must do this when the widget is realized """ + self.set_filechooser_button_visible(self.filechooser_visible) + self.set_path_entry_visible(self.path_entry_visible) + + def _on_button_open_dialog_clicked(self, widget): + dialog = self.filechooserdialog + dialog.set_current_folder(self.get_text()) + response_id = dialog.run() + + if response_id == 0: + text = self.filechooserdialog.get_filename() + self.set_text(text) + dialog.hide() + + def _on_entry_text_key_press_event(self, widget, event): + """ + Listen to key events on the entry widget. + + Arrow up/down will change the value of the entry according to the + current selection in the list. + Enter will show the popup. + + Return True whenever we want no other event listeners to be called. + + """ + keyval = event.keyval + state = event.state & gtk.accelerator_get_default_mod_mask() + ctrl = event.state & gtk.gdk.CONTROL_MASK + + # Select new row with arrow up/down is pressed + if key_is_up_or_down(keyval): + self.handle_list_scroll(next=key_is_down(keyval), + set_entry=True) + return True + elif self.auto_completer.is_auto_completion_accelerator(keyval, state): + if self.auto_completer.auto_complete_enabled: + self.auto_completer.do_completion() + return True + # Show popup when Enter is pressed + elif key_is_enter(keyval): + # This sets the toggle active which results in + # on_button_toggle_dropdown_toggled being called which initiates the popup + self.button_toggle.set_active(True) + return True + elif ctrl: + # Swap the show hidden files value on CTRL-h + if is_ascii_value(keyval, 'h'): + # Set show/hide hidden files + self.set_show_hidden_files(not self.get_show_hidden_files(), emit_event=True) + return True + elif is_ascii_value(keyval, 's'): + super(PathChooserComboBox, self).add_current_value_to_saved_list() + return True + elif is_ascii_value(keyval, 'd'): + # Set the default value in the text entry + self.set_text(self.default_text) + return True + return False + + + def _on_button_toggle_dropdown_toggled(self, button): + """ + Shows the popup when clicking the toggle button. + """ + if self._stored_values_popping_down: + return + self.popup() + + def _on_stored_values_popup_window_hide(self, popup): + """Make sure the button toggle is removed when popup is closed""" + self._stored_values_popping_down = True + self.button_toggle.set_active(False) + self._stored_values_popping_down = False + +###################################### +## Config dialog +###################################### + + def _on_button_toggle_dropdown_button_press_event(self, widget, event): + """Show config when right clicking dropdown toggle button""" + if not self.properties_enabled: + return False + # This is right click + if event.button == 3: + self._on_button_properties_clicked(widget) + return True + + def _on_button_properties_clicked(self, widget): + self.popdown() + self.enable_completion.set_active(self.get_auto_complete_enabled()) + # Set the value of the label to the current accelerator + keyval, mask = gtk.accelerator_parse(self.auto_completer.accelerator_string) + self.accelerator_label.set_text(gtk.accelerator_get_label(keyval, mask)) + self.visible_rows.set_value(self.get_max_popup_rows()) + self.show_filechooser_checkbutton.set_active(self.get_filechooser_button_visible()) + self.show_path_entry_checkbutton.set_active(self.path_entry_visible) + self.show_hidden_files_checkbutton.set_active(self.get_show_hidden_files()) + self.show_folder_name_on_button_checkbutton.set_active(self.get_show_folder_name_on_button()) + self._set_properties_widgets_sensitive(True) + self.config_dialog.show_all() + + def _set_properties_widgets_sensitive(self, val): + self.enable_completion.set_sensitive(val) + self.config_short_cuts_frame.set_sensitive(val) + self.config_general_frame.set_sensitive(val) + self.show_hidden_files_checkbutton.set_sensitive(val) + + def _setup_config_dialog(self): + self.config_dialog = self.builder.get_object("completion_config_dialog") + close_button = self.builder.get_object("config_dialog_button_close") + self.enable_completion = self.builder.get_object("enable_auto_completion_checkbutton") + self.show_filechooser_checkbutton = self.builder.get_object("show_filechooser_checkbutton") + self.show_path_entry_checkbutton = self.builder.get_object("show_path_entry_checkbutton") + set_key_button = self.builder.get_object("set_completion_accelerator_button") + default_set_accelerator_tooltip = set_key_button.get_tooltip_text() + self.config_short_cuts_frame = self.builder.get_object("config_short_cuts_frame") + self.config_general_frame = self.builder.get_object("config_general_frame") + self.accelerator_label = self.builder.get_object("completion_accelerator_label") + self.visible_rows = self.builder.get_object("visible_rows_spinbutton") + self.visible_rows_label = self.builder.get_object("visible_rows_label") + self.show_hidden_files_checkbutton = self.builder.get_object("show_hidden_files_checkbutton") + self.show_folder_name_on_button_checkbutton = self.builder.get_object("show_folder_name_on_button_checkbutton") + self.config_dialog.set_transient_for(self.popup_window) + + def on_close(widget, event=None): + if not self.setting_accelerator_key: + self.config_dialog.hide() + else: + stop_setting_accelerator() + return True + + def on_enable_completion_toggled(widget): + self.set_auto_complete_enabled(self.enable_completion.get_active()) + self.emit("auto-complete-enabled-toggled", self.enable_completion.get_active()) + + def on_show_filechooser_toggled(widget): + self.set_filechooser_button_visible(self.show_filechooser_checkbutton.get_active()) + self.emit("show-filechooser-toggled", self.show_filechooser_checkbutton.get_active()) + self.show_folder_name_on_button_checkbutton.set_sensitive(self.show_path_entry_checkbutton.get_active() and + self.show_filechooser_checkbutton.get_active()) + if not self.filechooser_visible and not self.path_entry_visible: + self.show_path_entry_checkbutton.set_active(True) + on_show_path_entry_toggled(None) + + def on_show_path_entry_toggled(widget): + self.set_path_entry_visible(self.show_path_entry_checkbutton.get_active()) + self.emit("show-path-entry-toggled", self.show_path_entry_checkbutton.get_active()) + self.show_folder_name_on_button_checkbutton.set_sensitive(self.show_path_entry_checkbutton.get_active() and + self.show_filechooser_checkbutton.get_active()) + if not self.filechooser_visible and not self.path_entry_visible: + self.show_filechooser_checkbutton.set_active(True) + on_show_filechooser_toggled(None) + + def on_show_folder_name_on_button(widget): + self.set_show_folder_name_on_button(self.show_folder_name_on_button_checkbutton.get_active()) + self._set_path_entry_filechooser_widths() + self.emit("show-folder-name-on-button", self.show_folder_name_on_button_checkbutton.get_active()) + + def on_show_hidden_files_toggled(widget): + self.set_show_hidden_files(self.show_hidden_files_checkbutton.get_active(), emit_event=True) + + def on_max_rows_changed(widget): + self.set_max_popup_rows(self.visible_rows.get_value_as_int()) + self.emit("max-rows-changed", self.visible_rows.get_value_as_int()) + + def set_accelerator(widget): + self.setting_accelerator_key = True + self.tooltips.set_tip(set_key_button, "Press the accelerator keys for triggering auto-completion") + self._set_properties_widgets_sensitive(False) + return True + + def stop_setting_accelerator(): + self.setting_accelerator_key = False + self._set_properties_widgets_sensitive(True) + set_key_button.set_active(False) + # Restore default tooltip + self.tooltips.set_tip(set_key_button, default_set_accelerator_tooltip) + + def on_completion_config_dialog_key_release_event(widget, event): + # We are listening for a new key + if set_key_button.get_active(): + state = event.state & gtk.accelerator_get_default_mod_mask() + accelerator_mask = state.numerator + # If e.g. only CTRL key is pressed. + if not gtk.accelerator_valid(event.keyval, accelerator_mask): + accelerator_mask = 0 + self.auto_completer.accelerator_string = gtk.accelerator_name(event.keyval, accelerator_mask) + self.accelerator_label.set_text(gtk.accelerator_get_label(event.keyval, accelerator_mask)) + self.emit("accelerator-set", self.auto_completer.accelerator_string) + stop_setting_accelerator() + return True + else: + keyval = event.keyval + ctrl = event.state & gtk.gdk.CONTROL_MASK + if ctrl: + # Set show/hide hidden files + if is_ascii_value(keyval, 'h'): + self.show_hidden_files_checkbutton.set_active(not self.get_show_hidden_files()) + return True + + def on_set_completion_accelerator_button_clicked(widget): + if not set_key_button.get_active(): + stop_setting_accelerator() + return True + + self.config_dialog_signal_handlers = { + "on_enable_auto_completion_checkbutton_toggled": on_enable_completion_toggled, + "on_show_filechooser_checkbutton_toggled": on_show_filechooser_toggled, + "on_show_path_entry_checkbutton_toggled": on_show_path_entry_toggled, + "on_show_folder_name_on_button_checkbutton_toggled": on_show_folder_name_on_button, + "on_config_dialog_button_close_clicked": on_close, + "on_visible_rows_spinbutton_value_changed": on_max_rows_changed, + "on_completion_config_dialog_delete_event": on_close, + "on_set_completion_accelerator_button_pressed": set_accelerator, + "on_completion_config_dialog_key_release_event": on_completion_config_dialog_key_release_event, + "on_set_completion_accelerator_button_clicked": on_set_completion_accelerator_button_clicked, + "on_show_hidden_files_checkbutton_toggled": on_show_hidden_files_toggled, + } + +gobject.type_register(PathChooserComboBox) + +if __name__ == "__main__": + import sys + w = gtk.Window() + w.set_position(gtk.WIN_POS_CENTER) + w.set_size_request(600, -1) + w.set_title('ComboEntry example') + w.connect('delete-event', gtk.main_quit) + + box1 = gtk.VBox(gtk.FALSE, 0) + + def get_resource2(filename): + return "%s/glade/%s" % (os.path.abspath(os.path.dirname(sys.argv[0])), filename) + + # Override get_resource which fetches from deluge install + get_resource = get_resource2 + + entry1 = PathChooserComboBox(max_visible_rows=15) + entry2 = PathChooserComboBox() + + box1.add(entry1) + box1.add(entry2) + + paths = [ + "/home/bro/Downloads", + "/media/Movies-HD", + "/media/torrent/in", + "/media/Live-show/Misc", + "/media/Live-show/Consert", + "/media/Series/1/", + "/media/Series/2", + "/media/Series/17", + "/media/Series/18", + "/media/Series/19" + ] + + entry1.add_values(paths) + entry1.set_text("/home/bro/", default_text=True) + entry2.set_text("/home/bro/programmer/deluge/deluge-yarss-plugin/build/lib/yarss2/include/bs4/tests/", cursor_end=False) + + entry2.set_filechooser_button_visible(False) + #entry2.set_enable_properties(False) + entry2.set_filechooser_button_enabled(False) + + def list_value_added_event(widget, values): + print "Current list values:", widget.get_values() + + entry1.connect("list-value-added", list_value_added_event) + entry2.connect("list-value-added", list_value_added_event) + w.add(box1) + w.show_all() + gtk.main() -- cgit