Beispiel #1
0
    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()
Beispiel #2
0
    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()
Beispiel #3
0
    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)
Beispiel #4
0
    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)
Beispiel #5
0
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
Beispiel #6
0
    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()
Beispiel #7
0
    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
Beispiel #8
0
    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))
Beispiel #9
0
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
Beispiel #10
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.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)
Beispiel #12
0
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
Beispiel #13
0
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()
Beispiel #14
0
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()
Beispiel #15
0
    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)
Beispiel #17
0
    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()
Beispiel #18
0
    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()
Beispiel #19
0
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()
Beispiel #20
0
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()
Beispiel #21
0
    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()
Beispiel #22
0
    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()
Beispiel #24
0
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()
Beispiel #25
0
    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()
Beispiel #26
0
    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()
Beispiel #27
0
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"])
Beispiel #28
0
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)
Beispiel #29
0
    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()
Beispiel #30
0
    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"])