From 9d70ecd65e78b9d5062406f967a5537c588120f2 Mon Sep 17 00:00:00 2001 From: Jakob Meier Date: Wed, 28 Feb 2024 11:41:44 +0100 Subject: [PATCH] implement multi-value preferencerow --- melon/playlist/pick.py | 2 +- melon/widgets/preferencerow.py | 215 ++++++++++++++++++++++++++------- melon/widgets/simpledialog.py | 4 +- 3 files changed, 175 insertions(+), 46 deletions(-) diff --git a/melon/playlist/pick.py b/melon/playlist/pick.py index d295468..7a2e222 100644 --- a/melon/playlist/pick.py +++ b/melon/playlist/pick.py @@ -2,7 +2,7 @@ import sys import gi gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') -from gi.repository import Gtk, Adw, Gio, GLib +from gi.repository import Gtk, Adw, Gio, GLib, Gdk from melon.widgets.feeditem import AdaptiveFeedItem from melon.widgets.iconbutton import IconButton diff --git a/melon/widgets/preferencerow.py b/melon/widgets/preferencerow.py index 810a794..090e866 100644 --- a/melon/widgets/preferencerow.py +++ b/melon/widgets/preferencerow.py @@ -6,6 +6,8 @@ from gi.repository import Gtk, Adw, GLib, Gdk from melon.servers import Resource,Video,Playlist,Channel,Preference,PreferenceType from melon.servers.utils import pixbuf_from_url from melon.widgets.iconbutton import IconButton +from melon.widgets.simpledialog import SimpleDialog +from copy import deepcopy as copy class PreferenceRow(): def __init__(self, pref: Preference, *args, **kwargs): @@ -62,49 +64,8 @@ class PreferenceRow(): lambda a: self.pass_to_callback(self.widget.get_value()) ) elif pref.type is PreferenceType.MULTI: - self.widget = Adw.PreferencesRow() - self.inner = Adw.PreferencesGroup() - padding = 12 - self.inner.set_margin_top(padding) - self.inner.set_margin_bottom(padding) - self.inner.set_margin_start(padding) - self.inner.set_margin_end(padding) - self.widget.set_child(self.inner) - self.inner.set_title(pref.name) - self.inner.set_description(pref.description) - self.children = [] - # TODO: implement functionality - self.adder = IconButton("Add", "list-add-symbolic") - self.inner.set_header_suffix(self.adder) - counter = 0 - length = len(self.pref.value) - for item in self.pref.value: - row = Adw.ActionRow() - row.set_title(item) - actions = Gtk.Box() - # move up button - if counter > 0: - # TODO: implement functionality - move_up = IconButton("","go-up-symbolic") - move_up.set_tooltip_text("Move up") - move_up.add_css_class("circular") - actions.append(move_up) - if counter < length-1: - # TODO: implement functionality - move_down = IconButton("", "go-down-symbolic") - move_down.set_tooltip_text("Move down") - move_down.add_css_class("circular") - actions.append(move_down) - counter += 1 - # TODO: implement functionality - remove = IconButton("", "list-remove-symbolic") - remove.set_tooltip_text("Remove from list") - remove.add_css_class("circular") - actions.append(remove) - row.add_suffix(actions) - # store rows in seperate list to make removing them possible - self.children.append(row) - self.inner.add(row) + self.widget = MultiRow(pref) + self.widget.connect(lambda v: self.pass_to_callback(v)) else: # shouldn't be reachable self.widget = Adw.ActionRow() @@ -117,7 +78,7 @@ class PreferenceRow(): # that the callback would be called multiple times, # because the "notify" signal might be called more than once return - self.current_value = value + self.current_value = copy(value) if not self.callback is None: self.callback(value) def get_widget(self): @@ -127,3 +88,169 @@ class PreferenceRow(): def on_active(self): if not self.callback is None: self.callback(self.get_active()) + +class MultiRow(Adw.PreferencesRow): + callback = None + values = [] + def __init__(self, pref, *args, **kwargs): + super().__init__(*args, **kwargs) + self.pref = pref + self.values = copy(pref.value) + self.update() + def connect(self, cb): + self.callback = cb + def update(self): + self.inner = Adw.PreferencesGroup() + padding = 12 + self.inner.set_margin_top(padding) + self.inner.set_margin_bottom(padding) + self.inner.set_margin_start(padding) + self.inner.set_margin_end(padding) + self.set_child(self.inner) + self.inner.set_title(self.pref.name) + self.inner.set_description(self.pref.description) + self.adder = IconButton("Add", "list-add-symbolic") + self.adder.connect("clicked", lambda x: self.open_add()) + self.inner.set_header_suffix(self.adder) + counter = 0 + length = len(self.values) + # create items list + for item in self.values: + row = Adw.ActionRow() + row.set_title(item) + actions = Gtk.Box() + # move up button + # not shown for first element + if counter > 0: + move_up = IconButton("","go-up-symbolic") + move_up.set_tooltip_text("Move up") + move_up.add_css_class("circular") + move_up.connect( + "clicked", + pass_me(self.move_up, item, counter) + ) + actions.append(move_up) + # move down button + # not shown for last element + if counter < length-1: + move_down = IconButton("", "go-down-symbolic") + move_down.set_tooltip_text("Move down") + move_down.add_css_class("circular") + move_down.connect( + "clicked", + pass_me(self.move_down, item, counter) + ) + actions.append(move_down) + # remove button + remove = IconButton("", "list-remove-symbolic") + remove.set_tooltip_text("Remove from list") + remove.add_css_class("circular") + remove.connect("clicked", pass_me(self.confirm_delete, item, counter)) + actions.append(remove) + row.add_suffix(actions) + self.inner.add(row) + counter += 1 + def notify(self): + if not self.callback is None: + self.callback(copy(self.values)) + self.update() + def open_add(self): + diag = SimpleDialog() + diag.set_title("Add Item") + # preview prferences group + box = Adw.PreferencesGroup(); + box.set_title("Create a new list entry") + box.set_description("Enter the new value here") + # text input + inp = Adw.EntryRow() + inp.set_title("Value") + box.add(inp) + diag.set_widget(box) + # place button bar in toolbar + bottom_bar = Gtk.Box() + btn_cancel = IconButton("Cancel", "process-stop-symbolic", tooltip="Do not create a new item") + btn_confirm = IconButton("Create", "list-add-symbolic", tooltip="Add entry to list") + btn_confirm.connect( + "clicked", + lambda _: many( + self.values.append(inp.get_text()), + self.notify(), + diag.hide()) + ) + btn_cancel.connect( + "clicked", + lambda x: diag.hide()) + padding = 12 + btn_confirm.set_vexpand(True) + btn_confirm.set_hexpand(True) + btn_confirm.set_margin_end(padding) + btn_confirm.set_margin_start(padding/2) + btn_confirm.set_margin_top(padding) + btn_confirm.set_margin_bottom(padding) + btn_cancel.set_vexpand(True) + btn_cancel.set_hexpand(True) + btn_cancel.set_margin_start(padding) + btn_cancel.set_margin_end(padding/2) + btn_cancel.set_margin_top(padding) + btn_cancel.set_margin_bottom(padding) + bottom_bar.append(btn_cancel) + bottom_bar.append(btn_confirm) + diag.toolbar_view.add_bottom_bar(bottom_bar) + + diag.show() + def confirm_delete(self, _, item:str, counter:int): + diag = SimpleDialog() + diag.set_title("Delete") + # preview prferences group + preview = Adw.PreferencesGroup(); + preview.set_title("Do you really want to delete this item?") + preview.set_description("You won't be able to restore it afterwards") + row = Adw.ActionRow() + row.set_title(item) + preview.add(row) + diag.set_widget(preview) + # place button bar in toolbar + bottom_bar = Gtk.Box() + btn_cancel = IconButton("Cancel", "process-stop-symbolic", tooltip="Do not remove item") + btn_confirm = IconButton("Delete", "list-add-symbolic", tooltip="Remove item from list") + btn_confirm.connect( + "clicked", + lambda _: many( + self.values.pop(counter), + self.notify(), + diag.hide()) + ) + btn_cancel.connect( + "clicked", + lambda x: diag.hide()) + padding = 12 + btn_confirm.set_vexpand(True) + btn_confirm.set_hexpand(True) + btn_confirm.set_margin_end(padding) + btn_confirm.set_margin_start(padding/2) + btn_confirm.set_margin_top(padding) + btn_confirm.set_margin_bottom(padding) + btn_cancel.set_vexpand(True) + btn_cancel.set_hexpand(True) + btn_cancel.set_margin_start(padding) + btn_cancel.set_margin_end(padding/2) + btn_cancel.set_margin_top(padding) + btn_cancel.set_margin_bottom(padding) + bottom_bar.append(btn_cancel) + bottom_bar.append(btn_confirm) + diag.toolbar_view.add_bottom_bar(bottom_bar) + diag.show() + pass + def move_up(self, _, item:str, counter:int): + val = self.values.pop(counter) + self.values.insert(counter-1, val) + self.notify() + def move_down(self, _, item:str, counter:int): + val = self.values.pop(counter) + self.values.insert(counter+1, val) + self.notify() + +def pass_me(func, *args): + return lambda *a: func(*a, *args) +def many(*funcs): + return [funcs] diff --git a/melon/widgets/simpledialog.py b/melon/widgets/simpledialog.py index 01dbb3a..f7b32e3 100644 --- a/melon/widgets/simpledialog.py +++ b/melon/widgets/simpledialog.py @@ -14,8 +14,10 @@ class SimpleDialog(Adw.Window): self.set_content(self.toolbar_view) def set_title(self, text): self.title.set_title(text) - def set_widget(self, child): + def set_widget(self, child,padding=12): self.toolbar_view.set_content(child) + child.set_margin_end(padding) + child.set_margin_start(padding) def show(self): self.set_visible(True) def hide(self): -- 2.38.5