mirror of
https://github.com/codex-storage/deluge.git
synced 2025-01-12 12:34:43 +00:00
Small fixes to path chooser
* Handle completion when removing characteres while the popup is visible * Set Apply button sensitive when changing move completed path in options tab
This commit is contained in:
parent
19184518e9
commit
02bc00bfa5
@ -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):
|
||||
|
@ -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
|
||||
|
@ -955,6 +955,7 @@
|
||||
<property name="secondary_icon_activatable">False</property>
|
||||
<property name="primary_icon_sensitive">True</property>
|
||||
<property name="secondary_icon_sensitive">True</property>
|
||||
<signal name="changed" handler="on_entry_text_changed" swapped="no"/>
|
||||
<signal name="insert-text" handler="on_entry_text_insert_text" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_entry_text_key_press_event" swapped="no"/>
|
||||
<signal name="delete-text" handler="on_entry_text_delete_text" swapped="no"/>
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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,6 +107,7 @@ class ValueList(object):
|
||||
self.tree_store.clear()
|
||||
|
||||
for path in paths:
|
||||
if self.paths_without_trailing_path_sep:
|
||||
path = path_without_trailing_path_sep(path)
|
||||
if append:
|
||||
tree_iter = self.tree_store.append([path])
|
||||
@ -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,7 +540,7 @@ class PathChooserPopup(object):
|
||||
# Entry is not yet visible
|
||||
if not (self.path_entry.flags() & gtk.REALIZED):
|
||||
return
|
||||
if not self.is_popped_up():
|
||||
#if not self.is_popped_up():
|
||||
self.set_window_position_and_size()
|
||||
|
||||
def popdown(self):
|
||||
@ -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]
|
||||
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]
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
if self.popup_buttonbox:
|
||||
# Minimum height is the height of the button box
|
||||
if height < butonbox_height + height_extra:
|
||||
height = butonbox_height + height_extra
|
||||
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):
|
||||
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):
|
||||
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)
|
||||
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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user