def test_gtp(self): w = Gtk.Window() l = Gtk.Label() self.failUnlessEqual(qltk.get_top_parent(w), w) self.failUnlessEqual(qltk.get_top_parent(l), None) w.destroy() l.destroy()
def test_gtp_packed(self): w = Gtk.Window() l = Gtk.Label() w.add(l) self.failUnlessEqual(qltk.get_top_parent(w), w) self.failUnlessEqual(qltk.get_top_parent(l), w) w.destroy() l.destroy()
def __drag_data_received(self, view, ctx, x, y, sel, tid, etime, library): # TreeModelSort doesn't support GtkTreeDragDestDrop. view.emit_stop_by_name('drag-data-received') model = view.get_model() if tid == DND_QL: filenames = qltk.selection_get_filenames(sel) songs = list(filter(None, [library.get(f) for f in filenames])) if not songs: Gtk.drag_finish(ctx, False, False, etime) return try: path, pos = view.get_dest_row_at_pos(x, y) except TypeError: playlist = FileBackedPlaylist.from_songs(PLAYLISTS, songs, library) GLib.idle_add(self._select_playlist, playlist) else: playlist = model[path][0] playlist.extend(songs) self.changed(playlist) Gtk.drag_finish(ctx, True, False, etime) # Cause a refresh to the dragged-to playlist if it is selected # so that the dragged (duplicate) track(s) appears if playlist is self.__get_name_of_current_selected_playlist(): model, plist_iter = self.__selected_playlists() songlist = qltk.get_top_parent(self).songlist self.activate(resort=not songlist.is_sorted()) else: if tid == DND_URI_LIST: uri = sel.get_uris()[0] name = os.path.basename(uri) elif tid == DND_MOZ_URL: data = sel.get_data() uri, name = data.decode('utf16', 'replace').split('\n') else: Gtk.drag_finish(ctx, False, False, etime) return name = _name_for(name or os.path.basename(uri)) try: sock = urlopen(uri) if uri.lower().endswith('.pls'): playlist = parse_pls(sock, name, library=library) elif (uri.lower().endswith('.m3u') or uri.lower().endswith('.m3u8')): playlist = parse_m3u(sock, name, library=library) else: raise IOError library.add(playlist.songs) self.changed(playlist) Gtk.drag_finish(ctx, True, False, etime) except IOError: Gtk.drag_finish(ctx, False, False, etime) qltk.ErrorMessage( qltk.get_top_parent(self), _("Unable to import playlist"), _("Quod Libet can only import playlists in the M3U/M3U8 " "and PLS formats.")).run()
def __init__(self, browser): if self.is_not_unique(): return super(Preferences, self).__init__() self.set_border_width(12) self.set_title(_("Playlist Browser Preferences")) self.set_default_size(420, 240) self.set_transient_for(qltk.get_top_parent(browser)) box = Gtk.VBox(spacing=6) edit_frame = self.edit_display_pane(browser, _("Playlist display")) box.pack_start(edit_frame, False, True, 12) main_box = Gtk.VBox(spacing=12) close = Button(_("_Close"), Icons.WINDOW_CLOSE) close.connect('clicked', lambda *x: self.destroy()) b = Gtk.HButtonBox() b.set_layout(Gtk.ButtonBoxStyle.END) b.pack_start(close, True, True, 0) main_box.pack_start(box, True, True, 0) self.use_header_bar() if not self.has_close_button(): main_box.pack_start(b, False, True, 0) self.add(main_box) close.grab_focus() self.show_all()
def __init__(self, title, fileobj, parent): super(BigCenteredImage, self).__init__(type=Gtk.WindowType.POPUP) self.set_type_hint(Gdk.WindowTypeHint.TOOLTIP) assert parent parent = qltk.get_top_parent(parent) self.set_transient_for(parent) self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) #If image fails to set, abort construction. if not self.set_image(fileobj, parent): self.destroy() return event_box = Gtk.EventBox() event_box.add(self.__image) frame = Gtk.Frame() frame.set_shadow_type(Gtk.ShadowType.OUT) frame.add(event_box) self.add(frame) event_box.connect('button-press-event', self.__destroy) event_box.connect('key-press-event', self.__destroy) self.get_child().show_all()
def __clicked(self, button): if self.__window.get_property('visible'): return if self._disable_slider: return if self.__grabbed: self.__popup_hide() window = self.__window frame = window.get_child() frame.show_all() window.set_transient_for(get_top_parent(self)) # this type hint tells the wayland backend to create a popup window.set_type_hint(Gdk.WindowTypeHint.DROPDOWN_MENU) position_window_beside_widget(window, self) self.__grabbed = window_grab_and_map( window, Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.BUTTON_MOTION_MASK | Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.SCROLL_MASK)
def pos_func(menu, data, widget=widget): screen = widget.get_screen() ref = get_top_parent(widget) menu.set_screen(screen) x, y = widget.translate_coordinates(ref, 0, 0) dx, dy = ref.get_window().get_origin()[1:] wa = widget.get_allocation() # fit menu to screen, aligned per text direction screen_width = screen.get_width() screen_height = screen.get_height() menu.realize() ma = menu.get_allocation() menu_y_under = y + dy + wa.height menu_y_above = y + dy - ma.height if under: menu_y = menu_y_under if menu_y + ma.height > screen_height and menu_y_above > 0: menu_y = menu_y_above else: menu_y = menu_y_above if menu_y < 0 and menu_y_under + ma.height < screen_height: menu_y = menu_y_under if Gtk.Widget.get_default_direction() == Gtk.TextDirection.LTR: menu_x = min(x + dx, screen_width - ma.width) else: menu_x = max(0, x + dx - ma.width + wa.width) return (menu_x, menu_y, True) # x, y, move_within_screen
def __show_cover(self, box, event): """Show the cover as a detached BigCenteredImage. If one is already showing, destroy it instead If there is no image, run the AlbumArt plugin """ song = self.__song if not song: return if event.button != 1 or event.type != gtk.gdk.BUTTON_PRESS: return if not self.__file: from quodlibet.qltk.songsmenu import SongsMenu from quodlibet import app SongsMenu.plugins.handle("Download Album art", app.library, qltk.get_top_parent(self), [song]) return if self.__current_bci is not None: # We're displaying it; destroy it. self.__current_bci.destroy() return try: self.__current_bci = BigCenteredImage( song.comma("album"), self.__file.name, parent=self) except gobject.GError: # reload in case the image file is gone self.refresh() else: self.__current_bci.connect('destroy', self.__reset_bci)
def __init__(self, cbes, title, validator=None): # Do this before calling parent constructor self.cbes = cbes super(CBESEditor, self).__init__(title, validator) self.set_transient_for(qltk.get_top_parent(cbes)) connect_obj(self, 'destroy', self.__finish, cbes) self.value.set_text(cbes.get_child().get_text())
def __init__(self, parent): if self.is_not_unique(): return super(PreferencesWindow, self).__init__() self.set_title(_("Preferences") + " - Quod Libet") self.set_border_width(12) self.set_resizable(False) self.set_transient_for(qltk.get_top_parent(parent)) self.__notebook = notebook = qltk.Notebook() for Page in [self.Connection, self.Player, self.SongList, self.Browsers, self.Library, self.Tagging]: notebook.append_page(Page()) close = gtk.Button(stock=gtk.STOCK_CLOSE) close.connect_object('clicked', lambda x: x.destroy(), self) button_box = gtk.HButtonBox() button_box.set_layout(gtk.BUTTONBOX_END) button_box.pack_start(close) vbox = gtk.VBox(spacing=12) vbox.pack_start(notebook) vbox.pack_start(button_box, expand=False) self.add(vbox) self.connect_object('destroy', PreferencesWindow.__destroy, self) self.show_all()
def __init__(self, parent): super(AddFeedDialog, self).__init__( qltk.get_top_parent(parent), _("New Feed"), _("Enter the location of an audio feed:"), okbutton=Gtk.STOCK_ADD, )
def __init__(self, parent, default="", **kwargs): if self.is_not_unique(): return super(TextEdit, self).__init__() self.set_title(_("Edit Display")) self.set_transient_for(qltk.get_top_parent(parent)) self.set_border_width(12) self.set_default_size(420, 190) vbox = Gtk.VBox(spacing=12) close = Button(_("_Close"), Icons.WINDOW_CLOSE) close.connect('clicked', lambda *x: self.destroy()) b = Gtk.HButtonBox() b.set_layout(Gtk.ButtonBoxStyle.END) b.pack_start(close, True, True, 0) self.box = box = self.Box(default, **kwargs) vbox.pack_start(box, True, True, 0) self.use_header_bar() if not self.has_close_button(): vbox.pack_start(b, False, True, 0) self.add(vbox) self.apply = box.apply self.revert = box.revert close.grab_focus() self.get_child().show_all()
def __show_cover(self, song): """Show the cover as a detached BigCenteredImage. If one is already showing, destroy it instead If there is no image, run the AlbumArt plugin """ if not self.__file and song.is_file: from quodlibet.qltk.songsmenu import SongsMenu from quodlibet import app SongsMenu.plugins.handle(ALBUM_ART_PLUGIN_ID, app.library, qltk.get_top_parent(self), [song]) return True if self.__current_bci is not None: # We're displaying it; destroy it. self.__current_bci.destroy() return True if not self.__file: return False try: self.__current_bci = BigCenteredImage( song.comma("album"), self.__file, parent=self) except GLib.GError: # reload in case the image file is gone self.refresh() else: self.__current_bci.show() self.__current_bci.connect('destroy', self.__reset_bci) return True
def _run_chooser(parent, chooser): """Run the chooser ("blocking") and return a list of paths. Args: parent (Gtk.Widget) chooser (Gtk.FileChooser) Returns: List[fsnative] """ chooser.set_current_folder(fsn2glib(get_current_dir())) chooser.set_transient_for(get_top_parent(parent)) if _response is not None: response = _response while Gtk.events_pending(): Gtk.main_iteration() else: response = chooser.run() if response == Gtk.ResponseType.ACCEPT: result = [glib2fsn(fn) for fn in chooser.get_filenames()] current_dir = chooser.get_current_folder() if current_dir: set_current_dir(glib2fsn(current_dir)) else: result = [] chooser.destroy() return result
def __drag_data_get(self, view, ctx, sel, tid, etime): model, paths = self.get_selection().get_selected_rows() if tid == DND_QL: songs = [model[path][0] for path in paths if model[path][0].can_add] if len(songs) != len(paths): qltk.ErrorMessage( qltk.get_top_parent(self), _("Unable to copy songs"), _("The files selected cannot be copied to other " "song lists or the queue.")).run() Gdk.drag_abort(ctx, etime) return qltk.selection_set_songs(sel, songs) # DEM 2018/05/25: The below check is a deliberate repitition of # code in the drag-motion signal handler. In MacOS/Quartz, the # context action is not propogated between event handlers for # drag-motion and drag-data-get using "ctx.get_actions()". It is # unclear if this is a bug or expected behavior. Regardless, the # context widget information is the same so identical behavior can # be achieved by simply using the same widget check as in the move # action. if Gtk.drag_get_source_widget(ctx) == self and \ not self.__force_copy: self.__drag_iters = list(map(model.get_iter, paths)) else: self.__drag_iters = [] else: uris = [model[path][0]("~uri") for path in paths] sel.set_uris(uris) self.__drag_iters = []
def __clicked(self, button): if self.__window.get_property('visible'): return self.__window.child.show_all() self.__window.size_request() x, y = self.child.window.get_origin() w, h = self.child.window.get_size() ww, wh = self.__window.child.parent.get_size() sx, sy = self._move_to(x, y, w, h, ww, wh, pad=3) self.__window.set_transient_for(get_top_parent(self)) self.__window.move(sx, sy) self.__window.show() self.__window.grab_focus() self.__window.grab_add() pointer = gtk.gdk.pointer_grab( self.__window.window, True, gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.BUTTON_MOTION_MASK | gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.SCROLL_MASK, None, None, gtk.get_current_event_time()) keyboard = gtk.gdk.keyboard_grab( self.__window.window, True, gtk.get_current_event_time()) if pointer != gtk.gdk.GRAB_SUCCESS or keyboard != gtk.gdk.GRAB_SUCCESS: self.__window.grab_remove() self.__window.hide() if pointer == gtk.gdk.GRAB_SUCCESS: gtk.gdk.pointer_ungrab(gtk.get_current_event_time()) if keyboard == gtk.gdk.GRAB_SUCCESS: gtk.gdk.keyboard_ungrab(gtk.get_current_event_time())
def __init__(self, parent, default=""): if self.is_not_unique(): return super(TextEdit, self).__init__() self.set_title(_("Edit Display")) self.set_transient_for(qltk.get_top_parent(parent)) self.set_border_width(12) self.set_default_size(420, 190) vbox = gtk.VBox(spacing=12) close = gtk.Button(stock=gtk.STOCK_CLOSE) close.connect('clicked', lambda *x: self.destroy()) b = gtk.HButtonBox() b.set_layout(gtk.BUTTONBOX_END) b.pack_start(close) self.box = box = self.Box(default) vbox.pack_start(box) vbox.pack_start(b, expand=False) self.add(vbox) self.apply = box.apply self.revert = box.revert close.grab_focus() self.show_all()
def __init__(self, parent=None): super(LoggingWindow, self).__init__() self.set_default_size(400, 400) self.set_title(_("Output Log")) self.set_border_width(12) self.set_transient_for(qltk.get_top_parent(parent)) notebook = qltk.Notebook() for logname in quodlibet.util.logging.names(): text = "\n".join(quodlibet.util.logging.contents(logname)) view = gtk.TextView() sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.set_shadow_type(gtk.SHADOW_IN) sw.add(view) buffer = view.get_buffer() buffer.set_text(text) notebook.append_page(sw, logname) close = gtk.Button(stock=gtk.STOCK_CLOSE) close.connect_object('clicked', lambda x: x.destroy(), self) button_box = gtk.HButtonBox() button_box.set_layout(gtk.BUTTONBOX_END) button_box.pack_start(close) vbox = gtk.VBox(spacing=12) vbox.pack_start(notebook) vbox.pack_start(button_box, expand=False) self.add(vbox) self.show_all()
def show_uri(label, uri): """Shows a uri. The uri can be anything handled by GIO or a quodlibet specific one. Currently handled quodlibet uris: - quodlibet:///prefs/plugins/<plugin id> Args: label (str) uri (str) the uri to show Returns: True on success, False on error """ parsed = urlparse(uri) if parsed.scheme == "quodlibet": if parsed.netloc != "": print_w("Unknown QuodLibet URL format (%s)" % uri) return False else: return __show_quodlibet_uri(parsed) else: # Gtk.show_uri_on_window exists since 3.22 if hasattr(Gtk, "show_uri_on_window"): from quodlibet.qltk import get_top_parent return Gtk.show_uri_on_window(get_top_parent(label), uri, 0) else: return Gtk.show_uri(None, uri, 0)
def __init__(self, parent, title, description, traceback): super(MinExceptionDialog, self).__init__( get_top_parent(parent), title, description) assert isinstance(title, unicode) assert isinstance(description, unicode) assert isinstance(traceback, unicode) exp = Gtk.Expander(label=_("Error Details")) lab = Gtk.Label(label=traceback) lab.set_alignment(0.0, 0.0) lab.set_selectable(True) win = Gtk.ScrolledWindow() win.add_with_viewport(Align(lab, border=6)) win.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) win.set_shadow_type(Gtk.ShadowType.ETCHED_OUT) win.set_size_request(500, 150) win.show_all() exp.add(win) exp.set_resize_toplevel(True) area = self.get_message_area() exp.show() area.pack_start(exp, False, True, 0)
def __init__(self, parent, device): super(DeviceProperties, self).__init__( title=_("Device Properties"), transient_for=qltk.get_top_parent(parent)) self.add_icon_button(_("_Close"), Icons.WINDOW_CLOSE, Gtk.ResponseType.CLOSE) self.set_default_size(400, -1) self.connect('response', self.__close) table = Gtk.Table() table.set_border_width(8) table.set_row_spacings(8) table.set_col_spacings(8) self.vbox.pack_start(table, False, True, 0) props = [] props.append((_("Device:"), device.block_device, None)) mountpoint = util.escape( device.mountpoint or ("<i>%s</i>" % _("Not mounted"))) props.append((_("Mount point:"), mountpoint, None)) props.append((None, None, None)) entry = Gtk.Entry() entry.set_text(device['name']) props.append((_("_Name:"), entry, 'name')) y = 0 for title, value, key in props + device.Properties(): if title is None: table.attach(Gtk.HSeparator(), 0, 2, y, y + 1) else: if key and isinstance(value, Gtk.CheckButton): value.set_label(title) value.set_use_underline(True) value.connect('toggled', self.__changed, key, device) table.attach(value, 0, 2, y, y + 1, xoptions=Gtk.AttachOptions.FILL) else: label = Gtk.Label() label.set_markup("<b>%s</b>" % util.escape(title)) label.set_alignment(0.0, 0.5) table.attach(label, 0, 1, y, y + 1, xoptions=Gtk.AttachOptions.FILL) if key and isinstance(value, Gtk.Widget): widget = value label.set_mnemonic_widget(widget) label.set_use_underline(True) widget.connect('changed', self.__changed, key, device) else: widget = Gtk.Label(label=value) widget.set_use_markup(True) widget.set_selectable(True) widget.set_alignment(0.0, 0.5) table.attach(widget, 1, 2, y, y + 1) y += 1 self.get_child().show_all()
def __event(self, event): if not self.__view: return True # hack: present the main window on key press if event.type == Gdk.EventType.BUTTON_PRESS: # hack: present is overridden to present all windows. # bypass to only select one if not is_wayland(): # present duplicates windows in weston Gtk.Window.present(get_top_parent(self.__view)) def translate_enter_leave_event(event): # enter/leave events have different x/y values as motion events # so it makes sense to push them to the underlying view as # additional motion events. # Warning: this may result in motion events outside of the # view window.. ? new_event = Gdk.Event.new(Gdk.EventType.MOTION_NOTIFY) struct = new_event.motion for attr in ["x", "y", "x_root", "y_root", "time", "window", "state", "send_event"]: setattr(struct, attr, getattr(event.crossing, attr)) device = Gtk.get_current_event_device() if device is not None: struct.set_device(device) return new_event # FIXME: We should translate motion events on the tooltip # to crossing events for the underlying view. # (I think, no tested) Currently the hover scrollbar stays visible # if the mouse leaves the view through the tooltip without the # knowledge of the view. type_ = event.type real_event = None if type_ == Gdk.EventType.BUTTON_PRESS: real_event = event.button elif type_ == Gdk.EventType.BUTTON_RELEASE: real_event = event.button elif type_ == Gdk.EventType.MOTION_NOTIFY: real_event = event.motion elif type_ == Gdk.EventType.ENTER_NOTIFY: event = translate_enter_leave_event(event) real_event = event.motion elif type_ == Gdk.EventType.LEAVE_NOTIFY: event = translate_enter_leave_event(event) real_event = event.motion if real_event: real_event.x += self.__dx real_event.y += self.__dy # modifying event.window is a necessary evil, made okay because # nobody else should tie to any TreeViewHints events ever. event.any.window = self.__view.get_bin_window() Gtk.main_do_event(event) return True
def __init__(self, parent): super(AddFeedDialog, self).__init__( qltk.get_top_parent(parent), _("New Feed"), _("Enter the location of an audio feed:"), button_label=_("_Add"), button_icon=Icons.LIST_ADD, )
def __init__(self, parent, can_change, library): super(AddTagDialog, self).__init__( title=_("Add a Tag"), transient_for=qltk.get_top_parent(parent), use_header_bar=True) self.set_border_width(6) self.set_resizable(False) self.add_button(_("_Cancel"), Gtk.ResponseType.CANCEL) add = self.add_icon_button(_("_Add"), Icons.LIST_ADD, Gtk.ResponseType.OK) self.vbox.set_spacing(6) self.set_default_response(Gtk.ResponseType.OK) table = Gtk.Table(n_rows=2, n_columns=2) table.set_row_spacings(12) table.set_col_spacings(6) table.set_border_width(6) self.__tag = (TagsComboBoxEntry() if can_change is True else TagsComboBox(can_change)) label = Gtk.Label() label.set_alignment(0.0, 0.5) label.set_text(_("_Tag:")) label.set_use_underline(True) label.set_mnemonic_widget(self.__tag) table.attach(label, 0, 1, 0, 1) table.attach(self.__tag, 1, 2, 0, 1) self.__val = Gtk.Entry() self.__val.set_completion(LibraryValueCompletion("", library)) label = Gtk.Label() label.set_text(_("_Value:")) label.set_alignment(0.0, 0.5) label.set_use_underline(True) label.set_mnemonic_widget(self.__val) valuebox = Gtk.EventBox() table.attach(label, 0, 1, 1, 2) table.attach(valuebox, 1, 2, 1, 2) hbox = Gtk.HBox() valuebox.add(hbox) hbox.pack_start(self.__val, True, True, 0) hbox.set_spacing(6) invalid = Gtk.Image.new_from_icon_name( Icons.DIALOG_WARNING, Gtk.IconSize.SMALL_TOOLBAR) hbox.pack_start(invalid, True, True, 0) self.vbox.pack_start(table, True, True, 0) self.get_child().show_all() invalid.hide() for entry in [self.__tag, self.__val]: entry.connect( 'changed', self.__validate, add, invalid, valuebox) self.__tag.connect('changed', self.__set_value_completion, library) self.__set_value_completion(self.__tag, library) if can_change is True: connect_obj(self.__tag.get_child(), 'activate', Gtk.Entry.grab_focus, self.__val)
def __init__(self, parent, title, initial_dir=None, action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER): super(FolderChooser, self).__init__( title=title, parent=get_top_parent(parent), action=action, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) if initial_dir: self.set_current_folder(initial_dir) self.set_local_only(True) self.set_select_multiple(True)
def __init__( self, kind, parent, title, description, buttons=gtk.BUTTONS_OK): parent = get_top_parent(parent) text = ("<span weight='bold' size='larger'>%s</span>\n\n%s" % (title, description)) super(Message, self).__init__( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, kind, buttons) self.set_markup(text)
def _on_new_playlist_activate(self, item, songs): parent = get_menu_item_top_parent(item) title = Playlist.suggested_name_for(songs) title = GetPlaylistName(qltk.get_top_parent(parent)).run(title) if title is None: return playlist = FileBackedPlaylist.new(PLAYLISTS, title) playlist.extend(songs) PlaylistsBrowser.changed(playlist)
def __init__( self, kind, parent, title, description, buttons=Gtk.ButtonsType.OK): parent = get_top_parent(parent) text = ("<span weight='bold' size='larger'>%s</span>\n\n%s" % (title, description)) super(Message, self).__init__( transient_for=parent, modal=True, destroy_with_parent=True, message_type=kind, buttons=buttons) self.set_markup(text)
def __drag_data_received(self, view, ctx, x, y, sel, tid, etime, library): # TreeModelSort doesn't support GtkTreeDragDestDrop. view.emit_stop_by_name('drag-data-received') model = view.get_model() if tid == DND_QL: filenames = qltk.selection_get_filenames(sel) songs = filter(None, map(library.get, filenames)) if not songs: Gtk.drag_finish(ctx, False, False, etime) return try: path, pos = view.get_dest_row_at_pos(x, y) except TypeError: playlist = FileBackedPlaylist.from_songs(PLAYLISTS, songs, library) GLib.idle_add(self._select_playlist, playlist) else: playlist = model[path][0] playlist.extend(songs) self.changed(playlist) Gtk.drag_finish(ctx, True, False, etime) else: if tid == DND_URI_LIST: uri = sel.get_uris()[0] name = os.path.basename(uri) elif tid == DND_MOZ_URL: data = sel.get_data() uri, name = data.decode('utf16', 'replace').split('\n') else: Gtk.drag_finish(ctx, False, False, etime) return name = name or os.path.basename(uri) or _("New Playlist") uri = uri.encode('utf-8') try: sock = urlopen(uri) f = NamedTemporaryFile() f.write(sock.read()) f.flush() if uri.lower().endswith('.pls'): playlist = parse_pls(f.name, library=library) elif uri.lower().endswith('.m3u'): playlist = parse_m3u(f.name, library=library) else: raise IOError library.add_filename(playlist) if name: playlist.rename(name) self.changed(playlist) Gtk.drag_finish(ctx, True, False, etime) except IOError: Gtk.drag_finish(ctx, False, False, etime) qltk.ErrorMessage( qltk.get_top_parent(self), _("Unable to import playlist"), _("Quod Libet can only import playlists in the M3U " "and PLS formats.")).run()
def __config_cols(self, button, buttons): def __closed(widget): cols = widget.get_strings() self.__update(buttons, self._toggle_data, cols) columns = self.__get_current_columns(buttons) m = TagListEditor(_("Edit Columns"), columns) m.set_transient_for(qltk.get_top_parent(self)) m.connect('destroy', __closed) m.show()
def __init__(self, library, songs, parent=None): super(SongProperties, self).__init__(dialog=False) self.set_transient_for(qltk.get_top_parent(parent)) default_width = 600 config_suffix = "" if len(songs) <= 1: default_width -= 200 config_suffix += "single" self.set_default_size(default_width, 400) self.enable_window_tracking("quodlibet_properties", size_suffix=config_suffix) self.auto_save_on_change = config.getboolean( 'editing', 'auto_save_changes', False) paned = Gtk.HPaned() notebook = qltk.Notebook() notebook.props.scrollable = True pages = [] pages.extend([Ctr(self, library) for Ctr in [EditTags, TagsFromPath, RenameFiles]]) if len(songs) > 1: pages.append(TrackNumbers(self, library)) for page in pages: page.show() notebook.append_page(page) fbasemodel = Gtk.ListStore(object, str) fmodel = Gtk.TreeModelSort(model=fbasemodel) fview = HintedTreeView(model=fmodel) fview.connect('button-press-event', self.__pre_selection_changed) fview.set_rules_hint(True) selection = fview.get_selection() selection.set_mode(Gtk.SelectionMode.MULTIPLE) self.__save = None if len(songs) > 1: render = Gtk.CellRendererText() c1 = Gtk.TreeViewColumn(_('File'), render, text=1) render.set_property('ellipsize', Pango.EllipsizeMode.END) render.set_property('xpad', 3) c1.set_sort_column_id(1) fview.append_column(c1) sw = ScrolledWindow() sw.add(fview) sw.set_shadow_type(Gtk.ShadowType.IN) sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw.show_all() paned.pack1(sw, shrink=False, resize=True) # Invisible selections behave a little strangely. So, when # handling this selection, there's a lot of if len(model) == 1 # checks that "hardcode" the first row being selected. for song in songs: fbasemodel.append(row=[song, fsdecode(song("~basename"))]) self.connect_object('changed', SongProperties.__set_title, self) selection.select_all() paned.pack2(notebook, shrink=False, resize=True) csig = selection.connect('changed', self.__selection_changed) s1 = library.connect( 'changed', self.__refresh, fbasemodel, fview) s2 = library.connect( 'removed', self.__remove, fbasemodel, selection, csig) self.connect_object('destroy', library.disconnect, s1) self.connect_object('destroy', library.disconnect, s2) self.connect_object('changed', self.set_pending, None) self.emit('changed', songs) self.add(paned) paned.set_position(175) notebook.show() paned.show()
def __drag_data_received(self, view, ctx, x, y, sel, tid, etime): # TreeModelSort doesn't support GtkTreeDragDestDrop. view.emit_stop_by_name('drag-data-received') model = view.get_model() if tid == DND_QL: filenames = qltk.selection_get_filenames(sel) songs = list( filter(None, [self.songs_lib.get(f) for f in filenames])) if not songs: Gtk.drag_finish(ctx, False, False, etime) return try: path, pos = view.get_dest_row_at_pos(x, y) except TypeError: playlist = self.pl_lib.create_from_songs(songs) GLib.idle_add(self._select_playlist, playlist) else: playlist = model[path][0] playlist.extend(songs) # self.changed(playlist) Gtk.drag_finish(ctx, True, False, etime) # Cause a refresh to the dragged-to playlist if it is selected # so that the dragged (duplicate) track(s) appears if playlist is self._selected_playlist(): model, plist_iter = self.__selected_playlists() songlist = qltk.get_top_parent(self).songlist self.activate(resort=not songlist.is_sorted()) else: if tid == DND_URI_LIST: uri = sel.get_uris()[0] name = os.path.basename(uri) elif tid == DND_MOZ_URL: data = sel.get_data() uri, name = data.decode('utf16', 'replace').split('\n') else: Gtk.drag_finish(ctx, False, False, etime) return name = _name_for(name or os.path.basename(uri)) try: sock = urlopen(uri) uri = uri.lower() if uri.endswith('.pls'): playlist = parse_pls(sock, name, songs_lib=self.songs_lib, pl_lib=self.pl_lib) elif uri.endswith('.m3u') or uri.endswith('.m3u8'): playlist = parse_m3u(sock, name, songs_lib=self.songs_lib, pl_lib=self.pl_lib) else: raise IOError self.songs_lib.add(playlist.songs) # TODO: change to use playlist library too? # self.changed(playlist) Gtk.drag_finish(ctx, True, False, etime) except IOError: Gtk.drag_finish(ctx, False, False, etime) qltk.ErrorMessage( qltk.get_top_parent(self), _("Unable to import playlist"), _("Quod Libet can only import playlists in the M3U/M3U8 " "and PLS formats.")).run()
def _get_new_name(self, parent, title): """Ask the user for a name for the new playlist""" return GetPlaylistName(qltk.get_top_parent(parent)).run(title)
def __init__(self, parent): super(AddFeedDialog, self).__init__(qltk.get_top_parent(parent), _("New Feed"), _("Enter the location of an audio feed:"), okbutton=Gtk.STOCK_ADD)
def __drag_data_browser_dropped(self, songs): window = qltk.get_top_parent(self) return window.browser.dropped(songs)
def test_none(self): self.failUnless(qltk.get_top_parent(None) is None)
def __init__(self, library, songs, parent=None): super().__init__(dialog=False) self.set_transient_for(qltk.get_top_parent(parent)) default_width = 600 config_suffix = "" if len(songs) <= 1: default_width -= 200 config_suffix += "single" self.set_default_size(default_width, 400) self.enable_window_tracking("quodlibet_properties", size_suffix=config_suffix) self.auto_save_on_change = config.getboolean( 'editing', 'auto_save_changes', False) paned = ConfigRPaned("memory", "quodlibet_properties_pos", 0.4) notebook = qltk.Notebook() notebook.props.scrollable = True pages = [] pages.extend([Ctr(self, library) for Ctr in [EditTags, TagsFromPath, RenameFiles]]) if len(songs) > 1: pages.append(TrackNumbers(self, library)) for page in pages: page.show() notebook.append_page(page) fbasemodel = ObjectStore() fmodel = ObjectModelSort(model=fbasemodel) fview = HintedTreeView(model=fmodel) fview.connect('button-press-event', self.__pre_selection_changed) fview.set_rules_hint(True) selection = fview.get_selection() selection.set_mode(Gtk.SelectionMode.MULTIPLE) self.__save = None render = Gtk.CellRendererText() c1 = Gtk.TreeViewColumn(_('File'), render) if fview.supports_hints(): render.set_property('ellipsize', Pango.EllipsizeMode.END) render.set_property('xpad', 3) def cell_data(column, cell, model, iter_, data): entry = model.get_value(iter_) cell.set_property('text', entry.name) c1.set_cell_data_func(render, cell_data) def sort_func(model, a, b, data): a = model.get_value(a) b = model.get_value(b) return cmp(a.name, b.name) fmodel.set_sort_func(100, sort_func) c1.set_sort_column_id(100) fview.append_column(c1) sw = ScrolledWindow() sw.add(fview) sw.set_shadow_type(Gtk.ShadowType.IN) sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) # only show the list if there are is more than one song if len(songs) > 1: sw.show_all() paned.pack1(sw, shrink=False, resize=True) for song in songs: fbasemodel.append(row=[_ListEntry(song)]) self.connect("changed", self.__on_changed) selection.select_all() paned.pack2(notebook, shrink=False, resize=True) csig = selection.connect('changed', self.__selection_changed) connect_destroy(library, 'changed', self.__on_library_changed, fbasemodel, fview) connect_destroy(library, 'removed', self.__on_library_removed, fbasemodel, selection, csig) self.emit('changed', songs) self.add(paned) paned.set_position(175) notebook.show() paned.show()
def __event(self, event): if not self.__view: return True # hack: present the main window on key press if event.type == Gdk.EventType.BUTTON_PRESS: # hack: present is overridden to present all windows. # bypass to only select one if not is_wayland(): # present duplicates windows in weston Gtk.Window.present(get_top_parent(self.__view)) def translate_enter_leave_event(event): # enter/leave events have different x/y values as motion events # so it makes sense to push them to the underlying view as # additional motion events. # Warning: this may result in motion events outside of the # view window.. ? new_event = Gdk.Event.new(Gdk.EventType.MOTION_NOTIFY) struct = new_event.motion for attr in [ "x", "y", "x_root", "y_root", "time", "window", "state", "send_event" ]: setattr(struct, attr, getattr(event.crossing, attr)) device = Gtk.get_current_event_device() if device is not None: struct.set_device(device) return new_event # FIXME: We should translate motion events on the tooltip # to crossing events for the underlying view. # (I think, no tested) Currently the hover scrollbar stays visible # if the mouse leaves the view through the tooltip without the # knowledge of the view. type_ = event.type real_event = None if type_ == Gdk.EventType.BUTTON_PRESS: real_event = event.button elif type_ == Gdk.EventType.BUTTON_RELEASE: real_event = event.button elif type_ == Gdk.EventType.MOTION_NOTIFY: real_event = event.motion elif type_ == Gdk.EventType.ENTER_NOTIFY: event = translate_enter_leave_event(event) real_event = event.motion elif type_ == Gdk.EventType.LEAVE_NOTIFY: event = translate_enter_leave_event(event) real_event = event.motion if real_event: real_event.x += self.__dx real_event.y += self.__dy # modifying event.window is a necessary evil, made okay because # nobody else should tie to any TreeViewHints events ever. event.any.window = self.__view.get_bin_window() Gtk.main_do_event(event) return True
def __handle_songlist_delete(self, *args): songlist = qltk.get_top_parent(self).songlist model, iters = self.__get_selected_songs(songlist) self.__remove(iters, model)
def __get_selected_songs(self): songlist = qltk.get_top_parent(self).songlist model, rows = songlist.get_selection().get_selected_rows() iters = map(model.get_iter, rows) return model, iters
def __check_current(self, model, path, iter): model, citer = self.__view.get_selection().get_selected() if citer and model.get_path(citer) == path: songlist = qltk.get_top_parent(self).songlist self.activate(resort=not songlist.is_sorted())
def focus(widget, *args): qltk.get_top_parent(widget).songlist.grab_focus()
def __motion(self, view, event): label = self.__label clabel = self.__clabel # trigger over row area, not column headers if event.window is not view.get_bin_window(): self.__undisplay() return False # hide if any modifier is active if event.get_state() & Gtk.accelerator_get_default_mod_mask(): self.__undisplay() return False # get the cell at the mouse position x, y = map(int, [event.x, event.y]) try: path, col, cellx, celly = view.get_path_at_pos(x, y) except TypeError: # no hints where no rows exist self.__undisplay() return False col_area = view.get_cell_area(path, col) # make sure we are on the same level if x < col_area.x: self.__undisplay() return False # hide for partial hidden rows at the bottom if y > view.get_visible_rect().height: self.__undisplay() return False # get the renderer at the mouse position and get the xpos/width renderers = col.get_cells() pos = sorted(zip(map(col.cell_get_position, renderers), renderers)) pos = filter(lambda ((x, w), r): x < cellx, pos) if not pos: self.__undisplay() return False (render_offset, render_width), renderer = pos[-1] if self.__current_renderer == renderer and self.__current_path == path: return False # only ellipsized text renderers if not isinstance(renderer, Gtk.CellRendererText): self.__undisplay() return False ellipsize = renderer.get_property('ellipsize') if ellipsize == Pango.EllipsizeMode.END: expand_left = False elif ellipsize == Pango.EllipsizeMode.MIDDLE: # depending on where the cursor is expand_left = x > col_area.x + render_offset + render_width / 2 elif ellipsize == Pango.EllipsizeMode.START: expand_left = True else: self.__undisplay() return False # don't display if the renderer is in editing mode if renderer.props.editing: self.__undisplay() return False # set the cell renderer attributes for the active cell model = view.get_model() col.cell_set_cell_data(model, model.get_iter(path), False, False) # the markup attribute is write only, so the markup text needs # to be saved on the python side, so we can copy it to the label markup = getattr(renderer, "markup", None) if markup is None: text = renderer.get_property('text') set_text = lambda l: l.set_text(text) else: # markup can also be column index if isinstance(markup, int): markup = model[path][markup] set_text = lambda l: l.set_markup(markup) # Use the renderer padding as label padding so the text offset matches render_xpad = renderer.get_property("xpad") label.set_padding(render_xpad, 0) set_text(clabel) clabel.set_padding(render_xpad, 0) label_width = clabel.get_layout().get_pixel_size()[0] label_width += clabel.get_layout_offsets()[0] or 0 # layout offset includes the left padding, so add one more label_width += render_xpad # CellRenderer width is too large if it's the last one in a column. # Use cell_area width as a maximum and limit render_width. max_width = col_area.width if render_width + render_offset > max_width: render_width = max_width - render_offset # don't display if it doesn't need expansion if label_width < render_width: self.__undisplay() return False dummy, ox, oy = view.get_window().get_origin() # save for adjusting passthrough events self.__dx, self.__dy = col_area.x + render_offset, col_area.y if expand_left: # shift to the left # FIXME: ellipsize start produces a space at the end depending # on the text. I don't know how to compute it.. self.__dx -= (label_width - render_width) # final window coordinates/size x = ox + self.__dx y = oy + self.__dy x, y = view.convert_bin_window_to_widget_coords(x, y) w = label_width h = col_area.height if not is_wayland(): # clip if it's bigger than the screen screen_border = 5 # leave some space if not expand_left: space_right = Gdk.Screen.width() - x - w - screen_border if space_right < 0: w += space_right label.set_ellipsize(Pango.EllipsizeMode.END) else: label.set_ellipsize(Pango.EllipsizeMode.NONE) else: space_left = x - screen_border if space_left < 0: x -= space_left self.__dx -= space_left w += space_left label.set_ellipsize(Pango.EllipsizeMode.START) else: label.set_ellipsize(Pango.EllipsizeMode.NONE) else: label.set_ellipsize(Pango.EllipsizeMode.NONE) # Don't show if the resulting tooltip would be smaller # than the visible area (if not all is on the display) if w < render_width: self.__undisplay() return False self.__view = view self.__current_renderer = renderer self.__edit_id = renderer.connect('editing-started', self.__undisplay) self.__current_path = path self.__current_col = col if self.__hide_id: GLib.source_remove(self.__hide_id) self.__hide_id = None self.set_transient_for(get_top_parent(view)) set_text(label) self.set_size_request(w, h) window = self.get_window() if self.get_visible() and window: window.move_resize(x, y, w, h) else: self.move(x, y) self.resize(w, h) self.show() return False
def __init__(self, browser): if self.is_not_unique(): return super().__init__() self.set_border_width(12) self.set_title(_("Cover Grid Preferences")) self.set_default_size(420, 380) self.set_transient_for(qltk.get_top_parent(browser)) # Do this config-driven setup at instance-time self._PREVIEW_ITEM["~rating"] = format_rating(0.75) self.mag_lock = False box = Gtk.VBox(spacing=6) vbox = Gtk.VBox(spacing=6) cb = ConfigCheckButton(_("Show album _text"), "browsers", "album_text") cb.set_active(config.getboolean("browsers", "album_text")) cb.connect('toggled', lambda s: browser.toggle_text()) vbox.pack_start(cb, False, True, 0) cb2 = ConfigCheckButton(_("Show \"All Albums\" Item"), "browsers", "covergrid_all") cb2.set_active(config.getboolean("browsers", "covergrid_all", True)) def refilter(s): mod = browser.view.get_model() if mod: mod.refilter() cb2.connect('toggled', refilter) vbox.pack_start(cb2, False, True, 0) cb3 = ConfigCheckButton(_("Wide Mode"), "browsers", "covergrid_wide") cb3.set_active(config.getboolean("browsers", "covergrid_wide", False)) cb3.connect('toggled', lambda s: browser.toggle_wide()) vbox.pack_start(cb3, False, True, 0) # Redraws the covers only when the user releases the slider def mag_button_press(*_): self.mag_lock = True def mag_button_release(mag, _): self.mag_lock = False mag_changed(mag) def mag_changed(mag): if self.mag_lock: return newmag = mag.get_value() oldmag = config.getfloat("browsers", "covergrid_magnification", 3.) if newmag == oldmag: print_d("Covergrid magnification haven't changed: {0}".format( newmag)) return print_d('Covergrid magnification update from {0} to {1}'.format( oldmag, newmag)) config.set("browsers", "covergrid_magnification", mag.get_value()) browser.update_mag() mag_scale = Gtk.HScale(adjustment=Gtk.Adjustment.new( config.getfloat("browsers", "covergrid_magnification", 3), 0., 10., .5, .5, 0)) mag_scale.set_tooltip_text(_("Cover Magnification")) l = Gtk.Label(label=_("Cover Magnification")) mag_scale.set_value_pos(Gtk.PositionType.RIGHT) mag_scale.connect('button-press-event', mag_button_press) mag_scale.connect('button-release-event', mag_button_release) mag_scale.connect('value-changed', mag_changed) vbox.pack_start(l, False, True, 0) vbox.pack_start(mag_scale, False, True, 0) f = qltk.Frame(_("Options"), child=vbox) box.pack_start(f, False, True, 12) display_frame = self.edit_display_pane(browser, _("Album Display")) box.pack_start(display_frame, True, True, 0) main_box = Gtk.VBox(spacing=12) close = Button(_("_Close"), Icons.WINDOW_CLOSE) close.connect('clicked', lambda *x: self.destroy()) b = Gtk.HButtonBox() b.set_layout(Gtk.ButtonBoxStyle.END) b.pack_start(close, True, True, 0) main_box.pack_start(box, True, True, 0) self.use_header_bar() if not self.has_close_button(): main_box.pack_start(b, False, True, 0) self.add(main_box) close.grab_focus() self.show_all()
def __init__(self, parent, device): super(DeviceProperties, self).__init__(title=_("Device Properties"), transient_for=qltk.get_top_parent(parent)) self.add_icon_button(_("_Close"), Icons.WINDOW_CLOSE, Gtk.ResponseType.CLOSE) self.set_default_size(400, -1) self.connect('response', self.__close) table = Gtk.Table() table.set_border_width(8) table.set_row_spacings(8) table.set_col_spacings(8) self.vbox.pack_start(table, False, True, 0) props = [] props.append((_("Device:"), device.block_device, None)) mountpoint = util.escape(device.mountpoint or ("<i>%s</i>" % _("Not mounted"))) props.append((_("Mount point:"), mountpoint, None)) props.append((None, None, None)) entry = Gtk.Entry() entry.set_text(device['name']) props.append((_("_Name:"), entry, 'name')) y = 0 for title, value, key in props + device.Properties(): if title is None: table.attach(Gtk.HSeparator(), 0, 2, y, y + 1) else: if key and isinstance(value, Gtk.CheckButton): value.set_label(title) value.set_use_underline(True) value.connect('toggled', self.__changed, key, device) table.attach(value, 0, 2, y, y + 1, xoptions=Gtk.AttachOptions.FILL) else: label = Gtk.Label() label.set_markup("<b>%s</b>" % util.escape(title)) label.set_alignment(0.0, 0.5) table.attach(label, 0, 1, y, y + 1, xoptions=Gtk.AttachOptions.FILL) if key and isinstance(value, Gtk.Widget): widget = value label.set_mnemonic_widget(widget) label.set_use_underline(True) widget.connect('changed', self.__changed, key, device) else: widget = Gtk.Label(label=value) widget.set_use_markup(True) widget.set_selectable(True) widget.set_alignment(0.0, 0.5) table.attach(widget, 1, 2, y, y + 1) y += 1 self.get_child().show_all()
def __init__(self, browser): if self.is_not_unique(): return super(Preferences, self).__init__() self.set_border_width(12) self.set_title(_("Album List Preferences")) self.set_default_size(420, 380) self.set_transient_for(qltk.get_top_parent(browser)) # Do this config-driven setup at instance-time self._EXAMPLE_ALBUM["~rating"] = format_rating(0.75) box = Gtk.VBox(spacing=6) vbox = Gtk.VBox(spacing=6) cb = ConfigCheckButton(_("Show album _covers"), "browsers", "album_covers") cb.set_active(config.getboolean("browsers", "album_covers")) cb.connect('toggled', lambda s: browser.toggle_covers()) vbox.pack_start(cb, False, True, 0) cb = ConfigCheckButton(_("Inline _search includes people"), "browsers", "album_substrings") cb.set_active(config.getboolean("browsers", "album_substrings")) vbox.pack_start(cb, False, True, 0) f = qltk.Frame(_("Options"), child=vbox) box.pack_start(f, False, True, 12) vbox = Gtk.VBox(spacing=6) label = Gtk.Label() label.set_alignment(0.0, 0.5) label.set_padding(6, 6) eb = Gtk.EventBox() eb.get_style_context().add_class("entry") eb.add(label) edit = PatternEditBox(PATTERN) edit.text = browser._pattern_text edit.apply.connect('clicked', self.__set_pattern, edit, browser) connect_obj(edit.buffer, 'changed', self.__preview_pattern, edit, label) vbox.pack_start(eb, False, True, 3) vbox.pack_start(edit, True, True, 0) self.__preview_pattern(edit, label) f = qltk.Frame(_("Album Display"), child=vbox) box.pack_start(f, True, True, 0) main_box = Gtk.VBox(spacing=12) close = Button(_("_Close"), Icons.WINDOW_CLOSE) close.connect('clicked', lambda *x: self.destroy()) b = Gtk.HButtonBox() b.set_layout(Gtk.ButtonBoxStyle.END) b.pack_start(close, True, True, 0) main_box.pack_start(box, True, True, 0) self.use_header_bar() if not self.has_close_button(): main_box.pack_start(b, False, True, 0) self.add(main_box) close.grab_focus() self.show_all()
def __drag_data_browser_dropped(self, songs): window = qltk.get_top_parent(self) if callable(window.browser.dropped): return window.browser.dropped(self, songs) else: return False
def __add(self, button): parent = qltk.get_top_parent(self) uri = (AddNewStation(parent).run(clipboard=True) or "").strip() if uri != "": self.__add_station(uri)
def __song_added(self, librarian, songs): window = qltk.get_top_parent(self) filter_ = window.browser.active_filter if callable(filter_): self.add_songs(filter(filter_, songs))
def __motion(self, view, event): label = self.__label clabel = self.__clabel # trigger over row area, not column headers if event.window is not view.get_bin_window(): self.__undisplay() return False x, y = map(int, [event.x, event.y]) # For gtk3.16 overlay scrollbars: if our event x coordinate # is contained in the scrollbar, hide the tooltip. Unlike other # hiding events we don't want to send a leave event to the scrolled # window so the overlay scrollbar does't hide and can be interacted # with. parent = view.get_parent() # We only need to check if the tooltip is there since events # on the scrollbars don't get forwarded to us anyway. if self.__view and parent and isinstance(parent, Gtk.ScrolledWindow): vscrollbar = parent.get_vscrollbar() res = vscrollbar.translate_coordinates(view, 0, 0) if res is not None: x_offset = res[0] vbar_width = vscrollbar.get_allocation().width if x_offset <= x <= x_offset + vbar_width: self.__undisplay(send_leave=False) return False # hide if any modifier is active if event.get_state() & Gtk.accelerator_get_default_mod_mask(): self.__undisplay() return False # get the cell at the mouse position try: path, col, cellx, celly = view.get_path_at_pos(x, y) except TypeError: # no hints where no rows exist self.__undisplay() return False col_area = view.get_cell_area(path, col) # make sure we are on the same level if x < col_area.x: self.__undisplay() return False # hide for partial hidden rows at the bottom if y > view.get_visible_rect().height: self.__undisplay() return False # get the renderer at the mouse position and get the xpos/width renderers = col.get_cells() pos = sorted(zip(map(col.cell_get_position, renderers), renderers)) pos = filter(lambda p: p[0][0] < cellx, pos) if not pos: self.__undisplay() return False (render_offset, render_width), renderer = pos[-1] if self.__current_renderer == renderer and self.__current_path == path: return False # only ellipsized text renderers if not isinstance(renderer, Gtk.CellRendererText): self.__undisplay() return False ellipsize = renderer.get_property('ellipsize') if ellipsize == Pango.EllipsizeMode.END: expand_left = False elif ellipsize == Pango.EllipsizeMode.MIDDLE: # depending on where the cursor is expand_left = x > col_area.x + render_offset + render_width / 2 elif ellipsize == Pango.EllipsizeMode.START: expand_left = True else: self.__undisplay() return False # don't display if the renderer is in editing mode if renderer.props.editing: self.__undisplay() return False # set the cell renderer attributes for the active cell model = view.get_model() col.cell_set_cell_data(model, model.get_iter(path), False, False) # the markup attribute is write only, so the markup text needs # to be saved on the python side, so we can copy it to the label markup = getattr(renderer, "markup", None) if markup is None: text = renderer.get_property('text') set_text = lambda l: l.set_text(text) else: # markup can also be column index if isinstance(markup, int): markup = model[path][markup] set_text = lambda l: l.set_markup(markup) # Use the renderer padding as label padding so the text offset matches render_xpad = renderer.get_property("xpad") # the renderer xpad is not enough for the tooltip, especially with # rounded corners the label gets nearly clipped. MIN_HINT_X_PAD = 4 if render_xpad < MIN_HINT_X_PAD: extra_xpad = MIN_HINT_X_PAD - render_xpad else: extra_xpad = 0 label.set_padding(render_xpad + extra_xpad, 0) set_text(clabel) clabel.set_padding(render_xpad, 0) label_width = clabel.get_layout().get_pixel_size()[0] label_width += clabel.get_layout_offsets()[0] or 0 # layout offset includes the left padding, so add one more label_width += render_xpad # CellRenderer width is too large if it's the last one in a column. # Use cell_area width as a maximum and limit render_width. max_width = col_area.width if render_width + render_offset > max_width: render_width = max_width - render_offset # don't display if it doesn't need expansion if label_width < render_width: self.__undisplay() return False dummy, ox, oy = view.get_window().get_origin() bg_area = view.get_background_area(path, None) # save for adjusting passthrough events self.__dx, self.__dy = col_area.x + render_offset, bg_area.y self.__dx -= extra_xpad if expand_left: # shift to the left # FIXME: ellipsize start produces a space at the end depending # on the text. I don't know how to compute it.. self.__dx -= (label_width - render_width) # final window coordinates/size x = ox + self.__dx y = oy + self.__dy x, y = view.convert_bin_window_to_widget_coords(x, y) w = label_width + extra_xpad * 2 h = bg_area.height if not is_wayland(): # clip if it's bigger than the monitor mon_border = 5 # leave some space screen = Gdk.Screen.get_default() if not expand_left: monitor_idx = screen.get_monitor_at_point(x, y) mon = screen.get_monitor_geometry(monitor_idx) space_right = mon.x + mon.width - x - w - mon_border if space_right < 0: w += space_right label.set_ellipsize(Pango.EllipsizeMode.END) else: label.set_ellipsize(Pango.EllipsizeMode.NONE) else: monitor_idx = screen.get_monitor_at_point(x + w, y) mon = screen.get_monitor_geometry(monitor_idx) space_left = x - mon.x - mon_border if space_left < 0: x -= space_left self.__dx -= space_left w += space_left label.set_ellipsize(Pango.EllipsizeMode.START) else: label.set_ellipsize(Pango.EllipsizeMode.NONE) else: label.set_ellipsize(Pango.EllipsizeMode.NONE) # Don't show if the resulting tooltip would be smaller # than the visible area (if not all is on the display) if w < render_width: self.__undisplay() return False self.__view = view self.__current_renderer = renderer self.__edit_id = renderer.connect('editing-started', self.__undisplay) self.__current_path = path self.__current_col = col if self.__hide_id: GLib.source_remove(self.__hide_id) self.__hide_id = None self.set_transient_for(get_top_parent(view)) set_text(label) self.set_size_request(w, h) window = self.get_window() if self.get_visible() and window: window.move_resize(x, y, w, h) else: self.move(x, y) self.resize(w, h) self.show() return False
def __init__(self, parent): super(AddFeedDialog, self).__init__( qltk.get_top_parent(parent), _("New Feed"), _("Enter the location of an audio feed:"), button_label=_("_Add"), button_icon=Icons.LIST_ADD)