diff --git a/setup.py b/setup.py index af2f8bc..1041cf5 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ setuptools.setup( ] }, install_requires=["PyGObject", "importlib_metadata", "platformdirs", "Pillow"], - version='2.1.3', + version='2.2', python_requires='>3.9', classifiers=[ "Development Status :: 4 - Beta", diff --git a/waypaper/app.py b/waypaper/app.py index 3e8a7a2..12dddd4 100644 --- a/waypaper/app.py +++ b/waypaper/app.py @@ -94,12 +94,6 @@ class App(Gtk.Window): self.grid.set_column_spacing(0) self.scrolled_window.add(self.grid) - # Create subfolder toggle: - self.include_subfolders_checkbox = Gtk.ToggleButton(label=self.txt.msg_subfolders) - self.include_subfolders_checkbox.set_active(self.cf.include_subfolders) - self.include_subfolders_checkbox.connect("toggled", self.on_include_subfolders_toggled) - self.include_subfolders_checkbox.set_tooltip_text(self.txt.tip_subfolder) - # Create a backend dropdown menu: self.backend_option_combo = Gtk.ComboBoxText() for backend in self.cf.installed_backends: @@ -140,7 +134,6 @@ class App(Gtk.Window): self.sort_option_combo.connect("changed", self.on_sort_option_changed) self.sort_option_combo.set_tooltip_text(self.txt.tip_sorting) - # Create exit button: self.exit_button = Gtk.Button(label=self.txt.msg_exit) self.exit_button.connect("clicked", self.on_exit_clicked) @@ -173,12 +166,16 @@ class App(Gtk.Window): # Create a monitor option dropdown menu: self.monitor_option_combo = Gtk.ComboBoxText() + # Create the options menu button: + self.options_button = Gtk.Button(label="Options") + self.options_button.connect("clicked", self.on_options_button_clicked) + # Create a horizontal box for display option and exit button: self.options_box = Gtk.HBox(spacing=10) self.options_box.pack_end(self.exit_button, False, False, 0) + self.options_box.pack_end(self.options_button, False, False, 0) self.options_box.pack_end(self.refresh_button, False, False, 0) self.options_box.pack_end(self.random_button, False, False, 0) - self.options_box.pack_end(self.include_subfolders_checkbox, False, False, 0) self.options_box.pack_end(self.sort_option_combo, False, False, 0) self.options_box.pack_end(self.color_picker_button, False, False, 0) self.options_box.pack_end(self.fill_option_combo, False, False, 0) @@ -192,6 +189,37 @@ class App(Gtk.Window): self.show_all() + def create_menu(self): + """Create a GTK menu and items inside it""" + self.menu = Gtk.Menu() + + # Create gifs toggle: + self.filter_gifs_checkbox = Gtk.CheckMenuItem(label=self.txt.msg_gifs) + self.filter_gifs_checkbox.set_active(self.cf.show_gifs_only) + self.filter_gifs_checkbox.connect("toggled", self.on_filter_gifs_toggled) + + # Create subfolder toggle: + self.include_subfolders_checkbox = Gtk.CheckMenuItem(label=self.txt.msg_subfolders) + self.include_subfolders_checkbox.set_active(self.cf.include_subfolders) + self.include_subfolders_checkbox.connect("toggled", self.on_include_subfolders_toggled) + + # Create hidden toggle: + self.include_hidden_checkbox = Gtk.CheckMenuItem(label=self.txt.msg_hidden) + self.include_hidden_checkbox.set_active(self.cf.show_hidden) + self.include_hidden_checkbox.connect("toggled", self.on_hidden_files_toggled) + + self.menu.append(self.filter_gifs_checkbox) + self.menu.append(self.include_subfolders_checkbox) + self.menu.append(self.include_hidden_checkbox) + self.menu.show_all() + + + def on_options_button_clicked(self, widget): + '''Position the menu at the button and show it''' + self.create_menu() + self.menu.popup_at_widget(widget, Gdk.Gravity.NORTH, Gdk.Gravity.SOUTH, None) + + def monitor_option_display(self): """Display monitor option if backend is swww""" self.options_box.remove(self.monitor_option_combo) @@ -257,7 +285,8 @@ class App(Gtk.Window): def process_images(self): """Load images from the selected folder, resize them, and arrange into a grid""" - self.image_paths = get_image_paths(self.cf.backend, self.cf.image_folder, self.cf.include_subfolders, self.cf.show_hidden, depth=1) + self.image_paths = get_image_paths(self.cf.backend, self.cf.image_folder, self.cf.include_subfolders, + self.cf.show_hidden, self.cf.show_gifs_only, depth=1) self.sort_images() # Show caching label: @@ -338,7 +367,6 @@ class App(Gtk.Window): def choose_folder(self): """Choosing the folder of images, saving the path, and reloading images""" - dialog = Gtk.FileChooserDialog( self.txt.msg_choosefolder, self, Gtk.FileChooserAction.SELECT_FOLDER, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, self.txt.msg_select, Gtk.ResponseType.OK) @@ -356,12 +384,36 @@ class App(Gtk.Window): self.cf.save() + def on_filter_gifs_toggled(self, toggle): + """Toggle only gifs checkbox via menu""" + self.cf.show_gifs_only = toggle.get_active() + threading.Thread(target=self.process_images).start() + + def on_include_subfolders_toggled(self, toggle): - """On chosing to include subfolders""" + """Toggle subfolders visibility via menu""" self.cf.include_subfolders = toggle.get_active() threading.Thread(target=self.process_images).start() + def toggle_include_subfolders(self): + """Toggle subfolders visibility via key""" + self.cf.include_subfolders = not self.cf.include_subfolders + threading.Thread(target=self.process_images).start() + + + def on_hidden_files_toggled(self, toggle): + """Toggle visibility of hidden files via menu""" + self.cf.show_hidden = toggle.get_active() + threading.Thread(target=self.process_images).start() + + + def toggle_hidden_files(self): + """Toggle visibility of hidden files via keys""" + self.cf.show_hidden = not self.cf.show_hidden + threading.Thread(target=self.process_images).start() + + def on_fill_option_changed(self, combo): """Save fill parameter when it was changed""" self.cf.fill_option = combo.get_active_text() @@ -436,13 +488,6 @@ class App(Gtk.Window): self.cf.save() - def toggle_hidden_files(self): - """Toggle visibility of hidden files""" - self.cf.show_hidden = not self.cf.show_hidden - threading.Thread(target=self.process_images).start() - self.cf.save() - - def clear_cache(self): """Delete cache folder and reprocess the images""" try: @@ -467,6 +512,9 @@ class App(Gtk.Window): elif event.keyval in [Gdk.KEY_period]: self.toggle_hidden_files() + elif event.keyval in [Gdk.KEY_s]: + self.toggle_include_subfolders() + elif event.keyval in [Gdk.KEY_h, Gdk.KEY_Left]: self.selected_index = max(self.selected_index - 1, 0) self.load_image_grid() @@ -514,7 +562,7 @@ class App(Gtk.Window): self.cf.save() # Prevent other default key handling: - if event.keyval in [Gdk.KEY_Up, Gdk.KEY_Down, Gdk.KEY_Left, Gdk.KEY_Right, Gdk.KEY_Return, Gdk.KEY_KP_Enter]: + if event.keyval in [Gdk.KEY_Up, Gdk.KEY_Down, Gdk.KEY_Left, Gdk.KEY_Right, Gdk.KEY_Return, Gdk.KEY_KP_Enter, Gdk.KEY_period]: return True diff --git a/waypaper/common.py b/waypaper/common.py index 8c28b01..c62f0c8 100644 --- a/waypaper/common.py +++ b/waypaper/common.py @@ -13,18 +13,14 @@ def has_image_extension(file_path, backend): return ext in image_extensions -def get_image_paths(backend, root_folder, include_subfolders=False, include_hidden=False, depth=None): - """Get a list of file paths depending of weather we include subfolders and how deep we scan""" +def get_image_paths(backend, root_folder, include_subfolders=False, include_hidden=False, only_gifs=False, depth=None): + """Get a list of file paths depending on the filters that were requested""" image_paths = [] for root, directories, files in os.walk(root_folder): - # Remove hidden files from consideration: for directory in directories: if directory.startswith('.') and not include_hidden: directories.remove(directory) - for file in files: - if file.startswith('.') and not include_hidden: - files.remove(file) # Remove subfolders from consideration: if not include_subfolders and str(root) != str(root_folder): @@ -38,16 +34,21 @@ def get_image_paths(backend, root_folder, include_subfolders=False, include_hidd # Remove files that are not images from consideration: for filename in files: + if filename.startswith('.') and not include_hidden: + continue if not has_image_extension(filename, backend): continue + if not filename.endswith('.gif') and only_gifs: + continue image_paths.append(os.path.join(root, filename)) + # print(root, directories, files) return image_paths def get_random_file(backend, folder, include_subfolders, include_hidden=False): """Pick a random file from the folder""" try: - image_paths = get_image_paths(backend, folder, include_subfolders, include_hidden, depth=1) + image_paths = get_image_paths(backend, folder, include_subfolders, include_hidden, only_gifs=False, depth=1) return random.choice(image_paths) except: return None diff --git a/waypaper/config.py b/waypaper/config.py index 40fe62e..f2e2f6b 100644 --- a/waypaper/config.py +++ b/waypaper/config.py @@ -34,6 +34,7 @@ class Config: self.post_command = "" self.include_subfolders = False self.show_hidden = False + self.show_gifs_only = False self.about = AboutData() self.cache_dir = user_cache_path(self.about.applicationName()) self.config_dir = user_config_path(self.about.applicationName()) @@ -73,6 +74,7 @@ class Config: self.lang = config.get("Settings", "language", fallback=self.lang) self.include_subfolders = config.getboolean("Settings", "subfolders", fallback=self.include_subfolders) self.show_hidden = config.getboolean("Settings", "show_hidden", fallback=self.show_hidden) + self.show_gifs_only = config.getboolean("Settings", "show_gifs_only", fallback=self.show_gifs_only) self.monitors_str = config.get("Settings", "monitors", fallback=self.selected_monitor, raw=True) self.wallpapers_str = config.get("Settings", "wallpaper", fallback="", raw=True) self.number_of_columns = config.get("Settings", "number_of_columns", fallback=self.number_of_columns) @@ -107,10 +109,10 @@ class Config: if 0 > int(self.swww_transition_duration): self.swww_transition_duration = 2 - def save(self): - """Update the parameters and save them to the configuration file""" - - # If only certain monitor was affected, change only its wallpaper: + def attribute_selected_wallpaper(self): + """If only certain monitor was affected, change only its wallpaper""" + if self.selected_wallpaper == "": + return if self.selected_monitor == "All": self.monitors = [self.selected_monitor] self.wallpapers = [self.shorten_path(self.selected_wallpaper)] @@ -121,6 +123,11 @@ class Config: self.monitors.append(self.selected_monitor) self.wallpapers.append(self.shorten_path(self.selected_wallpaper)) + def save(self): + """Update the parameters and save them to the configuration file""" + + self.attribute_selected_wallpaper() + # Write configuration to the file: config = configparser.ConfigParser() config.read(self.config_file) @@ -136,6 +143,7 @@ class Config: config.set("Settings", "color", self.color) config.set("Settings", "subfolders", str(self.include_subfolders)) config.set("Settings", "show_hidden", str(self.show_hidden)) + config.set("Settings", "show_gifs_only", str(self.show_gifs_only)) config.set("Settings", "post_command", self.post_command) config.set("Settings", "number_of_columns", str(self.number_of_columns)) config.set("Settings", "swww_transition_type", str(self.swww_transition_type)) diff --git a/waypaper/translations.py b/waypaper/translations.py index ee63900..74ebdec 100644 --- a/waypaper/translations.py +++ b/waypaper/translations.py @@ -17,7 +17,9 @@ class English: self.msg_refresh = "Refresh" self.msg_random = "Random" self.msg_exit = "Exit" - self.msg_subfolders = "Subfolders" + self.msg_subfolders = "Show subfolders" + self.msg_hidden = "Show hidden" + self.msg_gifs = "Show gifs only" self.msg_changefolder = "Change wallpaper folder" self.msg_choosefolder = "Please choose a folder" self.msg_caching = "Caching wallpapers..." @@ -25,7 +27,7 @@ class English: self.msg_help = "Waypaper's hotkeys:\n\nhjkl - Navigation (←↓↑→)\nEnter - Set selected wallpaper\nf - Change wallpaper folder\n" self.msg_help += "g - Scroll to top\nG - Scroll to bottom\nR - Set random wallpaper\nr - Recache wallpapers\n" - self.msg_help += ". - Include/exclude hidden images\ns - Include/exclude images in subfolders\n? - Help\nq - Exit\n\n" + self.msg_help += ". - Toggle hidden images\ns - Toggle images in subfolders\n? - Help\nq - Exit\n\n" self.msg_help += self.msg_info self.err_cache = "Error deleting cache" @@ -39,7 +41,6 @@ class English: self.err_disp = "Error determining monitor names:" self.err_kill = "Warning related to killall:" - self.tip_subfolder = "Include/exclude images in subfolders" self.tip_refresh = "Recache the folder of images" self.tip_fill = "Choose fill type" self.tip_backend = "Choose backend" @@ -67,6 +68,8 @@ class German: self.msg_random = "Zufällig" self.msg_exit = "Beenden" self.msg_subfolders = "Unterordner" + self.msg_hidden = "Hidden" + self.msg_gifs = "Show only gifs" self.msg_changefolder = "Hintergrundbild-Ordner ändern" self.msg_choosefolder = "Bitte wählen Sie einen Ordner aus" self.msg_caching = "Hintergrundbilder werden zwischengespeichert..." @@ -88,7 +91,6 @@ class German: self.err_disp = "Fehler beim Ermitteln der Monitor-Namen:" self.err_kill = "Warnung im Zusammenhang mit dem Befehl killall:" - self.tip_subfolder = "Wählen, ob Unterordner mit einbezogen werden sollen" self.tip_refresh = "Erneutes einlesen des Hintergrundbild-Ordners" self.tip_fill = "Skalierungsart auswählen" self.tip_backend = "Backend auswählen" @@ -116,6 +118,8 @@ class French: self.msg_random = "Aléatoire" self.msg_exit = "Quitter" self.msg_subfolders = "Sous-dossiers" + self.msg_hidden = "Hidden" + self.msg_gifs = "Show only gifs" self.msg_changefolder = "Changer de dossier de papier peint" self.msg_choosefolder = "Veuillez choisir un dossier" self.msg_caching = "Mise en cache des papiers peints..." @@ -137,7 +141,6 @@ class French: self.err_disp = "Erreur lors de la détermination des noms des moniteurs :" self.err_kill = "Avertissement lié à killall :" - self.tip_subfolder = "Inclure/exclure les images des sous-dossiers" self.tip_refresh = "Recréer le dossier d'images" self.tip_fill = "Choisir le type de remplissage" self.tip_backend = "Choisir le backend" @@ -165,6 +168,8 @@ class Polish: self.msg_random = "Losowo" self.msg_exit = "Wyjście" self.msg_subfolders = "Podkatalogi" + self.msg_hidden = "Hidden" + self.msg_gifs = "Show only gifs" self.msg_changefolder = "Zmień folder z tapetami" self.msg_choosefolder = "Proszę wybrać folder" self.msg_caching = "Kasowanie tapet..." @@ -186,7 +191,6 @@ class Polish: self.err_disp = "Błąd podczas określania nazw monitorów:" self.err_kill = "Ostrzeżenie związane z poleceniem killall:" - self.tip_subfolder = "Dołącz/wyłącz obrazy z podkatalogów" self.tip_refresh = "Odśwież folder z obrazami" self.tip_fill = "Wybierz typ wypełnienia" self.tip_backend = "Wybierz backend" @@ -213,7 +217,9 @@ class Russian: self.msg_refresh = "Обновить" self.msg_random = "Случайно" self.msg_exit = "Выход" - self.msg_subfolders = "Подпапки" + self.msg_subfolders = "Показать подпапки" + self.msg_hidden = "Показать скрытые" + self.msg_gifs = "Показать только gif" self.msg_changefolder = "Изменить папку с обоями" self.msg_choosefolder = "Пожалуйста, выберите папку" self.msg_caching = "Кэширование обоев..." @@ -221,12 +227,12 @@ class Russian: self.msg_help = "Горячие клавиши Waypaper:\n\nhjkl - Навигация (←↓↑→)\nf - Изменить папку с обоями\n" self.msg_help += "g - Прокрутка в начало\nG - Прокрутка в конец\nR - Установить случайные обои\nr - Обновить кэш обоев\n" - self.msg_help += ". - Включить/исключить скрытые файлы \ns - Включить/исключить подпапки\n? - Справка\nq - Выход\n\n" + self.msg_help += ". - Показать/скрыть скрытые файлы \ns - Показать/скрыть вложенные папки\n? - Справка\nq - Выход\n\n" self.msg_help += self.msg_info self.err_cache = "Ошибка при удалении кэша" self.err_backend = "Похоже, что ни один из бэкендов для установки обоев не установлен в системе.\n" - self.err_backend += "Используйте менеджер пакетов для установки хотя бы одного из этих бэкендов:\n\n" + self.err_backend += "Используйте менеджер пакетов для установки хотя бы одного из следующих бэкендов:\n\n" self.err_backend += "- swaybg (для Wayland)\n- swww (для Wayland)\n" self.err_backend += "- feh (для Xorg)\n- wallutils (для Xorg и Wayland)\n\n" self.err_backend += self.msg_info @@ -235,7 +241,6 @@ class Russian: self.err_disp = "Ошибка определения названий мониторов:" self.err_kill = "Предупреждение связанное с killall:" - self.tip_subfolder = "Включить/отключить изображения в подпапках" self.tip_refresh = "Обновить папку с изображениями" self.tip_fill = "Выбрать тип заполнения" self.tip_backend = "Выбрать бэкенд" @@ -263,6 +268,8 @@ class Chinese: self.msg_random = "随机" self.msg_exit = "退出" self.msg_subfolders = "子文件夹" + self.msg_hidden = "Show hidden" + self.msg_gifs = "Show only gifs" self.msg_changefolder = "更改壁纸文件夹" self.msg_choosefolder = "请选择一个文件夹" self.msg_caching = "缓存壁纸..." @@ -284,7 +291,6 @@ class Chinese: self.err_disp = "确定监视器名称时出错:" self.err_kill = "与killall相关的警告:" - self.tip_subfolder = "在子文件夹中包含/排除图像" self.tip_refresh = "重新缓存图像文件夹" self.tip_fill = "选择填充类型" self.tip_backend = "选择后端"