diff --git a/glade/files_dialog.glade b/glade/files_dialog.glade new file mode 100644 index 000000000..9869cac6d --- /dev/null +++ b/glade/files_dialog.glade @@ -0,0 +1,93 @@ + + + + + + + 5 + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Deluge File Selection + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_CENTER_ON_PARENT + False + 550 + 550 + True + True + True + True + True + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + False + + + + True + False + 1 + + + + True + GTK_BUTTONBOX_END + + + + True + gtk-cancel + True + GTK_RELIEF_NORMAL + True + 0 + + + + + + True + gtk-ok + True + GTK_RELIEF_NORMAL + True + 1 + + + + + 0 + False + True + GTK_PACK_END + + + + + + True + True + GTK_POLICY_ALWAYS + GTK_POLICY_ALWAYS + GTK_SHADOW_NONE + GTK_CORNER_TOP_LEFT + + + + True + True + + + + + 2 + True + True + + + + + + + diff --git a/glade/preferences_dialog.glade b/glade/preferences_dialog.glade index 145523f18..b7cc5dabd 100644 --- a/glade/preferences_dialog.glade +++ b/glade/preferences_dialog.glade @@ -158,35 +158,55 @@ 2 12 - + True - 10 - + True - The number of active torrents that Deluge will run. Set to -1 for unlimited. - 0 - Maximum simultaneous active torrents: + 10 + + + True + The number of active torrents that Deluge will run. Set to -1 for unlimited. + 0 + Maximum simultaneous active torrents: + + + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + The number of active torrents that Deluge will run. Set to -1 for unlimited. + 1 + -1 -1 1000 1 10 10 + 1 + True + GTK_UPDATE_IF_VALID + + + False + 2 + 1 + + - - False - - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - The number of active torrents that Deluge will run. Set to -1 for unlimited. - 1 - -1 -1 1000 1 10 10 - 1 - True - GTK_UPDATE_IF_VALID + Enable selecting files for torrents before loading + Enable selecting files for torrents before loading + 0 + True False - 2 1 diff --git a/src/dialogs.py b/src/dialogs.py index e722184b8..160f67ebd 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -35,6 +35,7 @@ import gtk import gtk.glade import os import os.path +import files PREFS_FILENAME = "prefs.state" @@ -85,6 +86,7 @@ class PreferencesDlg: self.glade.get_widget("finished_path_button").set_sensitive(False) self.glade.get_widget("finished_path_button").set_filename(self.preferences.get("default_finished_path")) self.glade.get_widget("download_path_button").set_filename(self.preferences.get("default_download_path")) + self.glade.get_widget("chk_enable_files_dialog").set_active(self.preferences.get("enable_files_dialog")) self.glade.get_widget("chk_compact").set_active(self.preferences.get("use_compact_storage")) self.glade.get_widget("active_port_label").set_text(str(self.parent.manager.get_state()['port'])) self.glade.get_widget("spin_port_min").set_value(self.preferences.get("listen_on")[0]) @@ -131,6 +133,7 @@ class PreferencesDlg: self.preferences.set("default_download_path", self.glade.get_widget("download_path_button").get_filename()) self.preferences.set("enable_move_completed", self.glade.get_widget("chk_move_completed").get_active()) self.preferences.set("default_finished_path", self.glade.get_widget("finished_path_button").get_filename()) + self.preferences.set("enable_files_dialog", self.glade.get_widget("chk_enable_files_dialog").get_active()) self.preferences.set("auto_end_seeding", self.glade.get_widget("chk_autoseed").get_active()) self.preferences.set("auto_seed_ratio", self.glade.get_widget("ratio_spinner").get_value()) self.preferences.set("use_compact_storage", self.glade.get_widget("chk_compact").get_active()) @@ -168,6 +171,28 @@ class PreferencesDlg: self.glade.get_widget("chk_move_completed").set_sensitive(True) self.glade.get_widget("finished_path_button").set_sensitive(True) +class FilesDlg: + def __init__(self, parent, files_for_dialog): + self.files_for_dialog = files_for_dialog + self.parent = parent + self.glade = gtk.glade.XML(common.get_glade_file("files_dialog.glade"), domain='deluge') + self.dialog = self.glade.get_widget("file_dialog") + self.dialog.set_icon_from_file(common.get_pixmap("deluge32.png")) + self.file_view = self.glade.get_widget("file_view") + + def show(self, manager, unique_id): + self.manager = manager + self.files_for_dialog.clear_file_store() + self.files_for_dialog.use_unique_id(unique_id) + self.files_for_dialog.file_view_actions(self.file_view) + self.files_for_dialog.prepare_store() + self.dialog.show() + r = self.dialog.run() + self.dialog.hide() + self.files_for_dialog.remove_columns() + self.files_for_dialog.clear_file_store() + return r + class PluginDlg: def __init__(self, parent, plugins): self.glade = gtk.glade.XML(common.get_glade_file("plugin_dialog.glade"), domain='deluge') diff --git a/src/files.py b/src/files.py new file mode 100644 index 000000000..858c2f083 --- /dev/null +++ b/src/files.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python +# +# files.py +# +# Copyright (C) Zach Tibbitts 2006 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program 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 this program. 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 sys +import imp +import gtk +import dgtk +import common +from itertools import izip +import gobject + +class FilesManager: + def __init__(self, manager, is_file_tab): + self.manager = manager + self.file_glade = gtk.glade.XML(common.get_glade_file("file_tab_menu.glade"), domain='deluge') + self.file_menu = self.file_glade.get_widget("file_tab_menu") + self.file_glade.signal_autoconnect({ + "select_all": self.file_select_all, + "unselect_all": self.file_unselect_all, + "check_selected": self.file_check_selected, + "uncheck_selected": self.file_uncheck_selected, + }) + self.file_unique_id = -1 + # Stores file path -> gtk.TreeIter's iter mapping for quick look up + # in self.update_torrent_info_widget + self.file_store_dict = {} + self.file_store = gtk.ListStore(bool, str, gobject.TYPE_UINT64) + self.file_store_sorted = gtk.TreeModelSort(self.file_store) + self.is_file_tab = is_file_tab + if self.is_file_tab: + self.file_store = gtk.ListStore(bool, str, gobject.TYPE_UINT64, float) + self.file_store_sorted = gtk.TreeModelSort(self.file_store) + + def use_unique_id(self, unique_id): + self.file_unique_id = unique_id + + def file_view_actions(self, file_view): + self.file_view = file_view + def percent(column, cell, model, iter, data): + percent = float(model.get_value(iter, data)) + percent_str = "%.2f%%"%percent + cell.set_property("text", percent_str) + self.file_selected = [] + self.toggle_column = dgtk.add_toggle_column(self.file_view, _("Download"), 0, toggled_signal=self.file_toggled) + self.filename_column = dgtk.add_text_column(self.file_view, _("Filename"), 1) + self.filename_column.set_expand(True) + self.size_column = dgtk.add_func_column(self.file_view, _("Size"), dgtk.cell_data_size, 2) + if self.is_file_tab: + dgtk.add_func_column(self.file_view, _("Progress"), percent, 3) + self.file_view.set_model(self.file_store_sorted) + self.file_view.get_selection().set_mode(gtk.SELECTION_MULTIPLE) + self.file_view.get_selection().set_select_function(self.file_clicked) + self.file_view.connect("button-press-event", self.file_view_clicked) + + def remove_columns(self): + self.file_view.remove_column(self.size_column) + self.file_view.remove_column(self.filename_column) + self.file_view.remove_column(self.toggle_column) + + def clear_file_store(self): + self.file_store.clear() + self.file_store_dict = {} + + def prepare_store(self): + if not self.file_store_dict: + all_files = self.manager.get_torrent_file_info(self.file_unique_id) + file_filter = self.manager.get_file_filter(self.file_unique_id) + if file_filter is None: + file_filter = [False] * len(all_files) + if self.is_file_tab: + for file, filt in izip(all_files, file_filter): + iter = self.file_store.append([not filt, file['path'], + file['size'], + round(file['progress'], 2)]) + self.file_store_dict[file['path']] = iter + else: + for file, filt in izip(all_files, file_filter): + iter = self.file_store.append([not filt, file['path'], + file['size']]) + self.file_store_dict[file['path']] = iter + + + def update_store(self): + new_file_info = self.manager.get_torrent_file_info(self.file_unique_id) + for file in new_file_info: + iter = self.file_store_dict[file['path']] + if self.file_store.get_value(iter, 3) != round(file['progress'], 2): + self.file_store.set(iter, 3, file['progress']) + + def file_select_all(self, widget): + self.file_view.get_selection().select_all() + + def file_unselect_all(self, widget): + self.file_view.get_selection().unselect_all() + + def file_check_selected(self, widget): + self.file_view.get_selection().selected_foreach(self.file_toggle_selected, True) + self.file_toggled_update_filter() + + def file_uncheck_selected(self, widget): + self.file_view.get_selection().selected_foreach(self.file_toggle_selected, False) + self.file_toggled_update_filter() + + def file_clicked(self, path): + return not self.file_selected + + def file_view_clicked(self, widget, event): + if event.button == 3: + self.file_menu.popup(None, None, None, event.button, event.time) + return True + else: + self.file_selected = False + return False + + def file_toggle_selected(self, treemodel, path, selected_iter, value): + child_iter = self.file_store_sorted.convert_iter_to_child_iter(None, + selected_iter) + self.file_store_sorted.get_model().set_value(child_iter, 0, value) + + def file_toggled(self, renderer, path): + self.file_selected = True + file_iter = self.file_store_sorted.get_iter_from_string(path) + value = not renderer.get_active() + selection = self.file_view.get_selection() + if selection.iter_is_selected(file_iter): + selection.selected_foreach(self.file_toggle_selected, value) + else: + child_iter = self.file_store_sorted.convert_iter_to_child_iter( + None, file_iter) + self.file_store_sorted.get_model().set_value(child_iter, 0, value) + + self.file_toggled_update_filter() + + def file_toggled_update_filter(self): + file_filter = [not x[0] for x in self.file_store] + self.manager.set_file_filter(self.file_unique_id, file_filter) diff --git a/src/interface.py b/src/interface.py index 058631eb8..d124b5f28 100644 --- a/src/interface.py +++ b/src/interface.py @@ -47,6 +47,7 @@ import common import dialogs import dgtk import ipc_manager +import files import plugins class DelugeGTK: @@ -66,6 +67,8 @@ class DelugeGTK: #Start the Deluge Manager: self.manager = core.Manager(common.CLIENT_CODE, common.CLIENT_VERSION, '%s %s'%(common.PROGRAM_NAME, common.PROGRAM_VERSION), common.CONFIG_DIR) + self.files_for_tab = files.FilesManager(self.manager, True) + self.files_for_dialog = files.FilesManager(self.manager, False) self.plugins = plugins.PluginManager(self.manager, self) self.plugins.add_plugin_dir(common.PLUGIN_DIR) if os.path.isdir(os.path.join(common.CONFIG_DIR , 'plugins')): @@ -102,6 +105,7 @@ class DelugeGTK: self.preferences_dialog = dialogs.PreferencesDlg(self, self.config) self.plugin_dialog = dialogs.PluginDlg(self, self.plugins) + self.files_dialog = dialogs.FilesDlg(self, self.files_for_dialog) self.build_torrent_table() self.build_summary_tab() self.build_file_tab() @@ -658,88 +662,13 @@ class DelugeGTK: self.peer_store_dict = {} def build_file_tab(self): - def percent(column, cell, model, iter, data): - percent = float(model.get_value(iter, data)) - percent_str = "%.2f%%"%percent - cell.set_property("text", percent_str) - - + self.files_for_tab.clear_file_store() + self.files_for_tab.use_unique_id(self.get_selected_torrent()) self.file_view = self.wtree.get_widget("file_view") - self.file_glade = gtk.glade.XML(common.get_glade_file("file_tab_menu.glade"), domain='deluge') - self.file_menu = self.file_glade.get_widget("file_tab_menu") - self.file_glade.signal_autoconnect({ - "select_all": self.file_select_all, - "unselect_all": self.file_unselect_all, - "check_selected": self.file_check_selected, - "uncheck_selected": self.file_uncheck_selected, - }) - self.file_store = gtk.ListStore(bool, str, gobject.TYPE_UINT64, float) - self.file_store_sorted = gtk.TreeModelSort(self.file_store) - # Stores file path -> gtk.TreeIter's iter mapping for quick look up - # in self.update_torrent_info_widget - self.file_store_dict = {} - self.file_view.set_model(self.file_store_sorted) - self.file_view.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - self.file_view.get_selection().set_select_function(self.file_clicked) - self.file_selected = [] - self.file_view.connect("button-press-event", self.file_view_clicked) - - dgtk.add_toggle_column(self.file_view, _("Download"), 0, toggled_signal=self.file_toggled) - dgtk.add_text_column(self.file_view, _("Filename"), 1).set_expand(True) - dgtk.add_func_column(self.file_view, _("Size"), dgtk.cell_data_size, 2) - dgtk.add_func_column(self.file_view, _("Progress"), percent, 3) + self.files_for_tab.file_view_actions(self.file_view) def clear_file_store(self): - self.file_store.clear() - self.file_store_dict = {} - - def file_select_all(self, widget): - self.file_view.get_selection().select_all() - - def file_unselect_all(self, widget): - self.file_view.get_selection().unselect_all() - - def file_check_selected(self, widget): - self.file_view.get_selection().selected_foreach(self.file_toggle_selected, True) - self.file_toggled_update_filter() - - def file_uncheck_selected(self, widget): - self.file_view.get_selection().selected_foreach(self.file_toggle_selected, False) - self.file_toggled_update_filter() - - def file_clicked(self, path): - return not self.file_selected - - def file_view_clicked(self, widget, event): - if event.button == 3: - self.file_menu.popup(None, None, None, event.button, event.time) - return True - else: - self.file_selected = False - return False - - def file_toggle_selected(self, treemodel, path, selected_iter, value): - child_iter = self.file_store_sorted.convert_iter_to_child_iter(None, - selected_iter) - self.file_store_sorted.get_model().set_value(child_iter, 0, value) - - def file_toggled(self, renderer, path): - self.file_selected = True - file_iter = self.file_store_sorted.get_iter_from_string(path) - value = not renderer.get_active() - selection = self.file_view.get_selection() - if selection.iter_is_selected(file_iter): - selection.selected_foreach(self.file_toggle_selected, value) - else: - child_iter = self.file_store_sorted.convert_iter_to_child_iter( - None, file_iter) - self.file_store_sorted.get_model().set_value(child_iter, 0, value) - - self.file_toggled_update_filter() - - def file_toggled_update_filter(self): - file_filter = [not x[0] for x in self.file_store] - self.manager.set_file_filter(self.get_selected_torrent(), file_filter) + self.files_for_tab.clear_file_store() def show_about_dialog(self, arg=None): dialogs.show_about_dialog() @@ -1108,23 +1037,9 @@ class DelugeGTK: elif page_num == 2: # Files # Fill self.file_store with files only once and only when we click to # Files tab or it's already open - if not self.file_store_dict: - all_files = self.manager.get_torrent_file_info(unique_id) - file_filter = self.manager.get_file_filter(unique_id) - if file_filter is None: - file_filter = [False] * len(all_files) - for file, filt in izip(all_files, file_filter): - iter = self.file_store.append([not filt, file['path'], - file['size'], - round(file['progress'], 2)]) - self.file_store_dict[file['path']] = iter - - new_file_info = self.manager.get_torrent_file_info(unique_id) - - for file in new_file_info: - iter = self.file_store_dict[file['path']] - if self.file_store.get_value(iter, 3) != round(file['progress'], 2): - self.file_store.set(iter, 3, file['progress']) + self.files_for_tab.use_unique_id(unique_id) + self.files_for_tab.prepare_store() + self.files_for_tab.update_store() def calc_share_ratio(self, unique_id, torrent_state): @@ -1178,6 +1093,13 @@ class DelugeGTK: return try: unique_id = self.manager.add_torrent(torrent, path, self.config.get('use_compact_storage')) + if not append and self.config.get('enable_files_dialog'): + self.manager.set_user_pause(unique_id, True) + if self.files_dialog.show(self.manager, unique_id) == 1: + self.manager.set_user_pause(unique_id, False) + else: + self.manager.remove_torrent(unique_id, True, True) + except core.InvalidEncodingError, e: print "InvalidEncodingError", e dialogs.show_popup_warning(self.window, _("An error occured while trying to add the torrent. It's possible your .torrent file is corrupted.")) @@ -1191,7 +1113,15 @@ class DelugeGTK: _("Available Space:") + " " + nice_free) else: if append: - self.torrent_model_append(unique_id) + if self.config.get('enable_files_dialog'): + self.manager.set_user_pause(unique_id, True) + if self.files_dialog.show(self.manager, unique_id) == 1: + self.manager.set_user_pause(unique_id, False) + self.torrent_model_append(unique_id) + else: + self.manager.remove_torrent(unique_id, True, True) + else: + self.torrent_model_append(unique_id) def launchpad(self, obj=None): common.open_url_in_browser('self', 'https://translations.launchpad.net/deluge/trunk/+pots/deluge') diff --git a/src/pref.py b/src/pref.py index 3d41d1712..eace2c707 100644 --- a/src/pref.py +++ b/src/pref.py @@ -41,6 +41,7 @@ DEFAULT_PREFS = { "auto_end_seeding" : False, "auto_seed_ratio" : 0, "close_to_tray" : False, + "enable_files_dialog" : False, "default_download_path" : os.path.expanduser("~/"), "default_load_path" : os.path.expanduser("~/"), "default_finished_path" : os.path.expanduser("~/"),