diff --git a/deluge/core/core.py b/deluge/core/core.py
index 710993ad3..ba0953f19 100644
--- a/deluge/core/core.py
+++ b/deluge/core/core.py
@@ -860,11 +860,11 @@ class Core(component.Component):
return lt.version
@export
- def get_completion_paths(self, value, hidden_files=False):
+ def get_completion_paths(self, args):
"""
Returns the available path completions for the input value.
"""
- return path_chooser_common.get_completion_paths(value, hidden_files)
+ return path_chooser_common.get_completion_paths(args)
@export(AUTH_LEVEL_ADMIN)
def get_known_accounts(self):
diff --git a/deluge/path_chooser_common.py b/deluge/path_chooser_common.py
index f44d1fbd0..d92731f0b 100644
--- a/deluge/path_chooser_common.py
+++ b/deluge/path_chooser_common.py
@@ -55,19 +55,23 @@ def is_hidden(filepath):
return has_hidden_attribute(filepath)
return name.startswith('.')
-def get_completion_paths(path_value, hidden_files=False):
+def get_completion_paths(args):
"""
Takes a path value and returns the available completions.
If the path_value is a valid path, return all sub-directories.
If the path_value is not a valid path, remove the basename from the
path and return all sub-directories of path that start with basename.
- :param path_value: path to complete
- :type path_value: string
- :returns: a sorted list of available completions for the input value
+ :param args: options
+ :type args: dict
+ :returns: the args argument containing the available completions for the completion_text
:rtype: list
"""
+ args["paths"] = []
+ path_value = args["completion_text"]
+ hidden_files = args["show_hidden_files"]
+
def get_subdirs(dirname):
try:
return os.walk(dirname).next()[1]
@@ -81,7 +85,7 @@ def get_completion_paths(path_value, hidden_files=False):
dirs = get_subdirs(dirname)
# No completions available
if not dirs:
- return []
+ return args
# path_value ends with path separator so
# we only want all the subdirectories
@@ -100,4 +104,6 @@ def get_completion_paths(path_value, hidden_files=False):
if not p.endswith(os.path.sep):
p += os.path.sep
matching_dirs.append(p)
- return sorted(matching_dirs)
+
+ args["paths"] = sorted(matching_dirs)
+ return args
diff --git a/deluge/ui/gtkui/glade/path_combo_chooser.ui b/deluge/ui/gtkui/glade/path_combo_chooser.ui
index af98c4a6a..eeb29f365 100644
--- a/deluge/ui/gtkui/glade/path_combo_chooser.ui
+++ b/deluge/ui/gtkui/glade/path_combo_chooser.ui
@@ -955,6 +955,7 @@
False
True
True
+
diff --git a/deluge/ui/gtkui/options_tab.py b/deluge/ui/gtkui/options_tab.py
index 3586a68ec..39bf15831 100644
--- a/deluge/ui/gtkui/options_tab.py
+++ b/deluge/ui/gtkui/options_tab.py
@@ -70,6 +70,7 @@ class OptionsTab(Tab):
self.move_completed_path_chooser.set_sensitive(self.chk_move_completed.get_active())
self.move_completed_hbox.add(self.move_completed_path_chooser)
self.move_completed_hbox.show_all()
+ self.move_completed_path_chooser.connect("text-changed", self._on_path_chooser_text_changed_event)
self.prev_torrent_id = None
self.prev_status = None
@@ -300,3 +301,6 @@ class OptionsTab(Tab):
def _on_entry_move_completed_changed(self, widget):
if not self.button_apply.is_sensitive():
self.button_apply.set_sensitive(True)
+
+ def _on_path_chooser_text_changed_event(self, widget, path):
+ self.button_apply.set_sensitive(True)
diff --git a/deluge/ui/gtkui/path_chooser.py b/deluge/ui/gtkui/path_chooser.py
index 533839c66..5874a5e82 100644
--- a/deluge/ui/gtkui/path_chooser.py
+++ b/deluge/ui/gtkui/path_chooser.py
@@ -184,8 +184,8 @@ class PathChooser(PathChooserComboBox):
if self.paths_config_key and self.paths_config_key in config:
self.set_values(config[self.paths_config_key])
- def on_completion(self, value, hidden_files):
- def on_paths_cb(paths):
- self.complete(value, paths)
- d = client.core.get_completion_paths(value, hidden_files=hidden_files)
+ def on_completion(self, args):
+ def on_paths_cb(args):
+ self.complete(args)
+ d = client.core.get_completion_paths(args)
d.addCallback(on_paths_cb)
diff --git a/deluge/ui/gtkui/path_combo_chooser.py b/deluge/ui/gtkui/path_combo_chooser.py
index fad769a58..91ba93141 100755
--- a/deluge/ui/gtkui/path_combo_chooser.py
+++ b/deluge/ui/gtkui/path_combo_chooser.py
@@ -76,6 +76,8 @@ def path_without_trailing_path_sep(path):
class ValueList(object):
+ paths_without_trailing_path_sep = False
+
def get_values_count(self):
return len(self.tree_store)
@@ -105,7 +107,8 @@ class ValueList(object):
self.tree_store.clear()
for path in paths:
- path = path_without_trailing_path_sep(path)
+ if self.paths_without_trailing_path_sep:
+ path = path_without_trailing_path_sep(path)
if append:
tree_iter = self.tree_store.append([path])
else:
@@ -327,6 +330,7 @@ class StoredValuesList(ValueList):
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")
+ self.paths_without_trailing_path_sep = False
# Add signal handlers
self.signal_handlers["on_stored_values_treeview_mouse_button_press_event"] = \
@@ -526,6 +530,7 @@ class PathChooserPopup(object):
self.set_max_popup_rows(max_visible_rows)
self.popup_window.realize()
self.alignment_widget = popup_alignment_widget
+ self.popup_buttonbox = None # If set, the height of this widget is the minimum height
def popup(self):
"""
@@ -535,8 +540,8 @@ class PathChooserPopup(object):
# 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()
+ #if not self.is_popped_up():
+ self.set_window_position_and_size()
def popdown(self):
if not self.is_popped_up():
@@ -566,7 +571,6 @@ class PathChooserPopup(object):
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()
@@ -580,46 +584,36 @@ class PathChooserPopup(object):
y += self.alignment_widget.allocation.y
height_extra = 8
-
+ buttonbox_width = 0
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 self.popup_buttonbox:
+ buttonbox_height = max(self.popup_buttonbox.size_request()[1], self.popup_buttonbox.allocation.height)
+ buttonbox_width = max(self.popup_buttonbox.size_request()[0], self.popup_buttonbox.allocation.width)
+ treeview_width = self.treeview.size_request()[0]
+ # 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 + buttonbox_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
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
+ content_width = self.treeview.size_request()[0] + buttonbox_width + 10
- # If self.max_visible_rows is -1, not restriction is set
+ # Adjust height according to number of list items
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)
+ # Set height to number of rows
+ height = len(self.tree_store) * self.row_height + height_extra
# 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
@@ -629,9 +623,10 @@ class PathChooserPopup(object):
# 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 self.popup_buttonbox:
+ # Minimum height is the height of the button box
+ if height < buttonbox_height + height_extra:
+ height = buttonbox_height + height_extra
if content_width > width:
width = content_width
@@ -680,7 +675,7 @@ class PathChooserPopup(object):
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)
+ self.path_entry.set_text(self.tree_store[path][0], set_file_chooser_folder=True, trigger_event=True)
if popdown:
self.popdown()
@@ -731,7 +726,6 @@ class StoredValuesPopup(StoredValuesList, PathChooserPopup):
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
@@ -740,6 +734,8 @@ class StoredValuesPopup(StoredValuesList, PathChooserPopup):
PathChooserPopup.__init__(self, 0, max_visible_rows, popup_alignment_widget)
StoredValuesList.__init__(self)
+ self.popup_buttonbox = self.builder.get_object("buttonbox")
+
# Add signal handlers
self.signal_handlers["on_buttonbox_key_press_event"] = \
self.on_buttonbox_key_press_event
@@ -798,8 +794,9 @@ class StoredValuesPopup(StoredValuesList, PathChooserPopup):
"""
swap = event.state & gtk.gdk.CONTROL_MASK
+ scroll_window = event.state & gtk.gdk.SHIFT_MASK
self.handle_list_scroll(next=event.direction == gdk.SCROLL_DOWN,
- set_entry=widget != self.treeview, swap=swap)
+ set_entry=widget != self.treeview, swap=swap, scroll_window=scroll_window)
return True
def on_buttonbox_key_press_event(self, widget, event):
@@ -854,7 +851,7 @@ class StoredValuesPopup(StoredValuesList, PathChooserPopup):
def on_button_default_clicked(self, widget):
if self.default_text:
- self.set_text(self.default_text)
+ self.set_text(self.default_text, trigger_event=True)
class PathCompletionPopup(CompletionList, PathChooserPopup):
"""
@@ -901,6 +898,7 @@ class PathCompletionPopup(CompletionList, PathChooserPopup):
self.popup_window.grab_add()
self.text_entry.grab_focus()
self.text_entry.set_position(len(self.path_entry.text_entry.get_text()))
+ self.set_selected_value(path_without_trailing_path_sep(self.path_entry.get_text()), select_first=True)
###################################################
# Callbacks
@@ -943,7 +941,6 @@ class PathAutoCompleter(object):
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):
@@ -953,22 +950,27 @@ class PathAutoCompleter(object):
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)
+ self.completion_popup.set_selected_value(new_complete_text, select_first=True)
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
+ Do completion when characters are removed
"""
if self.completion_popup.is_popped_up():
- self.completion_popup.popdown()
+ cur_text = self.path_entry.get_text()
+ pos = entry.get_position()
+ new_complete_text = cur_text[:start] + cur_text[end:]
+ self.do_completion(value=new_complete_text, forward_completion=False)
def set_use_popup(self, use):
self.use_popup = use
def on_completion_popup_window_key_press_event(self, entry, event):
"""
+ Handles key pressed events on the auto-completion popup window
"""
# If on_completion_treeview_key_press_event handles the event, do nothing
ret = self.completion_popup.on_completion_treeview_key_press_event(entry, event)
@@ -976,16 +978,12 @@ class PathAutoCompleter(object):
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()
+ self.do_completion()
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)
@@ -993,25 +991,35 @@ class PathAutoCompleter(object):
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()
+ def do_completion(self, value=None, forward_completion=True):
+ if not value:
+ 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)
+ opts = {}
+ opts["show_hidden_files"] = self.completion_popup.show_hidden_files
+ opts["completion_text"] = value
+ opts["forward_completion"] = forward_completion
+ self._start_completion(opts)
- def _start_completion(self, value, hidden_files):
- completion_paths = get_completion_paths(value, hidden_files)
- self._end_completion(value, completion_paths)
+ def _start_completion(self, args):
+ args = get_completion_paths(args)
+ self._end_completion(args)
- 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)
+ def _end_completion(self, args):
+ value = args["completion_text"]
+ paths = args["paths"]
+
+ if args["forward_completion"]:
+ common_prefix = os.path.commonprefix(paths)
+ if len(common_prefix) > len(value):
+ self.path_entry.set_text(common_prefix, set_file_chooser_folder=True, trigger_event=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():
+ elif self.completion_popup.is_popped_up() and args["forward_completion"]:
self.completion_popup.popdown()
class PathChooserComboBox(gtk.HBox, StoredValuesPopup, gobject.GObject):
@@ -1028,6 +1036,7 @@ class PathChooserComboBox(gtk.HBox, StoredValuesPopup, gobject.GObject):
"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, )),
+ "text-changed": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object, )),
}
def __init__(self, max_visible_rows=20, auto_complete=True, use_completer_popup=True):
@@ -1070,6 +1079,7 @@ class PathChooserComboBox(gtk.HBox, StoredValuesPopup, gobject.GObject):
"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,
+ "on_entry_text_changed": self.on_entry_text_changed,
}
signal_handlers.update(self.signal_handlers)
signal_handlers.update(self.auto_completer.signal_handlers)
@@ -1082,11 +1092,12 @@ class PathChooserComboBox(gtk.HBox, StoredValuesPopup, gobject.GObject):
"""
return self.text_entry.get_text()
- def set_text(self, text, set_file_chooser_folder=True, cursor_end=True, default_text=False):
+ def set_text(self, text, set_file_chooser_folder=True, cursor_end=True, default_text=False, trigger_event=False):
"""
Set the text for the entry.
"""
+ old_text = self.text_entry.get_text()
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)
@@ -1097,13 +1108,15 @@ class PathChooserComboBox(gtk.HBox, StoredValuesPopup, gobject.GObject):
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
+ folder_name = ""
if self.show_folder_name_on_button or not self.path_entry_visible:
- 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)
+ folder_name = path_without_trailing_path_sep(text)
+ if not folder_name is "/" and os.path.basename(folder_name):
+ folder_name = os.path.basename(folder_name)
+ self.folder_name_label.set_text(folder_name)
+ # Only trigger event if text has changed
+ if old_text != text and trigger_event:
+ self.on_entry_text_changed(self.text_entry)
def set_sensitive(self, sensitive):
"""
@@ -1236,17 +1249,21 @@ class PathChooserComboBox(gtk.HBox, StoredValuesPopup, gobject.GObject):
"""
self.auto_completer._start_completion = func
- def complete(self, value, paths):
+ def complete(self, args):
"""
Perform the auto completion with the provided paths
"""
- self.auto_completer._end_completion(value, paths)
+ self.auto_completer._end_completion(args)
######################################
## Callbacks and internal functions
######################################
+ def on_entry_text_changed(self, entry):
+ self.emit("text-changed", self.get_text())
+
def _on_entry_text_focus_out_event(self, widget, event):
+ # Update text on the button label
self.set_text(self.get_text())
def _set_path_entry_filechooser_widths(self):
@@ -1276,7 +1293,7 @@ class PathChooserComboBox(gtk.HBox, StoredValuesPopup, gobject.GObject):
if response_id == 0:
text = self.filechooserdialog.get_filename()
- self.set_text(text)
+ self.set_text(text, trigger_event=True)
dialog.hide()
def _on_entry_text_key_press_event(self, widget, event):
@@ -1320,7 +1337,7 @@ class PathChooserComboBox(gtk.HBox, StoredValuesPopup, gobject.GObject):
return True
elif is_ascii_value(keyval, 'd'):
# Set the default value in the text entry
- self.set_text(self.default_text)
+ self.set_text(self.default_text, trigger_event=True)
return True
return False