def __init__(self, images, tabclosers=False, show_hilite_image=True, show_status_image=False, notebookraw=None): # We store the real Gtk.Notebook object self.notebook = notebookraw self.notebook.set_show_border(False) self.tabclosers = tabclosers self.images = images self._show_hilite_image = show_hilite_image self._show_status_image = show_status_image self.key_controller = connect_key_press_event(self.notebook, self.on_key_press_event) self.notebook.connect("switch-page", self.on_switch_page) self.unread_button = Gtk.MenuButton.new() if Gtk.get_major_version() == 4: self.window = self.notebook.get_root() self.unread_button.set_icon_name("emblem-important-symbolic") self.unread_button.set_has_frame(False) else: self.window = self.notebook.get_toplevel() self.popup_enable() self.unread_button.set_image( Gtk.Image.new_from_icon_name("emblem-important-symbolic", Gtk.IconSize.BUTTON)) self.unread_button.set_relief(Gtk.ReliefStyle.NONE) self.unread_button.set_tooltip_text(_("Unread Tabs")) self.unread_button.set_halign(Gtk.Align.CENTER) self.unread_button.set_valign(Gtk.Align.CENTER) context = self.unread_button.get_style_context() context.add_class("circular") self.notebook.set_action_widget(self.unread_button, Gtk.PackType.END) self.popup_menu_unread = PopupMenu(widget=self.unread_button, connect_events=False) self.unread_button.set_menu_model(self.popup_menu_unread) self.unread_pages = [] self.notebook.hide()
def __init__(self, chats, user, status): self.user = user self.chats = chats self.frame = chats.frame load_ui_elements( self, os.path.join(self.frame.gui_dir, "ui", "privatechat.ui")) self.autoreplied = False self.offlinemessage = False self.status = status # Text Search TextSearchBar(self.ChatScroll, self.SearchBar, self.SearchEntry) # Chat Entry self.entry = ChatEntry(self.frame, self.ChatLine, user, slskmessages.MessageUser, self.send_message, self.chats.CMDS, self.ChatScroll) self.Log.set_active(config.sections["logging"]["privatechat"]) self.popup_menu_user = popup = PopupMenu(self.frame) popup.setup_user_menu(user, page="privatechat") popup.setup(("", None), ("#" + _("Close All Tabs"), self.on_close_all_tabs), ("#" + _("_Close Tab"), self.on_close)) self.popup_menu = popup = PopupMenu(self.frame) popup.setup( ("#" + _("Find"), self.on_find_chat_log), ("", None), ("#" + _("Copy"), self.on_copy_chat_log), ("#" + _("Copy All"), self.on_copy_all_chat_log), ("", None), ("#" + _("View Chat Log"), self.on_view_chat_log), ("#" + _("Delete Chat Log"), self.on_delete_chat_log), ("", None), ("#" + _("Clear Message View"), self.on_clear_messages), ("", None), (">" + _("User"), self.popup_menu_user), ) popup.set_user(user) self.create_tags() self.update_visuals() self.set_completion_list(list(self.chats.completion_list)) self.read_private_log()
def __init__(self, frame): super().__init__("ui/popovers/roomlist.ui") self.frame = frame self.room_iters = {} self.initializing_feed = False self.room_model = Gtk.ListStore(str, int, Pango.Weight, Pango.Underline) self.room_filter = self.room_model.filter_new() self.room_filter.set_visible_func(self.room_match_function) self.room_model_filtered = Gtk.TreeModelSort(model=self.room_filter) self.list_view.set_model(self.room_model_filtered) self.column_numbers = list(range(self.room_model.get_n_columns())) attribute_columns = (2, 3) self.cols = initialise_columns( frame, None, self.list_view, ["room", _("Room"), 260, "text", attribute_columns], ["users", _("Users"), 100, "number", attribute_columns]) self.cols["room"].set_sort_column_id(0) self.cols["users"].set_sort_column_id(1) self.popup_room = None self.popup_menu = PopupMenu(self.frame, self.list_view, self.on_popup_menu) self.popup_menu.add_items(("#" + _("Join Room"), self.on_popup_join), ("#" + _("Leave Room"), self.on_popup_leave), ("", None), ("#" + _("Disown Private Room"), self.on_popup_private_room_disown), ("#" + _("Cancel Room Membership"), self.on_popup_private_room_dismember)) self.private_room_check.set_active( config.sections["server"]["private_chatrooms"]) self.private_room_check.connect("toggled", self.on_toggle_accept_private_room) Accelerator("<Primary>f", self.popover, self.on_search_accelerator) CompletionEntry(frame.ChatroomsEntry, self.room_model, column=0) if Gtk.get_major_version() == 4: frame.RoomList.get_first_child().get_style_context().add_class( "arrow-button") frame.RoomList.set_popover(self.popover)
def __init__(self, frame, joined_rooms, private_rooms): # Build the window self.frame = frame self.server_rooms = set() self.joined_rooms = joined_rooms self.private_rooms = private_rooms load_ui_elements( self, os.path.join(self.frame.gui_dir, "ui", "popovers", "roomlist.ui")) self.room_model = Gtk.ListStore(str, int, int) self.column_numbers = list(range(self.room_model.get_n_columns())) self.cols = initialise_columns( None, self.RoomsList, ["room", _("Room"), 260, "text", self.room_status], ["users", _("Users"), 100, "number", self.room_status]) self.cols["room"].set_sort_column_id(0) self.cols["users"].set_sort_column_id(1) self.popup_room = None self.popup_menu = PopupMenu(self.frame) self.popup_menu.setup( ("#" + _("Join Room"), self.on_popup_join), ("#" + _("Leave Room"), self.on_popup_leave), ("", None), ("#" + _("Disown Private Room"), self.on_popup_private_room_disown), ("#" + _("Cancel Room Membership"), self.on_popup_private_room_dismember), ("", None), ("#" + _("Join Public Room"), self.on_join_public_room)) self.RoomsList.connect("button_press_event", self.on_list_clicked) self.RoomsList.connect("popup-menu", self.on_popup_menu) self.RoomsList.connect("touch_event", self.on_list_clicked) self.RoomsList.set_headers_clickable(True) self.search_iter = None self.query = "" self.AcceptPrivateRoom.set_active( config.sections["server"]["private_chatrooms"]) self.AcceptPrivateRoom.connect("toggled", self.on_toggle_accept_private_room) frame.RoomList.connect("clicked", self.show) self.RoomListPopover.set_relative_to(frame.RoomList)
def press_header(widget, event): if not triggers_context_menu(event): return False treeview = widget.get_parent() columns = treeview.get_columns() visible_columns = [column for column in columns if column.get_visible()] menu = PopupMenu(window=widget.get_toplevel()) actions = menu.get_actions() pos = 1 for column in columns: title = column.get_widget().get_text() if title == "": title = _("Column #%i") % pos menu.setup(("$" + title, None)) actions[title].set_state( GLib.Variant.new_boolean(column in visible_columns)) if column in visible_columns: actions[title].set_enabled(len(visible_columns) > 1) actions[title].connect("activate", header_toggle, treeview, columns, pos - 1) pos += 1 menu.popup() return True
def __init__(self, frame): IconNotebook.__init__(self, frame, frame.search_notebook, "search") self.notebook.connect("switch-page", self.on_switch_search_page) self.modes = { "global": _("_Global"), "buddies": _("_Buddies"), "rooms": _("_Rooms"), "user": _("_User") } mode_menu = PopupMenu(frame) mode_menu.add_items( ("O" + self.modes["global"], "win.searchmode", "global"), ("O" + self.modes["buddies"], "win.searchmode", "buddies"), ("O" + self.modes["rooms"], "win.searchmode", "rooms"), ("O" + self.modes["user"], "win.searchmode", "user")) mode_menu.update_model() frame.SearchMode.set_menu_model(mode_menu.model) frame.SearchModeLabel.set_label(self.modes["global"]) if Gtk.get_major_version() == 4: frame.SearchMode.get_first_child().get_style_context().add_class( "arrow-button") CompletionEntry(frame.RoomSearchEntry, frame.RoomSearchCombo.get_model()) CompletionEntry(frame.SearchEntry, frame.SearchCombo.get_model()) self.wish_list = WishList(frame, self) self.populate_search_history() self.update_visuals()
def __init__(self, images, angle=0, tabclosers=False, show_hilite_image=True, reorderable=True, show_status_image=False, notebookraw=None): # We store the real Gtk.Notebook object self.notebook = notebookraw self.notebook.set_show_border(False) self.tabclosers = tabclosers self.reorderable = reorderable self.images = images self._show_hilite_image = show_hilite_image self._show_status_image = show_status_image self.notebook.connect("key-press-event", self.on_key_press_event) self.notebook.connect("switch-page", self.on_switch_page) self.unread_button = Gtk.Button.new_from_icon_name( "emblem-important-symbolic", Gtk.IconSize.BUTTON) self.unread_button.set_relief(Gtk.ReliefStyle.NONE) self.unread_button.set_tooltip_text(_("Unread Tabs")) self.unread_button.set_halign(Gtk.Align.CENTER) self.unread_button.set_valign(Gtk.Align.CENTER) self.unread_button.connect("clicked", self.on_unread_notifications_menu) context = self.unread_button.get_style_context() context.add_class("circular") self.notebook.set_action_widget(self.unread_button, Gtk.PackType.END) self.popup_menu_unread = PopupMenu(window=self.notebook.get_toplevel()) self.unread_pages = [] self.angle = angle
def populate_popup_menu_users(self): self.popup_menu_users.clear() if not self.selected_users: return for user in self.selected_users: popup = PopupMenu(self.frame) popup.setup_user_menu(user) popup.setup(("", None), ("#" + _("Select User's Transfers"), self.on_select_user_transfers, user)) popup.toggle_user_items() self.popup_menu_users.setup((">" + user, popup))
def hide_columns(treeview, cols, config): for (column_id, column) in cols.items(): parent = column.get_button() if parent: PopupMenu(widget=parent, callback=press_header) # Read Show / Hide column settings from last session if config: try: column.set_visible(config[column_id]["visible"]) except Exception: # Invalid value pass
def populate_popup_menu_users(self): self.popup_menu_users.clear() if not self.selected_users: return # Multiple users, create submenus for each user if len(self.selected_users) > 1: for user in self.selected_users: popup = PopupMenu(self.frame) self.add_popup_menu_user(popup, user) self.popup_menu_users.setup((">" + user, popup)) return # Single user, add items directly to "User(s)" submenu self.add_popup_menu_user(self.popup_menu_users, self.selected_users[0])
def populate_popup_menu_users(self): self.popup_menu_users.clear() if not self.selected_users: return # Multiple users, create submenus for each user if len(self.selected_users) > 1: for user in self.selected_users: popup = PopupMenu(self.frame) self.add_popup_menu_user(popup, user) self.popup_menu_users.add_items((">" + user, popup)) self.popup_menu_users.update_model() return # Single user, add items directly to "User(s)" submenu user = next(iter(self.selected_users), None) self.add_popup_menu_user(self.popup_menu_users, user)
def initialise_columns(frame, treeview_name, treeview, *args): i = 0 cols = OrderedDict() num_cols = len(args) column_config = None # GTK 4 rows need more padding to match GTK 3 if Gtk.get_major_version() == 4: progress_padding = 1 height_padding = 5 else: progress_padding = 0 height_padding = 3 width_padding = 10 for column_id, title, width, column_type, extra in args: if treeview_name: try: column_config = config.sections["columns"][treeview_name[0]][treeview_name[1]] except KeyError: column_config = config.sections["columns"][treeview_name] try: width = column_config[column_id]["width"] except Exception: # Invalid value pass if not isinstance(width, int): width = 0 xalign = 0 if column_type == "text": renderer = Gtk.CellRendererText(xpad=width_padding, ypad=height_padding) column = Gtk.TreeViewColumn(column_id, renderer, text=i) elif column_type == "number": xalign = 1 renderer = Gtk.CellRendererText(xalign=xalign, xpad=width_padding, ypad=height_padding) column = Gtk.TreeViewColumn(column_id, renderer, text=i) column.set_alignment(xalign) elif column_type == "edit": renderer = Gtk.CellRendererText(editable=True, xpad=width_padding, ypad=height_padding) column = Gtk.TreeViewColumn(column_id, renderer, text=i) elif column_type == "progress": renderer = Gtk.CellRendererProgress(ypad=progress_padding) column = Gtk.TreeViewColumn(column_id, renderer, value=i) elif column_type == "toggle": xalign = 0.5 renderer = Gtk.CellRendererToggle(xalign=xalign, xpad=13) column = Gtk.TreeViewColumn(column_id, renderer, active=i) elif column_type == "icon": renderer = Gtk.CellRendererPixbuf() if column_id == "country": if Gtk.get_major_version() == 4: # Custom icon size defined in theme.py renderer.set_property("icon-size", Gtk.IconSize.NORMAL) else: # Use the same size as the original icon renderer.set_property("stock-size", 0) column = Gtk.TreeViewColumn(column_id, renderer, icon_name=i) else: column = Gtk.TreeViewColumn(column_id, renderer, gicon=i) if width == -1: column.set_resizable(False) column.set_expand(True) column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) else: column.set_resizable(True) column.set_min_width(0) if width == 0: column.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY) else: column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) column.set_fixed_width(width) if isinstance(extra, int): column.add_attribute(renderer, "foreground", extra) elif isinstance(extra, tuple): weight, underline = extra column.add_attribute(renderer, "weight", weight) column.add_attribute(renderer, "underline", underline) # Allow individual cells to receive visual focus if num_cols > 1 and column_type != "edit": renderer.set_property("mode", Gtk.CellRendererMode.ACTIVATABLE) column.set_reorderable(True) column.set_min_width(20) label = Gtk.Label(label=title, margin_start=5, margin_end=5, visible=True) column.set_widget(label) if xalign == 1 and Gtk.get_major_version() == 4: # Gtk.TreeViewColumn.set_alignment() only changes the sort arrow position in GTK 4 # Actually align the label to the right here instead label.get_parent().set_halign(Gtk.Align.END) cols[column_id] = column i += 1 append_columns(treeview, cols, column_config) hide_columns(treeview, cols, column_config) treeview.connect("columns-changed", set_last_column_autosize) treeview.emit("columns-changed") Accelerator("<Primary>c", treeview, on_copy_cell_data_accelerator) treeview.column_menu = PopupMenu(frame, treeview, callback=press_header, connect_events=False) if Gtk.get_major_version() == 4: # Hotfix: disable rubber-band selection in GTK 4 to avoid crash bug # when clicking column headers treeview.set_rubber_banding(False) return cols
class Search: def __init__(self, searches, text, id, mode, remember, showtab): self.searches = searches self.frame = searches.frame # Build the window load_ui_elements(self, os.path.join(self.frame.gui_dir, "ui", "search.ui")) self.text = text self.searchterm_words_include = [ p for p in text.lower().split() if not p.startswith('-') ] self.searchterm_words_ignore = [ p[1:] for p in text.lower().split() if p.startswith('-') and len(p) > 1 ] self.id = id self.mode = mode self.remember = remember self.showtab = showtab self.usersiters = {} self.directoryiters = {} self.users = set() self.all_data = [] self.filters = None self.clearing_filters = False self.resultslimit = 2000 self.numvisibleresults = 0 self.active_filter_count = 0 self.operators = { '<': operator.lt, '<=': operator.le, '==': operator.eq, '!=': operator.ne, '>=': operator.ge, '>': operator.gt } if mode not in ("global", "wishlist"): self.RememberCheckButton.hide() self.RememberCheckButton.set_active(remember) """ Columns """ self.resultsmodel = Gtk.TreeStore( GObject.TYPE_UINT64, # (0) num str, # (1) user GObject.TYPE_OBJECT, # (2) flag str, # (3) immediatedl str, # (4) h_speed str, # (5) h_queue str, # (6) directory str, # (7) filename str, # (8) h_size str, # (9) h_bitrate str, # (10) h_length GObject.TYPE_UINT64, # (11) bitrate str, # (12) fullpath str, # (13) country GObject.TYPE_UINT64, # (14) size GObject.TYPE_UINT64, # (15) speed GObject.TYPE_UINT64, # (16) queue GObject.TYPE_UINT64, # (17) length str # (18) color ) self.column_numbers = list(range(self.resultsmodel.get_n_columns())) color_col = 18 self.cols = cols = initialise_columns( "file_search", self.ResultsList, ["id", _("ID"), 50, "text", color_col], ["user", _("User"), 200, "text", color_col], ["country", _("Country"), 25, "pixbuf", None], [ "immediate_download", _("Immediate Download"), 50, "center", color_col ], ["speed", _("Speed"), 90, "number", color_col], ["in_queue", _("In Queue"), 90, "center", color_col], ["folder", _("Folder"), 400, "text", color_col], ["filename", _("Filename"), 400, "text", color_col], ["size", _("Size"), 100, "number", color_col], ["bitrate", _("Bitrate"), 100, "number", color_col], ["length", _("Length"), 100, "number", color_col]) cols["id"].set_sort_column_id(0) cols["user"].set_sort_column_id(1) cols["country"].set_sort_column_id(13) cols["immediate_download"].set_sort_column_id(3) cols["speed"].set_sort_column_id(15) cols["in_queue"].set_sort_column_id(16) cols["folder"].set_sort_column_id(6) cols["filename"].set_sort_column_id(7) cols["size"].set_sort_column_id(14) cols["bitrate"].set_sort_column_id(11) cols["length"].set_sort_column_id(17) cols["country"].get_widget().hide() self.ResultsList.set_model(self.resultsmodel) self.update_visuals() """ Filters """ self.ShowFilters.set_active( config.sections["searches"]["filters_visible"]) self.populate_filters() """ Popup """ self.popup_menu_users = PopupMenu(self.frame) self.popup_menu = PopupMenu(self.frame) self.popup_menu.setup( ("#" + "selected_files", None), ("", None), ("#" + _("_Download File(s)"), self.on_download_files), ("#" + _("Download File(s) _To..."), self.on_download_files_to), ("#" + _("Download _Folder(s)"), self.on_download_folders), ("#" + _("Download F_older(s) To..."), self.on_download_folders_to), ("#" + _("_Browse Folder"), self.on_browse_folder), ("#" + _("File _Properties"), self.on_file_properties), ("", None), ("#" + _("Copy _File Path"), self.on_copy_file_path), ("#" + _("Copy _URL"), self.on_copy_url), ("#" + _("Copy Folder U_RL"), self.on_copy_dir_url), ("", None), (">" + _("User(s)"), self.popup_menu_users)) self.tab_menu = PopupMenu(self.frame) self.tab_menu.setup( ("#" + _("Copy Search Term"), self.on_copy_search_term), ("", None), ("#" + _("Clear All Results"), self.on_clear), ("#" + _("Close All Tabs"), self.on_close_all_tabs), ("#" + _("_Close Tab"), self.on_close)) """ Grouping """ self.ResultGrouping.set_active( config.sections["searches"]["group_searches"]) self.ExpandButton.set_active( config.sections["searches"]["expand_searches"]) def on_tooltip(self, widget, x, y, keyboard_mode, tooltip): country_tooltip = show_country_tooltip(widget, x, y, tooltip, 13, strip_prefix="") file_path_tooltip = show_file_path_tooltip(widget, x, y, tooltip, 12) if country_tooltip: return country_tooltip elif file_path_tooltip: return file_path_tooltip def populate_filters(self, set_default_filters=True): for combobox in (self.FilterIn, self.FilterOut, self.FilterType, self.FilterSize, self.FilterBitrate, self.FilterCountry): combobox.remove_all() if set_default_filters and config.sections["searches"]["enablefilters"]: sfilter = config.sections["searches"]["defilter"] self.FilterInEntry.set_text(str(sfilter[0])) self.FilterOutEntry.set_text(str(sfilter[1])) self.FilterSizeEntry.set_text(str(sfilter[2])) self.FilterBitrateEntry.set_text(str(sfilter[3])) self.FilterFreeSlot.set_active(sfilter[4]) if len(sfilter) > 5: self.FilterCountryEntry.set_text(str(sfilter[5])) if len(sfilter) > 6: self.FilterTypeEntry.set_text(str(sfilter[6])) self.on_refilter(None) for i in ['0', '128', '160', '192', '256', '320']: self.FilterBitrate.append_text(i) for i in [">10MiB", "<10MiB", "<5MiB", "<1MiB", ">0"]: self.FilterSize.append_text(i) for i in [ 'flac|wav|ape|aiff|wv|cue', 'mp3|m4a|aac|ogg|opus|wma', '!mp3' ]: self.FilterType.append_text(i) for i in config.sections["searches"]["filterin"]: self.add_combo(self.FilterIn, i, True) for i in config.sections["searches"]["filterout"]: self.add_combo(self.FilterOut, i, True) for i in config.sections["searches"]["filtersize"]: self.add_combo(self.FilterSize, i, True) for i in config.sections["searches"]["filterbr"]: self.add_combo(self.FilterBitrate, i, True) for i in config.sections["searches"]["filtercc"]: self.add_combo(self.FilterCountry, i, True) for i in config.sections["searches"]["filtertype"]: self.add_combo(self.FilterType, i, True) def focus_combobox(self, button): # We have the button of a combobox, find the entry parent = button.get_parent() if parent is None: return if isinstance(parent, Gtk.ComboBox): entry = parent.get_child() entry.grab_focus() GLib.idle_add(entry.emit, "activate") return self.focus_combobox(parent) def add_combo(self, combobox, text, list=False): text = str(text).strip() if not text: return False model = combobox.get_model() iterator = model.get_iter_first() match = False while iterator is not None: value = model.get_value(iterator, 0) if value.strip() == text: match = True iterator = model.iter_next(iterator) if not match: if list: combobox.append_text(text) else: combobox.prepend_text(text) def add_user_results(self, msg, user, country): if user in self.users: return self.users.add(user) counter = len(self.all_data) + 1 inqueue = msg.inqueue ulspeed = msg.ulspeed h_speed = human_speed(ulspeed) if msg.freeulslots: imdl = "Y" inqueue = 0 else: imdl = "N" color_id = (imdl == "Y" and "search" or "searchq") color = config.sections["ui"][color_id] or None h_queue = humanize(inqueue) update_ui = False maxstoredresults = config.sections["searches"]["max_stored_results"] for result in msg.list: if counter > maxstoredresults: break fullpath = result[1] fullpath_lower = fullpath.lower() if any(word in fullpath_lower for word in self.searchterm_words_ignore): """ Filter out results with filtered words (e.g. nicotine -music) """ log.add_search( _("Filtered out excluded search result " + fullpath + " from user " + user)) continue if not any(word in fullpath_lower for word in self.searchterm_words_include): """ Some users may send us wrong results, filter out such ones """ log.add_search( _("Filtered out inexact or incorrect search result " + fullpath + " from user " + user)) continue fullpath_split = reversed(fullpath.split('\\')) name = next(fullpath_split) directory = '\\'.join(fullpath_split) size = result[2] h_size = human_size(size) h_bitrate, bitrate, h_length, length = get_result_bitrate_length( size, result[4]) is_result_visible = self.append([ GObject.Value(GObject.TYPE_UINT64, counter), user, GObject.Value(GObject.TYPE_OBJECT, self.frame.get_flag_image(country)), imdl, h_speed, h_queue, directory, name, h_size, h_bitrate, h_length, GObject.Value(GObject.TYPE_UINT64, bitrate), fullpath, country, GObject.Value(GObject.TYPE_UINT64, size), GObject.Value(GObject.TYPE_UINT64, ulspeed), GObject.Value(GObject.TYPE_UINT64, inqueue), GObject.Value(GObject.TYPE_UINT64, length), GObject.Value(GObject.TYPE_STRING, color) ]) if is_result_visible: update_ui = True counter += 1 if update_ui: # If this search wasn't initiated by us (e.g. wishlist), and the results aren't spoofed, show tab if not self.showtab: self.searches.show_tab(self, self.id, self.text, self.mode) self.showtab = True # Update number of results self.update_result_counter() # Update tab notification self.frame.searches.request_changed(self.Main) self.frame.request_tab_icon(self.frame.SearchTabLabel) def append(self, row): self.all_data.append(row) if self.numvisibleresults >= config.sections["searches"][ "max_displayed_results"]: return False if not self.check_filter(row): return False iterator = self.add_row_to_model(row) if self.ResultGrouping.get_active_id() != "ungrouped": # Group by folder or user if self.ExpandButton.get_active(): path = None if iterator is not None: path = self.resultsmodel.get_path(iterator) if path is not None: self.ResultsList.expand_to_path(path) else: collapse_treeview(self.ResultsList, self.ResultGrouping.get_active_id()) return True def add_row_to_model(self, row): counter, user, flag, immediatedl, h_speed, h_queue, directory, filename, h_size, h_bitrate, h_length, bitrate, fullpath, country, size, speed, queue, length, color = row if self.ResultGrouping.get_active_id() != "ungrouped": # Group by folder or user empty_int = 0 empty_str = "" if user not in self.usersiters: self.usersiters[user] = self.resultsmodel.insert_with_values( None, -1, self.column_numbers, [ empty_int, user, flag, immediatedl, h_speed, h_queue, empty_str, empty_str, empty_str, empty_str, empty_str, empty_int, empty_str, country, empty_int, speed, queue, empty_int, color ]) parent = self.usersiters[user] if self.ResultGrouping.get_active_id() == "folder_grouping": # Group by folder if directory not in self.directoryiters: self.directoryiters[ directory] = self.resultsmodel.insert_with_values( self.usersiters[user], -1, self.column_numbers, [ empty_int, user, flag, immediatedl, h_speed, h_queue, directory, empty_str, empty_str, empty_str, empty_str, empty_int, fullpath.rsplit('\\', 1)[0] + '\\', country, empty_int, speed, queue, empty_int, color ]) row = row[:] row[6] = "" # Directory not visible for file row if "group by folder" is enabled parent = self.directoryiters[directory] else: parent = None try: """ Note that we use insert_with_values instead of append, as this reduces overhead by bypassing useless row conversion to GObject.Value in PyGObject. """ iterator = self.resultsmodel.insert_with_values( parent, -1, self.column_numbers, row) self.numvisibleresults += 1 except Exception as e: types = [] for i in row: types.append(type(i)) log.add_warning(_("Search row error: %(exception)s %(row)s"), { 'exception': e, 'row': row }) iterator = None return iterator def check_digit(self, sfilter, value, factorize=True): op = ">=" if sfilter[:1] in (">", "<", "="): op, sfilter = sfilter[:1] + "=", sfilter[1:] if not sfilter: return True factor = 1 if factorize: base = 1024 # Default to binary for "k", "m", "g" suffixes if sfilter[-1:].lower() == 'b': base = 1000 # Byte suffix detected, prepare to use decimal if necessary sfilter = sfilter[:-1] if sfilter[-1:].lower() == 'i': base = 1024 # Binary requested, stop using decimal sfilter = sfilter[:-1] if sfilter.lower()[-1:] == "g": factor = pow(base, 3) sfilter = sfilter[:-1] elif sfilter.lower()[-1:] == "m": factor = pow(base, 2) sfilter = sfilter[:-1] elif sfilter.lower()[-1:] == "k": factor = base sfilter = sfilter[:-1] if not sfilter: return True try: sfilter = int(sfilter) * factor except ValueError: return True operation = self.operators.get(op) return operation(value, sfilter) def check_country(self, sfilter, value): if not isinstance(value, str): return False value = value.upper() allowed = False for cc in sfilter.split("|"): if cc == value: allowed = True elif cc.startswith("!") and cc[1:] != value: allowed = True elif cc.startswith("!") and cc[1:] == value: return False return allowed def check_file_type(self, sfilter, value): if not isinstance(value, str): return False value = value.lower() allowed = False for ext in sfilter.split("|"): exclude_ext = None if ext.startswith("!"): exclude_ext = ext[1:] if not exclude_ext.startswith("."): exclude_ext = "." + exclude_ext elif not ext.startswith("."): ext = "." + ext if not ext.startswith("!") and value.endswith(ext): allowed = True elif ext.startswith("!") and not value.endswith(exclude_ext): allowed = True elif ext.startswith("!") and value.endswith(exclude_ext): return False return allowed def check_filter(self, row): filters = self.filters if self.active_filter_count == 0: return True # "Included text"-filter, check full file path (located at index 12 in row) if filters["include"] and not filters["include"].search( row[12].lower()): return False # "Excluded text"-filter, check full file path (located at index 12 in row) if filters["exclude"] and filters["exclude"].search(row[12].lower()): return False if filters["size"] and not self.check_digit(filters["size"], row[14].get_uint64()): return False if filters["bitrate"] and not self.check_digit( filters["bitrate"], row[11].get_uint64(), False): return False if filters["freeslot"] and row[3] != "Y": return False if filters["country"] and not self.check_country( filters["country"], row[13]): return False if filters["type"] and not self.check_file_type( filters["type"], row[12]): return False return True def set_filters(self, enable, f_in, f_out, size, bitrate, freeslot, country, f_type): self.filters = { "include": None, "exclude": None, "size": None, "bitrate": None, "freeslot": freeslot, "country": None, "type": None } self.active_filter_count = 0 if f_in: try: f_in = re.compile(f_in.lower()) self.filters["include"] = f_in except sre_constants.error: set_widget_fg_bg_css(self.FilterInEntry, "red", "white") else: set_widget_fg_bg_css(self.FilterInEntry) self.active_filter_count += 1 if f_out: try: f_out = re.compile(f_out.lower()) self.filters["exclude"] = f_out except sre_constants.error: set_widget_fg_bg_css(self.FilterOutEntry, "red", "white") else: set_widget_fg_bg_css(self.FilterOutEntry) self.active_filter_count += 1 if size: self.filters["size"] = size self.active_filter_count += 1 if bitrate: self.filters["bitrate"] = bitrate self.active_filter_count += 1 if country: self.filters["country"] = country.upper() self.active_filter_count += 1 if f_type: self.filters["type"] = f_type.lower() self.active_filter_count += 1 if freeslot: self.active_filter_count += 1 self.usersiters.clear() self.directoryiters.clear() self.resultsmodel.clear() self.numvisibleresults = 0 for row in self.all_data: if self.numvisibleresults >= config.sections["searches"][ "max_displayed_results"]: break if self.check_filter(row): self.add_row_to_model(row) # Update number of visible results self.update_result_counter() self.update_filter_counter(self.active_filter_count) def populate_popup_menu_users(self): self.popup_menu_users.clear() if not self.selected_users: return for user in self.selected_users: popup = PopupMenu(self.frame) popup.setup_user_menu(user) popup.setup(("", None), ("#" + _("Select User's Transfers"), self.on_select_user_results, user)) popup.toggle_user_items() self.popup_menu_users.setup((">" + user, popup)) def on_select_user_results(self, *args): if not self.selected_users: return selected_user = args[-1] sel = self.ResultsList.get_selection() fmodel = self.ResultsList.get_model() sel.unselect_all() iterator = fmodel.get_iter_first() select_user_row_iter(fmodel, sel, 1, selected_user, iterator) self.select_results() def select_results(self): self.selected_results = [] self.selected_users = [] self.selected_files_count = 0 model, paths = self.ResultsList.get_selection().get_selected_rows() for path in paths: iterator = model.get_iter(path) user = model.get_value(iterator, 1) if user is None: continue if user not in self.selected_users: self.selected_users.append(user) filepath = model.get_value(iterator, 12) if not filepath: # Result is not a file or directory, don't add it continue bitrate = model.get_value(iterator, 9) length = model.get_value(iterator, 10) size = model.get_value(iterator, 14) self.selected_results.append( (user, filepath, size, bitrate, length)) filename = model.get_value(iterator, 7) if filename: self.selected_files_count += 1 def update_result_counter(self): self.Counter.set_text(str(self.numvisibleresults)) def update_visuals(self): for widget in list(self.__dict__.values()): update_widget_visuals(widget, list_font_target="searchfont") def save_columns(self): save_columns("file_search", self.ResultsList.get_columns()) def on_list_clicked(self, widget, event): if triggers_context_menu(event): set_treeview_selected_row(widget, event) return self.on_popup_menu() pathinfo = widget.get_path_at_pos(event.x, event.y) if pathinfo is None: widget.get_selection().unselect_all() elif event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS: self.select_results() self.on_download_files() self.ResultsList.get_selection().unselect_all() return True return False def on_key_press_event(self, widget, event): self.select_results() if event.get_state() & Gdk.ModifierType.CONTROL_MASK and \ event.hardware_keycode in keyval_to_hardware_keycode(Gdk.KEY_c): self.on_copy_file_path() else: # No key match, continue event return False widget.stop_emission_by_name("key_press_event") return True def on_popup_menu(self, *args): self.select_results() actions = self.popup_menu.get_actions() users = len(self.selected_users) > 0 files = len(self.selected_results) > 0 for i in (_("_Download File(s)"), _("Download File(s) _To..."), _("File _Properties"), _("Copy _URL")): actions[i].set_enabled(False) for i in (_("Download _Folder(s)"), _("Download F_older(s) To..."), _("_Browse Folder"), _("Copy _File Path"), _("Copy Folder U_RL")): actions[i].set_enabled(files) actions[_("User(s)")].set_enabled(users) self.populate_popup_menu_users() for result in self.selected_results: if not result[1].endswith('\\'): # At least one selected result is a file, activate file-related items for i in (_("_Download File(s)"), _("Download File(s) _To..."), _("File _Properties"), _("Copy _URL")): actions[i].set_enabled(True) break self.popup_menu.set_num_selected_files(self.selected_files_count) self.popup_menu.popup() return True def on_browse_folder(self, *args): requested_folders = set() for file in self.selected_results: user = file[0] folder = file[1].rsplit('\\', 1)[0] if folder not in requested_folders: self.frame.browse_user(user, folder) requested_folders.add(folder) def on_file_properties(self, *args): if not self.frame.np.transfers: return data = [] model, paths = self.ResultsList.get_selection().get_selected_rows() for path in paths: iterator = model.get_iter(path) filename = model.get_value(iterator, 7) # We only want to see the metadata of files, not directories if not filename: continue num = model.get_value(iterator, 0) user = model.get_value(iterator, 1) immediate = model.get_value(iterator, 3) speed = model.get_value(iterator, 4) queue = model.get_value(iterator, 5) size = model.get_value(iterator, 8) bitratestr = model.get_value(iterator, 9) length = model.get_value(iterator, 10) fn = model.get_value(iterator, 12) directory = fn.rsplit('\\', 1)[0] cc = model.get_value(iterator, 13) country = "%s / %s" % (cc, code2name(cc)) data.append({ "user": user, "fn": fn, "position": num, "filename": filename, "directory": directory, "size": size, "speed": speed, "queue": queue, "immediate": immediate, "bitrate": bitratestr, "length": length, "country": country }) if paths: FileProperties(self.frame, data).show() def on_download_files(self, *args, prefix=""): if not self.frame.np.transfers: return for file in self.selected_results: # Make sure the selected result is not a directory if not file[1].endswith('\\'): self.frame.np.transfers.get_file(file[0], file[1], prefix, size=file[2], bitrate=file[3], length=file[4], checkduplicate=True) def on_download_files_to_selected(self, selected, data): self.on_download_files(prefix=selected) def on_download_files_to(self, *args): choose_dir(parent=self.frame.MainWindow, callback=self.on_download_files_to_selected, initialdir=config.sections["transfers"]["downloaddir"], multichoice=False) def on_download_folders(self, *args, download_location=""): if not self.frame.np.transfers: return if download_location: """ Custom download location specified, remember it when peer sends a folder contents reply """ requested_folders = self.frame.np.transfers.requested_folders else: requested_folders = defaultdict(dict) for i in self.selected_results: user = i[0] folder = i[1].rsplit('\\', 1)[0] if folder in requested_folders[user]: """ Ensure we don't send folder content requests for a folder more than once, e.g. when several selected resuls belong to the same folder. """ continue requested_folders[user][folder] = download_location # First queue the visible search results files = [] for row in self.all_data: # Find the wanted directory if folder != row[12].rsplit('\\', 1)[0]: continue destination = self.frame.np.transfers.get_folder_destination( user, folder) counter, user, flag, immediatedl, h_speed, h_queue, directory, filename, h_size, h_bitrate, h_length, bitrate, fullpath, country, size, speed, queue, length, color = row files.append((user, fullpath, destination, size.get_uint64(), bitrate.get_uint64(), length.get_uint64())) if config.sections["transfers"]["reverseorder"]: files.sort(key=lambda x: x[1], reverse=True) for file in files: user, fullpath, destination, size, bitrate, length = file self.frame.np.transfers.get_file(user, fullpath, destination, size=size, bitrate=bitrate, length=length, checkduplicate=True) # Ask for the rest of the files in the folder self.frame.np.send_message_to_peer( user, slskmessages.FolderContentsRequest(None, folder)) def on_download_folders_to_selected(self, selected, data): self.on_download_folders(download_location=selected) def on_download_folders_to(self, *args): choose_dir(parent=self.frame.MainWindow, callback=self.on_download_folders_to_selected, initialdir=config.sections["transfers"]["downloaddir"], multichoice=False) def on_copy_file_path(self, *args): if self.selected_results: user, path = self.selected_results[0][:2] self.frame.clipboard.set_text(path, -1) def on_copy_url(self, *args): if self.selected_results: user, path = self.selected_results[0][:2] copy_file_url(user, path, self.frame.clipboard) def on_copy_dir_url(self, *args): if self.selected_results: user, path = self.selected_results[0][:2] copy_file_url(user, path.rsplit('\\', 1)[0] + '\\', self.frame.clipboard) def on_group(self, widget): self.on_refilter(widget) active = widget.get_active() self.ResultsList.set_show_expanders(active) config.sections["searches"]["group_searches"] = active self.cols["id"].set_visible(not active) self.ExpandButton.set_visible(active) def on_toggle_expand_all(self, widget): active = self.ExpandButton.get_active() if active: self.ResultsList.expand_all() self.expand.set_from_icon_name("go-up-symbolic", Gtk.IconSize.BUTTON) else: collapse_treeview(self.ResultsList, self.ResultGrouping.get_active_id()) self.expand.set_from_icon_name("go-down-symbolic", Gtk.IconSize.BUTTON) config.sections["searches"]["expand_searches"] = active def on_toggle_filters(self, widget): visible = widget.get_active() self.FiltersContainer.set_visible(visible) config.sections["searches"]["filters_visible"] = visible def on_copy_search_term(self, *args): self.frame.clipboard.set_text(self.text, -1) def on_toggle_remember(self, widget): self.remember = widget.get_active() search = self.searches.searches[self.id] if not self.remember: self.searches.wish_list.remove_wish(search["term"]) else: self.searches.wish_list.add_wish(search["term"]) def push_history(self, widget, title): text = widget.get_active_text() if not text.strip(): return None text = text.strip() history = config.sections["searches"][title] if text in history: history.remove(text) elif len(history) >= 5: del history[-1] history.insert(0, text) config.write_configuration() self.add_combo(widget, text) widget.get_child().set_text(text) return text def on_refilter(self, *args): if self.clearing_filters: return f_in = self.push_history(self.FilterIn, "filterin") f_out = self.push_history(self.FilterOut, "filterout") f_size = self.push_history(self.FilterSize, "filtersize") f_br = self.push_history(self.FilterBitrate, "filterbr") f_free = self.FilterFreeSlot.get_active() f_country = self.push_history(self.FilterCountry, "filtercc") f_type = self.push_history(self.FilterType, "filtertype") self.ResultsList.set_model(None) self.set_filters(1, f_in, f_out, f_size, f_br, f_free, f_country, f_type) self.ResultsList.set_model(self.resultsmodel) if self.ResultGrouping.get_active_id() != "ungrouped": # Group by folder or user if self.ExpandButton.get_active(): self.ResultsList.expand_all() else: collapse_treeview(self.ResultsList, self.ResultGrouping.get_active_id()) def on_clear_filters(self, *args): self.clearing_filters = True self.FilterInEntry.set_text("") self.FilterOutEntry.set_text("") self.FilterSizeEntry.set_text("") self.FilterBitrateEntry.set_text("") self.FilterCountryEntry.set_text("") self.FilterTypeEntry.set_text("") self.FilterFreeSlot.set_active(False) self.clearing_filters = False self.FilterInEntry.grab_focus() self.on_refilter() def on_about_filters(self, *args): if not hasattr(self, "AboutSearchFiltersPopover"): load_ui_elements( self, os.path.join(self.frame.gui_dir, "ui", "popovers", "searchfilters.ui")) self.AboutSearchFiltersPopover.set_relative_to(self.ShowChatHelp) try: self.AboutSearchFiltersPopover.popup() except AttributeError: # GTK <3.22 support self.AboutSearchFiltersPopover.set_transitions_enabled(True) self.AboutSearchFiltersPopover.show() def update_filter_counter(self, count): if count > 0: self.FilterLabel.set_text(_("Result Filters") + " *") else: self.FilterLabel.set_text(_("Result Filters")) self.FilterLabel.set_tooltip_text("%d active filter(s)" % count) def on_clear(self, *args): self.all_data = [] self.usersiters.clear() self.directoryiters.clear() self.resultsmodel.clear() self.numvisibleresults = 0 # Update number of visible results self.update_result_counter() def on_close(self, *args): self.searches.remove_tab(self) def on_close_all_tabs(self, *args): self.searches.remove_all_pages()
class RoomList: def __init__(self, frame, joined_rooms, private_rooms): # Build the window self.frame = frame self.server_rooms = set() self.joined_rooms = joined_rooms self.private_rooms = private_rooms load_ui_elements( self, os.path.join(self.frame.gui_dir, "ui", "popovers", "roomlist.ui")) self.room_model = Gtk.ListStore(str, int, int) self.column_numbers = list(range(self.room_model.get_n_columns())) self.cols = initialise_columns( None, self.RoomsList, ["room", _("Room"), 260, "text", self.room_status], ["users", _("Users"), 100, "number", self.room_status]) self.cols["room"].set_sort_column_id(0) self.cols["users"].set_sort_column_id(1) self.popup_room = None self.popup_menu = PopupMenu(self.frame) self.popup_menu.setup( ("#" + _("Join Room"), self.on_popup_join), ("#" + _("Leave Room"), self.on_popup_leave), ("", None), ("#" + _("Disown Private Room"), self.on_popup_private_room_disown), ("#" + _("Cancel Room Membership"), self.on_popup_private_room_dismember), ("", None), ("#" + _("Join Public Room"), self.on_join_public_room)) self.RoomsList.connect("button_press_event", self.on_list_clicked) self.RoomsList.connect("popup-menu", self.on_popup_menu) self.RoomsList.connect("touch_event", self.on_list_clicked) self.RoomsList.set_headers_clickable(True) self.search_iter = None self.query = "" self.AcceptPrivateRoom.set_active( config.sections["server"]["private_chatrooms"]) self.AcceptPrivateRoom.connect("toggled", self.on_toggle_accept_private_room) frame.RoomList.connect("clicked", self.show) self.RoomListPopover.set_relative_to(frame.RoomList) def get_selected_room(self, treeview): model, iterator = treeview.get_selection().get_selected() if iterator is None: return None return model.get_value(iterator, 0) def is_private_room_owned(self, room): if room in self.private_rooms: if self.private_rooms[room]["owner"] == config.sections["server"][ "login"]: return True return False def is_private_room_member(self, room): if room in self.private_rooms: return True return False def is_private_room_operator(self, room): if room in self.private_rooms: if config.sections["server"]["login"] in self.private_rooms[room][ "operators"]: return True return False def private_rooms_sort(self, model, iter1, iter2, column): try: private1 = model.get_value(iter1, 2) * 10000 private1 += model.get_value(iter1, 1) except Exception: private1 = 0 try: private2 = model.get_value(iter2, 2) * 10000 private2 += model.get_value(iter2, 1) except Exception: private2 = 0 return (private1 > private2) - (private1 < private2) def room_match_function(self, model, iterator, data=None): query = self.SearchRooms.get_text().lower() value = model.get_value(iterator, 0) if query == "" or query in value.lower(): return True return False def room_status(self, column, cellrenderer, model, iterator, dummy='dummy'): if self.room_model_filtered.get_value(iterator, 2) >= 2: cellrenderer.set_property("underline", Pango.Underline.SINGLE) cellrenderer.set_property("weight", Pango.Weight.BOLD) elif self.room_model_filtered.get_value(iterator, 2) >= 1: cellrenderer.set_property("weight", Pango.Weight.BOLD) cellrenderer.set_property("underline", Pango.Underline.NONE) else: cellrenderer.set_property("weight", Pango.Weight.NORMAL) cellrenderer.set_property("underline", Pango.Underline.NONE) def set_room_list(self, rooms, owned_rooms, other_private_rooms): self.room_model.clear() for room, users in rooms: self.room_model.insert_with_valuesv(-1, self.column_numbers, [room, users, 0]) self.server_rooms = set() for room, users in rooms: self.server_rooms.add(room) self.set_private_rooms(owned_rooms, other_private_rooms) self.room_model.set_sort_func(1, self.private_rooms_sort, 1) self.room_model.set_sort_column_id(1, Gtk.SortType.DESCENDING) self.room_model.set_default_sort_func(self.private_rooms_sort) self.room_filter = self.room_model.filter_new() self.room_filter.set_visible_func(self.room_match_function) try: self.room_model_filtered = Gtk.TreeModelSort.new_with_model( self.room_filter) except AttributeError: # Older GTK versions self.room_model_filtered = Gtk.TreeModelSort.sort_new_with_model( self.room_filter) self.RoomsList.set_model(self.room_model_filtered) def set_private_rooms(self, ownedrooms=[], otherrooms=[]): myusername = config.sections["server"]["login"] for room in ownedrooms: try: self.private_rooms[room[0]]['joined'] = room[1] if self.private_rooms[room[0]]['owner'] != myusername: log.add_warning( _("I remember the room %(room)s being owned by %(previous)s, but the server says its owned by %(new)s." ), { 'room': room[0], 'previous': self.private_rooms[room[0]]['owner'], 'new': myusername }) self.private_rooms[room[0]]['owner'] = myusername except KeyError: self.private_rooms[room[0]] = { "users": [], "joined": room[1], "operators": [], "owner": myusername } for room in otherrooms: try: self.private_rooms[room[0]]['joined'] = room[1] if self.private_rooms[room[0]]['owner'] == myusername: log.add_warning( _("I remember the room %(room)s being owned by %(old)s, but the server says that's not true." ), { 'room': room[0], 'old': self.private_rooms[room[0]]['owner'], }) self.private_rooms[room[0]]['owner'] = None except KeyError: self.private_rooms[room[0]] = { "users": [], "joined": room[1], "operators": [], "owner": None } iterator = self.room_model.get_iter_first() while iterator: room = self.room_model.get_value(iterator, 0) if self.is_private_room_owned(room) or self.is_private_room_member( room): self.room_model.remove(iterator) iterator = self.room_model.iter_next(iterator) for room in self.private_rooms: num = self.private_rooms[room]["joined"] if self.is_private_room_owned(room): self.room_model.prepend([str(room), num, 2]) elif self.is_private_room_member(room): self.room_model.prepend([str(room), num, 1]) def update_room(self, room, user_count): if room in self.server_rooms: iterator = self.room_model.get_iter_first() while iterator: if self.room_model.get_value(iterator, 0) == room: self.room_model.set_value(iterator, 1, user_count) break iterator = self.room_model.iter_next(iterator) else: self.room_model.insert_with_valuesv(-1, self.column_numbers, [room, user_count, 0]) self.server_rooms.add(room) def on_list_clicked(self, widget, event): set_treeview_selected_row(widget, event) if triggers_context_menu(event): return self.on_popup_menu(widget) if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS: room = self.get_selected_room(widget) if room is not None and room not in self.joined_rooms: self.popup_room = room self.on_popup_join() return True return False def on_popup_menu(self, widget): if self.room_model is None: return False room = self.get_selected_room(widget) if room is not None: if room in self.joined_rooms: act = (False, True) else: act = (True, False) else: act = (False, False) self.popup_room = room prooms_enabled = True actions = self.popup_menu.get_actions() actions[_("Join Room")].set_enabled(act[0]) actions[_("Leave Room")].set_enabled(act[1]) actions[_("Disown Private Room")].set_enabled( self.is_private_room_owned(self.popup_room)) actions[_("Cancel Room Membership")].set_enabled( (prooms_enabled and self.is_private_room_member(self.popup_room))) self.popup_menu.popup() return True def on_popup_join(self, *args): self.frame.np.queue.append(slskmessages.JoinRoom(self.popup_room)) def on_join_public_room(self, *args): self.frame.chatrooms.join_room(slskmessages.JoinRoom("Public ")) self.frame.np.queue.append(slskmessages.JoinPublicRoom()) def on_popup_private_room_disown(self, *args): if self.is_private_room_owned(self.popup_room): self.frame.np.queue.append( slskmessages.PrivateRoomDisown(self.popup_room)) del self.private_rooms[self.popup_room] def on_popup_private_room_dismember(self, *args): if self.is_private_room_member(self.popup_room): self.frame.np.queue.append( slskmessages.PrivateRoomDismember(self.popup_room)) del self.private_rooms[self.popup_room] def on_popup_leave(self, *args): self.frame.np.queue.append(slskmessages.LeaveRoom(self.popup_room)) def on_search_room(self, *args): self.room_filter.refilter() def on_refresh(self, *args): self.frame.np.queue.append(slskmessages.RoomList()) def on_toggle_accept_private_room(self, widget): value = self.AcceptPrivateRoom.get_active() self.frame.np.queue.append(slskmessages.PrivateRoomToggle(value)) def update_visuals(self): for widget in list(self.__dict__.values()): update_widget_visuals(widget) def clear(self): self.room_model.clear() def show(self, *args): try: self.RoomListPopover.popup() except AttributeError: # GTK <3.22 support self.RoomListPopover.set_transitions_enabled(True) self.RoomListPopover.show()
def __init__(self, chatrooms, room, users, meta=False): self.chatrooms = chatrooms self.frame = chatrooms.frame self.room = room self.meta = meta # not a real room if set to True # Build the window load_ui_elements( self, os.path.join(self.frame.gui_dir, "ui", "chatrooms.ui")) self.tickers = Tickers() self.room_wall = RoomWall(self.frame, self) self.leaving = False self.users = {} # Log Text Search TextSearchBar(self.RoomLog, self.LogSearchBar, self.LogSearchEntry) # Chat Text Search TextSearchBar(self.ChatScroll, self.ChatSearchBar, self.ChatSearchEntry) # Chat Entry self.entry = ChatEntry(self.frame, self.ChatEntry, room, slskmessages.SayChatroom, self.send_message, self.chatrooms.CMDS, self.ChatScroll, is_chatroom=True) self.Log.set_active(config.sections["logging"]["chatrooms"]) if not self.Log.get_active(): self.Log.set_active((self.room in config.sections["logging"]["rooms"])) self.AutoJoin.set_active((room in config.sections["server"]["autojoin"])) self.toggle_chat_buttons() if room not in config.sections["columns"]["chat_room"]: config.sections["columns"]["chat_room"][room] = {} self.usersmodel = Gtk.ListStore( GObject.TYPE_OBJECT, # (0) status_image GObject.TYPE_OBJECT, # (1) flag str, # (2) username str, # (3) h_speed str, # (4) h_files int, # (5) status GObject.TYPE_UINT64, # (6) avgspeed GObject.TYPE_UINT64, # (7) files str # (8) country ) self.UserList.set_model(self.usersmodel) self.column_numbers = list(range(self.usersmodel.get_n_columns())) self.cols = cols = initialise_columns( ("chat_room", room), self.UserList, ["status", _("Status"), 25, "pixbuf", None], ["country", _("Country"), 25, "pixbuf", None], ["user", _("User"), 100, "text", self.user_column_draw], ["speed", _("Speed"), 100, "number", None], ["files", _("Files"), 100, "number", None]) cols["status"].set_sort_column_id(5) cols["country"].set_sort_column_id(8) cols["user"].set_sort_column_id(2) cols["speed"].set_sort_column_id(6) cols["files"].set_sort_column_id(7) cols["status"].get_widget().hide() cols["country"].get_widget().hide() if config.sections["columns"]["hideflags"]: cols["country"].set_visible(False) for userdata in users: self.add_user_row(userdata) self.usersmodel.set_sort_column_id(2, Gtk.SortType.ASCENDING) self.popup_menu_private_rooms = PopupMenu(self.frame) self.popup_menu = popup = PopupMenu(self.frame) popup.setup_user_menu() popup.setup(("", None), ("#" + _("Sear_ch User's Files"), popup.on_search_user), (">" + _("Private Rooms"), self.popup_menu_private_rooms)) self.activitylogpopupmenu = PopupMenu(self.frame) self.activitylogpopupmenu.setup( ("#" + _("Find"), self.on_find_activity_log), ("", None), ("#" + _("Copy"), self.on_copy_activity_log), ("#" + _("Copy All"), self.on_copy_all_activity_log), ("", None), ("#" + _("Clear Activity View"), self.on_clear_activity_log), ("", None), ("#" + _("_Leave Room"), self.on_leave)) self.roomlogpopmenu = PopupMenu(self.frame) self.roomlogpopmenu.setup( ("#" + _("Find"), self.on_find_room_log), ("", None), ("#" + _("Copy"), self.on_copy_room_log), ("#" + _("Copy All"), self.on_copy_all_room_log), ("", None), ("#" + _("View Room Log"), self.on_view_room_log), ("#" + _("Delete Room Log"), self.on_delete_room_log), ("", None), ("#" + _("Clear Message View"), self.on_clear_messages), ("#" + _("_Leave Room"), self.on_leave)) self.tab_menu = PopupMenu(self.frame) self.tab_menu.setup(("#" + _("_Leave Room"), self.on_leave)) self.ChatEntry.grab_focus() self.set_completion_list(list(self.chatrooms.completion_list)) self.count_users() self.create_tags() self.update_visuals() self.read_room_logs()
class PrivateChat(UserInterface): def __init__(self, chats, user): super().__init__("ui/privatechat.ui") self.user = user self.chats = chats self.frame = chats.frame self.opened = False self.offline_message = False self.status = 0 if user in self.frame.np.user_statuses: self.status = self.frame.np.user_statuses[user] or 0 # Text Search TextSearchBar(self.ChatScroll, self.SearchBar, self.SearchEntry, controller_widget=self.Main, focus_widget=self.ChatLine) self.chat_textview = TextView(self.ChatScroll, font="chatfont") # Chat Entry ChatEntry(self.frame, self.ChatLine, chats.completion, user, slskmessages.MessageUser, self.frame.np.privatechats.send_message, self.frame.np.privatechats.CMDS) self.Log.set_active(config.sections["logging"]["privatechat"]) self.toggle_chat_buttons() self.popup_menu_user_chat = PopupMenu(self.frame, self.ChatScroll, connect_events=False) self.popup_menu_user_tab = PopupMenu(self.frame, None, self.on_popup_menu_user) for menu in (self.popup_menu_user_chat, self.popup_menu_user_tab): menu.setup_user_menu(user, page="privatechat") menu.add_items( ("", None), ("#" + _("Close All Tabs…"), self.on_close_all_tabs), ("#" + _("_Close Tab"), self.on_close)) popup = PopupMenu(self.frame, self.ChatScroll, self.on_popup_menu_chat) popup.add_items( ("#" + _("Find…"), self.on_find_chat_log), ("", None), ("#" + _("Copy"), self.chat_textview.on_copy_text), ("#" + _("Copy Link"), self.chat_textview.on_copy_link), ("#" + _("Copy All"), self.chat_textview.on_copy_all_text), ("", None), ("#" + _("View Chat Log"), self.on_view_chat_log), ("#" + _("Delete Chat Log…"), self.on_delete_chat_log), ("", None), ("#" + _("Clear Message View"), self.chat_textview.on_clear_all_text), ("", None), (">" + _("User"), self.popup_menu_user_tab), ) self.create_tags() self.update_visuals() self.read_private_log() def read_private_log(self): numlines = config.sections["logging"]["readprivatelines"] if not numlines: return filename = clean_file(self.user) + ".log" path = os.path.join(config.sections["logging"]["privatelogsdir"], filename) try: self.append_log_lines(path, numlines) except OSError: pass def append_log_lines(self, path, numlines): with open(path, "rb") as lines: # Only show as many log lines as specified in config lines = deque(lines, numlines) for line in lines: try: line = line.decode("utf-8") except UnicodeDecodeError: line = line.decode("latin-1") self.chat_textview.append_line(line, self.tag_hilite, timestamp_format="", scroll=False) def server_login(self): timestamp_format = config.sections["logging"]["private_timestamp"] self.chat_textview.append_line(_("--- reconnected ---"), self.tag_hilite, timestamp_format=timestamp_format) def server_disconnect(self): timestamp_format = config.sections["logging"]["private_timestamp"] self.chat_textview.append_line(_("--- disconnected ---"), self.tag_hilite, timestamp_format=timestamp_format) self.status = -1 self.offline_message = False # Offline color for usernames self.update_remote_username_tag(status=0) self.update_local_username_tag(status=0) def set_label(self, label): self.popup_menu_user_tab.set_parent(label) def on_popup_menu_chat(self, menu, _widget): self.popup_menu_user_tab.toggle_user_items() menu.actions[_("Copy")].set_enabled( self.chat_textview.get_has_selection()) menu.actions[_("Copy Link")].set_enabled( bool(self.chat_textview.get_url_for_selected_pos())) def on_popup_menu_user(self, _menu, _widget): self.popup_menu_user_tab.toggle_user_items() def toggle_chat_buttons(self): self.Speech.set_visible(config.sections["ui"]["speechenabled"]) def on_find_chat_log(self, *_args): self.SearchBar.set_search_mode(True) def on_view_chat_log(self, *_args): open_log(config.sections["logging"]["privatelogsdir"], self.user) def on_delete_chat_log_response(self, dialog, response_id, _data): dialog.destroy() if response_id == 2: delete_log(config.sections["logging"]["privatelogsdir"], self.user) self.chats.history.remove_user(self.user) self.chat_textview.clear() def on_delete_chat_log(self, *_args): option_dialog( parent=self.frame.MainWindow, title=_('Delete Logged Messages?'), message= _('Do you really want to permanently delete all logged messages for this user?' ), callback=self.on_delete_chat_log_response) def show_notification(self, text): self.chats.request_tab_hilite(self.Main) if (self.chats.get_current_page() == self.chats.page_num(self.Main) and self.frame.current_page_id == self.chats.page_id and self.frame.MainWindow.is_active()): # Don't show notifications if the chat is open and the window is in use return # Update tray icon and show urgency hint self.frame.notifications.add("private", self.user) if config.sections["notifications"][ "notification_popup_private_message"]: self.frame.notifications.new_text_notification( text, title=_("Private message from %s") % self.user, priority=Gio.NotificationPriority.HIGH) def message_user(self, msg): text = msg.msg newmessage = msg.newmessage timestamp = msg.timestamp if not newmessage else None usertag = self.tag_username self.show_notification(text) if text.startswith("/me "): line = "* %s %s" % (self.user, text[4:]) tag = self.tag_action speech = line[2:] else: line = "[%s] %s" % (self.user, text) tag = self.tag_remote speech = text timestamp_format = config.sections["logging"]["private_timestamp"] if not newmessage: tag = usertag = self.tag_hilite if not self.offline_message: self.chat_textview.append_line( _("* Message(s) sent while you were offline."), tag, timestamp_format=timestamp_format) self.offline_message = True else: self.offline_message = False self.chat_textview.append_line(line, tag, timestamp=timestamp, timestamp_format=timestamp_format, username=self.user, usertag=usertag) if self.Speech.get_active(): self.frame.np.notifications.new_tts( config.sections["ui"]["speechprivate"], { "user": self.user, "message": speech }) if self.Log.get_active(): timestamp_format = config.sections["logging"]["log_timestamp"] self.chats.history.update_user( self.user, "%s %s" % (time.strftime(timestamp_format), line)) log.write_log(config.sections["logging"]["privatelogsdir"], self.user, line, timestamp, timestamp_format) def echo_message(self, text, message_type): tag = self.tag_local timestamp_format = config.sections["logging"]["private_timestamp"] if hasattr(self, "tag_" + str(message_type)): tag = getattr(self, "tag_" + str(message_type)) self.chat_textview.append_line(text, tag, timestamp_format=timestamp_format) def send_message(self, text): my_username = self.frame.np.login_username if text.startswith("/me "): line = "* %s %s" % (my_username, text[4:]) tag = self.tag_action else: line = "[%s] %s" % (my_username, text) tag = self.tag_local self.chat_textview.append_line( line, tag, timestamp_format=config.sections["logging"]["private_timestamp"], username=my_username, usertag=self.tag_my_username) if self.Log.get_active(): timestamp_format = config.sections["logging"]["log_timestamp"] self.chats.history.update_user( self.user, "%s %s" % (time.strftime(timestamp_format), line)) log.write_log(config.sections["logging"]["privatelogsdir"], self.user, line, timestamp_format=timestamp_format) def update_visuals(self): for widget in list(self.__dict__.values()): update_widget_visuals(widget) def user_name_event(self, pos_x, pos_y, user): self.popup_menu_user_chat.update_model() self.popup_menu_user_chat.set_user(user) self.popup_menu_user_chat.toggle_user_items() self.popup_menu_user_chat.popup(pos_x, pos_y, button=1) def create_tags(self): self.tag_remote = self.chat_textview.create_tag("chatremote") self.tag_local = self.chat_textview.create_tag("chatlocal") self.tag_action = self.chat_textview.create_tag("chatme") self.tag_hilite = self.chat_textview.create_tag("chathilite") color = get_user_status_color(self.status) self.tag_username = self.chat_textview.create_tag( color, callback=self.user_name_event, username=self.user) if not self.frame.np.logged_in: color = "useroffline" else: color = "useraway" if self.frame.np.away else "useronline" my_username = config.sections["server"]["login"] self.tag_my_username = self.chat_textview.create_tag( color, callback=self.user_name_event, username=my_username) def update_remote_username_tag(self, status): if status == self.status: return self.status = status color = get_user_status_color(status) self.chat_textview.update_tag(self.tag_username, color) def update_local_username_tag(self, status): color = get_user_status_color(status) self.chat_textview.update_tag(self.tag_my_username, color) def update_tags(self): for tag in (self.tag_remote, self.tag_local, self.tag_action, self.tag_hilite, self.tag_username, self.tag_my_username): self.chat_textview.update_tag(tag) def on_close(self, *_args): self.frame.notifications.clear("private", self.user) del self.chats.pages[self.user] self.frame.np.privatechats.remove_user(self.user) self.chats.remove_page(self.Main) def on_close_all_tabs(self, *_args): self.chats.remove_all_pages() def set_completion_list(self, completion_list): # Tab-complete the recepient username completion_list.append(self.user) # No duplicates completion_list = list(set(completion_list)) completion_list.sort(key=lambda v: v.lower()) self.chats.completion.set_completion_list(completion_list)
def __init__(self, frame, type): self.frame = frame self.type = type load_ui_elements(self, os.path.join(frame.gui_dir, "ui", type + "s.ui")) getattr(frame, type + "svbox").add(self.Main) self.widget = widget = getattr(self, type.title() + "List") self.last_ui_update = self.last_save = 0 self.list = [] self.users = {} self.paths = {} # Status list self.statuses = {} self.statuses["Queued"] = _("Queued") self.statuses["Getting status"] = _("Getting status") self.statuses["Establishing connection"] = _("Establishing connection") self.statuses["Transferring"] = _("Transferring") self.statuses["Cannot connect"] = _("Cannot connect") self.statuses["User logged off"] = _("User logged off") self.statuses["Connection closed by peer"] = _( "Connection closed by peer") self.statuses["Aborted"] = _("Aborted") self.statuses["Finished"] = _("Finished") self.statuses["Filtered"] = _("Filtered") self.statuses["File not shared"] = _("File not shared") self.statuses["File not shared."] = _( "File not shared" ) # The official client sends a variant containing a dot self.statuses["Download directory error"] = _( "Download directory error") self.statuses["Local file error"] = _("Local file error") self.statuses["Remote file error"] = _("Remote file error") # String templates self.extension_list_template = _("All %(ext)s") self.files_template = _("%(number)2s files ") self.transfersmodel = Gtk.TreeStore( str, # (0) user str, # (1) path str, # (2) file name str, # (3) status str, # (4) hqueue position GObject.TYPE_UINT64, # (5) percent str, # (6) hsize str, # (7) hspeed str, # (8) htime elapsed str, # (9) time left str, # (10) path str, # (11) status (non-translated) GObject.TYPE_UINT64, # (12) size GObject.TYPE_UINT64, # (13) current bytes GObject.TYPE_UINT64, # (14) speed GObject.TYPE_UINT64, # (15) time elapsed GObject.TYPE_UINT64, # (16) file count GObject.TYPE_UINT64, # (17) queue position GObject.TYPE_PYOBJECT # (18) transfer object ) self.column_numbers = list(range(self.transfersmodel.get_n_columns())) self.cols = cols = initialise_columns( type, widget, ["user", _("User"), 200, "text", None], ["path", _("Path"), 400, "text", None], ["filename", _("Filename"), 400, "text", None], ["status", _("Status"), 140, "text", None], ["queue_position", _("Queue Position"), 50, "number", None], ["percent", _("Percent"), 70, "progress", None], ["size", _("Size"), 170, "number", None], ["speed", _("Speed"), 90, "number", None], ["time_elapsed", _("Time Elapsed"), 140, "number", None], ["time_left", _("Time Left"), 140, "number", None], ) cols["user"].set_sort_column_id(0) cols["path"].set_sort_column_id(1) cols["filename"].set_sort_column_id(2) cols["status"].set_sort_column_id(11) cols["queue_position"].set_sort_column_id(17) cols["percent"].set_sort_column_id(5) cols["size"].set_sort_column_id(12) cols["speed"].set_sort_column_id(14) cols["time_elapsed"].set_sort_column_id(8) cols["time_left"].set_sort_column_id(9) widget.set_model(self.transfersmodel) self.group_dropdown = getattr(frame, "ToggleTree%ss" % self.type.title()) self.expand_button = getattr(frame, "Expand%ss" % self.type.title()) self.group_dropdown.connect("changed", self.on_toggle_tree) self.group_dropdown.set_active( config.sections["transfers"]["group%ss" % self.type]) self.expand_button.connect("toggled", self.on_expand_tree) self.expand_button.set_active( config.sections["transfers"]["%ssexpanded" % self.type]) self.popup_menu_users = PopupMenu(frame) self.popup_menu_clear = PopupMenu(frame) self.popup_menu = PopupMenu(frame) self.popup_menu.setup( ("#" + "selected_files", None), ("", None), ("#" + _("Send to _Player"), self.on_play_files), ("#" + _("_Open Folder"), self.on_open_directory), ("#" + _("File P_roperties"), self.on_file_properties), ("", None), ("#" + _("Copy _File Path"), self.on_copy_file_path), ("#" + _("Copy _URL"), self.on_copy_url), ("#" + _("Copy Folder URL"), self.on_copy_dir_url), ("", None), ("#" + _("_Search"), self.on_file_search), (">" + _("User(s)"), self.popup_menu_users), ("", None), ("#" + _("_Retry"), self.on_retry_transfer), ("#" + _("Abor_t"), self.on_abort_transfer), ("#" + _("_Clear"), self.on_clear_transfer), ("", None), (">" + _("Clear Groups"), self.popup_menu_clear)) self.update_visuals()
def __init__(self, frame): super().__init__("ui/buddylist.ui") self.frame = frame self.page_id = "userlist" # Columns self.user_iterators = {} self.usersmodel = Gtk.ListStore( Gio.Icon, # (0) status icon str, # (1) flag str, # (2) username str, # (3) hspeed str, # (4) hfile count bool, # (5) trusted bool, # (6) notify bool, # (7) prioritized str, # (8) hlast seen str, # (9) note int, # (10) status GObject.TYPE_UINT, # (11) speed GObject.TYPE_UINT, # (12) file count int, # (13) last seen str # (14) country ) self.column_numbers = list(range(self.usersmodel.get_n_columns())) self.cols = cols = initialise_columns( frame, "buddy_list", self.UserListTree, ["status", _("Status"), 25, "icon", None], ["country", _("Country"), 25, "icon", None], ["user", _("User"), 250, "text", None], ["speed", _("Speed"), 150, "number", None], ["files", _("Files"), 150, "number", None], ["trusted", _("Trusted"), 0, "toggle", None], ["notify", _("Notify"), 0, "toggle", None], ["privileged", _("Prioritized"), 0, "toggle", None], ["last_seen", _("Last Seen"), 160, "text", None], ["comments", _("Note"), 400, "text", None]) cols["status"].set_sort_column_id(10) cols["country"].set_sort_column_id(14) cols["user"].set_sort_column_id(2) cols["speed"].set_sort_column_id(11) cols["files"].set_sort_column_id(12) cols["trusted"].set_sort_column_id(5) cols["notify"].set_sort_column_id(6) cols["privileged"].set_sort_column_id(7) cols["last_seen"].set_sort_column_id(13) cols["comments"].set_sort_column_id(9) cols["status"].get_widget().hide() cols["country"].get_widget().hide() for render in cols["trusted"].get_cells(): render.connect('toggled', self.cell_toggle_callback, self.UserListTree, 5) for render in cols["notify"].get_cells(): render.connect('toggled', self.cell_toggle_callback, self.UserListTree, 6) for render in cols["privileged"].get_cells(): render.connect('toggled', self.cell_toggle_callback, self.UserListTree, 7) for render in cols["comments"].get_cells(): render.connect('edited', self.cell_edited_callback, self.UserListTree, 9) self.UserListTree.set_model(self.usersmodel) # Lists for row in config.sections["server"]["userlist"]: self.append_user_row(row) self.usersmodel.set_sort_column_id(2, Gtk.SortType.ASCENDING) for combo_box in (self.frame.UserSearchCombo, self.frame.UserInfoCombo, self.frame.UserBrowseCombo): combo_box.set_model(self.usersmodel) combo_box.set_entry_text_column(2) CompletionEntry(combo_box.get_child(), self.usersmodel, column=2) # Popup menus self.popup_menu_private_rooms = PopupMenu(self.frame) self.popup_menu = popup = PopupMenu(frame, self.UserListTree, self.on_popup_menu) popup.setup_user_menu(page="userlist") popup.add_items( ("", None), ("#" + _("Add User _Note…"), self.on_add_note), (">" + _("Private Rooms"), self.popup_menu_private_rooms), ("#" + _("_Remove"), self.on_remove_user)) self.update_visuals()
class UserBrowse: def __init__(self, userbrowses, user): self.userbrowses = userbrowses self.frame = userbrowses.frame # Build the window load_ui_elements( self, os.path.join(self.frame.gui_dir, "ui", "userbrowse.ui")) self.info_bar = InfoBar(self.InfoBar, Gtk.MessageType.INFO) self.key_controller_folder = connect_key_press_event( self.FolderTreeView, self.on_folder_key_press_event) self.key_controller_file = connect_key_press_event( self.FileTreeView, self.on_file_key_press_event) if Gtk.get_major_version() == 4: self.MainPaned.set_resize_start_child(True) else: self.MainPaned.child_set_property(self.FolderPane, "resize", True) self.user = user self.conn = None self.local_shares_type = None # selected_folder is the current selected folder self.selected_folder = None # queued_folder is a folder that should be opened once the share has loaded self.queued_folder = None self.search_list = [] self.query = None self.search_position = 0 self.selected_files = {} self.shares = [] # Iters for current DirStore self.directories = {} # Iters for current FileStore self.files = {} self.totalsize = 0 self.dir_store = Gtk.TreeStore(str, str) self.FolderTreeView.set_model(self.dir_store) self.dir_column_numbers = list(range(self.dir_store.get_n_columns())) cols = initialise_columns( None, self.FolderTreeView, ["folders", _("Folders"), -1, "text", None] # 0 ) cols["folders"].set_sort_column_id(0) self.user_popup = popup = PopupMenu(self.frame, None, self.on_tab_popup) popup.setup_user_menu(user, page="userbrowse") popup.setup(("", None), ("#" + _("_Save Shares List to Disk"), self.on_save), ("#" + _("Close All Tabs..."), self.on_close_all_tabs), ("#" + _("_Close Tab"), self.on_close)) self.popup_menu_downloads_folders = PopupMenu(self.frame) self.popup_menu_downloads_folders.setup( ("#" + _("_Download Folder"), self.on_download_directory), ("#" + _("Download Folder _To..."), self.on_download_directory_to), ("#" + _("Download _Recursive"), self.on_download_directory_recursive), ("#" + _("Download R_ecursive To..."), self.on_download_directory_recursive_to)) self.popup_menu_downloads_files = PopupMenu(self.frame) self.popup_menu_downloads_files.setup( ("#" + _("_Download File(s)"), self.on_download_files), ("#" + _("Download _To..."), self.on_download_files_to), ("", None), ("#" + _("_Download Folder"), self.on_download_directory), ("#" + _("Download Folder _To..."), self.on_download_directory_to), ("#" + _("Download _Recursive"), self.on_download_directory_recursive), ("#" + _("Download R_ecursive To..."), self.on_download_directory_recursive_to)) self.popup_menu_uploads_folders = PopupMenu(self.frame) self.popup_menu_uploads_folders.setup( ("#" + _("Upload Folder To..."), self.on_upload_directory_to), ("#" + _("Upload Folder Recursive To..."), self.on_upload_directory_recursive_to)) self.popup_menu_uploads_files = PopupMenu(self.frame) self.popup_menu_uploads_files.setup( ("#" + _("Upload Folder To..."), self.on_upload_directory_to), ("#" + _("Upload Folder Recursive To..."), self.on_upload_directory_recursive_to), ("#" + _("Up_load File(s)"), self.on_upload_files)) self.folder_popup_menu = PopupMenu(self.frame, self.FolderTreeView, self.on_folder_popup_menu) if user == config.sections["server"]["login"]: self.folder_popup_menu.setup( ("#" + _("_Download Folder"), self.on_download_directory), ("#" + _("Download Folder _To..."), self.on_download_directory_to), ("#" + _("Download _Recursive"), self.on_download_directory_recursive), ("#" + _("Download R_ecursive To..."), self.on_download_directory_recursive_to), ("", None), ("#" + _("Upload Folder To..."), self.on_upload_directory_to), ("#" + _("Upload Folder Recursive To..."), self.on_upload_directory_recursive_to), ("", None), ("#" + _("Open in File _Manager"), self.on_file_manager), ("", None), ("#" + _("Copy _Folder Path"), self.on_copy_folder_path), ("#" + _("Copy _URL"), self.on_copy_dir_url), ("", None), (">" + _("User"), self.user_popup)) else: self.folder_popup_menu.setup( ("#" + _("_Download Folder"), self.on_download_directory), ("#" + _("Download Folder _To..."), self.on_download_directory_to), ("#" + _("Download _Recursive"), self.on_download_directory_recursive), ("#" + _("Download R_ecursive To..."), self.on_download_directory_recursive_to), ("", None), ("#" + _("Copy _Folder Path"), self.on_copy_folder_path), ("#" + _("Copy _URL"), self.on_copy_dir_url), ("", None), (">" + _("User"), self.user_popup)) self.FolderTreeView.get_selection().connect("changed", self.on_select_dir) self.file_store = Gtk.ListStore( str, # (0) file name str, # (1) hsize str, # (2) hbitrate str, # (3) hlength GObject.TYPE_UINT64, # (4) size GObject.TYPE_UINT64, # (5) bitrate GObject.TYPE_UINT64 # (6) length ) self.FileTreeView.set_model(self.file_store) self.file_column_numbers = [ i for i in range(self.file_store.get_n_columns()) ] cols = initialise_columns( "user_browse", self.FileTreeView, ["filename", _("Filename"), 600, "text", None], ["size", _("Size"), 100, "number", None], ["bitrate", _("Bitrate"), 100, "number", None], ["length", _("Length"), 100, "number", None]) cols["filename"].set_sort_column_id(0) cols["size"].set_sort_column_id(4) cols["bitrate"].set_sort_column_id(5) cols["length"].set_sort_column_id(6) self.file_popup_menu = PopupMenu(self.frame, self.FileTreeView, self.on_file_popup_menu) if user == config.sections["server"]["login"]: self.file_popup_menu.setup( ("#" + "selected_files", None), ("", None), (">" + _("Download"), self.popup_menu_downloads_files), (">" + _("Upload"), self.popup_menu_uploads_files), ("", None), ("#" + _("Send to _Player"), self.on_play_files), ("#" + _("Open in File _Manager"), self.on_file_manager), ("#" + _("File _Properties"), self.on_file_properties), ("", None), ("#" + _("Copy _File Path"), self.on_copy_file_path), ("#" + _("Copy _URL"), self.on_copy_url), ("", None), (">" + _("User"), self.user_popup)) else: self.file_popup_menu.setup( ("#" + "selected_files", None), ("", None), (">" + _("Download"), self.popup_menu_downloads_files), ("", None), ("#" + _("File _Properties"), self.on_file_properties), ("", None), ("#" + _("Copy _File Path"), self.on_copy_file_path), ("#" + _("Copy _URL"), self.on_copy_url), ("", None), (">" + _("User"), self.user_popup)) self.update_visuals() def set_label(self, label): self.user_popup.set_widget(label) def update_visuals(self): for widget in list(self.__dict__.values()): update_widget_visuals(widget, list_font_target="browserfont") def on_expand(self, widget): if self.ExpandButton.get_active(): self.FolderTreeView.expand_all() self.expand.set_property("icon-name", "go-up-symbolic") else: self.FolderTreeView.collapse_all() self.expand.set_property("icon-name", "go-down-symbolic") def on_folder_row_activated(self, treeview, path, column): if self.user != config.sections["server"]["login"]: self.on_download_directory() def on_folder_popup_menu(self, menu, widget): actions = menu.get_actions() if self.user == config.sections["server"]["login"]: for i in (_("_Download Folder"), _("Download Folder _To..."), _("Download _Recursive"), _("Download R_ecursive To..."), _("Upload Folder To..."), _("Upload Folder Recursive To..."), _("Open in File _Manager"), _("Copy _Folder Path"), _("Copy _URL")): actions[i].set_enabled(self.selected_folder) else: for i in (_("_Download Folder"), _("Download Folder _To..."), _("Download _Recursive"), _("Download R_ecursive To..."), _("Copy _Folder Path"), _("Copy _URL")): actions[i].set_enabled(self.selected_folder) self.user_popup.toggle_user_items() def select_files(self): self.selected_files.clear() model, paths = self.FileTreeView.get_selection().get_selected_rows() for path in paths: iterator = model.get_iter(path) rawfilename = model.get_value(iterator, 0) filesize = model.get_value(iterator, 4) self.selected_files[rawfilename] = filesize def on_file_row_activated(self, treeview, path, column): self.select_files() if self.user == config.sections["server"]["login"]: self.on_play_files() else: self.on_download_files() def on_file_popup_menu(self, menu, widget): self.select_files() num_selected_files = len(self.selected_files) actions = menu.get_actions() if self.user == config.sections["server"]["login"]: for i in (_("Download"), _("Upload"), _("Send to _Player"), _("File _Properties"), _("Copy _File Path"), _("Copy _URL")): actions[i].set_enabled(num_selected_files) actions[_("Open in File _Manager")].set_enabled( self.selected_folder) else: for i in (_("Download"), _("File _Properties"), _("Copy _File Path"), _("Copy _URL")): actions[i].set_enabled(num_selected_files) menu.set_num_selected_files(num_selected_files) self.user_popup.toggle_user_items() def make_new_model(self, shares, private_shares=None): # Temporarily disable sorting for improved performance self.dir_store.set_default_sort_func(lambda *args: 0) self.dir_store.set_sort_column_id(-1, Gtk.SortType.ASCENDING) self.shares = shares private_size = num_private_folders = 0 self.query = None self.selected_folder = None self.selected_files.clear() self.directories.clear() self.files.clear() self.dir_store.clear() self.file_store.clear() self.search_list.clear() # Generate the directory tree and select first directory size, num_folders = self.create_folder_tree(shares) if config.sections["ui"]["private_shares"] and private_shares: self.shares = shares + private_shares private_size, num_private_folders = self.create_folder_tree( private_shares, private=True) self.AmountShared.set_text(human_size(size + private_size)) self.NumDirectories.set_text(str(num_folders + num_private_folders)) if self.ExpandButton.get_active(): self.FolderTreeView.expand_all() else: self.FolderTreeView.collapse_all() self.dir_store.set_sort_column_id(0, Gtk.SortType.ASCENDING) iterator = self.dir_store.get_iter_first() sel = self.FolderTreeView.get_selection() sel.unselect_all() if iterator: path = self.dir_store.get_path(iterator) sel.select_path(path) self.set_finished() def create_folder_tree(self, shares, private=False): size = 0 if not shares: num_folders = 0 return size, num_folders for folder, files in shares: current_path = "" root_processed = False for subfolder in folder.split('\\'): parent = self.directories.get(current_path) if not root_processed: current_path = subfolder root_processed = True else: current_path = '\\'.join([current_path, subfolder]) if current_path in self.directories: # Folder was already added to tree continue if not subfolder: # Most likely a root folder subfolder = '\\' if private: subfolder = "[PRIVATE FOLDER] " + subfolder self.directories[ current_path] = self.dir_store.insert_with_values( parent, -1, self.dir_column_numbers, [subfolder, current_path]) for filedata in files: size += filedata[2] return size, len(shares) def browse_queued_folder(self): """ Browse a queued folder in the share """ try: iterator = self.directories[self.queued_folder] except KeyError: # Folder not found return if self.queued_folder: sel = self.FolderTreeView.get_selection() sel.unselect_all() path = self.dir_store.get_path(iterator) self.FolderTreeView.expand_to_path(path) sel.select_path(path) self.FolderTreeView.scroll_to_cell(path, None, True, 0.5, 0.5) self.queued_folder = None def set_directory(self, directory): # Temporarily disable sorting for improved performance self.file_store.set_default_sort_func(lambda *args: 0) self.file_store.set_sort_column_id(-1, Gtk.SortType.ASCENDING) self.selected_folder = directory self.file_store.clear() self.files.clear() found_dir = False for d, f in self.shares: if d == directory: found_dir = True files = f break if not found_dir: return for file in files: # Filename, HSize, Bitrate, HLength, Size, Length, RawFilename try: size = int(file[2]) except ValueError: size = 0 f = [file[1], human_size(size)] h_bitrate, bitrate, h_length, length = get_result_bitrate_length( size, file[4]) f += [ h_bitrate, h_length, GObject.Value(GObject.TYPE_UINT64, int(size)), GObject.Value(GObject.TYPE_UINT64, bitrate), GObject.Value(GObject.TYPE_UINT64, length) ] try: self.files[f[0]] = self.file_store.insert_with_valuesv( -1, self.file_column_numbers, f) except Exception as msg: log.add( _("Error while attempting to display folder '%(folder)s', reported error: %(error)s" ), { 'folder': directory, 'error': msg }) self.file_store.set_sort_column_id(0, Gtk.SortType.ASCENDING) def on_save(self, *args): sharesdir = os.path.join(config.data_dir, "usershares") try: if not os.path.exists(sharesdir): os.makedirs(sharesdir) except Exception as msg: log.add( _("Can't create directory '%(folder)s', reported error: %(error)s" ), { 'folder': sharesdir, 'error': msg }) try: get_path(sharesdir, self.user, self.dump_shares_to_file) log.add( _("Saved list of shared files for user '%(user)s' to %(dir)s"), { 'user': self.user, 'dir': sharesdir }) except Exception as msg: log.add( _("Can't save shares, '%(user)s', reported error: %(error)s"), { 'user': self.user, 'error': msg }) def dump_shares_to_file(self, path, data): with open(path, "w", encoding="utf-8") as sharesfile: import json json.dump(self.shares, sharesfile, ensure_ascii=False) def save_columns(self): save_columns("user_browse", self.FileTreeView.get_columns()) def shared_file_list(self, msg): self.make_new_model(msg.list, msg.privatelist) self.info_bar.set_visible(False) if msg.list or (config.sections["ui"]["private_shares"] and msg.privatelist): self.browse_queued_folder() else: self.info_bar.show_message( _("User's list of shared files is empty. Either the user is not sharing anything, " "or they are sharing files privately.")) self.set_finished() def show_connection_error(self): self.info_bar.show_message( _("Unable to request shared files from user. Either the user is offline, you both have " "a closed listening port, or there's a temporary connectivity issue." )) self.set_finished() def set_in_progress(self, indeterminate_progress): if not indeterminate_progress: self.progressbar1.set_fraction(0.0) else: self.progressbar1.set_fraction(0.5) self.RefreshButton.set_sensitive(False) def set_finished(self): # Tab notification self.frame.request_tab_icon(self.frame.UserBrowseTabLabel) self.userbrowses.request_changed(self.Main) self.progressbar1.set_fraction(1.0) self.FolderTreeView.set_sensitive(True) self.FileTreeView.set_sensitive(True) self.RefreshButton.set_sensitive(True) def update_gauge(self, msg): if msg.total == 0 or msg.bufferlen == 0: fraction = 0.0 elif msg.bufferlen >= msg.total: fraction = 1.0 else: fraction = float(msg.bufferlen) / msg.total self.progressbar1.set_fraction(fraction) def copy_selected_path(self, is_file=False): if self.selected_folder is None: return text = self.selected_folder if is_file and self.selected_files: text = "\\".join( [self.selected_folder, next(iter(self.selected_files))]) copy_text(text) def on_select_dir(self, selection): model, iterator = selection.get_selected() if iterator is None: return directory = model.get_value(iterator, 1) self.set_directory(directory) def on_file_properties(self, *args): data = [] model, paths = self.FileTreeView.get_selection().get_selected_rows() for path in paths: iterator = model.get_iter(path) filename = model.get_value(iterator, 0) fn = "\\".join([self.selected_folder, filename]) size = model.get_value(iterator, 1) bitratestr = model.get_value(iterator, 2) length = model.get_value(iterator, 3) data.append({ "user": self.user, "fn": fn, "filename": filename, "directory": self.selected_folder, "size": size, "bitrate": bitratestr, "length": length, "immediate": None, "speed": None, "country": None }) if paths: FileProperties(self.frame, data).show() def on_download_directory(self, *args): if self.selected_folder is not None: self.download_directory(self.selected_folder) def on_download_directory_recursive(self, *args): self.download_directory(self.selected_folder, prefix="", recurse=True) def on_download_directory_to_selected(self, selected, recurse): try: self.download_directory(self.selected_folder, prefix=os.path.join(selected, ""), recurse=recurse) except IOError: # failed to open log.add('Failed to open %r for reading', selected) # notify user def on_download_directory_to(self, *args, recurse=False): choose_dir(parent=self.frame.MainWindow, callback=self.on_download_directory_to_selected, callback_data=recurse, initialdir=config.sections["transfers"]["downloaddir"], multichoice=False) def on_download_directory_recursive_to(self, *args): self.on_download_directory_to(recurse=True) def download_directory(self, folder, prefix="", recurse=False): if not self.frame.np.active_server_conn or folder is None: return # Remember custom download location self.frame.np.transfers.requested_folders[self.user][folder] = prefix # Get final download destination destination = self.frame.np.transfers.get_folder_destination( self.user, folder) for d, files in self.shares: # Find the wanted directory if d != folder: continue if config.sections["transfers"]["reverseorder"]: files.sort(key=lambda x: x[1], reverse=True) for file in files: virtualpath = "\\".join([folder, file[1]]) size = file[2] h_bitrate, bitrate, h_length, length = get_result_bitrate_length( size, file[4]) self.frame.np.transfers.get_file(self.user, virtualpath, destination, size=size, bitrate=h_bitrate, length=h_length, checkduplicate=True) if not recurse: return for subdir, subf in self.shares: if folder in subdir and folder != subdir: self.download_directory(subdir, prefix=os.path.join(destination, "")) def on_download_files(self, *args, prefix=""): if not self.frame.np.active_server_conn: return folder = self.selected_folder for d, f in self.shares: # Find the wanted directory if d != folder: continue for file in f: # Find the wanted file if file[1] not in self.selected_files: continue virtualpath = "\\".join([folder, file[1]]) size = file[2] h_bitrate, bitrate, h_length, length = get_result_bitrate_length( size, file[4]) # Get the file self.frame.np.transfers.get_file(self.user, virtualpath, prefix, size=size, bitrate=h_bitrate, length=h_length, checkduplicate=True) # We have found the wanted directory: we can break out of the loop break def on_download_files_to_selected(self, selected, data): try: self.on_download_files(prefix=selected) except IOError: # failed to open log.add('failed to open %r for reading', selected) # notify user def on_download_files_to(self, *args): try: _, folder = self.selected_folder.rsplit("\\", 1) except ValueError: folder = self.selected_folder download_folder = config.sections["transfers"]["downloaddir"] path = os.path.join(download_folder, folder) if not os.path.exists(path) or not os.path.isdir(path): path = download_folder choose_dir(parent=self.frame.MainWindow, callback=self.on_download_files_to_selected, initialdir=path, multichoice=False) def on_upload_directory_to_response(self, dialog, response_id, recurse): user = dialog.get_response_value() folder = self.selected_folder dialog.destroy() if response_id != Gtk.ResponseType.OK: return if not user or folder is None: return self.frame.np.send_message_to_peer( user, slskmessages.UploadQueueNotification(None)) self.upload_directory_to(user, folder, recurse) def on_upload_directory_to(self, *args, recurse=False): folder = self.selected_folder if folder is None: return users = [] for row in config.sections["server"]["userlist"]: if row and isinstance(row, list): user = str(row[0]) users.append(user) users.sort() entry_dialog( parent=self.frame.MainWindow, title=_("Upload Folder's Contents"), message=_('Enter the name of a user you wish to upload to:'), callback=self.on_upload_directory_to_response, callback_data=recurse, droplist=users) def on_upload_directory_recursive_to(self, *args): self.on_upload_directory_to(recurse=True) def upload_directory_to(self, user, folder, recurse=False): if not self.frame.np.active_server_conn: return if folder == "" or folder is None or user is None or user == "": return ldir = folder.split("\\")[-1] locally_queued = False for d, f in self.shares: # Find the wanted directory if d != folder: continue for file in f: filename = "\\".join([folder, file[1]]) size = file[2] self.frame.np.transfers.push_file( user, filename, ldir, size=size, locally_queued=locally_queued) locally_queued = True if not recurse: return for subdir, subf in self.shares: if folder in subdir and folder != subdir: self.upload_directory_to(user, subdir, recurse) def on_upload_files_response(self, dialog, response_id, data): user = dialog.get_response_value() folder = self.selected_folder dialog.destroy() if response_id != Gtk.ResponseType.OK: return if not user or folder is None: return self.frame.np.send_message_to_peer( user, slskmessages.UploadQueueNotification(None)) locally_queued = False prefix = "" for fn, size in self.selected_files.items(): self.frame.np.transfers.push_file(user, "\\".join([folder, fn]), prefix, size=size, locally_queued=locally_queued) locally_queued = True def on_upload_files(self, *args): if not self.frame.np.active_server_conn: return users = [] for row in config.sections["server"]["userlist"]: if row and isinstance(row, list): user = str(row[0]) users.append(user) users.sort() entry_dialog( parent=self.frame.MainWindow, title=_('Upload File(s)'), message=_('Enter the name of a user you wish to upload to:'), callback=self.on_upload_files_response, droplist=users) def on_folder_key_press_event(self, *args): is_file = False return self.on_key_press_event(is_file, *args) def on_file_key_press_event(self, *args): is_file = True return self.on_key_press_event(is_file, *args) def on_key_press_event(self, is_file, *args): keyval, keycode, state = get_key_press_event_args(*args) self.select_files() keycodes, mods = parse_accelerator("<Primary>c") if state & mods and keycode in keycodes: self.copy_selected_path(is_file=is_file) else: # No key match, continue event return False return True def on_play_files(self, *args): path = self.frame.np.shares.virtual2real(self.selected_folder) for fn in self.selected_files: playfile = os.sep.join([path, fn]) if os.path.exists(playfile): command = config.sections["players"]["default"] open_file_path(playfile, command) def find_matches(self): self.search_list.clear() for directory, files in self.shares: if self.query in directory.lower( ) and directory not in self.search_list: self.search_list.append(directory) continue for file in files: if self.query in file[1].lower( ) and directory not in self.search_list: self.search_list.append(directory) def on_search(self, *args): query = self.SearchEntry.get_text().lower() if not query: return if self.query == query: self.search_position += 1 else: self.search_position = 0 self.query = query self.find_matches() if not self.search_list: return if self.search_position >= len(self.search_list): self.search_position = 0 self.search_list = sorted(self.search_list, key=str.casefold) directory = self.search_list[self.search_position] path = self.dir_store.get_path(self.directories[directory]) self.FolderTreeView.expand_to_path(path) self.FolderTreeView.set_cursor(path) # Get matching files in the current directory resultfiles = [] for file in self.files: if query in file.lower(): resultfiles.append(file) sel = self.FileTreeView.get_selection() sel.unselect_all() not_selected = 1 resultfiles.sort() for fn in resultfiles: path = self.file_store.get_path(self.files[fn]) # Select each matching file in directory sel.select_path(path) if not_selected: # Position cursor at first match self.FileTreeView.scroll_to_cell(path, None, True, 0.5, 0.5) not_selected = 0 def on_refresh(self, *args): self.info_bar.set_visible(False) self.FolderTreeView.set_sensitive(False) self.FileTreeView.set_sensitive(False) self.set_in_progress(self.indeterminate_progress) self.frame.np.userbrowse.browse_user( self.user, local_shares_type=self.local_shares_type, new_request=True) def on_copy_folder_path(self, *args): self.copy_selected_path() def on_copy_file_path(self, *args): self.copy_selected_path(is_file=True) def on_copy_url(self, *args): if self.selected_files: path = "\\".join( [self.selected_folder, next(iter(self.selected_files))]) copy_file_url(self.user, path) def on_copy_dir_url(self, *args): if self.selected_folder is None: return path = self.selected_folder + '\\' copy_file_url(self.user, path) def on_file_manager(self, *args): if self.selected_folder is None: return path = self.frame.np.shares.virtual2real(self.selected_folder) command = config.sections["ui"]["filemanager"] open_file_path(path, command) def on_tab_popup(self, *args): self.user_popup.toggle_user_items() def on_close(self, *args): del self.userbrowses.pages[self.user] self.frame.np.userbrowse.remove_user(self.user) self.userbrowses.remove_page(self.Main) def on_close_all_tabs(self, *args): self.userbrowses.remove_all_pages()
class UserBrowse: def __init__(self, userbrowses, user): self.userbrowses = userbrowses self.frame = userbrowses.frame # Build the window load_ui_elements( self, os.path.join(self.frame.gui_dir, "ui", "userbrowse.ui")) self.info_bar = InfoBar(self.InfoBar, Gtk.MessageType.INFO) # Monitor user online status self.frame.np.watch_user(user) self.user = user self.conn = None self.local_shares_type = None self.refreshing = True # selected_folder is the current selected folder self.selected_folder = None # queued_folder is a folder that should be opened once the share has loaded self.queued_folder = None self.search_list = [] self.query = None self.search_position = 0 self.selected_files = {} self.shares = [] # Iters for current DirStore self.directories = {} # Iters for current FileStore self.files = {} self.totalsize = 0 self.dir_store = Gtk.TreeStore(str, str) self.dir_column_numbers = list(range(self.dir_store.get_n_columns())) cols = initialise_columns( None, self.FolderTreeView, ["folders", _("Folders"), -1, "text", None] # 0 ) cols["folders"].set_sort_column_id(0) self.user_popup = popup = PopupMenu(self.frame) popup.setup_user_menu(user, page="userbrowse") popup.setup(("", None), ("#" + _("_Save Shares List To Disk"), self.on_save), ("#" + _("Close All Tabs"), self.on_close_all_tabs), ("#" + _("_Close Tab"), self.on_close)) self.popup_menu_downloads_folders = PopupMenu(self.frame) self.popup_menu_downloads_folders.setup( ("#" + _("_Download Folder"), self.on_download_directory), ("#" + _("Download Folder _To..."), self.on_download_directory_to), ("#" + _("Download _Recursive"), self.on_download_directory_recursive), ("#" + _("Download R_ecursive To..."), self.on_download_directory_recursive_to)) self.popup_menu_downloads_files = PopupMenu(self.frame) self.popup_menu_downloads_files.setup( ("#" + _("_Download File(s)"), self.on_download_files), ("#" + _("Download _To..."), self.on_download_files_to), ("", None), ("#" + _("_Download Folder"), self.on_download_directory), ("#" + _("Download Folder _To..."), self.on_download_directory_to), ("#" + _("Download _Recursive"), self.on_download_directory_recursive), ("#" + _("Download R_ecursive To..."), self.on_download_directory_recursive_to)) self.popup_menu_uploads_folders = PopupMenu(self.frame) self.popup_menu_uploads_folders.setup( ("#" + _("Upload Folder To..."), self.on_upload_directory_to), ("#" + _("Upload Folder Recursive To..."), self.on_upload_directory_recursive_to)) self.popup_menu_uploads_files = PopupMenu(self.frame) self.popup_menu_uploads_files.setup( ("#" + _("Upload Folder To..."), self.on_upload_directory_to), ("#" + _("Upload Folder Recursive To..."), self.on_upload_directory_recursive_to), ("#" + _("Up_load File(s)"), self.on_upload_files)) self.folder_popup_menu = PopupMenu(self.frame) if user == config.sections["server"]["login"]: self.folder_popup_menu.setup( ("#" + _("_Download Folder"), self.on_download_directory), ("#" + _("Download Folder _To..."), self.on_download_directory_to), ("#" + _("Download _Recursive"), self.on_download_directory_recursive), ("#" + _("Download R_ecursive To..."), self.on_download_directory_recursive_to), ("", None), ("#" + _("Upload Folder To..."), self.on_upload_directory_to), ("#" + _("Upload Folder Recursive To..."), self.on_upload_directory_recursive_to), ("", None), ("#" + _("Open in File _Manager"), self.on_file_manager), ("", None), ("#" + _("Copy _Folder Path"), self.on_copy_folder_path), ("#" + _("Copy _URL"), self.on_copy_dir_url), ("", None), (">" + _("User"), self.user_popup)) else: self.folder_popup_menu.setup( ("#" + _("_Download Folder"), self.on_download_directory), ("#" + _("Download Folder _To..."), self.on_download_directory_to), ("#" + _("Download _Recursive"), self.on_download_directory_recursive), ("#" + _("Download R_ecursive To..."), self.on_download_directory_recursive_to), ("", None), ("#" + _("Copy _Folder Path"), self.on_copy_folder_path), ("#" + _("Copy _URL"), self.on_copy_dir_url), ("", None), (">" + _("User"), self.user_popup)) self.FolderTreeView.get_selection().connect("changed", self.on_select_dir) self.file_store = Gtk.ListStore( str, # (0) file name str, # (1) hsize str, # (2) hbitrate str, # (3) hlength GObject.TYPE_UINT64, # (4) size GObject.TYPE_UINT64, # (5) bitrate GObject.TYPE_UINT64 # (6) length ) self.FileTreeView.set_model(self.file_store) self.file_column_numbers = [ i for i in range(self.file_store.get_n_columns()) ] cols = initialise_columns( "user_browse", self.FileTreeView, ["filename", _("Filename"), 600, "text", None], ["size", _("Size"), 100, "number", None], ["bitrate", _("Bitrate"), 100, "number", None], ["length", _("Length"), 100, "number", None]) cols["filename"].set_sort_column_id(0) cols["size"].set_sort_column_id(4) cols["bitrate"].set_sort_column_id(5) cols["length"].set_sort_column_id(6) self.file_store.set_sort_column_id(0, Gtk.SortType.ASCENDING) self.file_popup_menu = PopupMenu(self.frame) if user == config.sections["server"]["login"]: self.file_popup_menu.setup( ("#" + "selected_files", None), ("", None), (">" + _("Download"), self.popup_menu_downloads_files), (">" + _("Upload"), self.popup_menu_uploads_files), ("", None), ("#" + _("Send to _Player"), self.on_play_files), ("#" + _("Open in File _Manager"), self.on_file_manager), ("#" + _("File _Properties"), self.on_file_properties), ("", None), ("#" + _("Copy _File Path"), self.on_copy_file_path), ("#" + _("Copy _URL"), self.on_copy_url), ("", None), (">" + _("User"), self.user_popup)) else: self.file_popup_menu.setup( ("#" + "selected_files", None), ("", None), (">" + _("Download"), self.popup_menu_downloads_files), ("", None), ("#" + _("File _Properties"), self.on_file_properties), ("", None), ("#" + _("Copy _File Path"), self.on_copy_file_path), ("#" + _("Copy _URL"), self.on_copy_url), ("", None), (">" + _("User"), self.user_popup)) self.update_visuals() for name, object in self.__dict__.items(): if isinstance(object, PopupMenu): object.set_user(self.user) def update_visuals(self): for widget in list(self.__dict__.values()): update_widget_visuals(widget, list_font_target="browserfont") def on_expand(self, widget): if self.ExpandButton.get_active(): self.FolderTreeView.expand_all() self.expand.set_from_icon_name("go-up-symbolic", Gtk.IconSize.BUTTON) else: self.FolderTreeView.collapse_all() self.expand.set_from_icon_name("go-down-symbolic", Gtk.IconSize.BUTTON) dirs = sorted(self.directories.keys()) if dirs: self.set_directory(dirs[0]) else: self.set_directory(None) def on_folder_clicked(self, widget, event): if triggers_context_menu(event): set_treeview_selected_row(widget, event) return self.on_folder_popup_menu(widget) if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS: if self.user != config.sections["server"]["login"]: self.on_download_directory() return True return False def on_folder_popup_menu(self, *args): actions = self.folder_popup_menu.get_actions() if self.user == config.sections["server"]["login"]: for i in (_("_Download Folder"), _("Download Folder _To..."), _("Download _Recursive"), _("Download R_ecursive To..."), _("Upload Folder To..."), _("Upload Folder Recursive To..."), _("Open in File _Manager"), _("Copy _Folder Path"), _("Copy _URL")): actions[i].set_enabled(self.selected_folder) else: for i in (_("_Download Folder"), _("Download Folder _To..."), _("Download _Recursive"), _("Download R_ecursive To..."), _("Copy _Folder Path"), _("Copy _URL")): actions[i].set_enabled(self.selected_folder) self.user_popup.toggle_user_items() self.folder_popup_menu.popup() return True def select_files(self): self.selected_files.clear() model, paths = self.FileTreeView.get_selection().get_selected_rows() for path in paths: iterator = model.get_iter(path) rawfilename = model.get_value(iterator, 0) filesize = model.get_value(iterator, 4) self.selected_files[rawfilename] = filesize def on_file_clicked(self, widget, event): if triggers_context_menu(event): set_treeview_selected_row(widget, event) return self.on_file_popup_menu() if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS: self.select_files() if self.user == config.sections["server"]["login"]: self.on_play_files() else: self.on_download_files() return True return False def on_file_popup_menu(self, *args): self.select_files() num_selected_files = len(self.selected_files) actions = self.file_popup_menu.get_actions() if self.user == config.sections["server"]["login"]: for i in (_("Download"), _("Upload"), _("Send to _Player"), _("File _Properties"), _("Copy _File Path"), _("Copy _URL")): actions[i].set_enabled(num_selected_files) actions[_("Open in File _Manager")].set_enabled( self.selected_folder) else: for i in (_("Download"), _("File _Properties"), _("Copy _File Path"), _("Copy _URL")): actions[i].set_enabled(num_selected_files) self.file_popup_menu.set_num_selected_files(num_selected_files) self.user_popup.toggle_user_items() self.file_popup_menu.popup() return True def make_new_model(self, list): self.shares = list self.selected_folder = None self.selected_files.clear() self.directories.clear() self.files.clear() self.dir_store.clear() self.file_store.clear() # Compute the number of shared dirs and total size self.totalsize = 0 for dir, files in self.shares: for filedata in files: if filedata[2] < maxsize: self.totalsize += filedata[2] self.AmountShared.set_text(human_size(self.totalsize)) self.NumDirectories.set_text(str(len(self.shares))) # Generate the directory tree and select first directory currentdir = self.browse_get_dirs() sel = self.FolderTreeView.get_selection() sel.unselect_all() if currentdir in self.directories: path = self.dir_store.get_path(self.directories[currentdir]) if path is not None: sel.select_path(path) if self.ExpandButton.get_active(): self.FolderTreeView.expand_all() else: self.FolderTreeView.collapse_all() self.set_finished() def browse_get_dirs(self): directory = "" dirseparator = '\\' # If there is no share if not self.shares: # Set the model of the treeviex self.FolderTreeView.set_model(self.dir_store) # Sort the DirStore self.dir_store.set_sort_column_id(0, Gtk.SortType.ASCENDING) return directory def builddicttree(p, s): """ Build recursively a hierarchical dict containing raw subdir 'p' is a reference to the parent 's' a list of the subdir of a path ex of 's': ['music', 'rock', 'doors'] """ if s: subdir = s.pop(0) if subdir not in p: p[subdir] = {} builddicttree(p[subdir], s) def buildgtktree(dictdir, parent, path): """ Build recursively self.directories with iters pointing to directories 'dictdir' is a hierarchical dict containing raw subdir 'parent' is the iter pointing to the parent 'path' is the current raw path """ # Foreach subdir for subdir in dictdir: if parent is None: # The first sudirs are attached to the root (None) current_path = subdir else: # Other sudirs futher down the path are attached to their parent current_path = dirseparator.join([path, subdir]) self.directories[ current_path] = self.dir_store.insert_with_values( parent, -1, self.dir_column_numbers, [subdir, current_path]) # If there are subdirs futher down the path: recurse if dictdir[subdir]: buildgtktree(dictdir[subdir], self.directories[current_path], current_path) # For each shared dir we will complete the dictionnary dictdir = {} for dirshares, f in self.shares: # Split the path s = dirshares.split(dirseparator) # and build a hierarchical dictionnary containing raw subdir if len(s) >= 1: builddicttree(dictdir, s) # Append data to the DirStore buildgtktree(dictdir, None, None) # Select the first directory sortlist = sorted(self.directories.keys()) directory = sortlist[0] # Sort the DirStore self.dir_store.set_sort_column_id(0, Gtk.SortType.ASCENDING) # Set the model of the treeviex self.FolderTreeView.set_model(self.dir_store) return directory def browse_folder(self, folder): """ Browse a specific folder in the share """ try: iterator = self.directories[folder] except KeyError: # Folder not found return if folder: sel = self.FolderTreeView.get_selection() sel.unselect_all() path = self.dir_store.get_path(iterator) self.FolderTreeView.expand_to_path(path) sel.select_path(path) self.FolderTreeView.scroll_to_cell(path, None, True, 0.5, 0.5) self.queued_folder = None def set_directory(self, directory): self.selected_folder = directory self.file_store.clear() self.files.clear() found_dir = False for d, f in self.shares: if d == directory: found_dir = True files = f break if not found_dir: return for file in files: # Filename, HSize, Bitrate, HLength, Size, Length, RawFilename try: size = int(file[2]) # Some clients send incorrect file sizes if size < 0 or size > maxsize: size = 0 except ValueError: size = 0 f = [file[1], human_size(size)] h_bitrate, bitrate, h_length, length = get_result_bitrate_length( size, file[4]) f += [ h_bitrate, h_length, GObject.Value(GObject.TYPE_UINT64, int(size)), GObject.Value(GObject.TYPE_UINT64, bitrate), GObject.Value(GObject.TYPE_UINT64, length) ] try: self.files[f[0]] = self.file_store.insert_with_valuesv( -1, self.file_column_numbers, f) except Exception as msg: log.add( _("Error while attempting to display folder '%(folder)s', reported error: %(error)s" ), { 'folder': directory, 'error': msg }) def on_save(self, *args): sharesdir = os.path.join(config.data_dir, "usershares") try: if not os.path.exists(sharesdir): os.makedirs(sharesdir) except Exception as msg: log.add( _("Can't create directory '%(folder)s', reported error: %(error)s" ), { 'folder': sharesdir, 'error': msg }) try: get_path(sharesdir, self.user, self.dump_shares_to_file) log.add( _("Saved list of shared files for user '%(user)s' to %(dir)s"), { 'user': self.user, 'dir': sharesdir }) except Exception as msg: log.add( _("Can't save shares, '%(user)s', reported error: %(error)s"), { 'user': self.user, 'error': msg }) def dump_shares_to_file(self, path, data): with open(path, "w", encoding="utf-8") as sharesfile: import json json.dump(self.shares, sharesfile, ensure_ascii=False) def save_columns(self): save_columns("user_browse", self.FileTreeView.get_columns()) def show_user(self, msg, folder=None, indeterminate_progress=False, local_shares_type=None): self.set_in_progress(indeterminate_progress) if folder: self.queued_folder = folder # If this is our own share, remember if it's public or buddy # (needed for refresh button) if local_shares_type: self.local_shares_type = local_shares_type """ Update the list model if: 1. This is a new user browse tab 2. We're refreshing the file list 3. This is the list of our own shared files (local_shares_type set) """ if self.refreshing or local_shares_type: if msg is None: return self.make_new_model(msg.list) if msg and not msg.list: self.info_bar.show_message( _("User's list of shared files is empty. Either the user is not sharing anything, or they are sharing files privately." )) else: self.info_bar.set_visible(False) self.browse_folder(self.queued_folder) self.set_finished() def show_connection_error(self): self.info_bar.show_message( _("Unable to request shared files from user. Either the user is offline, you both have a closed listening port, or there's a temporary connectivity issue." )) self.set_finished() def load_shares(self, list): self.make_new_model(list) def is_refreshing(self): return self.refreshing def set_in_progress(self, indeterminate_progress): if not indeterminate_progress: self.progressbar1.set_fraction(0.0) else: self.progressbar1.set_fraction(0.5) self.RefreshButton.set_sensitive(False) def set_finished(self): # Tab notification self.frame.request_tab_icon(self.frame.UserBrowseTabLabel) self.userbrowses.request_changed(self.Main) self.progressbar1.set_fraction(1.0) self.FolderTreeView.set_sensitive(True) self.FileTreeView.set_sensitive(True) self.RefreshButton.set_sensitive(True) self.refreshing = False def update_gauge(self, msg): if msg.total == 0 or msg.bytes == 0: fraction = 0.0 elif msg.bytes >= msg.total: fraction = 1.0 else: fraction = float(msg.bytes) / msg.total self.progressbar1.set_fraction(fraction) def copy_selected_path(self, is_file=False): if self.selected_folder is None: return text = self.selected_folder if is_file and self.selected_files: text = "\\".join( [self.selected_folder, next(iter(self.selected_files))]) self.frame.clipboard.set_text(text, -1) def on_select_dir(self, selection): model, iterator = selection.get_selected() if iterator is None: return path = model.get_path(iterator) directory = model.get_value(iterator, 1) self.FolderTreeView.expand_to_path(path) self.set_directory(directory) def on_file_properties(self, *args): data = [] model, paths = self.FileTreeView.get_selection().get_selected_rows() for path in paths: iterator = model.get_iter(path) filename = model.get_value(iterator, 0) fn = "\\".join([self.selected_folder, filename]) size = model.get_value(iterator, 1) bitratestr = model.get_value(iterator, 2) length = model.get_value(iterator, 3) data.append({ "user": self.user, "fn": fn, "filename": filename, "directory": self.selected_folder, "size": size, "bitrate": bitratestr, "length": length, "immediate": None, "speed": None, "country": None }) if paths: FileProperties(self.frame, data).show() def on_download_directory(self, *args): if self.selected_folder is not None: self.download_directory(self.selected_folder) def on_download_directory_recursive(self, *args): self.download_directory(self.selected_folder, prefix="", recurse=True) def on_download_directory_to_selected(self, selected, recurse): try: self.download_directory(self.selected_folder, prefix=os.path.join(selected, ""), recurse=recurse) except IOError: # failed to open log.add('Failed to open %r for reading', selected) # notify user def on_download_directory_to(self, *args, recurse=False): choose_dir(parent=self.frame.MainWindow, callback=self.on_download_directory_to_selected, callback_data=recurse, initialdir=config.sections["transfers"]["downloaddir"], multichoice=False) def on_download_directory_recursive_to(self, *args): self.on_download_directory_to(recurse=True) def download_directory(self, folder, prefix="", recurse=False): if self.frame.np.transfers is None or folder is None: return # Remember custom download location self.frame.np.transfers.requested_folders[self.user][folder] = prefix # Get final download destination destination = self.frame.np.transfers.get_folder_destination( self.user, folder) for d, files in self.shares: # Find the wanted directory if d != folder: continue if config.sections["transfers"]["reverseorder"]: files.sort(key=lambda x: x[1], reverse=True) for file in files: virtualpath = "\\".join([folder, file[1]]) size = file[2] h_bitrate, bitrate, h_length, length = get_result_bitrate_length( size, file[4]) self.frame.np.transfers.get_file(self.user, virtualpath, destination, size=size, bitrate=h_bitrate, length=h_length, checkduplicate=True) if not recurse: return for subdir, subf in self.shares: if folder in subdir and folder != subdir: self.download_directory(subdir, prefix=os.path.join(destination, "")) def on_download_files(self, *args, prefix=""): if not self.frame.np.transfers: return folder = self.selected_folder for d, f in self.shares: # Find the wanted directory if d != folder: continue for file in f: # Find the wanted file if file[1] not in self.selected_files: continue virtualpath = "\\".join([folder, file[1]]) size = file[2] h_bitrate, bitrate, h_length, length = get_result_bitrate_length( size, file[4]) # Get the file self.frame.np.transfers.get_file(self.user, virtualpath, prefix, size=size, bitrate=h_bitrate, length=h_length, checkduplicate=True) # We have found the wanted directory: we can break out of the loop break def on_download_files_to_selected(self, selected, data): try: self.on_download_files(prefix=selected) except IOError: # failed to open log.add('failed to open %r for reading', selected) # notify user def on_download_files_to(self, *args): try: _, folder = self.selected_folder.rsplit("\\", 1) except ValueError: folder = self.selected_folder download_folder = config.sections["transfers"]["downloaddir"] path = os.path.join(download_folder, folder) if not os.path.exists(path) or not os.path.isdir(path): path = download_folder choose_dir(parent=self.frame.MainWindow, callback=self.on_download_files_to_selected, initialdir=path, multichoice=False) def on_upload_directory_to_response(self, dialog, response_id, recurse): user = dialog.get_response_value() folder = self.selected_folder dialog.destroy() if not user or folder is None: return self.frame.np.send_message_to_peer( user, slskmessages.UploadQueueNotification(None)) self.upload_directory_to(user, folder, recurse) def on_upload_directory_to(self, *args, recurse=False): folder = self.selected_folder if folder is None: return users = [] for entry in config.sections["server"]["userlist"]: users.append(entry[0]) users.sort() combo_box_dialog(parent=self.frame.MainWindow, title=_("Upload Folder's Contents"), message=_('Enter the User you wish to upload to:'), callback=self.on_upload_directory_to_response, callback_data=recurse, droplist=users) def on_upload_directory_recursive_to(self, *args): self.on_upload_directory_to(recurse=True) def upload_directory_to(self, user, folder, recurse=False): if not self.frame.np.transfers: return if folder == "" or folder is None or user is None or user == "": return ldir = folder.split("\\")[-1] locally_queued = False for d, f in self.shares: # Find the wanted directory if d != folder: continue for file in f: filename = "\\".join([folder, file[1]]) size = file[2] self.frame.np.transfers.push_file( user, filename, ldir, size=size, locally_queued=locally_queued) locally_queued = True if not recurse: return for subdir, subf in self.shares: if folder in subdir and folder != subdir: self.upload_directory_to(user, subdir, recurse) def on_upload_files_response(self, dialog, response_id, data): user = dialog.get_response_value() folder = self.selected_folder dialog.destroy() if not user or folder is None: return self.frame.np.send_message_to_peer( user, slskmessages.UploadQueueNotification(None)) locally_queued = False prefix = "" for fn, size in self.selected_files.items(): self.frame.np.transfers.push_file(user, "\\".join([folder, fn]), prefix, size=size, locally_queued=locally_queued) locally_queued = True def on_upload_files(self, *args): if not self.frame.np.transfers: return users = [] for entry in config.sections["server"]["userlist"]: users.append(entry[0]) users.sort() combo_box_dialog(parent=self.frame.MainWindow, title=_('Upload File(s)'), message=_('Enter the User you wish to upload to:'), callback=self.on_upload_files_response, droplist=users) def on_key_press_event(self, widget, event): self.select_files() if event.get_state() & Gdk.ModifierType.CONTROL_MASK and \ event.hardware_keycode in keyval_to_hardware_keycode(Gdk.KEY_c): self.copy_selected_path(is_file=(widget == self.FileTreeView)) else: # No key match, continue event return False widget.stop_emission_by_name("key_press_event") return True def on_play_files(self, *args): path = self.frame.np.shares.virtual2real(self.selected_folder) for fn in self.selected_files: playfile = os.sep.join([path, fn]) if os.path.exists(playfile): command = config.sections["players"]["default"] open_file_path(playfile, command) def find_matches(self): self.search_list = [] for directory, files in self.shares: if self.query in directory.lower(): if directory not in self.search_list: self.search_list.append(directory) for file in files: if self.query in file[1].lower(): if directory not in self.search_list: self.search_list.append(directory) def on_search(self, *args): query = self.SearchEntry.get_text().lower() if self.query == query: self.search_position += 1 else: self.search_position = 0 self.query = query if self.query == "": return self.find_matches() if self.search_list: if self.search_position not in list(range(len(self.search_list))): self.search_position = 0 self.search_list.sort() directory = self.search_list[self.search_position] path = self.dir_store.get_path(self.directories[directory]) self.FolderTreeView.expand_to_path(path) self.FolderTreeView.set_cursor(path) # Get matching files in the current directory resultfiles = [] for file in self.files: if query in file.lower(): resultfiles.append(file) sel = self.FileTreeView.get_selection() sel.unselect_all() not_selected = 1 resultfiles.sort() for fn in resultfiles: path = self.file_store.get_path(self.files[fn]) # Select each matching file in directory sel.select_path(path) if not_selected: # Position cursor at first match self.FileTreeView.scroll_to_cell(path, None, True, 0.5, 0.5) not_selected = 0 else: self.search_position = 0 def on_refresh(self, *args): self.refreshing = True self.info_bar.set_visible(False) self.FolderTreeView.set_sensitive(False) self.FileTreeView.set_sensitive(False) self.frame.browse_user(self.user, local_shares_type=self.local_shares_type) def on_copy_folder_path(self, *args): self.copy_selected_path() def on_copy_file_path(self, *args): self.copy_selected_path(is_file=True) def on_copy_url(self, *args): if self.selected_files: path = "\\".join( [self.selected_folder, next(iter(self.selected_files))]) copy_file_url(self.user, path, self.frame.clipboard) def on_copy_dir_url(self, *args): if self.selected_folder is None: return path = self.selected_folder + '\\' copy_file_url(self.user, path, self.frame.clipboard) def on_file_manager(self, *args): if self.selected_folder is None: return path = self.frame.np.shares.virtual2real(self.selected_folder) command = config.sections["ui"]["filemanager"] open_file_path(path, command) def on_close(self, *args): del self.userbrowses.users[self.user] self.userbrowses.remove_page(self.Main) def on_close_all_tabs(self, *args): self.userbrowses.remove_all_pages()
def __init__(self, frame): # Build the window self.frame = frame load_ui_elements(self, os.path.join(self.frame.gui_dir, "ui", "buddylist.ui")) """ Columns """ self.user_iterators = {} self.usersmodel = Gtk.ListStore( GObject.TYPE_OBJECT, # (0) status icon GObject.TYPE_OBJECT, # (1) flag str, # (2) username str, # (3) hspeed str, # (4) hfile count bool, # (5) trusted bool, # (6) notify bool, # (7) privileged str, # (8) hlast seen str, # (9) comments int, # (10) status GObject.TYPE_UINT64, # (11) speed GObject.TYPE_UINT64, # (12) file count int, # (13) last seen str # (14) country ) self.column_numbers = list(range(self.usersmodel.get_n_columns())) self.cols = cols = initialise_columns( "buddy_list", self.UserListTree, ["status", _("Status"), 25, "pixbuf", None], ["country", _("Country"), 25, "pixbuf", None], ["user", _("User"), 250, "text", None], ["speed", _("Speed"), 150, "number", None], ["files", _("Files"), 150, "number", None], ["trusted", _("Trusted"), 0, "toggle", None], ["notify", _("Notify"), 0, "toggle", None], ["privileged", _("Privileged"), 0, "toggle", None], ["last_seen", _("Last seen"), 160, "text", None], ["comments", _("Comments"), 400, "edit", None] ) cols["status"].set_sort_column_id(10) cols["country"].set_sort_column_id(14) cols["user"].set_sort_column_id(2) cols["speed"].set_sort_column_id(11) cols["files"].set_sort_column_id(12) cols["trusted"].set_sort_column_id(5) cols["notify"].set_sort_column_id(6) cols["privileged"].set_sort_column_id(7) cols["last_seen"].set_sort_column_id(13) cols["comments"].set_sort_column_id(9) cols["status"].get_widget().hide() cols["country"].get_widget().hide() if config.sections["columns"]["hideflags"]: cols["country"].set_visible(False) for render in cols["trusted"].get_cells(): render.connect('toggled', self.cell_toggle_callback, self.UserListTree, 5) for render in cols["notify"].get_cells(): render.connect('toggled', self.cell_toggle_callback, self.UserListTree, 6) for render in cols["privileged"].get_cells(): render.connect('toggled', self.cell_toggle_callback, self.UserListTree, 7) for render in cols["comments"].get_cells(): render.connect('edited', self.cell_edited_callback, self.UserListTree, 9) self.UserListTree.set_model(self.usersmodel) """ Buddy list """ for user in config.sections["server"]["userlist"]: try: username, comment, notify, privileged, trusted, last_seen, country = user except ValueError: # Invalid user row continue try: time_from_epoch = time.mktime(time.strptime(last_seen, "%m/%d/%Y %H:%M:%S")) except ValueError: last_seen = _("Never seen") time_from_epoch = 0 username = str(username) row = [ GObject.Value(GObject.TYPE_OBJECT, self.frame.get_status_image(0)), GObject.Value(GObject.TYPE_OBJECT, self.frame.get_flag_image(country)), username, "", "", bool(trusted), bool(notify), bool(privileged), str(last_seen), str(comment), 0, 0, 0, time_from_epoch, str(country) ] self.user_iterators[username] = self.usersmodel.insert_with_valuesv(0, self.column_numbers, row) self.usersmodel.set_sort_column_id(2, Gtk.SortType.ASCENDING) self.buddies_combo_entries = ( self.frame.UserSearchCombo, self.frame.PrivateChatCombo, self.frame.UserInfoCombo, self.frame.UserBrowseCombo ) self.buddies_combos_fill() """ Popup """ self.popup_menu_private_rooms = PopupMenu(self.frame) self.popup_menu = popup = PopupMenu(frame) popup.setup_user_menu(page="userlist") popup.setup( ("", None), ("$" + _("_Online Notify"), self.on_notify), ("$" + _("_Privileged"), self.on_privileged), ("$" + _("_Trusted"), self.on_trusted), ("", None), (">" + _("Private Rooms"), self.popup_menu_private_rooms), ("#" + _("Edit _Comments"), self.on_edit_comments), ("#" + _("_Remove"), self.on_remove_user) ) self.update_visuals()
def __init__(self, frame): super().__init__("ui/interests.ui") frame.interests_container.add(self.Main) self.frame = frame self.page_id = "interests" self.populated_recommends = False # Columns self.likes = {} self.likes_model = Gtk.ListStore(str) self.likes_model.set_sort_column_id(0, Gtk.SortType.ASCENDING) self.likes_column_numbers = list( range(self.likes_model.get_n_columns())) cols = initialise_columns( frame, None, self.LikesList, ["likes", _("Likes"), -1, "text", None]) cols["likes"].set_sort_column_id(0) self.LikesList.set_model(self.likes_model) self.dislikes = {} self.dislikes_model = Gtk.ListStore(str) self.dislikes_model.set_sort_column_id(0, Gtk.SortType.ASCENDING) self.dislikes_column_numbers = list( range(self.dislikes_model.get_n_columns())) cols = initialise_columns( frame, None, self.DislikesList, ["dislikes", _("Dislikes"), -1, "text", None]) cols["dislikes"].set_sort_column_id(0) self.DislikesList.set_model(self.dislikes_model) self.recommendations_model = Gtk.ListStore( str, # (0) hrating str, # (1) item int # (2) rating ) self.recommendations_column_numbers = list( range(self.recommendations_model.get_n_columns())) cols = initialise_columns( frame, None, self.RecommendationsList, ["rating", _("Rating"), 0, "number", None], ["item", _("Item"), -1, "text", None]) cols["rating"].set_sort_column_id(2) cols["item"].set_sort_column_id(1) self.RecommendationsList.set_model(self.recommendations_model) self.recommendations_model.set_sort_column_id(2, Gtk.SortType.DESCENDING) self.recommendation_users = {} self.recommendation_users_model = Gtk.ListStore( Gio.Icon, # (0) status icon str, # (1) user str, # (2) hspeed str, # (3) hfiles int, # (4) status GObject.TYPE_UINT, # (5) speed GObject.TYPE_UINT # (6) file count ) self.recommendation_users_column_numbers = list( range(self.recommendation_users_model.get_n_columns())) cols = initialise_columns( frame, None, self.RecommendationUsersList, ["status", _("Status"), 25, "icon", None], ["user", _("User"), 135, "text", None], ["speed", _("Speed"), 60, "number", None], ["files", _("Files"), -1, "number", None], ) cols["status"].set_sort_column_id(4) cols["user"].set_sort_column_id(1) cols["speed"].set_sort_column_id(5) cols["files"].set_sort_column_id(6) cols["user"].set_expand(True) cols["speed"].set_expand(True) cols["files"].set_expand(True) cols["status"].get_widget().hide() self.RecommendationUsersList.set_model(self.recommendation_users_model) self.recommendation_users_model.set_sort_column_id( 1, Gtk.SortType.ASCENDING) for thing in config.sections["interests"]["likes"]: if thing and isinstance(thing, str): self.likes[thing] = self.likes_model.insert_with_valuesv( -1, self.likes_column_numbers, [thing]) for thing in config.sections["interests"]["dislikes"]: if thing and isinstance(thing, str): self.dislikes[thing] = self.dislikes_model.insert_with_valuesv( -1, self.dislikes_column_numbers, [thing]) # Popup menus self.til_popup_menu = popup = PopupMenu(self.frame, self.LikesList, self.on_popup_til_menu) popup.add_items( ("#" + _("Re_commendations for Item"), self.on_recommend_item, popup), ("#" + _("_Search for Item"), self.on_recommend_search, popup), ("", None), ("#" + _("_Remove Item"), self.on_remove_thing_i_like)) self.tidl_popup_menu = popup = PopupMenu(self.frame, self.DislikesList, self.on_popup_til_menu) popup.add_items( ("#" + _("Re_commendations for Item"), self.on_recommend_item, popup), ("#" + _("_Search for Item"), self.on_recommend_search, popup), ("", None), ("#" + _("_Remove Item"), self.on_remove_thing_i_dislike)) self.r_popup_menu = popup = PopupMenu(self.frame, self.RecommendationsList, self.on_popup_r_menu) popup.add_items( ("$" + _("I _Like This"), self.on_like_recommendation), ("$" + _("I _Dislike This"), self.on_dislike_recommendation), ("", None), ("#" + _("_Recommendations for Item"), self.on_recommend_item, popup), ("#" + _("_Search for Item"), self.on_recommend_search, popup)) popup = PopupMenu(self.frame, self.RecommendationUsersList, self.on_popup_ru_menu) popup.setup_user_menu() self.update_visuals()
def __init__(self, chats, user): super().__init__("ui/privatechat.ui") self.user = user self.chats = chats self.frame = chats.frame self.opened = False self.offline_message = False self.status = 0 if user in self.frame.np.user_statuses: self.status = self.frame.np.user_statuses[user] or 0 # Text Search TextSearchBar(self.ChatScroll, self.SearchBar, self.SearchEntry, controller_widget=self.Main, focus_widget=self.ChatLine) self.chat_textview = TextView(self.ChatScroll, font="chatfont") # Chat Entry ChatEntry(self.frame, self.ChatLine, chats.completion, user, slskmessages.MessageUser, self.frame.np.privatechats.send_message, self.frame.np.privatechats.CMDS) self.Log.set_active(config.sections["logging"]["privatechat"]) self.toggle_chat_buttons() self.popup_menu_user_chat = PopupMenu(self.frame, self.ChatScroll, connect_events=False) self.popup_menu_user_tab = PopupMenu(self.frame, None, self.on_popup_menu_user) for menu in (self.popup_menu_user_chat, self.popup_menu_user_tab): menu.setup_user_menu(user, page="privatechat") menu.add_items( ("", None), ("#" + _("Close All Tabs…"), self.on_close_all_tabs), ("#" + _("_Close Tab"), self.on_close)) popup = PopupMenu(self.frame, self.ChatScroll, self.on_popup_menu_chat) popup.add_items( ("#" + _("Find…"), self.on_find_chat_log), ("", None), ("#" + _("Copy"), self.chat_textview.on_copy_text), ("#" + _("Copy Link"), self.chat_textview.on_copy_link), ("#" + _("Copy All"), self.chat_textview.on_copy_all_text), ("", None), ("#" + _("View Chat Log"), self.on_view_chat_log), ("#" + _("Delete Chat Log…"), self.on_delete_chat_log), ("", None), ("#" + _("Clear Message View"), self.chat_textview.on_clear_all_text), ("", None), (">" + _("User"), self.popup_menu_user_tab), ) self.create_tags() self.update_visuals() self.read_private_log()
class Search(UserInterface): def __init__(self, searches, text, token, mode, mode_label, showtab): super().__init__("ui/search.ui") self.searches = searches self.frame = searches.frame self.filter_help = UserInterface("ui/popovers/searchfilters.ui") self.text = text self.searchterm_words_include = [] self.searchterm_words_ignore = [] for word in text.lower().split(): if word.startswith('*'): if len(word) > 1: self.searchterm_words_include.append(word[1:]) elif word.startswith('-'): if len(word) > 1: self.searchterm_words_ignore.append(word[1:]) else: self.searchterm_words_include.append(word) self.token = token self.mode = mode self.mode_label = mode_label self.showtab = showtab self.usersiters = {} self.directoryiters = {} self.users = set() self.all_data = [] self.selected_results = [] self.selected_users = [] self.selected_files_count = 0 self.grouping_mode = None self.filters = None self.clearing_filters = False self.active_filter_count = 0 self.num_results_found = 0 self.num_results_visible = 0 self.max_limit = config.sections["searches"]["max_displayed_results"] self.max_limited = False self.operators = { '<': operator.lt, '<=': operator.le, '==': operator.eq, '!=': operator.ne, '>=': operator.ge, '>': operator.gt } # Columns self.treeview_name = "file_search" self.resultsmodel = Gtk.TreeStore( int, # (0) num str, # (1) user str, # (2) flag str, # (3) h_speed str, # (4) h_queue str, # (5) directory str, # (6) filename str, # (7) h_size str, # (8) h_bitrate str, # (9) h_length GObject.TYPE_UINT, # (10) bitrate str, # (11) fullpath str, # (12) country GObject.TYPE_UINT64, # (13) size GObject.TYPE_UINT, # (14) speed GObject.TYPE_UINT64, # (15) queue GObject.TYPE_UINT, # (16) length str # (17) color ) self.column_offsets = {} self.column_numbers = list(range(self.resultsmodel.get_n_columns())) color_col = 17 self.cols = cols = initialise_columns( self.frame, "file_search", self.ResultsList, ["id", _("ID"), 50, "number", color_col], ["user", _("User"), 200, "text", color_col], ["country", _("Country"), 25, "icon", None], ["speed", _("Speed"), 100, "number", color_col], ["in_queue", _("In Queue"), 90, "number", color_col], ["folder", _("Folder"), 400, "text", color_col], ["filename", _("Filename"), 400, "text", color_col], ["size", _("Size"), 100, "number", color_col], ["bitrate", _("Bitrate"), 100, "number", color_col], ["length", _("Length"), 100, "number", color_col]) cols["id"].set_sort_column_id(0) cols["user"].set_sort_column_id(1) cols["country"].set_sort_column_id(12) cols["speed"].set_sort_column_id(14) cols["in_queue"].set_sort_column_id(15) cols["folder"].set_sort_column_id(5) cols["filename"].set_sort_column_id(6) cols["size"].set_sort_column_id(13) cols["bitrate"].set_sort_column_id(10) cols["length"].set_sort_column_id(16) cols["country"].get_widget().hide() self.ResultsList.set_model(self.resultsmodel) for column in self.ResultsList.get_columns(): self.column_offsets[column.get_title()] = 0 column.connect("notify::x-offset", self.on_column_position_changed) self.update_visuals() # Popup menus self.popup_menu_users = PopupMenu(self.frame) self.popup_menu_copy = PopupMenu(self.frame) self.popup_menu_copy.add_items( ("#" + _("Copy _File Path"), self.on_copy_file_path), ("#" + _("Copy _URL"), self.on_copy_url), ("#" + _("Copy Folder U_RL"), self.on_copy_dir_url)) self.popup_menu = PopupMenu(self.frame, self.ResultsList, self.on_popup_menu) self.popup_menu.add_items( ("#" + "selected_files", None), ("", None), ("#" + _("_Download File(s)"), self.on_download_files), ("#" + _("Download File(s) _To…"), self.on_download_files_to), ("#" + _("Download _Folder(s)"), self.on_download_folders), ("#" + _("Download F_older(s) To…"), self.on_download_folders_to), ("", None), ("#" + _("_Browse Folder(s)"), self.on_browse_folder), ("#" + _("F_ile Properties"), self.on_file_properties), ("", None), (">" + _("Copy"), self.popup_menu_copy), (">" + _("User(s)"), self.popup_menu_users)) self.tab_menu = PopupMenu(self.frame) self.tab_menu.add_items( ("#" + _("Copy Search Term"), self.on_copy_search_term), ("", None), ("#" + _("Clear All Results"), self.on_clear), ("#" + _("Close All Tabs…"), self.on_close_all_tabs), ("#" + _("_Close Tab"), self.on_close)) # Key bindings for widget in (self.Main, self.ResultsList): Accelerator("<Primary>f", widget, self.on_show_filter_bar_accelerator) Accelerator("Escape", self.FiltersContainer, self.on_close_filter_bar_accelerator) Accelerator("<Alt>Return", self.ResultsList, self.on_file_properties_accelerator) # Grouping menu = create_grouping_menu( self.frame.MainWindow, config.sections["searches"]["group_searches"], self.on_group) self.ResultGrouping.set_menu_model(menu) self.ExpandButton.set_active( config.sections["searches"]["expand_searches"]) # Filters self.filter_comboboxes = { "filterin": self.FilterIn, "filterout": self.FilterOut, "filtersize": self.FilterSize, "filterbr": self.FilterBitrate, "filtercc": self.FilterCountry, "filtertype": self.FilterType } self.ShowFilters.set_active( config.sections["searches"]["filters_visible"]) self.populate_filters() # Wishlist self.update_wish_button() def set_label(self, label): self.tab_menu.set_parent(label) @staticmethod def on_tooltip(widget, pos_x, pos_y, _keyboard_mode, tooltip): country_tooltip = show_country_tooltip(widget, pos_x, pos_y, tooltip, 12, strip_prefix="") file_path_tooltip = show_file_path_tooltip(widget, pos_x, pos_y, tooltip, 11) if country_tooltip: return country_tooltip if file_path_tooltip: return file_path_tooltip return False def focus_combobox(self, button): # We have the button of a combobox, find the entry parent = button.get_parent() if parent is None: return if isinstance(parent, Gtk.ComboBox): entry = parent.get_child() entry.grab_focus() GLib.idle_add(entry.emit, "activate") return self.focus_combobox(parent) def update_filter_comboboxes(self): for filter_id, widget in self.filter_comboboxes.items(): presets = "" widget.remove_all() if filter_id == "filterbr": presets = ("0", "128", "160", "192", "256", "320") elif filter_id == "filtersize": presets = (">10MiB", "<10MiB", "<5MiB", "<1MiB", ">0") elif filter_id == "filtertype": presets = ("flac|wav|ape|aiff|wv|cue", "mp3|m4a|aac|ogg|opus|wma", "!mp3") for value in presets: widget.append_text(value) for value in config.sections["searches"][filter_id]: if value not in presets: widget.append_text(value) def populate_filters(self): if not config.sections["searches"]["enablefilters"]: return sfilter = config.sections["searches"]["defilter"] num_filters = len(sfilter) if num_filters > 0: self.FilterIn.get_child().set_text(str(sfilter[0])) if num_filters > 1: self.FilterOut.get_child().set_text(str(sfilter[1])) if num_filters > 2: self.FilterSize.get_child().set_text(str(sfilter[2])) if num_filters > 3: self.FilterBitrate.get_child().set_text(str(sfilter[3])) if num_filters > 4: self.FilterFreeSlot.set_active(bool(sfilter[4])) if num_filters > 5: self.FilterCountry.get_child().set_text(str(sfilter[5])) if num_filters > 6: self.FilterType.get_child().set_text(str(sfilter[6])) self.on_refilter() def add_result_list(self, result_list, user, country, inqueue, ulspeed, h_speed, h_queue, color, private=False): """ Adds a list of search results to the treeview. Lists can either contain publicly or privately shared files. """ update_ui = False for result in result_list: if self.num_results_found >= self.max_limit: self.max_limited = True break fullpath = result[1] fullpath_lower = fullpath.lower() if any(word in fullpath_lower for word in self.searchterm_words_ignore): # Filter out results with filtered words (e.g. nicotine -music) log.add_debug(( "Filtered out excluded search result %(filepath)s from user %(user)s for " "search term \"%(query)s\""), { "filepath": fullpath, "user": user, "query": self.text }) continue if not any(word in fullpath_lower for word in self.searchterm_words_include): # Certain users may send us wrong results, filter out such ones log.add_search( _("Filtered out incorrect search result %(filepath)s from user %(user)s for " "search query \"%(query)s\""), { "filepath": fullpath, "user": user, "query": self.text }) continue self.num_results_found += 1 fullpath_split = fullpath.split('\\') if config.sections["ui"]["reverse_file_paths"]: # Reverse file path, file name is the first item. next() retrieves the name and removes # it from the iterator. fullpath_split = reversed(fullpath_split) name = next(fullpath_split) else: # Regular file path, file name is the last item. Retrieve it and remove it from the list. name = fullpath_split.pop() # Join the resulting items into a folder path directory = '\\'.join(fullpath_split) size = result[2] h_size = human_size(size) h_bitrate, bitrate, h_length, length = get_result_bitrate_length( size, result[4]) if private: name = _("[PRIVATE] %s") % name is_result_visible = self.append([ self.num_results_found, user, get_flag_icon_name(country), h_speed, h_queue, directory, name, h_size, h_bitrate, h_length, GObject.Value(GObject.TYPE_UINT, bitrate), fullpath, country, GObject.Value(GObject.TYPE_UINT64, size), GObject.Value(GObject.TYPE_UINT, ulspeed), GObject.Value(GObject.TYPE_UINT64, inqueue), GObject.Value(GObject.TYPE_UINT, length), GObject.Value(GObject.TYPE_STRING, color) ]) if is_result_visible: update_ui = True return update_ui def add_user_results(self, msg, user, country): if user in self.users: return self.users.add(user) if msg.freeulslots: inqueue = 0 h_queue = "" else: inqueue = msg.inqueue or 1 # Ensure value is always >= 1 h_queue = humanize(inqueue) h_speed = "" ulspeed = msg.ulspeed or 0 if ulspeed > 0: h_speed = human_speed(ulspeed) color_id = "search" if msg.freeulslots else "searchq" color = config.sections["ui"][color_id] or None update_ui = self.add_result_list(msg.list, user, country, inqueue, ulspeed, h_speed, h_queue, color) if msg.privatelist: update_ui_private = self.add_result_list(msg.privatelist, user, country, inqueue, ulspeed, h_speed, h_queue, color, private=True) if not update_ui and update_ui_private: update_ui = True if update_ui: # If this search wasn't initiated by us (e.g. wishlist), and the results aren't spoofed, show tab if not self.showtab: self.searches.show_tab(self, self.text) self.showtab = True self.searches.request_tab_hilite(self.Main) # Update number of results, even if they are all filtered self.update_result_counter() def append(self, row): self.all_data.append(row) if not self.check_filter(row): return False self.add_row_to_model(row) return True def add_row_to_model(self, row): (_counter, user, flag, h_speed, h_queue, directory, _filename, _h_size, _h_bitrate, _h_length, _bitrate, fullpath, country, _size, speed, queue, _length, color) = row expand_user = False expand_folder = False if self.grouping_mode != "ungrouped": # Group by folder or user empty_int = 0 empty_str = "" if user not in self.usersiters: self.usersiters[user] = self.resultsmodel.insert_with_values( None, -1, self.column_numbers, [ empty_int, user, flag, h_speed, h_queue, empty_str, empty_str, empty_str, empty_str, empty_str, empty_int, empty_str, country, empty_int, speed, queue, empty_int, color ]) if self.grouping_mode == "folder_grouping": expand_user = True else: expand_user = self.ExpandButton.get_active() parent = self.usersiters[user] if self.grouping_mode == "folder_grouping": # Group by folder user_directory = user + directory if user_directory not in self.directoryiters: self.directoryiters[ user_directory] = self.resultsmodel.insert_with_values( self.usersiters[user], -1, self.column_numbers, [ empty_int, user, flag, h_speed, h_queue, directory, empty_str, empty_str, empty_str, empty_str, empty_int, fullpath.rsplit('\\', 1)[0] + '\\', country, empty_int, speed, queue, empty_int, color ]) expand_folder = self.ExpandButton.get_active() row = row[:] row[5] = "" # Directory not visible for file row if "group by folder" is enabled parent = self.directoryiters[user_directory] else: parent = None try: """ Note that we use insert_with_values instead of append, as this reduces overhead by bypassing useless row conversion to GObject.Value in PyGObject. """ iterator = self.resultsmodel.insert_with_values( parent, -1, self.column_numbers, row) if expand_user: self.ResultsList.expand_row( self.resultsmodel.get_path(self.usersiters[user]), False) if expand_folder: self.ResultsList.expand_row( self.resultsmodel.get_path( self.directoryiters[user_directory]), False) self.num_results_visible += 1 except Exception as error: types = [] for i in row: types.append(type(i)) log.add("Search row error: %(exception)s %(row)s", { 'exception': error, 'row': row }) iterator = None return iterator def check_digit(self, sfilter, value, factorize=True): used_operator = ">=" if sfilter.startswith((">", "<", "=")): used_operator, sfilter = sfilter[:1] + "=", sfilter[1:] if not sfilter: return True factor = 1 if factorize: base = 1024 # Default to binary for "k", "m", "g" suffixes if sfilter[-1:].lower() == 'b': base = 1000 # Byte suffix detected, prepare to use decimal if necessary sfilter = sfilter[:-1] if sfilter[-1:].lower() == 'i': base = 1024 # Binary requested, stop using decimal sfilter = sfilter[:-1] if sfilter.lower()[-1:] == "g": factor = pow(base, 3) sfilter = sfilter[:-1] elif sfilter.lower()[-1:] == "m": factor = pow(base, 2) sfilter = sfilter[:-1] elif sfilter.lower()[-1:] == "k": factor = base sfilter = sfilter[:-1] if not sfilter: return True try: sfilter = int(sfilter) * factor except ValueError: return True operation = self.operators.get(used_operator) return operation(value, sfilter) @staticmethod def check_country(sfilter, value): if not isinstance(value, str): return False value = value.upper() allowed = False for country_code in sfilter.split("|"): if country_code == value: allowed = True elif country_code.startswith("!") and country_code[1:] != value: allowed = True elif country_code.startswith("!") and country_code[1:] == value: return False return allowed @staticmethod def check_file_type(sfilter, value): if not isinstance(value, str): return False value = value.lower() allowed = False for ext in sfilter.split("|"): exclude_ext = None if ext.startswith("!"): exclude_ext = ext[1:] if not exclude_ext.startswith("."): exclude_ext = "." + exclude_ext elif not ext.startswith("."): ext = "." + ext if not ext.startswith("!") and value.endswith(ext): allowed = True elif ext.startswith("!") and not value.endswith(exclude_ext): allowed = True elif ext.startswith("!") and value.endswith(exclude_ext): return False return allowed def check_filter(self, row): if self.active_filter_count == 0: return True filters = self.filters # "Included text"-filter, check full file path (located at index 11 in row) if filters["filterin"] and not filters["filterin"].search( row[11].lower()): return False # "Excluded text"-filter, check full file path (located at index 11 in row) if filters["filterout"] and filters["filterout"].search( row[11].lower()): return False if filters["filtersize"] and not self.check_digit( filters["filtersize"], row[13].get_uint64()): return False if filters["filterbr"] and not self.check_digit( filters["filterbr"], row[10].get_uint(), False): return False if filters["filterslot"] and row[15].get_uint64() > 0: return False if filters["filtercc"] and not self.check_country( filters["filtercc"], row[12]): return False if filters["filtertype"] and not self.check_file_type( filters["filtertype"], row[11]): return False return True def update_filter_counter(self, count): if count > 0: self.FilterLabel.set_label(_("_Result Filters [%d]") % count) else: self.FilterLabel.set_label(_("_Result Filters")) self.FilterLabel.set_tooltip_text("%d active filter(s)" % count) def update_results_model(self): # Temporarily disable sorting for increased performance sort_column, sort_type = self.resultsmodel.get_sort_column_id() self.resultsmodel.set_default_sort_func(lambda *_args: 0) self.resultsmodel.set_sort_column_id(-1, Gtk.SortType.ASCENDING) self.usersiters.clear() self.directoryiters.clear() self.resultsmodel.clear() self.num_results_visible = 0 for row in self.all_data: if self.check_filter(row): self.add_row_to_model(row) # Update number of results self.update_result_counter() self.update_filter_counter(self.active_filter_count) if sort_column is not None and sort_type is not None: self.resultsmodel.set_sort_column_id(sort_column, sort_type) if self.grouping_mode != "ungrouped": # Group by folder or user if self.ExpandButton.get_active(): self.ResultsList.expand_all() else: collapse_treeview(self.ResultsList, self.grouping_mode) def update_wish_button(self): if self.mode not in ("global", "wishlist"): self.AddWish.hide() return if not self.frame.np.search.is_wish(self.text): self.AddWishIcon.set_property("icon-name", "list-add-symbolic") self.AddWishLabel.set_label(_("Add Wi_sh")) return self.AddWishIcon.set_property("icon-name", "list-remove-symbolic") self.AddWishLabel.set_label(_("Remove Wi_sh")) def on_add_wish(self, *_args): if self.frame.np.search.is_wish(self.text): self.frame.np.search.remove_wish(self.text) else: self.frame.np.search.add_wish(self.text) def add_popup_menu_user(self, popup, user): popup.setup_user_menu(user) popup.add_items(("", None), ("#" + _("Select User's Results"), self.on_select_user_results, user)) popup.update_model() popup.toggle_user_items() def populate_popup_menu_users(self): self.popup_menu_users.clear() if not self.selected_users: return # Multiple users, create submenus for each user if len(self.selected_users) > 1: for user in self.selected_users: popup = PopupMenu(self.frame) self.add_popup_menu_user(popup, user) self.popup_menu_users.add_items((">" + user, popup)) self.popup_menu_users.update_model() return # Single user, add items directly to "User(s)" submenu self.add_popup_menu_user(self.popup_menu_users, self.selected_users[0]) def on_close_filter_bar_accelerator(self, *_args): """ Escape: hide filter bar """ self.ShowFilters.set_active(False) return True def on_show_filter_bar_accelerator(self, *_args): """ Ctrl+F: show filter bar """ self.ShowFilters.set_active(True) self.FilterIn.grab_focus() return True def on_file_properties_accelerator(self, *_args): """ Alt+Return: show file properties dialog """ self.on_file_properties() return True def on_select_user_results(self, *args): if not self.selected_users: return selected_user = args[-1] sel = self.ResultsList.get_selection() fmodel = self.ResultsList.get_model() sel.unselect_all() iterator = fmodel.get_iter_first() select_user_row_iter(fmodel, sel, 1, selected_user, iterator) self.select_results() def select_result(self, model, iterator): user = model.get_value(iterator, 1) if user not in self.selected_users: self.selected_users.append(user) filename = model.get_value(iterator, 6) if not filename: return path = self.resultsmodel.get_path(iterator) if path not in self.selected_results: self.selected_files_count += 1 self.selected_results.append(path) def select_child_results(self, model, iterator): while iterator is not None: self.select_result(model, iterator) self.select_child_results(model, model.iter_children(iterator)) iterator = model.iter_next(iterator) def select_results(self): self.selected_results.clear() self.selected_users.clear() self.selected_files_count = 0 model, paths = self.ResultsList.get_selection().get_selected_rows() for path in paths: iterator = model.get_iter(path) self.select_result(model, iterator) self.select_child_results(model, model.iter_children(iterator)) def update_result_counter(self): if self.max_limited or self.num_results_found > self.num_results_visible: # Append plus symbol "+" if Results are Filtered and/or reached 'Maximum per search' str_plus = "+" # Display total results on the tooltip, but only if we know the exact number of results if self.max_limited: total = "> " + str(self.max_limit) + "+" else: total = self.num_results_found self.CounterButton.set_tooltip_text(_("Total: %s") % total) else: # Hide the tooltip if there are no hidden results str_plus = "" self.CounterButton.set_has_tooltip(False) self.Counter.set_text(str(self.num_results_visible) + str_plus) def update_visuals(self): for widget in list(self.__dict__.values()): update_widget_visuals(widget, list_font_target="searchfont") def on_column_position_changed(self, column, _param): """ Save column position and width to config """ col_title = column.get_title() offset = column.get_x_offset() if self.column_offsets[col_title] == offset: return self.column_offsets[col_title] = offset save_columns(self.treeview_name, self.ResultsList.get_columns()) def on_row_activated(self, treeview, path, _column): self.select_results() iterator = self.resultsmodel.get_iter(path) folder = self.resultsmodel.get_value(iterator, 5) filename = self.resultsmodel.get_value(iterator, 6) if not folder and not filename: # Don't activate user rows return if not filename: self.on_download_folders() else: self.on_download_files() treeview.get_selection().unselect_all() def on_popup_menu(self, menu, _widget): self.select_results() self.populate_popup_menu_users() menu.set_num_selected_files(self.selected_files_count) def on_browse_folder(self, *_args): requested_users = set() requested_folders = set() for path in self.selected_results: iterator = self.resultsmodel.get_iter(path) user = self.resultsmodel.get_value(iterator, 1) folder = self.resultsmodel.get_value(iterator, 11).rsplit( '\\', 1)[0] + '\\' if user not in requested_users and folder not in requested_folders: self.frame.np.userbrowse.browse_user(user, path=folder) requested_users.add(user) requested_folders.add(folder) def on_file_properties(self, *_args): data = [] selected_size = 0 selected_length = 0 for path in self.selected_results: iterator = self.resultsmodel.get_iter(path) virtual_path = self.resultsmodel.get_value(iterator, 11) directory, filename = virtual_path.rsplit('\\', 1) file_size = self.resultsmodel.get_value(iterator, 13) selected_size += file_size selected_length += self.resultsmodel.get_value(iterator, 16) country_code = self.resultsmodel.get_value(iterator, 12) country = "%s (%s)" % (self.frame.np.geoip.country_code_to_name( country_code), country_code) data.append({ "user": self.resultsmodel.get_value(iterator, 1), "fn": virtual_path, "filename": filename, "directory": directory, "size": file_size, "speed": self.resultsmodel.get_value(iterator, 14), "queue_position": self.resultsmodel.get_value(iterator, 15), "bitrate": self.resultsmodel.get_value(iterator, 8), "length": self.resultsmodel.get_value(iterator, 9), "country": country }) if data: FileProperties(self.frame, data, selected_size, selected_length).show() def on_download_files(self, *_args, prefix=""): for path in self.selected_results: iterator = self.resultsmodel.get_iter(path) user = self.resultsmodel.get_value(iterator, 1) filepath = self.resultsmodel.get_value(iterator, 11) size = self.resultsmodel.get_value(iterator, 13) bitrate = self.resultsmodel.get_value(iterator, 8) length = self.resultsmodel.get_value(iterator, 9) self.frame.np.transfers.get_file(user, filepath, prefix, size=size, bitrate=bitrate, length=length) def on_download_files_to_selected(self, selected, _data): self.on_download_files(prefix=selected) def on_download_files_to(self, *_args): choose_dir(parent=self.frame.MainWindow, title=_("Select Destination Folder for File(s)"), callback=self.on_download_files_to_selected, initialdir=config.sections["transfers"]["downloaddir"], multichoice=False) def on_download_folders(self, *_args, download_location=""): if download_location: """ Custom download location specified, remember it when peer sends a folder contents reply """ requested_folders = self.frame.np.transfers.requested_folders else: requested_folders = defaultdict(dict) for path in self.selected_results: iterator = self.resultsmodel.get_iter(path) user = self.resultsmodel.get_value(iterator, 1) folder = self.resultsmodel.get_value(iterator, 11).rsplit('\\', 1)[0] if folder in requested_folders[user]: """ Ensure we don't send folder content requests for a folder more than once, e.g. when several selected resuls belong to the same folder. """ continue requested_folders[user][folder] = download_location visible_files = [] for row in self.all_data: # Find the wanted directory if folder != row[11].rsplit('\\', 1)[0]: continue destination = self.frame.np.transfers.get_folder_destination( user, folder) (_counter, user, _flag, _h_speed, _h_queue, _directory, _filename, _h_size, h_bitrate, h_length, _bitrate, fullpath, _country, size, _speed, _queue, _length, _color) = row visible_files.append((user, fullpath, destination, size.get_uint64(), h_bitrate, h_length)) self.frame.np.search.request_folder_download( user, folder, visible_files) def on_download_folders_to_selected(self, selected, _data): self.on_download_folders(download_location=selected) def on_download_folders_to(self, *_args): choose_dir(parent=self.frame.MainWindow, title=_("Select Destination Folder"), callback=self.on_download_folders_to_selected, initialdir=config.sections["transfers"]["downloaddir"], multichoice=False) def on_copy_file_path(self, *_args): for path in self.selected_results: iterator = self.resultsmodel.get_iter(path) filepath = self.resultsmodel.get_value(iterator, 11) copy_text(filepath) return def on_copy_url(self, *_args): for path in self.selected_results: iterator = self.resultsmodel.get_iter(path) user = self.resultsmodel.get_value(iterator, 1) filepath = self.resultsmodel.get_value(iterator, 11) url = self.frame.np.userbrowse.get_soulseek_url(user, filepath) copy_text(url) return def on_copy_dir_url(self, *_args): for path in self.selected_results: iterator = self.resultsmodel.get_iter(path) user = self.resultsmodel.get_value(iterator, 1) filepath = self.resultsmodel.get_value(iterator, 11) url = self.frame.np.userbrowse.get_soulseek_url( user, filepath.rsplit('\\', 1)[0] + '\\') copy_text(url) return def on_counter_button(self, *_args): if self.num_results_found > self.num_results_visible: self.on_clear_filters() else: self.frame.on_settings(page='Searches') def on_group(self, action, state): mode = state.get_string() active = mode != "ungrouped" config.sections["searches"]["group_searches"] = mode self.cols["id"].set_visible(not active) self.ResultsList.set_show_expanders(active) self.ExpandButton.set_visible(active) self.grouping_mode = mode self.update_results_model() action.set_state(state) def on_toggle_expand_all(self, *_args): active = self.ExpandButton.get_active() if active: self.ResultsList.expand_all() self.expand.set_property("icon-name", "go-up-symbolic") else: collapse_treeview(self.ResultsList, self.grouping_mode) self.expand.set_property("icon-name", "go-down-symbolic") config.sections["searches"]["expand_searches"] = active def on_toggle_filters(self, widget): visible = widget.get_active() self.FiltersContainer.set_reveal_child(visible) config.sections["searches"]["filters_visible"] = visible if visible: self.FilterIn.grab_focus() return self.ResultsList.grab_focus() def on_copy_search_term(self, *_args): copy_text(self.text) @staticmethod def push_history(filter_id, value): if not value: return history = config.sections["searches"].get(filter_id) if history is None: return if value in history: history.remove(value) elif len(history) >= 5: del history[-1] history.insert(0, value) config.write_configuration() def on_refilter(self, *_args): if self.clearing_filters: return filter_in = self.FilterIn.get_active_text().strip().lower() filter_out = self.FilterOut.get_active_text().strip().lower() if filter_in: try: filter_in = re.compile(filter_in) except sre_constants.error: filter_in = None if filter_out: try: filter_out = re.compile(filter_out) except sre_constants.error: filter_out = None filters = { "filterin": filter_in, "filterout": filter_out, "filtersize": self.FilterSize.get_active_text().strip(), "filterbr": self.FilterBitrate.get_active_text().strip(), "filterslot": self.FilterFreeSlot.get_active(), "filtercc": self.FilterCountry.get_active_text().strip().upper(), "filtertype": self.FilterType.get_active_text().strip().lower() } if self.filters == filters: # Filters have not changed, no need to refilter return self.active_filter_count = 0 # Set red background if invalid regex pattern is detected if filter_in is None: set_widget_fg_bg_css(self.FilterIn.get_child(), bg_color="#e04f5e", fg_color="white") else: update_widget_visuals(self.FilterIn.get_child()) if filter_out is None: set_widget_fg_bg_css(self.FilterOut.get_child(), bg_color="#e04f5e", fg_color="white") else: update_widget_visuals(self.FilterOut.get_child()) # Add filters to history for filter_id, value in filters.items(): try: value = value.pattern except AttributeError: pass if not value: continue self.push_history(filter_id, value) self.active_filter_count += 1 # Apply the new filters self.filters = filters self.update_filter_comboboxes() self.update_results_model() def on_filter_entry_changed(self, widget): if not widget.get_text(): self.on_refilter() def on_clear_filters(self, *_args): self.clearing_filters = True for widget in self.filter_comboboxes.values(): widget.get_child().set_text("") self.FilterFreeSlot.set_active(False) if self.ShowFilters.get_active(): self.FilterIn.get_child().grab_focus() else: self.ResultsList.grab_focus() self.clearing_filters = False self.on_refilter() def on_clear(self, *_args): self.all_data = [] self.usersiters.clear() self.directoryiters.clear() self.resultsmodel.clear() self.num_results_found = 0 self.num_results_visible = 0 self.max_limited = False self.max_limit = config.sections["searches"]["max_displayed_results"] # Allow parsing search result messages again self.frame.np.search.add_allowed_token(self.token) # Update number of results widget self.update_result_counter() def on_close(self, *_args): del self.searches.pages[self.token] self.frame.np.search.remove_search(self.token) self.searches.remove_page(self.Main) def on_close_all_tabs(self, *_args): self.searches.remove_all_pages()
def __init__(self, searches, text, token, mode, mode_label, showtab): super().__init__("ui/search.ui") self.searches = searches self.frame = searches.frame self.filter_help = UserInterface("ui/popovers/searchfilters.ui") self.text = text self.searchterm_words_include = [] self.searchterm_words_ignore = [] for word in text.lower().split(): if word.startswith('*'): if len(word) > 1: self.searchterm_words_include.append(word[1:]) elif word.startswith('-'): if len(word) > 1: self.searchterm_words_ignore.append(word[1:]) else: self.searchterm_words_include.append(word) self.token = token self.mode = mode self.mode_label = mode_label self.showtab = showtab self.usersiters = {} self.directoryiters = {} self.users = set() self.all_data = [] self.selected_results = [] self.selected_users = [] self.selected_files_count = 0 self.grouping_mode = None self.filters = None self.clearing_filters = False self.active_filter_count = 0 self.num_results_found = 0 self.num_results_visible = 0 self.max_limit = config.sections["searches"]["max_displayed_results"] self.max_limited = False self.operators = { '<': operator.lt, '<=': operator.le, '==': operator.eq, '!=': operator.ne, '>=': operator.ge, '>': operator.gt } # Columns self.treeview_name = "file_search" self.resultsmodel = Gtk.TreeStore( int, # (0) num str, # (1) user str, # (2) flag str, # (3) h_speed str, # (4) h_queue str, # (5) directory str, # (6) filename str, # (7) h_size str, # (8) h_bitrate str, # (9) h_length GObject.TYPE_UINT, # (10) bitrate str, # (11) fullpath str, # (12) country GObject.TYPE_UINT64, # (13) size GObject.TYPE_UINT, # (14) speed GObject.TYPE_UINT64, # (15) queue GObject.TYPE_UINT, # (16) length str # (17) color ) self.column_offsets = {} self.column_numbers = list(range(self.resultsmodel.get_n_columns())) color_col = 17 self.cols = cols = initialise_columns( self.frame, "file_search", self.ResultsList, ["id", _("ID"), 50, "number", color_col], ["user", _("User"), 200, "text", color_col], ["country", _("Country"), 25, "icon", None], ["speed", _("Speed"), 100, "number", color_col], ["in_queue", _("In Queue"), 90, "number", color_col], ["folder", _("Folder"), 400, "text", color_col], ["filename", _("Filename"), 400, "text", color_col], ["size", _("Size"), 100, "number", color_col], ["bitrate", _("Bitrate"), 100, "number", color_col], ["length", _("Length"), 100, "number", color_col]) cols["id"].set_sort_column_id(0) cols["user"].set_sort_column_id(1) cols["country"].set_sort_column_id(12) cols["speed"].set_sort_column_id(14) cols["in_queue"].set_sort_column_id(15) cols["folder"].set_sort_column_id(5) cols["filename"].set_sort_column_id(6) cols["size"].set_sort_column_id(13) cols["bitrate"].set_sort_column_id(10) cols["length"].set_sort_column_id(16) cols["country"].get_widget().hide() self.ResultsList.set_model(self.resultsmodel) for column in self.ResultsList.get_columns(): self.column_offsets[column.get_title()] = 0 column.connect("notify::x-offset", self.on_column_position_changed) self.update_visuals() # Popup menus self.popup_menu_users = PopupMenu(self.frame) self.popup_menu_copy = PopupMenu(self.frame) self.popup_menu_copy.add_items( ("#" + _("Copy _File Path"), self.on_copy_file_path), ("#" + _("Copy _URL"), self.on_copy_url), ("#" + _("Copy Folder U_RL"), self.on_copy_dir_url)) self.popup_menu = PopupMenu(self.frame, self.ResultsList, self.on_popup_menu) self.popup_menu.add_items( ("#" + "selected_files", None), ("", None), ("#" + _("_Download File(s)"), self.on_download_files), ("#" + _("Download File(s) _To…"), self.on_download_files_to), ("#" + _("Download _Folder(s)"), self.on_download_folders), ("#" + _("Download F_older(s) To…"), self.on_download_folders_to), ("", None), ("#" + _("_Browse Folder(s)"), self.on_browse_folder), ("#" + _("F_ile Properties"), self.on_file_properties), ("", None), (">" + _("Copy"), self.popup_menu_copy), (">" + _("User(s)"), self.popup_menu_users)) self.tab_menu = PopupMenu(self.frame) self.tab_menu.add_items( ("#" + _("Copy Search Term"), self.on_copy_search_term), ("", None), ("#" + _("Clear All Results"), self.on_clear), ("#" + _("Close All Tabs…"), self.on_close_all_tabs), ("#" + _("_Close Tab"), self.on_close)) # Key bindings for widget in (self.Main, self.ResultsList): Accelerator("<Primary>f", widget, self.on_show_filter_bar_accelerator) Accelerator("Escape", self.FiltersContainer, self.on_close_filter_bar_accelerator) Accelerator("<Alt>Return", self.ResultsList, self.on_file_properties_accelerator) # Grouping menu = create_grouping_menu( self.frame.MainWindow, config.sections["searches"]["group_searches"], self.on_group) self.ResultGrouping.set_menu_model(menu) self.ExpandButton.set_active( config.sections["searches"]["expand_searches"]) # Filters self.filter_comboboxes = { "filterin": self.FilterIn, "filterout": self.FilterOut, "filtersize": self.FilterSize, "filterbr": self.FilterBitrate, "filtercc": self.FilterCountry, "filtertype": self.FilterType } self.ShowFilters.set_active( config.sections["searches"]["filters_visible"]) self.populate_filters() # Wishlist self.update_wish_button()
def __init__(self, frame, np): self.frame = frame self.np = np load_ui_elements( self, os.path.join(self.frame.gui_dir, "ui", "interests.ui")) self.frame.interestsvbox.add(self.Main) self.likes = {} self.likes_model = Gtk.ListStore(str) self.likes_model.set_sort_column_id(0, Gtk.SortType.ASCENDING) self.likes_column_numbers = list( range(self.likes_model.get_n_columns())) cols = initialise_columns( None, self.LikesList, ["i_like", _("I Like"), -1, "text", None]) cols["i_like"].set_sort_column_id(0) self.LikesList.set_model(self.likes_model) self.dislikes = {} self.dislikes_model = Gtk.ListStore(str) self.dislikes_model.set_sort_column_id(0, Gtk.SortType.ASCENDING) self.dislikes_column_numbers = list( range(self.dislikes_model.get_n_columns())) cols = initialise_columns( None, self.DislikesList, ["i_dislike", _("I Dislike"), -1, "text", None]) cols["i_dislike"].set_sort_column_id(0) self.DislikesList.set_model(self.dislikes_model) self.recommendations_model = Gtk.ListStore( str, # (0) hrating str, # (1) item int # (2) rating ) self.recommendations_column_numbers = list( range(self.recommendations_model.get_n_columns())) cols = initialise_columns( None, self.RecommendationsList, ["rating", _("Rating"), 0, "number", None], ["item", _("Item"), -1, "text", None]) cols["rating"].set_sort_column_id(2) cols["item"].set_sort_column_id(1) self.RecommendationsList.set_model(self.recommendations_model) self.unrecommendations_model = Gtk.ListStore( str, # (0) hrating str, # (1) item int # (2) rating ) self.unrecommendations_column_numbers = list( range(self.unrecommendations_model.get_n_columns())) cols = initialise_columns( None, self.UnrecommendationsList, ["rating", _("Rating"), 0, "number", None], ["item", _("Item"), -1, "text", None]) cols["rating"].set_sort_column_id(2) cols["item"].set_sort_column_id(1) self.UnrecommendationsList.set_model(self.unrecommendations_model) self.recommendation_users = {} self.recommendation_users_model = Gtk.ListStore( GObject.TYPE_OBJECT, # (0) status icon str, # (1) user str, # (2) hspeed str, # (3) hfiles int, # (4) status GObject.TYPE_UINT64, # (5) speed GObject.TYPE_UINT64 # (6) file count ) self.recommendation_users_column_numbers = list( range(self.recommendation_users_model.get_n_columns())) cols = initialise_columns( None, self.RecommendationUsersList, ["status", _("Status"), 25, "pixbuf", None], ["user", _("User"), 100, "text", None], ["speed", _("Speed"), 100, "text", None], ["files", _("Files"), 100, "text", None], ) cols["status"].set_sort_column_id(4) cols["user"].set_sort_column_id(1) cols["speed"].set_sort_column_id(5) cols["files"].set_sort_column_id(6) cols["status"].get_widget().hide() self.RecommendationUsersList.set_model(self.recommendation_users_model) self.recommendation_users_model.set_sort_column_id( 1, Gtk.SortType.ASCENDING) for thing in config.sections["interests"]["likes"]: if thing and isinstance(thing, str): self.likes[thing] = self.likes_model.insert_with_valuesv( -1, self.likes_column_numbers, [thing]) for thing in config.sections["interests"]["dislikes"]: if thing and isinstance(thing, str): self.dislikes[thing] = self.dislikes_model.insert_with_valuesv( -1, self.dislikes_column_numbers, [thing]) """ Popup """ self.til_popup_menu = popup = PopupMenu(self.frame) popup.setup( ("#" + _("_Remove Item"), self.on_remove_thing_i_like), ("#" + _("Re_commendations For Item"), self.on_recommend_item), ("", None), ("#" + _("_Search For Item"), self.on_til_recommend_search)) self.tidl_popup_menu = popup = PopupMenu(self.frame) popup.setup( ("#" + _("_Remove Item"), self.on_remove_thing_i_dislike), ("", None), ("#" + _("_Search For Item"), self.on_tidl_recommend_search)) self.r_popup_menu = popup = PopupMenu(self.frame) popup.setup( ("$" + _("I _Like This"), self.on_like_recommendation), ("$" + _("I _Dislike This"), self.on_dislike_recommendation), ("#" + _("_Recommendations For Item"), self.on_recommend_recommendation), ("", None), ("#" + _("_Search For Item"), self.on_r_recommend_search)) self.ru_popup_menu = popup = PopupMenu(self.frame) popup.setup_user_menu() self.update_visuals()
class TransferList: def __init__(self, frame, type): self.frame = frame self.type = type load_ui_elements(self, os.path.join(frame.gui_dir, "ui", type + "s.ui")) getattr(frame, type + "svbox").add(self.Main) self.widget = widget = getattr(self, type.title() + "List") self.last_ui_update = self.last_save = 0 self.list = [] self.users = {} self.paths = {} # Status list self.statuses = {} self.statuses["Queued"] = _("Queued") self.statuses["Getting status"] = _("Getting status") self.statuses["Establishing connection"] = _("Establishing connection") self.statuses["Transferring"] = _("Transferring") self.statuses["Cannot connect"] = _("Cannot connect") self.statuses["User logged off"] = _("User logged off") self.statuses["Connection closed by peer"] = _( "Connection closed by peer") self.statuses["Aborted"] = _("Aborted") self.statuses["Finished"] = _("Finished") self.statuses["Filtered"] = _("Filtered") self.statuses["File not shared"] = _("File not shared") self.statuses["File not shared."] = _( "File not shared" ) # The official client sends a variant containing a dot self.statuses["Download directory error"] = _( "Download directory error") self.statuses["Local file error"] = _("Local file error") self.statuses["Remote file error"] = _("Remote file error") # String templates self.extension_list_template = _("All %(ext)s") self.files_template = _("%(number)2s files ") self.transfersmodel = Gtk.TreeStore( str, # (0) user str, # (1) path str, # (2) file name str, # (3) status str, # (4) hqueue position GObject.TYPE_UINT64, # (5) percent str, # (6) hsize str, # (7) hspeed str, # (8) htime elapsed str, # (9) time left str, # (10) path str, # (11) status (non-translated) GObject.TYPE_UINT64, # (12) size GObject.TYPE_UINT64, # (13) current bytes GObject.TYPE_UINT64, # (14) speed GObject.TYPE_UINT64, # (15) time elapsed GObject.TYPE_UINT64, # (16) file count GObject.TYPE_UINT64, # (17) queue position GObject.TYPE_PYOBJECT # (18) transfer object ) self.column_numbers = list(range(self.transfersmodel.get_n_columns())) self.cols = cols = initialise_columns( type, widget, ["user", _("User"), 200, "text", None], ["path", _("Path"), 400, "text", None], ["filename", _("Filename"), 400, "text", None], ["status", _("Status"), 140, "text", None], ["queue_position", _("Queue Position"), 50, "number", None], ["percent", _("Percent"), 70, "progress", None], ["size", _("Size"), 170, "number", None], ["speed", _("Speed"), 90, "number", None], ["time_elapsed", _("Time Elapsed"), 140, "number", None], ["time_left", _("Time Left"), 140, "number", None], ) cols["user"].set_sort_column_id(0) cols["path"].set_sort_column_id(1) cols["filename"].set_sort_column_id(2) cols["status"].set_sort_column_id(11) cols["queue_position"].set_sort_column_id(17) cols["percent"].set_sort_column_id(5) cols["size"].set_sort_column_id(12) cols["speed"].set_sort_column_id(14) cols["time_elapsed"].set_sort_column_id(8) cols["time_left"].set_sort_column_id(9) widget.set_model(self.transfersmodel) self.group_dropdown = getattr(frame, "ToggleTree%ss" % self.type.title()) self.expand_button = getattr(frame, "Expand%ss" % self.type.title()) self.group_dropdown.connect("changed", self.on_toggle_tree) self.group_dropdown.set_active( config.sections["transfers"]["group%ss" % self.type]) self.expand_button.connect("toggled", self.on_expand_tree) self.expand_button.set_active( config.sections["transfers"]["%ssexpanded" % self.type]) self.popup_menu_users = PopupMenu(frame) self.popup_menu_clear = PopupMenu(frame) self.popup_menu = PopupMenu(frame) self.popup_menu.setup( ("#" + "selected_files", None), ("", None), ("#" + _("Send to _Player"), self.on_play_files), ("#" + _("_Open Folder"), self.on_open_directory), ("#" + _("File P_roperties"), self.on_file_properties), ("", None), ("#" + _("Copy _File Path"), self.on_copy_file_path), ("#" + _("Copy _URL"), self.on_copy_url), ("#" + _("Copy Folder URL"), self.on_copy_dir_url), ("", None), ("#" + _("_Search"), self.on_file_search), (">" + _("User(s)"), self.popup_menu_users), ("", None), ("#" + _("_Retry"), self.on_retry_transfer), ("#" + _("Abor_t"), self.on_abort_transfer), ("#" + _("_Clear"), self.on_clear_transfer), ("", None), (">" + _("Clear Groups"), self.popup_menu_clear)) self.update_visuals() def init_interface(self, list): self.list = list self.widget.set_sensitive(True) self.update() def rebuild_transfers(self): if self.frame.np.transfers is None: return self.clear() self.update() def save_columns(self): save_columns(self.type, self.widget.get_columns()) def update_visuals(self): for widget in list(self.__dict__.values()): update_widget_visuals(widget, list_font_target="transfersfont") def conn_close(self): self.widget.set_sensitive(False) self.list = [] self.clear() def select_transfers(self): self.selected_transfers = set() self.selected_users = set() model, paths = self.widget.get_selection().get_selected_rows() for path in paths: iterator = model.get_iter(path) self.select_transfer(model, iterator, select_user=True) # If we're in grouping mode, select any transfers under the selected # user or folder self.select_child_transfers(model, model.iter_children(iterator)) def select_child_transfers(self, model, iterator): while iterator is not None: self.select_transfer(model, iterator) self.select_child_transfers(model, model.iter_children(iterator)) iterator = model.iter_next(iterator) def select_transfer(self, model, iterator, select_user=False): user = model.get_value(iterator, 0) transfer = model.get_value(iterator, 18) if isinstance(transfer, Transfer): self.selected_transfers.add(transfer) if select_user: self.selected_users.add(user) def new_transfer_notification(self): self.frame.request_tab_icon(self.tab_label) def on_ban(self, *args): self.select_transfers() for user in self.selected_users: self.frame.np.network_filter.ban_user(user) def on_file_search(self, *args): transfer = next(iter(self.selected_transfers), None) if not transfer: return self.frame.SearchEntry.set_text(transfer.filename.rsplit("\\", 1)[1]) self.frame.change_main_page("search") def translate_status(self, status): try: newstatus = self.statuses[status] except KeyError: newstatus = status return newstatus def update(self, transfer=None, forceupdate=False): if not self.widget.get_sensitive(): """ List is not initialized """ return curtime = time() if (curtime - self.last_save) > 15: """ Save downloads list to file every 15 seconds """ if self.frame.np.transfers is not None: self.frame.np.transfers.save_downloads() self.last_save = curtime finished = (transfer is not None and transfer.status == "Finished") if forceupdate or finished or \ (curtime - self.last_ui_update) > 1: self.frame.update_bandwidth() if not forceupdate and self.frame.current_tab_label != self.tab_label: """ No need to do unnecessary work if transfers are not visible """ return if transfer is not None: self.update_specific(transfer) elif self.list is not None: for transfer in reversed(self.list): self.update_specific(transfer) if forceupdate or finished or \ (curtime - self.last_ui_update) > 1: """ Unless a transfer finishes, use a cooldown to avoid updating too often """ self.update_parent_rows() def update_parent_rows(self, only_remove=False): # Remove empty parent rows for path, pathiter in list(self.paths.items()): if not self.transfersmodel.iter_has_child(pathiter): self.transfersmodel.remove(pathiter) del self.paths[path] elif not only_remove: self.update_parent_row(pathiter) for username, useriter in list(self.users.items()): if isinstance(useriter, Gtk.TreeIter): if not self.transfersmodel.iter_has_child(useriter): self.transfersmodel.remove(useriter) del self.users[username] elif not only_remove: self.update_parent_row(useriter) else: # No grouping if not self.users[username]: del self.users[username] self.frame.update_bandwidth() self.last_ui_update = time() def update_parent_row(self, initer): speed = 0.0 percent = totalsize = position = 0 hspeed = helapsed = left = "" elapsed = 0 filecount = 0 salientstatus = "" extensions = {} iterator = self.transfersmodel.iter_children(initer) while iterator is not None: status = self.transfersmodel.get_value(iterator, 11) if salientstatus in ( '', "Finished", "Filtered"): # we prefer anything over ''/finished salientstatus = status filename = self.transfersmodel.get_value(iterator, 2) parts = filename.rsplit('.', 1) if len(parts) == 2: ext = parts[1] try: extensions[ext.lower()] += 1 except KeyError: extensions[ext.lower()] = 1 filecount += self.transfersmodel.get_value(iterator, 16) if status == "Filtered": # We don't want to count filtered files when calculating the progress iterator = self.transfersmodel.iter_next(iterator) continue elapsed += self.transfersmodel.get_value(iterator, 15) totalsize += self.transfersmodel.get_value(iterator, 12) position += self.transfersmodel.get_value(iterator, 13) if status == "Transferring": speed += float(self.transfersmodel.get_value(iterator, 14)) left = self.transfersmodel.get_value(iterator, 9) if status in ("Transferring", "Banned", "Getting address", "Establishing connection"): salientstatus = status iterator = self.transfersmodel.iter_next(iterator) if totalsize > 0: percent = min(((100 * position) / totalsize), 100) else: percent = 100 if speed > 0: hspeed = human_speed(speed) left = self.frame.np.transfers.get_time( (totalsize - position) / speed) if elapsed > 0: helapsed = self.frame.np.transfers.get_time(elapsed) if not extensions: extensions = "" elif len(extensions) == 1: extensions = " (" + self.extension_list_template % { 'ext': next(iter(extensions)) } + ")" else: extensions = " (" + ", ".join( (str(count) + " " + ext for (ext, count) in extensions.items())) + ")" self.transfersmodel.set_value( initer, 2, self.files_template % {'number': filecount} + extensions) self.transfersmodel.set_value(initer, 3, self.translate_status(salientstatus)) self.transfersmodel.set_value( initer, 5, GObject.Value(GObject.TYPE_UINT64, percent)) self.transfersmodel.set_value( initer, 6, "%s / %s" % (human_size(position), human_size(totalsize))) self.transfersmodel.set_value(initer, 7, hspeed) self.transfersmodel.set_value(initer, 8, helapsed) self.transfersmodel.set_value(initer, 9, left) self.transfersmodel.set_value(initer, 11, salientstatus) self.transfersmodel.set_value( initer, 12, GObject.Value(GObject.TYPE_UINT64, totalsize)) self.transfersmodel.set_value( initer, 13, GObject.Value(GObject.TYPE_UINT64, position)) self.transfersmodel.set_value( initer, 14, GObject.Value(GObject.TYPE_UINT64, speed)) self.transfersmodel.set_value( initer, 15, GObject.Value(GObject.TYPE_UINT64, elapsed)) self.transfersmodel.set_value( initer, 16, GObject.Value(GObject.TYPE_UINT64, filecount)) def update_specific(self, transfer=None): currentbytes = transfer.currentbytes place = transfer.place or 0 hplace = "" if place > 0: hplace = str(place) hspeed = helapsed = "" if currentbytes is None: currentbytes = 0 status = transfer.status or "" hstatus = self.translate_status(status) try: size = int(transfer.size) if size < 0 or size > maxsize: size = 0 except TypeError: size = 0 hsize = "%s / %s" % (human_size(currentbytes), human_size(size)) if transfer.modifier: hsize += " (%s)" % transfer.modifier speed = transfer.speed or 0 elapsed = transfer.timeelapsed or 0 left = transfer.timeleft or "" if speed > 0: speed = float(speed) hspeed = human_speed(speed) if elapsed > 0: helapsed = self.frame.np.transfers.get_time(elapsed) try: icurrentbytes = int(currentbytes) percent = min(((100 * icurrentbytes) / int(size)), 100) except Exception: icurrentbytes = 0 percent = 100 # Modify old transfer if transfer.iter is not None: initer = transfer.iter self.transfersmodel.set_value(initer, 3, hstatus) self.transfersmodel.set_value(initer, 4, hplace) self.transfersmodel.set_value( initer, 5, GObject.Value(GObject.TYPE_UINT64, percent)) self.transfersmodel.set_value(initer, 6, hsize) self.transfersmodel.set_value(initer, 7, hspeed) self.transfersmodel.set_value(initer, 8, helapsed) self.transfersmodel.set_value(initer, 9, left) self.transfersmodel.set_value(initer, 11, status) self.transfersmodel.set_value( initer, 12, GObject.Value(GObject.TYPE_UINT64, size)) self.transfersmodel.set_value( initer, 13, GObject.Value(GObject.TYPE_UINT64, currentbytes)) self.transfersmodel.set_value( initer, 14, GObject.Value(GObject.TYPE_UINT64, speed)) self.transfersmodel.set_value( initer, 15, GObject.Value(GObject.TYPE_UINT64, elapsed)) self.transfersmodel.set_value( initer, 17, GObject.Value(GObject.TYPE_UINT64, place)) else: fn = transfer.filename user = transfer.user shortfn = fn.split("\\")[-1] filecount = 1 if self.tree_users != "ungrouped": # Group by folder or user empty_int = 0 empty_str = "" if user not in self.users: # Create Parent if it doesn't exist # ProgressRender not visible (last column sets 4th column) self.users[user] = self.transfersmodel.insert_with_values( None, -1, self.column_numbers, [ user, empty_str, empty_str, empty_str, empty_str, empty_int, empty_str, empty_str, empty_str, empty_str, empty_str, empty_str, empty_int, empty_int, empty_int, empty_int, filecount, empty_int, lambda: None ]) parent = self.users[user] if self.tree_users == "folder_grouping": # Group by folder """ Paths can be empty if files are downloaded individually, make sure we don't add files to the wrong user in the TreeView """ path = transfer.path user_path = user + path reverse_path = '/'.join(reversed(path.split('/'))) if user_path not in self.paths: self.paths[ user_path] = self.transfersmodel.insert_with_values( self.users[user], -1, self.column_numbers, [ user, reverse_path, empty_str, empty_str, empty_str, empty_int, empty_str, empty_str, empty_str, empty_str, empty_str, empty_str, empty_int, empty_int, empty_int, empty_int, filecount, empty_int, lambda: None ]) parent = self.paths[user_path] else: # No grouping # We use this list to get the total number of users self.users.setdefault(user, set()).add(transfer) parent = None # Add a new transfer if self.tree_users == "folder_grouping": # Group by folder, path not visible path = "" else: path = '/'.join(reversed(transfer.path.split('/'))) iterator = self.transfersmodel.insert_with_values( parent, -1, self.column_numbers, (user, path, shortfn, hstatus, hplace, GObject.Value(GObject.TYPE_UINT64, percent), hsize, hspeed, helapsed, left, fn, status, GObject.Value(GObject.TYPE_UINT64, size), GObject.Value(GObject.TYPE_UINT64, icurrentbytes), GObject.Value(GObject.TYPE_UINT64, speed), GObject.Value(GObject.TYPE_UINT64, elapsed), GObject.Value(GObject.TYPE_UINT64, filecount), GObject.Value(GObject.TYPE_UINT64, place), transfer)) transfer.iter = iterator # Expand path if parent is not None: transfer_path = self.transfersmodel.get_path(iterator) if self.tree_users == "folder_grouping": # Group by folder, we need the user path to expand it user_path = self.transfersmodel.get_path(self.users[user]) else: user_path = None self.expand(transfer_path, user_path) def retry_transfers(self): for transfer in self.selected_transfers: getattr(self.frame.np.transfers, "retry_" + self.type)(transfer) def abort_transfers(self, clear=False): for transfer in self.selected_transfers: if transfer.status != "Finished": self.frame.np.transfers.abort_transfer(transfer, send_fail_message=True) if not clear: transfer.status = "Aborted" self.update(transfer) if clear: self.remove_specific(transfer) def remove_specific(self, transfer, cleartreeviewonly=False): user = transfer.user if user in self.users and not isinstance(self.users[user], Gtk.TreeIter): # No grouping self.users[user].discard(transfer) if transfer in self.frame.np.transfers.transfer_request_times: del self.frame.np.transfers.transfer_request_times[transfer] if not cleartreeviewonly: self.list.remove(transfer) if transfer.iter is not None: self.transfersmodel.remove(transfer.iter) self.update_parent_rows(only_remove=True) def clear_transfers(self, status): for transfer in self.list.copy(): if transfer.status in status: self.frame.np.transfers.abort_transfer(transfer, send_fail_message=True) self.remove_specific(transfer) def clear(self): self.users.clear() self.paths.clear() self.selected_transfers = set() self.selected_users = set() self.transfersmodel.clear() if self.list is not None: for transfer in self.list: transfer.iter = None def double_click(self, event): self.select_transfers() dc = config.sections["transfers"]["%s_doubleclick" % self.type] if dc == 1: # Send to player self.on_play_files() elif dc == 2: # File manager self.on_open_directory() elif dc == 3: # Search self.on_file_search() elif dc == 4: # Abort self.abort_transfers() elif dc == 5: # Clear self.abort_transfers(clear=True) elif dc == 6: # Retry self.retry_transfers() def populate_popup_menu_users(self): self.popup_menu_users.clear() if not self.selected_users: return for user in self.selected_users: popup = PopupMenu(self.frame) popup.setup_user_menu(user) popup.setup(("", None), ("#" + _("Select User's Transfers"), self.on_select_user_transfers, user)) popup.toggle_user_items() self.popup_menu_users.setup((">" + user, popup)) def expand(self, transfer_path, user_path): if self.expand_button.get_active(): self.widget.expand_to_path(transfer_path) elif user_path and self.tree_users == "folder_grouping": # Group by folder, show user folders in collapsed mode self.widget.expand_to_path(user_path) def on_expand_tree(self, widget): expand_button_icon = getattr(self.frame, "Expand%ssImage" % self.type.title()) expanded = widget.get_active() if expanded: self.widget.expand_all() expand_button_icon.set_from_icon_name("go-up-symbolic", Gtk.IconSize.BUTTON) else: collapse_treeview(self.widget, self.tree_users) expand_button_icon.set_from_icon_name("go-down-symbolic", Gtk.IconSize.BUTTON) config.sections["transfers"]["%ssexpanded" % self.type] = expanded config.write_configuration() def on_toggle_tree(self, widget): active = widget.get_active() config.sections["transfers"]["group%ss" % self.type] = active self.widget.set_show_expanders(active) self.expand_button.set_visible(active) self.tree_users = widget.get_active_id() self.rebuild_transfers() def on_tooltip(self, widget, x, y, keyboard_mode, tooltip): return show_file_path_tooltip(widget, x, y, tooltip, 10) def on_popup_menu(self, *args): self.select_transfers() num_selected_transfers = len(self.selected_transfers) actions = self.popup_menu.get_actions() users = len(self.selected_users) > 0 files = num_selected_transfers > 0 actions[_("User(s)")].set_enabled(users) # Users Menu self.populate_popup_menu_users() if files: act = True else: # Disable options # Send to player, File manager, file properties, Copy File Path, Copy URL, Copy Folder URL, Search filename act = False for i in (_("Send to _Player"), _("_Open Folder"), _("File P_roperties"), _("Copy _File Path"), _("Copy _URL"), _("Copy Folder URL"), _("_Search")): actions[i].set_enabled(act) if not users or not files: # Disable options # Retry, Abort, Clear act = False else: act = True for i in (_("_Retry"), _("Abor_t"), _("_Clear")): actions[i].set_enabled(act) self.popup_menu.set_num_selected_files(num_selected_transfers) self.popup_menu.popup() return True def on_list_clicked(self, widget, event): if triggers_context_menu(event): set_treeview_selected_row(widget, event) return self.on_popup_menu() if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS: self.double_click(event) return True return False def on_select_user_transfers(self, *args): if not self.selected_users: return selected_user = args[-1] sel = self.widget.get_selection() fmodel = self.widget.get_model() sel.unselect_all() iterator = fmodel.get_iter_first() select_user_row_iter(fmodel, sel, 0, selected_user, iterator) self.select_transfers() def on_key_press_event(self, widget, event): keycode = event.hardware_keycode self.select_transfers() if keycode in keyval_to_hardware_keycode(Gdk.KEY_t): self.abort_transfers() elif keycode in keyval_to_hardware_keycode(Gdk.KEY_r): self.retry_transfers() elif event.get_state() & Gdk.ModifierType.CONTROL_MASK and \ keycode in keyval_to_hardware_keycode(Gdk.KEY_c): self.on_copy_file_path() elif keycode in keyval_to_hardware_keycode(Gdk.KEY_Delete): self.abort_transfers(clear=True) else: # No key match, continue event return False widget.stop_emission_by_name("key_press_event") return True def on_file_properties(self, *args): if not self.frame.np.transfers: return data = [] model, paths = self.widget.get_selection().get_selected_rows() for path in paths: iterator = model.get_iter(path) transfer = model.get_value(iterator, 18) if not isinstance(transfer, Transfer): continue user = model.get_value(iterator, 0) filename = model.get_value(iterator, 2) fullname = model.get_value(iterator, 10) size = speed = length = queue = immediate = num = country = bitratestr = "" size = str(human_size(transfer.size)) if transfer.speed: speed = str(human_speed(transfer.speed)) bitratestr = str(transfer.bitrate) length = str(transfer.length) directory = fullname.rsplit("\\", 1)[0] data.append({ "user": user, "fn": fullname, "position": num, "filename": filename, "directory": directory, "size": size, "speed": speed, "queue": queue, "immediate": immediate, "bitrate": bitratestr, "length": length, "country": country }) if paths: FileProperties(self.frame, data).show() def on_copy_file_path(self, *args): transfer = next(iter(self.selected_transfers), None) if transfer: self.frame.clipboard.set_text(transfer.filename, -1) def on_copy_url(self, *args): transfer = next(iter(self.selected_transfers), None) if transfer: copy_file_url(transfer.user, transfer.filename, self.frame.clipboard) def on_copy_dir_url(self, *args): transfer = next(iter(self.selected_transfers), None) if transfer: copy_file_url(transfer.user, transfer.filename.rsplit('\\', 1)[0] + '\\', self.frame.clipboard) def on_retry_transfer(self, *args): self.select_transfers() self.retry_transfers() def on_abort_transfer(self, *args): self.select_transfers() self.abort_transfers() def on_clear_transfer(self, *args): self.select_transfers() self.abort_transfers(clear=True) def on_clear_response(self, dialog, response_id, data): dialog.destroy() if response_id == Gtk.ResponseType.OK: self.clear_transfers(["Queued"]) def on_clear_queued(self, *args): self.clear_transfers(["Queued"]) def on_clear_finished(self, *args): self.clear_transfers(["Finished"])
class ChatRoom: def __init__(self, chatrooms, room, users, meta=False): self.chatrooms = chatrooms self.frame = chatrooms.frame self.room = room self.meta = meta # not a real room if set to True # Build the window load_ui_elements( self, os.path.join(self.frame.gui_dir, "ui", "chatrooms.ui")) self.tickers = Tickers() self.room_wall = RoomWall(self.frame, self) self.leaving = False self.users = {} # Log Text Search TextSearchBar(self.RoomLog, self.LogSearchBar, self.LogSearchEntry) # Chat Text Search TextSearchBar(self.ChatScroll, self.ChatSearchBar, self.ChatSearchEntry) # Chat Entry self.entry = ChatEntry(self.frame, self.ChatEntry, room, slskmessages.SayChatroom, self.send_message, self.chatrooms.CMDS, self.ChatScroll, is_chatroom=True) self.Log.set_active(config.sections["logging"]["chatrooms"]) if not self.Log.get_active(): self.Log.set_active((self.room in config.sections["logging"]["rooms"])) self.AutoJoin.set_active((room in config.sections["server"]["autojoin"])) self.toggle_chat_buttons() if room not in config.sections["columns"]["chat_room"]: config.sections["columns"]["chat_room"][room] = {} self.usersmodel = Gtk.ListStore( GObject.TYPE_OBJECT, # (0) status_image GObject.TYPE_OBJECT, # (1) flag str, # (2) username str, # (3) h_speed str, # (4) h_files int, # (5) status GObject.TYPE_UINT64, # (6) avgspeed GObject.TYPE_UINT64, # (7) files str # (8) country ) self.UserList.set_model(self.usersmodel) self.column_numbers = list(range(self.usersmodel.get_n_columns())) self.cols = cols = initialise_columns( ("chat_room", room), self.UserList, ["status", _("Status"), 25, "pixbuf", None], ["country", _("Country"), 25, "pixbuf", None], ["user", _("User"), 100, "text", self.user_column_draw], ["speed", _("Speed"), 100, "number", None], ["files", _("Files"), 100, "number", None]) cols["status"].set_sort_column_id(5) cols["country"].set_sort_column_id(8) cols["user"].set_sort_column_id(2) cols["speed"].set_sort_column_id(6) cols["files"].set_sort_column_id(7) cols["status"].get_widget().hide() cols["country"].get_widget().hide() if config.sections["columns"]["hideflags"]: cols["country"].set_visible(False) for userdata in users: self.add_user_row(userdata) self.usersmodel.set_sort_column_id(2, Gtk.SortType.ASCENDING) self.popup_menu_private_rooms = PopupMenu(self.frame) self.popup_menu = popup = PopupMenu(self.frame) popup.setup_user_menu() popup.setup(("", None), ("#" + _("Sear_ch User's Files"), popup.on_search_user), (">" + _("Private Rooms"), self.popup_menu_private_rooms)) self.activitylogpopupmenu = PopupMenu(self.frame) self.activitylogpopupmenu.setup( ("#" + _("Find"), self.on_find_activity_log), ("", None), ("#" + _("Copy"), self.on_copy_activity_log), ("#" + _("Copy All"), self.on_copy_all_activity_log), ("", None), ("#" + _("Clear Activity View"), self.on_clear_activity_log), ("", None), ("#" + _("_Leave Room"), self.on_leave)) self.roomlogpopmenu = PopupMenu(self.frame) self.roomlogpopmenu.setup( ("#" + _("Find"), self.on_find_room_log), ("", None), ("#" + _("Copy"), self.on_copy_room_log), ("#" + _("Copy All"), self.on_copy_all_room_log), ("", None), ("#" + _("View Room Log"), self.on_view_room_log), ("#" + _("Delete Room Log"), self.on_delete_room_log), ("", None), ("#" + _("Clear Message View"), self.on_clear_messages), ("#" + _("_Leave Room"), self.on_leave)) self.tab_menu = PopupMenu(self.frame) self.tab_menu.setup(("#" + _("_Leave Room"), self.on_leave)) self.ChatEntry.grab_focus() self.set_completion_list(list(self.chatrooms.completion_list)) self.count_users() self.create_tags() self.update_visuals() self.read_room_logs() def add_user_row(self, userdata): username = userdata.username status = userdata.status country = userdata.country or "" # country can be None, ensure string is used status_image = self.frame.get_status_image(status) flag_image = self.frame.get_flag_image(country) # Request user's IP address, so we can get the country self.frame.np.queue.append(slskmessages.GetPeerAddress(username)) avgspeed = userdata.avgspeed files = userdata.files hspeed = human_speed(avgspeed) hfiles = humanize(files) iterator = self.usersmodel.insert_with_valuesv( -1, self.column_numbers, [ GObject.Value(GObject.TYPE_OBJECT, status_image), GObject.Value(GObject.TYPE_OBJECT, flag_image), username, hspeed, hfiles, status, GObject.Value(GObject.TYPE_UINT64, avgspeed), GObject.Value(GObject.TYPE_UINT64, files), country ]) self.users[username] = iterator def read_room_logs(self): if not config.sections["logging"]["readroomlogs"]: return filename = self.room.replace(os.sep, "-") + ".log" try: numlines = int(config.sections["logging"]["readroomlines"]) except Exception: numlines = 15 try: get_path(config.sections["logging"]["roomlogsdir"], filename, self.append_log_lines, numlines) except IOError: pass GLib.idle_add(scroll_bottom, self.ChatScroll.get_parent()) def append_log_lines(self, path, numlines): try: self._append_log_lines(path, numlines, 'utf-8') except UnicodeDecodeError: self._append_log_lines(path, numlines, 'latin-1') def _append_log_lines(self, path, numlines, encoding='utf-8'): with open(path, 'r', encoding=encoding) as lines: # Only show as many log lines as specified in config lines = deque(lines, numlines) for line in lines: # Try to parse line for username if len(line) > 20 and line[10].isspace() and line[11].isdigit( ) and line[20] in ("[", "*"): if line[20] == "[" and line[20:].find("] ") != -1: namepos = line[20:].find("] ") user = line[21:20 + namepos].strip() self.get_user_tag(user) usertag = self.tag_users[user] else: user = None usertag = None if user == config.sections["server"]["login"]: tag = self.tag_local elif line[20] == "*": tag = self.tag_me elif line[20 + namepos:].upper().find( config.sections["server"]["login"].upper()) > -1: tag = self.tag_hilite else: tag = self.tag_remote else: user = None tag = None usertag = None line = re.sub(r"\\s\\s+", " ", line) if user != config.sections["server"]["login"]: append_line(self.ChatScroll, censor_chat(line), tag, username=user, usertag=usertag, timestamp_format="", scroll=False) else: append_line(self.ChatScroll, line, tag, username=user, usertag=usertag, timestamp_format="", scroll=False) if lines: append_line(self.ChatScroll, _("--- old messages above ---"), self.tag_hilite) def populate_user_menu(self, user): self.popup_menu.set_user(user) self.popup_menu.toggle_user_items() me = (self.popup_menu.user is None or self.popup_menu.user == config.sections["server"]["login"]) self.popup_menu.get_actions()[_("Private Rooms")].set_enabled(not me) self.popup_menu.populate_private_rooms(self.popup_menu_private_rooms) def on_find_activity_log(self, *args): self.LogSearchBar.set_search_mode(True) def on_find_room_log(self, *args): self.ChatSearchBar.set_search_mode(True) def get_selected_username(self, treeview): model, iterator = treeview.get_selection().get_selected() if iterator is None: return None return model.get_value(iterator, 2) def on_list_clicked(self, widget, event): if triggers_context_menu(event): set_treeview_selected_row(widget, event) return self.on_popup_menu(widget) if event.type == Gdk.EventType._2BUTTON_PRESS: user = self.get_selected_username(widget) if user is not None: self.frame.privatechats.send_message(user, show_user=True) self.frame.change_main_page("private") return True return False def on_popup_menu(self, widget): user = self.get_selected_username(widget) if user is None: return False self.populate_user_menu(user) self.popup_menu.popup() return True def on_show_room_wall(self, *args): self.room_wall.show() def on_show_chat_help(self, *args): if not hasattr(self, "AboutChatRoomCommandsPopover"): load_ui_elements( self, os.path.join(self.frame.gui_dir, "ui", "popovers", "chatroomcommands.ui")) self.AboutChatRoomCommandsPopover.set_relative_to( self.ShowChatHelp) try: self.AboutChatRoomCommandsPopover.popup() except AttributeError: # GTK <3.22 support self.AboutChatRoomCommandsPopover.set_transitions_enabled(True) self.AboutChatRoomCommandsPopover.show() def toggle_chat_buttons(self): self.Speech.set_visible(config.sections["ui"]["speechenabled"]) def ticker_set(self, msg): self.tickers.set_ticker([]) for user in msg.msgs: if self.frame.np.network_filter.is_user_ignored( user) or self.frame.np.network_filter.is_user_ip_ignored( user): # User ignored, ignore Ticker messages return self.tickers.add_ticker(user, msg.msgs[user]) def ticker_add(self, msg): user = msg.user if self.frame.np.network_filter.is_user_ignored( user) or self.frame.np.network_filter.is_user_ip_ignored(user): # User ignored, ignore Ticker messages return self.tickers.add_ticker(msg.user, msg.msg) def ticker_remove(self, msg): self.tickers.remove_ticker(msg.user) def show_notification(self, login, user, text, tag): if user == login: return if tag == self.tag_hilite: # Hilight top-level tab label self.frame.request_tab_icon(self.frame.ChatTabLabel, status=1) # Hilight sub-level tab label self.chatrooms.request_hilite(self.Main) if config.sections["notifications"][ "notification_popup_chatroom_mention"]: self.frame.notifications.new_notification( text, title=_("%s mentioned you in the %s room") % (user, self.room), priority=Gio.NotificationPriority.HIGH) else: # Hilight top-level tab label self.frame.request_tab_icon(self.frame.ChatTabLabel, status=0) # Hilight sub-level tab label self.chatrooms.request_changed(self.Main) # Don't show notifications if the chat is open and the window # is in use if self.chatrooms.get_current_page() == self.chatrooms.page_num(self.chatrooms.joinedrooms[self.room].Main) and \ self.frame.MainNotebook.get_current_page() == self.frame.MainNotebook.page_num(self.frame.chatroomsvbox) and \ self.frame.MainWindow.is_active(): return if tag == self.tag_hilite: # We were mentioned, update tray icon and show urgency hint self.frame.notifications.add("rooms", user, self.room) elif config.sections["notifications"]["notification_popup_chatroom"]: self.frame.notifications.new_notification( text, title=_("Message by %s in the %s room") % (user, self.room), priority=Gio.NotificationPriority.HIGH) def say_chat_room(self, msg, text, public=False): user = msg.user if self.frame.np.network_filter.is_user_ignored(user): return if self.frame.np.network_filter.is_user_ip_ignored(user): return text = re.sub("\\s\\s+", " ", text) login = config.sections["server"]["login"] if user == login: tag = self.tag_local elif text.upper().find(login.upper()) > -1: tag = self.tag_hilite else: tag = self.tag_remote self.show_notification(login, user, text, tag) if text[:4] == "/me ": if public: line = "%s | * %s %s" % (msg.room, user, text[4:]) else: line = "* %s %s" % (user, text[4:]) speech = line[2:] tag = self.tag_me else: if public: line = "%s | [%s] %s" % (msg.room, user, text) else: line = "[%s] %s" % (user, text) speech = text line = "\n-- ".join(line.split("\n")) if self.Log.get_active(): timestamp_format = config.sections["logging"]["log_timestamp"] log.write_log(config.sections["logging"]["roomlogsdir"], self.room, line, timestamp_format) self.get_user_tag(user) timestamp_format = config.sections["logging"]["rooms_timestamp"] if user != login: append_line(self.ChatScroll, censor_chat(line), tag, username=user, usertag=self.tag_users[user], timestamp_format=timestamp_format) if self.Speech.get_active(): self.frame.notifications.new_tts( config.sections["ui"]["speechrooms"] % { "room": self.room, "user": self.frame.notifications.tts_clean(user), "message": self.frame.notifications.tts_clean(speech) }) else: append_line(self.ChatScroll, line, tag, username=user, usertag=self.tag_users[user], timestamp_format=timestamp_format) def get_user_tag(self, user): if user not in self.users: color = "useroffline" else: color = get_user_status_color( self.usersmodel.get_value(self.users[user], 5)) if user not in self.tag_users: self.tag_users[user] = self.create_tag( self.ChatScroll.get_buffer(), color, user) else: update_tag_visuals(self.tag_users[user], color) def show_tickers(self): tickers = self.tickers.get_tickers() header = _("All tickers / wall messages for %(room)s:") % { 'room': self.room } log.add("%s\n%s", (header, "\n".join( ["[%s] %s" % (user, msg) for (user, msg) in tickers]))) def send_message(self, text): event = self.frame.np.pluginhandler.outgoing_public_chat_event( self.room, text) if event is not None: (r, text) = event self.say(auto_replace(text)) self.frame.np.pluginhandler.outgoing_public_chat_notification( self.room, text) def say(self, text): text = re.sub("\\s\\s+", " ", text) self.frame.np.queue.append(slskmessages.SayChatroom(self.room, text)) def user_joined_room(self, userdata): username = userdata.username if username in self.users: return # Add to completion list, and completion drop-down self.entry.add_completion(username) if not self.frame.np.network_filter.is_user_ignored( username ) and not self.frame.np.network_filter.is_user_ip_ignored(username): append_line(self.RoomLog, _("%s joined the room") % username, self.tag_log) self.frame.np.pluginhandler.user_join_chatroom_notification( self.room, username) self.add_user_row(userdata) self.get_user_tag(username) self.count_users() def user_left_room(self, username): if username not in self.users: return # Remove from completion list, and completion drop-down if username not in (i[0] for i in config.sections["server"]["userlist"]): self.entry.remove_completion(username) if not self.frame.np.network_filter.is_user_ignored( username ) and not self.frame.np.network_filter.is_user_ip_ignored(username): append_line(self.RoomLog, _("%s left the room") % username, self.tag_log) self.frame.np.pluginhandler.user_leave_chatroom_notification( self.room, username) self.usersmodel.remove(self.users[username]) del self.users[username] self.get_user_tag(username) self.count_users() def count_users(self): user_count = len(self.users) self.LabelPeople.set_text(str(user_count)) self.chatrooms.roomlist.update_room(self.room, user_count) def user_column_draw(self, column, cellrenderer, model, iterator, dummy="dummy"): if self.room in self.chatrooms.private_rooms: user = self.usersmodel.get_value(iterator, 2) if user == self.chatrooms.private_rooms[self.room]["owner"]: cellrenderer.set_property("underline", Pango.Underline.SINGLE) cellrenderer.set_property("weight", Pango.Weight.BOLD) elif user in ( self.chatrooms.private_rooms[self.room]["operators"]): cellrenderer.set_property("weight", Pango.Weight.BOLD) cellrenderer.set_property("underline", Pango.Underline.NONE) else: cellrenderer.set_property("weight", Pango.Weight.NORMAL) cellrenderer.set_property("underline", Pango.Underline.NONE) def get_user_stats(self, user, avgspeed, files): if user not in self.users: return self.usersmodel.set_value(self.users[user], 3, human_speed(avgspeed)) self.usersmodel.set_value(self.users[user], 4, humanize(files)) self.usersmodel.set_value(self.users[user], 6, GObject.Value(GObject.TYPE_UINT64, avgspeed)) self.usersmodel.set_value(self.users[user], 7, GObject.Value(GObject.TYPE_UINT64, files)) def get_user_status(self, user, status): if user not in self.users: return img = self.frame.get_status_image(status) if img == self.usersmodel.get_value(self.users[user], 0): return if status == 1: action = _("%s has gone away") else: action = _("%s has returned") if not self.frame.np.network_filter.is_user_ignored( user) and not self.frame.np.network_filter.is_user_ip_ignored( user): append_line(self.RoomLog, action % user, self.tag_log) if user in self.tag_users: color = get_user_status_color(status) update_tag_visuals(self.tag_users[user], color) self.usersmodel.set_value(self.users[user], 0, GObject.Value(GObject.TYPE_OBJECT, img)) self.usersmodel.set_value(self.users[user], 5, status) def set_user_flag(self, user, country): if user not in self.users: return if self.usersmodel.get_value(self.users[user], 8) == country: # Country didn't change, no need to update return self.usersmodel.set_value( self.users[user], 1, GObject.Value(GObject.TYPE_OBJECT, self.frame.get_flag_image(country))) self.usersmodel.set_value(self.users[user], 8, country) def update_visuals(self): for widget in list(self.__dict__.values()): update_widget_visuals(widget, update_text_tags=False) self.room_wall.update_visuals() def user_name_event(self, tag, widget, event, iterator, user): """ Mouse buttons: 1 = left button 2 = middle button 3 = right button """ if event.button.type == Gdk.EventType.BUTTON_PRESS and event.button.button in ( 1, 2, 3): # Chat, Userlists use the normal popup system self.populate_user_menu(user) self.popup_menu.popup(button=event.button.button) return True def create_tag(self, buffer, color, username=None): tag = buffer.create_tag() update_tag_visuals(tag, color) if username: tag.connect("event", self.user_name_event, username) return tag def create_tags(self): log_buffer = self.RoomLog.get_buffer() self.tag_log = self.create_tag(log_buffer, "chatremote") chat_buffer = self.ChatScroll.get_buffer() self.tag_remote = self.create_tag(chat_buffer, "chatremote") self.tag_local = self.create_tag(chat_buffer, "chatlocal") self.tag_me = self.create_tag(chat_buffer, "chatme") self.tag_hilite = self.create_tag(chat_buffer, "chathilite") self.tag_users = {} def update_tags(self): update_tag_visuals(self.tag_remote, "chatremote") update_tag_visuals(self.tag_local, "chatlocal") update_tag_visuals(self.tag_me, "chatme") update_tag_visuals(self.tag_hilite, "chathilite") update_tag_visuals(self.tag_log, "chatremote") for user in self.tag_users: self.get_user_tag(user) def on_leave(self, *args): if self.leaving: return self.leaving = True if self.room in config.sections["columns"]["chat_room"]: del config.sections["columns"]["chat_room"][self.room] if not self.meta: self.frame.np.queue.append(slskmessages.LeaveRoom(self.room)) else: if self.room == 'Public ': self.frame.np.queue.append(slskmessages.LeavePublicRoom()) self.chatrooms.leave_room(slskmessages.LeaveRoom( self.room)) # Faking protocol msg else: log.add_warning(_("Unknown meta chatroom closed")) self.frame.np.pluginhandler.leave_chatroom_notification(self.room) def save_columns(self): save_columns("chat_room", self.UserList.get_columns(), subpage=self.room) def conn_close(self): append_line(self.ChatScroll, _("--- disconnected ---"), self.tag_hilite) self.usersmodel.clear() self.UserList.set_sensitive(False) self.users.clear() self.count_users() if not self.AutoJoin.get_active( ) and self.room in config.sections["columns"]["chat_room"]: del config.sections["columns"]["chat_room"][self.room] for tag in self.tag_users.values(): update_tag_visuals(tag, "useroffline") self.tickers.set_ticker([]) def rejoined(self, users): # Update user list with an inexpensive sorting function self.usersmodel.set_default_sort_func(lambda *args: -1) self.usersmodel.set_sort_column_id(-1, Gtk.SortType.ASCENDING) for userdata in users: username = userdata.username if username in self.users: self.usersmodel.remove(self.users[username]) self.add_user_row(userdata) self.UserList.set_sensitive(True) # Reinitialize sorting after loop is complet self.usersmodel.set_sort_column_id(2, Gtk.SortType.ASCENDING) self.usersmodel.set_default_sort_func(lambda *args: -1) # Spit this line into chat log append_line(self.ChatScroll, _("--- reconnected ---"), self.tag_hilite) # Update user count self.count_users() # Build completion list self.set_completion_list(list(self.chatrooms.completion_list)) # Update all username tags in chat log for user in self.tag_users: self.get_user_tag(user) def on_autojoin(self, widget): autojoin = config.sections["server"]["autojoin"] if not widget.get_active(): if self.room in autojoin: autojoin.remove(self.room) else: if self.room not in autojoin: autojoin.append(self.room) config.write_configuration() def on_tooltip(self, widget, x, y, keyboard_mode, tooltip): status_tooltip = show_user_status_tooltip(widget, x, y, tooltip, 5) country_tooltip = show_country_tooltip(widget, x, y, tooltip, 8, strip_prefix="") if status_tooltip: return status_tooltip if country_tooltip: return country_tooltip def on_log_toggled(self, widget): if not widget.get_active(): if self.room in config.sections["logging"]["rooms"]: config.sections["logging"]["rooms"].remove(self.room) return if self.room not in config.sections["logging"]["rooms"]: config.sections["logging"]["rooms"].append(self.room) def on_room_log_clicked(self, widget, event): if triggers_context_menu(event): return self.on_popup_room_log_menu() return False def on_popup_room_log_menu(self, *args): self.roomlogpopmenu.popup() return True def on_activity_log_clicked(self, widget, event): if triggers_context_menu(event): return self.on_popup_activity_log_menu() return False def on_popup_activity_log_menu(self, *args): self.activitylogpopupmenu.popup() return True def on_copy_all_activity_log(self, *args): copy_all_text(self.RoomLog, self.frame.clipboard) def on_copy_all_room_log(self, *args): copy_all_text(self.ChatScroll, self.frame.clipboard) def on_copy_activity_log(self, *args): self.RoomLog.emit("copy-clipboard") def on_copy_room_log(self, *args): self.ChatScroll.emit("copy-clipboard") def on_view_room_log(self, *args): open_log(config.sections["logging"]["roomlogsdir"], self.room) def on_delete_room_log_response(self, dialog, response_id, data): dialog.destroy() if response_id == Gtk.ResponseType.OK: delete_log(config.sections["logging"]["roomlogsdir"], self.room) self.on_clear_messages() self.on_clear_activity_log() def on_delete_room_log(self, *args): option_dialog( parent=self.frame.MainWindow, title=_('Delete Logged Messages?'), message= _('Are you sure you wish to permanently delete all logged messages for this room?' ), callback=self.on_delete_room_log_response) def on_clear_messages(self, *args): self.ChatScroll.get_buffer().set_text("") def on_clear_activity_log(self, *args): self.RoomLog.get_buffer().set_text("") def set_completion_list(self, completion_list): # We want to include users for this room only if config.sections["words"]["roomusers"]: completion_list += self.users.keys() # No duplicates completion_list = list(set(completion_list)) completion_list.sort(key=lambda v: v.lower()) self.entry.set_completion_list(completion_list)
def __init__(self, userbrowses, user): self.userbrowses = userbrowses self.frame = userbrowses.frame # Build the window load_ui_elements( self, os.path.join(self.frame.gui_dir, "ui", "userbrowse.ui")) self.info_bar = InfoBar(self.InfoBar, Gtk.MessageType.INFO) self.key_controller_folder = connect_key_press_event( self.FolderTreeView, self.on_folder_key_press_event) self.key_controller_file = connect_key_press_event( self.FileTreeView, self.on_file_key_press_event) if Gtk.get_major_version() == 4: self.MainPaned.set_resize_start_child(True) else: self.MainPaned.child_set_property(self.FolderPane, "resize", True) self.user = user self.conn = None self.local_shares_type = None # selected_folder is the current selected folder self.selected_folder = None # queued_folder is a folder that should be opened once the share has loaded self.queued_folder = None self.search_list = [] self.query = None self.search_position = 0 self.selected_files = {} self.shares = [] # Iters for current DirStore self.directories = {} # Iters for current FileStore self.files = {} self.totalsize = 0 self.dir_store = Gtk.TreeStore(str, str) self.FolderTreeView.set_model(self.dir_store) self.dir_column_numbers = list(range(self.dir_store.get_n_columns())) cols = initialise_columns( None, self.FolderTreeView, ["folders", _("Folders"), -1, "text", None] # 0 ) cols["folders"].set_sort_column_id(0) self.user_popup = popup = PopupMenu(self.frame, None, self.on_tab_popup) popup.setup_user_menu(user, page="userbrowse") popup.setup(("", None), ("#" + _("_Save Shares List to Disk"), self.on_save), ("#" + _("Close All Tabs..."), self.on_close_all_tabs), ("#" + _("_Close Tab"), self.on_close)) self.popup_menu_downloads_folders = PopupMenu(self.frame) self.popup_menu_downloads_folders.setup( ("#" + _("_Download Folder"), self.on_download_directory), ("#" + _("Download Folder _To..."), self.on_download_directory_to), ("#" + _("Download _Recursive"), self.on_download_directory_recursive), ("#" + _("Download R_ecursive To..."), self.on_download_directory_recursive_to)) self.popup_menu_downloads_files = PopupMenu(self.frame) self.popup_menu_downloads_files.setup( ("#" + _("_Download File(s)"), self.on_download_files), ("#" + _("Download _To..."), self.on_download_files_to), ("", None), ("#" + _("_Download Folder"), self.on_download_directory), ("#" + _("Download Folder _To..."), self.on_download_directory_to), ("#" + _("Download _Recursive"), self.on_download_directory_recursive), ("#" + _("Download R_ecursive To..."), self.on_download_directory_recursive_to)) self.popup_menu_uploads_folders = PopupMenu(self.frame) self.popup_menu_uploads_folders.setup( ("#" + _("Upload Folder To..."), self.on_upload_directory_to), ("#" + _("Upload Folder Recursive To..."), self.on_upload_directory_recursive_to)) self.popup_menu_uploads_files = PopupMenu(self.frame) self.popup_menu_uploads_files.setup( ("#" + _("Upload Folder To..."), self.on_upload_directory_to), ("#" + _("Upload Folder Recursive To..."), self.on_upload_directory_recursive_to), ("#" + _("Up_load File(s)"), self.on_upload_files)) self.folder_popup_menu = PopupMenu(self.frame, self.FolderTreeView, self.on_folder_popup_menu) if user == config.sections["server"]["login"]: self.folder_popup_menu.setup( ("#" + _("_Download Folder"), self.on_download_directory), ("#" + _("Download Folder _To..."), self.on_download_directory_to), ("#" + _("Download _Recursive"), self.on_download_directory_recursive), ("#" + _("Download R_ecursive To..."), self.on_download_directory_recursive_to), ("", None), ("#" + _("Upload Folder To..."), self.on_upload_directory_to), ("#" + _("Upload Folder Recursive To..."), self.on_upload_directory_recursive_to), ("", None), ("#" + _("Open in File _Manager"), self.on_file_manager), ("", None), ("#" + _("Copy _Folder Path"), self.on_copy_folder_path), ("#" + _("Copy _URL"), self.on_copy_dir_url), ("", None), (">" + _("User"), self.user_popup)) else: self.folder_popup_menu.setup( ("#" + _("_Download Folder"), self.on_download_directory), ("#" + _("Download Folder _To..."), self.on_download_directory_to), ("#" + _("Download _Recursive"), self.on_download_directory_recursive), ("#" + _("Download R_ecursive To..."), self.on_download_directory_recursive_to), ("", None), ("#" + _("Copy _Folder Path"), self.on_copy_folder_path), ("#" + _("Copy _URL"), self.on_copy_dir_url), ("", None), (">" + _("User"), self.user_popup)) self.FolderTreeView.get_selection().connect("changed", self.on_select_dir) self.file_store = Gtk.ListStore( str, # (0) file name str, # (1) hsize str, # (2) hbitrate str, # (3) hlength GObject.TYPE_UINT64, # (4) size GObject.TYPE_UINT64, # (5) bitrate GObject.TYPE_UINT64 # (6) length ) self.FileTreeView.set_model(self.file_store) self.file_column_numbers = [ i for i in range(self.file_store.get_n_columns()) ] cols = initialise_columns( "user_browse", self.FileTreeView, ["filename", _("Filename"), 600, "text", None], ["size", _("Size"), 100, "number", None], ["bitrate", _("Bitrate"), 100, "number", None], ["length", _("Length"), 100, "number", None]) cols["filename"].set_sort_column_id(0) cols["size"].set_sort_column_id(4) cols["bitrate"].set_sort_column_id(5) cols["length"].set_sort_column_id(6) self.file_popup_menu = PopupMenu(self.frame, self.FileTreeView, self.on_file_popup_menu) if user == config.sections["server"]["login"]: self.file_popup_menu.setup( ("#" + "selected_files", None), ("", None), (">" + _("Download"), self.popup_menu_downloads_files), (">" + _("Upload"), self.popup_menu_uploads_files), ("", None), ("#" + _("Send to _Player"), self.on_play_files), ("#" + _("Open in File _Manager"), self.on_file_manager), ("#" + _("File _Properties"), self.on_file_properties), ("", None), ("#" + _("Copy _File Path"), self.on_copy_file_path), ("#" + _("Copy _URL"), self.on_copy_url), ("", None), (">" + _("User"), self.user_popup)) else: self.file_popup_menu.setup( ("#" + "selected_files", None), ("", None), (">" + _("Download"), self.popup_menu_downloads_files), ("", None), ("#" + _("File _Properties"), self.on_file_properties), ("", None), ("#" + _("Copy _File Path"), self.on_copy_file_path), ("#" + _("Copy _URL"), self.on_copy_url), ("", None), (">" + _("User"), self.user_popup)) self.update_visuals()
def __init__(self, searches, text, id, mode, remember, showtab): self.searches = searches self.frame = searches.frame # Build the window load_ui_elements(self, os.path.join(self.frame.gui_dir, "ui", "search.ui")) self.text = text self.searchterm_words_include = [ p for p in text.lower().split() if not p.startswith('-') ] self.searchterm_words_ignore = [ p[1:] for p in text.lower().split() if p.startswith('-') and len(p) > 1 ] self.id = id self.mode = mode self.remember = remember self.showtab = showtab self.usersiters = {} self.directoryiters = {} self.users = set() self.all_data = [] self.filters = None self.clearing_filters = False self.resultslimit = 2000 self.numvisibleresults = 0 self.active_filter_count = 0 self.operators = { '<': operator.lt, '<=': operator.le, '==': operator.eq, '!=': operator.ne, '>=': operator.ge, '>': operator.gt } if mode not in ("global", "wishlist"): self.RememberCheckButton.hide() self.RememberCheckButton.set_active(remember) """ Columns """ self.resultsmodel = Gtk.TreeStore( GObject.TYPE_UINT64, # (0) num str, # (1) user GObject.TYPE_OBJECT, # (2) flag str, # (3) immediatedl str, # (4) h_speed str, # (5) h_queue str, # (6) directory str, # (7) filename str, # (8) h_size str, # (9) h_bitrate str, # (10) h_length GObject.TYPE_UINT64, # (11) bitrate str, # (12) fullpath str, # (13) country GObject.TYPE_UINT64, # (14) size GObject.TYPE_UINT64, # (15) speed GObject.TYPE_UINT64, # (16) queue GObject.TYPE_UINT64, # (17) length str # (18) color ) self.column_numbers = list(range(self.resultsmodel.get_n_columns())) color_col = 18 self.cols = cols = initialise_columns( "file_search", self.ResultsList, ["id", _("ID"), 50, "text", color_col], ["user", _("User"), 200, "text", color_col], ["country", _("Country"), 25, "pixbuf", None], [ "immediate_download", _("Immediate Download"), 50, "center", color_col ], ["speed", _("Speed"), 90, "number", color_col], ["in_queue", _("In Queue"), 90, "center", color_col], ["folder", _("Folder"), 400, "text", color_col], ["filename", _("Filename"), 400, "text", color_col], ["size", _("Size"), 100, "number", color_col], ["bitrate", _("Bitrate"), 100, "number", color_col], ["length", _("Length"), 100, "number", color_col]) cols["id"].set_sort_column_id(0) cols["user"].set_sort_column_id(1) cols["country"].set_sort_column_id(13) cols["immediate_download"].set_sort_column_id(3) cols["speed"].set_sort_column_id(15) cols["in_queue"].set_sort_column_id(16) cols["folder"].set_sort_column_id(6) cols["filename"].set_sort_column_id(7) cols["size"].set_sort_column_id(14) cols["bitrate"].set_sort_column_id(11) cols["length"].set_sort_column_id(17) cols["country"].get_widget().hide() self.ResultsList.set_model(self.resultsmodel) self.update_visuals() """ Filters """ self.ShowFilters.set_active( config.sections["searches"]["filters_visible"]) self.populate_filters() """ Popup """ self.popup_menu_users = PopupMenu(self.frame) self.popup_menu = PopupMenu(self.frame) self.popup_menu.setup( ("#" + "selected_files", None), ("", None), ("#" + _("_Download File(s)"), self.on_download_files), ("#" + _("Download File(s) _To..."), self.on_download_files_to), ("#" + _("Download _Folder(s)"), self.on_download_folders), ("#" + _("Download F_older(s) To..."), self.on_download_folders_to), ("#" + _("_Browse Folder"), self.on_browse_folder), ("#" + _("File _Properties"), self.on_file_properties), ("", None), ("#" + _("Copy _File Path"), self.on_copy_file_path), ("#" + _("Copy _URL"), self.on_copy_url), ("#" + _("Copy Folder U_RL"), self.on_copy_dir_url), ("", None), (">" + _("User(s)"), self.popup_menu_users)) self.tab_menu = PopupMenu(self.frame) self.tab_menu.setup( ("#" + _("Copy Search Term"), self.on_copy_search_term), ("", None), ("#" + _("Clear All Results"), self.on_clear), ("#" + _("Close All Tabs"), self.on_close_all_tabs), ("#" + _("_Close Tab"), self.on_close)) """ Grouping """ self.ResultGrouping.set_active( config.sections["searches"]["group_searches"]) self.ExpandButton.set_active( config.sections["searches"]["expand_searches"])