diff --git a/.gitignore b/.gitignore
index 67314e1d..89aa95db 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,3 +34,5 @@ venv/
*.log
*.prof
*.pyc
+
+.DS_Store
diff --git a/README.md b/README.md
index f8c06287..f202df48 100644
--- a/README.md
+++ b/README.md
@@ -70,9 +70,8 @@ See [DEVELOPMENT.md](DEVELOPMENT.md) for detailed instructions and developing Co
## Requirements
- `python3`
- `meson >= 0.40.0` as build system
-- `gtk3 >= 3.22`
-- `libhandy >= 1.0.0`
-- `libdazzle >= 3.34.0`
+- `gtk4 >= 4.10`
+- `libadwaita >= 1.4.0`
- `peewee >= 3.9.6` as object relation mapper
- `mutagen` for meta tag management
- `distro`
@@ -165,6 +164,7 @@ To the contributors on GitHub:
- paper42
- phpwutz
- rapenne-s
+- rdbende
- thibaultamartin
- umeboshi2
- worldofpeace
@@ -277,9 +277,9 @@ The translators:
To nedrichards for the Flatpak.
## Help me translate cozy!
-Cozy is on Transifex, where anyone can contribute and translate. Can't find your language in the list? Let me know!
+Cozy is on Transifex, where anyone can contribute and translate. Can't find your language in the list? Let me know!
-If you like this project, consider supporting me on Patreon :)
+If you like this project, consider supporting me on Patreon :)
----
[![Maintainability](https://api.codeclimate.com/v1/badges/fde8cbdff23033adaca2/maintainability)](https://codeclimate.com/github/geigi/cozy/maintainability)
diff --git a/com.github.geigi.cozy.json b/com.github.geigi.cozy.json
index dac90784..f8521006 100644
--- a/com.github.geigi.cozy.json
+++ b/com.github.geigi.cozy.json
@@ -1,7 +1,7 @@
{
"app-id": "com.github.geigi.cozy",
"runtime": "org.gnome.Platform",
- "runtime-version": "42",
+ "runtime-version": "45",
"sdk": "org.gnome.Sdk",
"command": "com.github.geigi.cozy",
"finish-args": [
@@ -130,37 +130,6 @@
}
]
},
- {
- "name": "libhandy",
- "buildsystem": "meson",
- "config-opts": [
- "-Dprofiling=false",
- "-Dintrospection=enabled",
- "-Dgtk_doc=false",
- "-Dtests=false",
- "-Dexamples=false",
- "-Dvapi=false",
- "-Dglade_catalog=disabled"
- ],
- "sources": [
- {
- "type": "git",
- "url": "https://gitlab.gnome.org/GNOME/libhandy",
- "tag": "1.7.90"
- }
- ]
- },
- {
- "name": "libdazzle",
- "buildsystem": "meson",
- "sources": [
- {
- "type": "git",
- "url": "https://gitlab.gnome.org/GNOME/libdazzle.git",
- "tag": "3.44.0"
- }
- ]
- },
{
"name": "cozy",
"buildsystem": "meson",
@@ -172,4 +141,4 @@
]
}
]
-}
\ No newline at end of file
+}
diff --git a/cozy.doap b/cozy.doap
index 38b2fd39..64b07bdd 100644
--- a/cozy.doap
+++ b/cozy.doap
@@ -8,8 +8,8 @@
Listen to audio books
Python
- GTK 3
- Libhandy
+ GTK 4
+ Libadwaita
diff --git a/cozy/app_controller.py b/cozy/app_controller.py
index 4458cae9..ec1fbd53 100644
--- a/cozy/app_controller.py
+++ b/cozy/app_controller.py
@@ -21,7 +21,7 @@
from cozy.ui.app_view import AppView
from cozy.ui.book_detail_view import BookDetailView
from cozy.ui.headerbar import Headerbar
-from cozy.ui.info_banner import InfoBanner
+from cozy.ui.toaster import ToastNotifier
from cozy.ui.library_view import LibraryView
from cozy.ui.main_view import CozyUI
from cozy.ui.media_controller import MediaController
@@ -105,7 +105,7 @@ def configure_inject(self, binder):
binder.bind_to_constructor(SleepTimerViewModel, lambda: SleepTimerViewModel())
binder.bind_to_constructor(GstPlayer, lambda: GstPlayer())
binder.bind_to_constructor(PowerManager, lambda: PowerManager())
- binder.bind_to_constructor(InfoBanner, lambda: InfoBanner())
+ binder.bind_to_constructor(ToastNotifier, lambda: ToastNotifier())
binder.bind_to_constructor(AppViewModel, lambda: AppViewModel())
binder.bind_to_constructor(SettingsViewModel, lambda: SettingsViewModel())
@@ -125,9 +125,6 @@ def open_library(self):
self.library_view_model.open_library()
self.app_view_model.view = View.LIBRARY_FILTER
- def navigate_back(self):
- self.app_view_model.navigate_back()
-
def _connect_popovers(self):
self.headerbar.search_button.set_popover(self.search_view.popover)
@@ -140,8 +137,6 @@ def _on_open_view(self, event, data):
self.open_book(data)
elif event == OpenView.LIBRARY:
self.open_library()
- elif event == OpenView.BACK:
- self.navigate_back()
def _on_library_view_event(self, event: str, _):
if event == "work-done":
diff --git a/cozy/application.py b/cozy/application.py
index dc57b7d2..33521fda 100644
--- a/cozy/application.py
+++ b/cozy/application.py
@@ -2,6 +2,7 @@
import locale
import logging
import os
+import platform
import sys
import threading
from pathlib import Path
@@ -15,9 +16,7 @@
from cozy.ui.widgets.filter_list_box import FilterListBox
from cozy.ui.widgets.seek_bar import SeekBar
-gi.require_version('Handy', '1')
-
-from gi.repository import Gtk, GLib, Handy
+from gi.repository import Gtk, GLib, Adw
from cozy.app_controller import AppController
from cozy.control.db import init_db
@@ -57,7 +56,7 @@ def run_with_except_hook(*args2, **kwargs2):
threading.Thread.__init__ = init
-class Application(Gtk.Application):
+class Application(Adw.Application):
ui: CozyUI
app_controller: AppController
@@ -65,7 +64,7 @@ def __init__(self, localedir: str, pkgdatadir: str):
self.localedir = localedir
self.pkgdatadir = pkgdatadir
- Gtk.Application.__init__(self, application_id='com.github.geigi.cozy')
+ super().__init__(application_id='com.github.geigi.cozy')
self.init_custom_widgets()
GLib.setenv("PULSE_PROP_media.role", "music", True)
@@ -87,17 +86,12 @@ def __init__(self, localedir: str, pkgdatadir: str):
def do_startup(self):
log.info(distro.linux_distribution(full_distribution_name=False))
- log.info("Starting up cozy " + __version__)
+ log.info(f"Starting up cozy {__version__}")
+ log.info(f"libadwaita version: {Adw._version}")
+
self.ui = CozyUI(self.pkgdatadir, self, __version__)
+ Adw.Application.do_startup(self)
init_db()
- Gtk.Application.do_startup(self)
- Handy.init()
- try:
- manager = Handy.StyleManager.get_default()
- manager.set_color_scheme(Handy.ColorScheme.PREFER_LIGHT)
- except:
- log.info("Not setting libhandy style manager, version is too old.")
- log.info("libhandy version: {}".format(Handy._version))
self.ui.startup()
def do_activate(self):
@@ -115,15 +109,17 @@ def do_activate(self):
os.makedirs(path, exist_ok=True)
self.add_window(self.ui.window)
- mpris = MPRIS(self)
- mpris._on_current_changed()
+
+ if platform.system().lower() == "linux":
+ mpris = MPRIS(self)
+ mpris._on_current_changed()
def handle_exception(self, exc_type, exc_value, exc_traceback):
print("handle exception")
try:
reporter.exception("uncaught", exc_value, "\n".join(format_exception(exc_type, exc_value, exc_traceback)))
- except:
- pass
+ except Exception:
+ None
self.old_except_hook(exc_type, exc_value, exc_traceback)
@@ -131,7 +127,6 @@ def quit(self):
self.app_controller.quit()
super(Application, self).quit()
-
@staticmethod
def init_custom_widgets():
FilterListBox()
diff --git a/cozy/application_settings.py b/cozy/application_settings.py
index 22d3c90e..8a4cbe62 100644
--- a/cozy/application_settings.py
+++ b/cozy/application_settings.py
@@ -115,7 +115,7 @@ def dark_mode(self) -> bool:
@dark_mode.setter
def dark_mode(self, new_value: bool):
- self._settings.set_boolean("dark_mode", new_value)
+ self._settings.set_boolean("dark-mode", new_value)
@property
def window_width(self) -> int:
diff --git a/cozy/architecture/event_sender.py b/cozy/architecture/event_sender.py
index 31072a43..11c440f4 100644
--- a/cozy/architecture/event_sender.py
+++ b/cozy/architecture/event_sender.py
@@ -1,10 +1,7 @@
from typing import List, Callable
import gi
-
-gi.require_version('Gdk', '3.0')
-
-from gi.repository import Gdk, GLib
+from gi.repository import GLib
class EventSender:
@@ -14,15 +11,14 @@ def __init__(self):
self._listeners = []
def emit_event(self, event, message=None):
- if type(event) is tuple and not message:
- message = event[1]
- event = event[0]
+ if isinstance(event, tuple) and message is None:
+ event, message = event
for function in self._listeners:
function(event, message)
def emit_event_main_thread(self, event: str, message=None):
- Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, self.emit_event, (event, message))
+ GLib.MainContext.default().invoke_full(GLib.PRIORITY_DEFAULT_IDLE, self.emit_event, (event, message))
def add_listener(self, function: Callable[[str, object], None]):
self._listeners.append(function)
diff --git a/cozy/architecture/observable.py b/cozy/architecture/observable.py
index 9fae8311..eb27831f 100644
--- a/cozy/architecture/observable.py
+++ b/cozy/architecture/observable.py
@@ -1,6 +1,6 @@
from typing import Callable
-from gi.repository import Gdk, GLib
+from gi.repository import GLib
from cozy.report import reporter
import logging
@@ -47,7 +47,7 @@ def _notify(self, prop: str):
reporter.exception("observable", e)
def _notify_main_thread(self, prop: str):
- Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, self._notify, (prop))
+ GLib.MainContext.default().invoke_full(GLib.PRIORITY_DEFAULT_IDLE, self._notify, (prop))
def _destroy_observers(self):
self._observers = {}
diff --git a/cozy/control/artwork_cache.py b/cozy/control/artwork_cache.py
index 62ae184f..462e7ddd 100644
--- a/cozy/control/artwork_cache.py
+++ b/cozy/control/artwork_cache.py
@@ -2,7 +2,7 @@
import uuid
import logging
-from gi.repository import GdkPixbuf
+from gi.repository import Gdk, GdkPixbuf
from cozy.application_settings import ApplicationSettings
from cozy.control.application_directories import get_cache_dir
@@ -21,31 +21,25 @@ def __init__(self):
_app_settings = inject.instance(ApplicationSettings)
_app_settings.add_listener(self._on_app_setting_changed)
- def get_cover_pixbuf(self, book, scale, size=0):
+ def get_cover_paintable(self, book, scale, size=0) -> Gdk.Texture | None:
pixbuf = None
size *= scale
if size > 0:
- # first try the cache
+ # First try the cache
pixbuf = self._load_pixbuf_from_cache(book, size)
- if pixbuf:
- return pixbuf
- else:
- # then try the db or file
+ if not pixbuf:
+ # Then try the db or file
pixbuf = self._load_cover_pixbuf(book)
- if pixbuf:
- # return original size if it is not greater than 0
- if not size > 0:
- return pixbuf
-
- # create cached version
- pixbuf = self._create_artwork_cache(book, pixbuf, size)
- else:
- pixbuf = None
+ if not pixbuf:
+ return None
+ elif size > 0:
+ # Resize and cache artwork if size is greater than 0
+ pixbuf = self._create_artwork_cache(book, pixbuf, size)
- return pixbuf
+ return Gdk.Texture.new_for_pixbuf(pixbuf)
def delete_artwork_cache(self):
"""
@@ -240,3 +234,4 @@ def _load_pixbuf_from_file(self, book):
def _on_app_setting_changed(self, event: str, data):
if event == "prefer-external-cover":
self.delete_artwork_cache()
+
diff --git a/cozy/control/db_updater.py b/cozy/control/db_updater.py
index 0e3a49ff..bd973110 100644
--- a/cozy/control/db_updater.py
+++ b/cozy/control/db_updater.py
@@ -283,8 +283,7 @@ def update_db():
_restore_db(backup_dir_name)
from cozy.ui.db_migration_failed_view import DBMigrationFailedView
- dialog = DBMigrationFailedView()
- dialog.show()
+ DBMigrationFailedView().present()
exit(1)
if version < 10:
@@ -298,8 +297,7 @@ def update_db():
_restore_db(backup_dir_name)
from cozy.ui.db_migration_failed_view import DBMigrationFailedView
- dialog = DBMigrationFailedView()
- dialog.show()
+ DBMigrationFailedView().present()
exit(1)
@@ -354,3 +352,4 @@ def _restore_db(backup_dir_name: str):
if os.path.exists(wal_path_backup):
log.info("Copying wal file")
shutil.copyfile(wal_path_backup, wal_path)
+
diff --git a/cozy/control/filesystem_monitor.py b/cozy/control/filesystem_monitor.py
index cdffc1f3..bc826f9c 100644
--- a/cozy/control/filesystem_monitor.py
+++ b/cozy/control/filesystem_monitor.py
@@ -48,9 +48,10 @@ def init_offline_mode(self):
# go through all audiobook locations and test if they can be found in the mounts list
for storage in self._settings.external_storage_locations:
- online = False
- if any(mount.get_root().get_path() in storage.path for mount in mounts):
- online = True
+ online = any(
+ mount.get_root().get_path() and mount.get_root().get_path() in storage.path
+ for mount in mounts
+ )
self.external_storage.append(ExternalStorage(storage=storage, online=online))
def close(self):
@@ -62,18 +63,14 @@ def close(self):
def get_book_online(self, book: Book):
try:
- result = next(
+ return next(
(storage.online for storage in self.external_storage if storage.storage.path in book.chapters[0].file),
True)
- return result
except IndexError:
return True
def is_track_online(self, track):
- """
- """
- result = next((storage.online for storage in self.external_storage if storage.storage.path in track.file), True)
- return (result)
+ return next((storage.online for storage in self.external_storage if storage.storage.path in track.file), True)
def get_offline_storages(self):
return [i.storage.path for i in self.external_storage if not i.online]
diff --git a/cozy/extensions/gtk_widget.py b/cozy/extensions/gtk_widget.py
deleted file mode 100644
index 5fc474b5..00000000
--- a/cozy/extensions/gtk_widget.py
+++ /dev/null
@@ -1,16 +0,0 @@
-import logging
-
-from gi.repository import Gtk, Gdk
-
-log = logging.getLogger("gtk_widget")
-
-
-def set_hand_cursor(widget: Gtk.Widget):
- try:
- widget.props.window.set_cursor(Gdk.Cursor(Gdk.CursorType.HAND2))
- except:
- log.error("Broken mouse theme, failed to set cursor.")
-
-
-def reset_cursor(widget: Gtk.Widget):
- widget.props.window.set_cursor(None)
diff --git a/cozy/media/files.py b/cozy/media/files.py
index 12b84d53..f31efb69 100644
--- a/cozy/media/files.py
+++ b/cozy/media/files.py
@@ -10,7 +10,7 @@
from cozy.media.importer import Importer
from cozy.model.settings import Settings
from cozy.report import reporter
-from cozy.ui.info_banner import InfoBanner
+from cozy.ui.toaster import ToastNotifier
log = logging.getLogger("files")
@@ -18,7 +18,7 @@
class Files(EventSender):
_settings = inject.attr(Settings)
_importer = inject.attr(Importer)
- _info_bar: InfoBanner = inject.attr(InfoBanner)
+ _toast: ToastNotifier = inject.attr(ToastNotifier)
_file_count = 0
_file_progess = 0
@@ -29,23 +29,21 @@ def __init__(self):
def copy(self, selection):
log.info("Start of copying files")
self.emit_event_main_thread("start-copy", None)
- uris = selection.get_uris()
+
+ paths = [f.get_path() for f in selection]
storage_location = self._settings.default_location.path
self._file_count = 0
self._file_progess = 0
- self._count_all_files(uris)
- self._copy_all(uris, storage_location)
+ self._count_all_files(paths)
+ self._copy_all(paths, storage_location)
log.info("Copying of files finished")
self._importer.scan()
def _copy_all(self, sources, destination: str):
- for uri in sources:
- parsed_path = urllib.parse.urlparse(uri)
- path = urllib.parse.unquote(parsed_path.path)
-
+ for path in sources:
if os.path.isdir(path):
self._copy_directory(path, destination)
else:
@@ -66,11 +64,11 @@ def _copy_file(self, source_path: str, dest_path: str):
if e.code == Gio.IOErrorEnum.CANCELLED:
pass
elif e.code == Gio.IOErrorEnum.READ_ONLY:
- self._info_bar.show(_("Cannot copy: Audiobook directory is read only"))
+ self._toast.show(_("Cannot copy: Audiobook directory is read only"))
elif e.code == Gio.IOErrorEnum.NO_SPACE:
- self._info_bar.show(_("Cannot copy: Disk is full"))
+ self._toast.show(_("Cannot copy: Disk is full"))
elif e.code == Gio.IOErrorEnum.PERMISSION_DENIED:
- self._info_bar.show(_("Cannot copy: Permission denied"))
+ self._toast.show(_("Cannot copy: Permission denied"))
else:
reporter.exception("files", e)
@@ -86,7 +84,7 @@ def _copy_directory(self, path, destination):
Path(destination_dir).mkdir(parents=True, exist_ok=True)
except PermissionError as e:
log.error(e)
- self._info_bar.show(_("Cannot copy: Permission denied"))
+ self._toast.show(_("Cannot copy: Permission denied"))
return
for file in filenames:
@@ -94,10 +92,8 @@ def _copy_directory(self, path, destination):
file_copy_destination = os.path.join(destination, dirname, file)
self._copy_file(source, file_copy_destination)
- def _count_all_files(self, uris):
- for uri in uris:
- parsed_path = urllib.parse.urlparse(uri)
- path = urllib.parse.unquote(parsed_path.path)
+ def _count_all_files(self, paths: list[str]) -> None:
+ for path in paths:
if os.path.isdir(path):
self._file_count += self._count_files_in_folder(path)
else:
diff --git a/cozy/media/gst_player.py b/cozy/media/gst_player.py
index 22fcde55..27396c40 100644
--- a/cozy/media/gst_player.py
+++ b/cozy/media/gst_player.py
@@ -5,12 +5,9 @@
from enum import Enum, auto
from typing import Optional
-import gi
-
from cozy.architecture.event_sender import EventSender
from cozy.report import reporter
-gi.require_version('Gst', '1.0')
from gi.repository import Gst
log = logging.getLogger("gst_player")
diff --git a/cozy/media/importer.py b/cozy/media/importer.py
index e79de6d3..d5429372 100644
--- a/cozy/media/importer.py
+++ b/cozy/media/importer.py
@@ -18,7 +18,7 @@
from cozy.model.library import Library
from cozy.model.settings import Settings
from cozy.report import reporter
-from cozy.ui.info_banner import InfoBanner
+from cozy.ui.toaster import ToastNotifier
log = logging.getLogger("importer")
@@ -56,7 +56,7 @@ class Importer(EventSender):
_settings = inject.attr(Settings)
_library = inject.attr(Library)
_database_importer = inject.attr(DatabaseImporter)
- _info_bar: InfoBanner = inject.attr(InfoBanner)
+ _toast: ToastNotifier = inject.attr(ToastNotifier)
def __init__(self):
super().__init__()
@@ -118,7 +118,7 @@ def _execute_import(self, files_to_scan: List[str]) -> (Set[str], Set[str]):
log.error("Error while inserting new tracks to the database")
reporter.exception("importer", e)
log.error(traceback.format_exc())
- self._info_bar.show("{}: {}".format(_("Error while importing new files"), str(e.__class__)))
+ self._toast.show("{}: {}".format(_("Error while importing new files"), str(e.__class__)))
if self._progress >= self._files_count:
break
diff --git a/cozy/media/media_detector.py b/cozy/media/media_detector.py
index 6a3fb705..c5f3fb19 100644
--- a/cozy/media/media_detector.py
+++ b/cozy/media/media_detector.py
@@ -3,13 +3,9 @@
from cozy.architecture.event_sender import EventSender
-import gi
-
from cozy.media.media_file import MediaFile
from cozy.media.tag_reader import TagReader
-gi.require_version('Gst', '1.0')
-gi.require_version('GstPbutils', '1.0')
from gi.repository import Gst, GstPbutils
log = logging.getLogger("media_detector")
diff --git a/cozy/media/player.py b/cozy/media/player.py
index a7521633..7a12122e 100644
--- a/cozy/media/player.py
+++ b/cozy/media/player.py
@@ -18,7 +18,7 @@
from cozy.report import reporter
from cozy.tools import IntervalTimer
from cozy.ui.file_not_found_dialog import FileNotFoundDialog
-from cozy.ui.info_banner import InfoBanner
+from cozy.ui.toaster import ToastNotifier
log = logging.getLogger("mediaplayer")
@@ -30,7 +30,7 @@ class Player(EventSender):
_library: Library = inject.attr(Library)
_app_settings: ApplicationSettings = inject.attr(ApplicationSettings)
_offline_cache: OfflineCache = inject.attr(OfflineCache)
- _info_bar: InfoBanner = inject.attr(InfoBanner)
+ _toast: ToastNotifier = inject.attr(ToastNotifier)
_importer: Importer = inject.attr(Importer)
_gst_player: GstPlayer = inject.attr(GstPlayer)
@@ -285,6 +285,7 @@ def _next_chapter(self):
if not self._book:
log.error("Cannot play next chapter because no book reference is stored.")
reporter.error("player", "Cannot play next chapter because no book reference is stored.")
+ return
index_current_chapter = self._book.chapters.index(self._book.current_chapter)
@@ -327,14 +328,14 @@ def _on_gst_player_event(self, event: str, message):
def _handle_gst_error(self, error: GLib.Error):
if error.code != Gst.ResourceError.BUSY:
- self._info_bar.show(error.message)
+ self._toast.show(error.message)
if error.code == Gst.ResourceError.OPEN_READ or Gst.ResourceError.READ:
self._stop_playback()
def _handle_file_not_found(self):
if self.loaded_chapter:
- FileNotFoundDialog(self.loaded_chapter).show()
+ FileNotFoundDialog(self.loaded_chapter).present()
self._stop_playback()
else:
log.warning("No chapter loaded, cannot display file not found dialog.")
diff --git a/cozy/media/tag_reader.py b/cozy/media/tag_reader.py
index 1f60886f..00425b48 100644
--- a/cozy/media/tag_reader.py
+++ b/cozy/media/tag_reader.py
@@ -2,12 +2,9 @@
from typing import List
from urllib.parse import unquote, urlparse
-import gi
import mutagen
from mutagen.mp4 import MP4
-gi.require_version('Gst', '1.0')
-gi.require_version('GstPbutils', '1.0')
from gi.repository import GstPbutils, Gst, GLib
from cozy.media.chapter import Chapter
diff --git a/cozy/model/book.py b/cozy/model/book.py
index 226b9d36..9e4d5bc2 100644
--- a/cozy/model/book.py
+++ b/cozy/model/book.py
@@ -202,7 +202,6 @@ def remove(self):
self.destroy_listeners()
self._destroy_observers()
- @timing
def _fetch_chapters(self):
tracks = TrackModel \
.select() \
diff --git a/cozy/report/report_to_loki.py b/cozy/report/report_to_loki.py
index 0bee75d2..6b779065 100644
--- a/cozy/report/report_to_loki.py
+++ b/cozy/report/report_to_loki.py
@@ -13,10 +13,6 @@
from peewee import __version__ as PeeweeVersion
from mutagen import version_string as MutagenVersion
-import gi
-
-gi.require_version('Gtk', '3.0')
-
from gi.repository import Gtk
URL = 'https://errors.cozy.sh:3100/api/prom/push'
diff --git a/cozy/ui/app_view.py b/cozy/ui/app_view.py
index ad2950a5..cc16e548 100644
--- a/cozy/ui/app_view.py
+++ b/cozy/ui/app_view.py
@@ -1,4 +1,4 @@
-from gi.repository import Gtk, Handy
+from gi.repository import Gtk, Adw
from cozy.ext import inject
from cozy.view_model.app_view_model import AppViewModel
@@ -9,9 +9,6 @@
PREPARING_LIBRARY = "import"
BOOK_DETAIL = "book_overview"
-LIBRARY_FILTER = "filter"
-LIBRARY_BOOKS = "books"
-
class AppView:
_view_model: AppViewModel = inject.attr(AppViewModel)
@@ -27,12 +24,11 @@ def __init__(self, builder: Gtk.Builder):
def _get_ui_elements(self):
self._main_stack: Gtk.Stack = self._builder.get_object("main_stack")
- self._library_leaflet: Handy.Leaflet = self._builder.get_object("library_leaflet")
+ self._navigation_view: Adw.NavigationView = self._builder.get_object("navigation_view")
def _connect_ui_elements(self):
self._main_stack.connect("notify::visible-child", self._update_view_model_view)
- self._library_leaflet.connect("notify::folded", self._update_view_model_view)
- self._library_leaflet.connect("notify::visible-child", self._update_view_model_view)
+ self._navigation_view.connect("notify::visible-page", self._update_view_model_view)
def _connect_view_model(self):
self._view_model.bind_to("view", self._on_view_changed)
@@ -46,28 +42,17 @@ def _on_view_changed(self):
self._main_stack.set_visible_child_name(PREPARING_LIBRARY)
elif view == View.LIBRARY:
self._main_stack.set_visible_child_name(LIBRARY)
- elif view == View.LIBRARY_FILTER:
- self._main_stack.set_visible_child_name(LIBRARY)
- self._library_leaflet.set_visible_child_name(LIBRARY_FILTER)
- elif view == View.LIBRARY_BOOKS:
- self._main_stack.set_visible_child_name(LIBRARY)
- self._library_leaflet.set_visible_child_name(LIBRARY_BOOKS)
- def _update_view_model_view(self, _, __):
+ def _update_view_model_view(self, *_):
page = self._main_stack.props.visible_child_name
- library_folded = self._library_leaflet.props.folded
- library_page = self._library_leaflet.props.visible_child_name
- if page == EMPTY_STATE:
+ if page == LIBRARY:
+ if self._navigation_view.props.visible_page.props.tag == BOOK_DETAIL:
+ self._view_model.view = View.BOOK_DETAIL
+ else:
+ self._view_model.view = View.LIBRARY
+ elif page == EMPTY_STATE:
self._view_model.view = View.EMPTY_STATE
elif page == PREPARING_LIBRARY:
self._view_model.view = View.PREPARING_LIBRARY
- elif page == BOOK_DETAIL:
- self._view_model.view = View.BOOK_DETAIL
- elif page == LIBRARY:
- if library_folded and library_page == LIBRARY_FILTER:
- self._view_model.view = View.LIBRARY_FILTER
- elif library_folded and library_page == LIBRARY_BOOKS:
- self._view_model.view = View.LIBRARY_BOOKS
- elif not library_folded:
- self._view_model.view = View.LIBRARY
+
diff --git a/cozy/ui/book_detail_view.py b/cozy/ui/book_detail_view.py
index 18ee1577..3a25271c 100644
--- a/cozy/ui/book_detail_view.py
+++ b/cozy/ui/book_detail_view.py
@@ -12,22 +12,21 @@
from cozy.report import reporter
from cozy.ui.chapter_element import ChapterElement
from cozy.ui.disk_element import DiskElement
-from cozy.ui.widgets.album_art import AlbumArt
from cozy.view_model.book_detail_view_model import BookDetailViewModel
-gi.require_version('Gtk', '3.0')
-
-from gi.repository import Gtk, Gdk, GLib
+from gi.repository import Adw, GLib, Gtk
log = logging.getLogger("BookDetailView")
+ALBUM_ART_SIZE = 256
+
+
@Gtk.Template.from_resource('/com/github/geigi/cozy/book_detail.ui')
-class BookDetailView(Gtk.EventBox):
+class BookDetailView(Gtk.Box):
__gtype_name__ = 'BookDetail'
play_book_button: Gtk.Button = Gtk.Template.Child()
- play_img: Gtk.Image = Gtk.Template.Child()
book_label: Gtk.Label = Gtk.Template.Child()
author_label: Gtk.Label = Gtk.Template.Child()
@@ -45,6 +44,7 @@ class BookDetailView(Gtk.EventBox):
download_image: Gtk.Image = Gtk.Template.Child()
download_switch: Gtk.Switch = Gtk.Template.Child()
+ album_art: Gtk.Picture = Gtk.Template.Child()
album_art_container: Gtk.Box = Gtk.Template.Child()
unavailable_box: Gtk.Box = Gtk.Template.Child()
@@ -63,15 +63,16 @@ class BookDetailView(Gtk.EventBox):
def __init__(self, main_window_builder: Gtk.Builder):
super().__init__()
- self._main_stack: Gtk.Stack = main_window_builder.get_object("main_stack")
- # self._toolbar_revealer: Gtk.Revealer = main_window_builder.get_object("toolbar_revealer")
- self._main_stack.add_named(self, "book_overview")
+ self._navigation_view: Adw.NavigationView = main_window_builder.get_object("navigation_view")
+ self._book_details_container: Adw.ToolbarView = main_window_builder.get_object("book_details_container")
+ self._book_details_container.set_content(self)
- if Gtk.get_minor_version() > 20:
- self.book_overview_scroller.props.propagate_natural_height = True
+ headerbar = Adw.HeaderBar()
+ self.header_title = Adw.WindowTitle()
+ headerbar.set_title_widget(self.header_title)
+ self._book_details_container.add_top_bar(headerbar)
- self.art = AlbumArt()
- self.album_art_container.pack_start(self.art, True, True, 0)
+ self.book_overview_scroller.props.propagate_natural_height = True
self._chapters_event: Event = Event()
self._chapters_thread: Thread = None
@@ -79,7 +80,6 @@ def __init__(self, main_window_builder: Gtk.Builder):
self._connect_view_model()
self._connect_widgets()
- self._add_mouse_button_accel()
def _connect_view_model(self):
self._view_model.bind_to("book", self._on_book_changed)
@@ -98,13 +98,6 @@ def _connect_view_model(self):
def _connect_widgets(self):
self.play_book_button.connect("clicked", self._play_book_clicked)
self.download_switch.connect("state-set", self._download_switch_changed)
- self.main_flow_box.connect("size-allocate", self._main_flow_box_size_changed)
-
- def _add_mouse_button_accel(self):
- self.gesture = Gtk.GestureMultiPress(widget=self)
- self.gesture.set_button(0)
- self.gesture.set_propagation_phase(Gtk.PropagationPhase.CAPTURE)
- self.gesture.connect('pressed', self._on_mouse_event)
def _on_book_changed(self):
if not self._view_model.book:
@@ -130,6 +123,9 @@ def _on_book_changed(self):
self.book_label.set_text(book.name)
self.author_label.set_text(book.author)
+ self.header_title.set_title(book.name)
+ self.header_title.set_subtitle(book.author)
+
self.last_played_label.set_text(self._view_model.last_played_text)
self._set_cover_image(book)
@@ -137,15 +133,19 @@ def _on_book_changed(self):
self._display_external_section()
self._set_progress()
- def _open_book_overview(self):
- self._main_stack.set_visible_child_name("book_overview")
- #self._toolbar_revealer.set_reveal_child(False)
+ def _on_open(self):
+ if self._navigation_view.props.visible_page.props.tag == "book_overview":
+ self._navigation_view.pop_to_tag("book_overview")
+ else:
+ self._navigation_view.push_by_tag("book_overview")
def _on_play_changed(self):
playing = self._view_model.playing
- play_button_img = "pause-symbolic" if playing else "play-symbolic"
- self.play_img.set_from_icon_name(play_button_img, Gtk.IconSize.DND)
+ if playing:
+ self.play_book_button.set_icon_name("media-playback-pause-symbolic")
+ else:
+ self.play_book_button.set_icon_name("media-playback-start-symbolic")
if self._current_selected_chapter:
self._current_selected_chapter.set_playing(playing)
@@ -209,9 +209,9 @@ def _schedule_chapters_rendering(self, book: Book, callback: Callable):
return
if multiple_disks and disk_number != chapter.disk:
- Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, self._add_disk, book.id, chapter)
+ GLib.MainContext.default().invoke_full(GLib.PRIORITY_DEFAULT_IDLE, self._add_disk, book.id, chapter)
- Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, self._add_chapter, book.id, chapter)
+ GLib.MainContext.default().invoke_full(GLib.PRIORITY_DEFAULT_IDLE, self._add_chapter, book.id, chapter)
disk_number = chapter.disk
@@ -219,7 +219,7 @@ def _schedule_chapters_rendering(self, book: Book, callback: Callable):
self._chapters_event.wait()
self._chapters_event.clear()
- Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, callback)
+ GLib.MainContext.default().invoke_full(GLib.PRIORITY_DEFAULT_IDLE, callback)
def _on_chapters_displayed(self):
self.total_label.set_text(self._view_model.total_text)
@@ -248,8 +248,7 @@ def _add_disk(self, book_id: int, chapter: Chapter):
return
disc_element = DiskElement(chapter.disk)
- self.chapter_box.add(disc_element)
- disc_element.show_all()
+ self.chapter_box.append(disc_element)
self._chapters_event.set()
def _add_chapter(self, book_id: int, chapter: Chapter):
@@ -258,22 +257,22 @@ def _add_chapter(self, book_id: int, chapter: Chapter):
chapter_element = ChapterElement(chapter)
chapter_element.connect("play-pause-clicked", self._play_chapter_clicked)
- self.chapter_box.add(chapter_element)
- chapter_element.show_all()
+ self.chapter_box.append(chapter_element)
self._chapters_event.set()
def _schedule_chapters_clearing(self):
- Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, self.chapter_box.remove_all_children)
+ GLib.MainContext.default().invoke_full(GLib.PRIORITY_DEFAULT_IDLE, self.chapter_box.remove_all_children)
def _set_progress(self):
self.remaining_label.set_text(self._view_model.remaining_text)
self.book_progress_bar.set_fraction(self._view_model.progress_percent)
def _set_cover_image(self, book: Book):
- pixbuf = self._artwork_cache.get_cover_pixbuf(book, self.get_scale_factor(), 250)
- if pixbuf:
+ paintable = self._artwork_cache.get_cover_paintable(book, self.get_scale_factor(), ALBUM_ART_SIZE)
+ if paintable:
self.album_art_container.set_visible(True)
- self.art.set_art(pixbuf)
+ self.album_art.set_paintable(paintable)
+ self.album_art.set_overflow(True)
else:
self.album_art_container.set_visible(False)
@@ -291,19 +290,6 @@ def _prepare_chapters_job(self):
def _download_switch_changed(self, _, state: bool):
self._view_model.download_book(state)
- def _main_flow_box_size_changed(self, _, __):
- if self._is_chapter_box_wrapped():
- vertical_scroll_policy = Gtk.PolicyType.NEVER
- else:
- vertical_scroll_policy = Gtk.PolicyType.ALWAYS
-
- self.book_overview_scroller.set_policy(Gtk.PolicyType.NEVER, vertical_scroll_policy)
-
- def _is_chapter_box_wrapped(self):
- x, _ = self.book_overview_scroller.translate_coordinates(self.main_flow_box, 0, 0)
-
- return x < 100
-
def _set_book_download_status(self):
if not self._view_model.is_book_external:
return
@@ -315,7 +301,7 @@ def _set_book_download_status(self):
icon_name = "download-symbolic"
text = _("Download")
- self.download_image.set_from_icon_name(icon_name, Gtk.IconSize.LARGE_TOOLBAR)
+ self.download_image.set_from_icon_name(icon_name)
self.download_label.set_text(text)
def _play_chapter_clicked(self, _, chapter: Chapter):
@@ -324,13 +310,3 @@ def _play_chapter_clicked(self, _, chapter: Chapter):
def _play_book_clicked(self, _):
self._view_model.play_book()
- def _on_mouse_event(self, gesture: Gtk.GestureMultiPress, _, __, ___):
- btn = gesture.get_current_button()
- if btn == 8:
- self._view_model.navigate_back()
- return True
-
- return False
-
- def _on_open(self):
- self._open_book_overview()
\ No newline at end of file
diff --git a/cozy/ui/chapter_element.py b/cozy/ui/chapter_element.py
index de6318b8..10f20aae 100644
--- a/cozy/ui/chapter_element.py
+++ b/cozy/ui/chapter_element.py
@@ -5,10 +5,9 @@
@Gtk.Template.from_resource('/com/github/geigi/cozy/chapter_element.ui')
-class ChapterElement(Gtk.EventBox):
+class ChapterElement(Gtk.Box):
__gtype_name__ = "ChapterElement"
- icon_event_box: Gtk.EventBox = Gtk.Template.Child()
icon_stack: Gtk.Stack = Gtk.Template.Child()
number_label: Gtk.Label = Gtk.Template.Child()
play_icon: Gtk.Image = Gtk.Template.Child()
@@ -17,62 +16,36 @@ class ChapterElement(Gtk.EventBox):
def __init__(self, chapter: Chapter):
self.selected = False
- self.chapter: Chapter = chapter
+ self.chapter = chapter
super().__init__()
- self.connect("enter-notify-event", self._on_enter_notify)
- self.connect("leave-notify-event", self._on_leave_notify)
- self.connect("button-press-event", self._on_button_press)
- self.set_tooltip_text(_("Play this part"))
-
- # This box contains all content
- self.box = Gtk.Box()
- self.box.set_orientation(Gtk.Orientation.HORIZONTAL)
- self.box.set_spacing(3)
- self.box.set_halign(Gtk.Align.FILL)
- self.box.set_valign(Gtk.Align.CENTER)
-
- # These are the widgets that contain data
- self.play_img = Gtk.Image()
- no_label = Gtk.Label()
- title_label = Gtk.Label()
- dur_label = Gtk.Label()
-
- self.play_img.set_margin_right(5)
- self.play_img.props.width_request = 16
-
- if self.chapter.number > 0:
- no_label.set_text(str(self.chapter.number))
- no_label.props.margin = 4
- no_label.set_margin_right(7)
- no_label.set_margin_left(0)
- no_label.set_size_request(30, -1)
- no_label.set_xalign(1)
+ gesture = Gtk.GestureClick(button=Gdk.BUTTON_PRIMARY)
+ gesture.connect("released", self._on_button_press)
+ self.add_controller(gesture)
+
+ motion = Gtk.EventControllerMotion()
+ motion.connect("enter", self._on_enter_notify)
+ motion.connect("leave", self._on_leave_notify)
+ self.add_controller(motion)
self.number_label.set_text(str(self.chapter.number))
self.title_label.set_text(self.chapter.name)
self.duration_label.set_text(seconds_to_str(self.chapter.length))
- self.icon_event_box.connect("enter-notify-event", self._on_enter_notify)
- self.icon_event_box.connect("leave-notify-event", self._on_leave_notify)
-
- def _on_button_press(self, _, event):
- if event.type == Gdk.EventType.BUTTON_PRESS and event.button != 1:
- return False
-
+ def _on_button_press(self, *_):
self.emit("play-pause-clicked", self.chapter)
- return True
- def _on_enter_notify(self, _, __):
+ def _on_enter_notify(self, *_):
+ self.add_css_class("box_hover")
+ self.play_icon.add_css_class("box_hover")
+
if not self.selected:
self.icon_stack.set_visible_child_name("play_icon")
- self.get_style_context().add_class("box_hover")
- self.play_icon.get_style_context().add_class("box_hover")
- def _on_leave_notify(self, _, __):
- self.get_style_context().remove_class("box_hover")
- self.play_icon.get_style_context().remove_class("box_hover")
+ def _on_leave_notify(self, *_):
+ self.remove_css_class("box_hover")
+ self.play_icon.remove_css_class("box_hover")
if not self.selected:
self.icon_stack.set_visible_child_name("number")
@@ -87,9 +60,9 @@ def deselect(self):
def set_playing(self, playing):
if playing:
- self.play_icon.set_from_icon_name("pause-symbolic", Gtk.IconSize.SMALL_TOOLBAR)
+ self.play_icon.set_from_icon_name("media-playback-pause-symbolic")
else:
- self.play_icon.set_from_icon_name("play-symbolic", Gtk.IconSize.SMALL_TOOLBAR)
+ self.play_icon.set_from_icon_name("media-playback-start-symbolic")
GObject.type_register(ChapterElement)
diff --git a/cozy/ui/db_migration_failed_view.py b/cozy/ui/db_migration_failed_view.py
index b1df22a7..f2e81593 100644
--- a/cozy/ui/db_migration_failed_view.py
+++ b/cozy/ui/db_migration_failed_view.py
@@ -1,22 +1,31 @@
import webbrowser
import gi
+from gi.repository import Adw
-from cozy.ext import inject
+EXPLANATION = _("During an update of the database an error occurred and Cozy will not be able to startup.\
+ A backup of the database was created before the update and has been restored now.\
+ Until this issue is resolved please use version 0.9.5 of Cozy.\
+ You can help resolve this problem by reporting an issue on GitHub.")
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk
+class DBMigrationFailedView(Adw.MessageDialog):
+ def __init__(self):
+ super().__init__(
+ heading=_("Failed to Update Database"),
+ body=EXPLANATION,
+ default_response="help",
+ close_response="close",
+ modal=True,
+ )
-@Gtk.Template(resource_path='/com/github/geigi/cozy/db_migration_failed.ui')
-class DBMigrationFailedView(Gtk.Dialog):
- __gtype_name__ = 'DBMigrationFailedDialog'
+ self.add_response("close", _("Close Cozy"))
+ self.add_response("help", _("Receive help on GitHub"))
+ self.set_response_appearance("help", Adw.ResponseAppearance.SUGGESTED)
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
+ self.connect("response", self.get_help)
- def show(self):
- response = self.run()
-
- if response == Gtk.ResponseType.OK:
+ def get_help(self, *_, response):
+ if response == "help":
webbrowser.open("https://github.com/geigi/cozy/issues", new=2)
+
diff --git a/cozy/ui/delete_book_view.py b/cozy/ui/delete_book_view.py
index aa2dfac6..33743767 100644
--- a/cozy/ui/delete_book_view.py
+++ b/cozy/ui/delete_book_view.py
@@ -1,26 +1,43 @@
-import gi
+from gi.repository import Adw, Gtk
from cozy.ext import inject
+from cozy.control.artwork_cache import ArtworkCache
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk
-
-
-@Gtk.Template(resource_path='/com/github/geigi/cozy/delete_book_dialog.ui')
-class DeleteBookView(Gtk.Dialog):
- __gtype_name__ = 'DeleteBookDialog'
+class DeleteBookView(Adw.MessageDialog):
main_window = inject.attr("MainWindow")
+ artwork_cache: ArtworkCache = inject.attr(ArtworkCache)
+
+ def __init__(self, callback, book):
+ super().__init__(
+ heading=_("Delete Audiobook?"),
+ body=_("The audiobook will be removed from your disk and from Cozy's library."),
+ default_response="cancel",
+ close_response="cancel",
+ transient_for=self.main_window.window,
+ modal=True,
+ )
+
+ self.add_response("cancel", _("Cancel"))
+ self.add_response("delete", _("Remove Audiobook"))
+ self.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
+
+ book_row = Adw.ActionRow(
+ title=book.name,
+ subtitle=book.author,
+ selectable=False,
+ use_markup=False,
+ )
+ album_art = Gtk.Picture(margin_top=6, margin_bottom=6)
+
+ paintable = self.artwork_cache.get_cover_paintable(book, 1, 48)
+ if paintable:
+ album_art.set_paintable(paintable)
+ book_row.add_prefix(album_art)
+
+ list_box = Gtk.ListBox(margin_top=12, css_classes=["boxed-list"])
+ list_box.append(book_row)
+ self.set_extra_child(list_box)
+
+ self.connect("response", callback, book)
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
-
- self.set_modal(self.main_window.window)
-
- def get_delete_book(self):
- response = self.run()
-
- if response == Gtk.ResponseType.APPLY:
- return True
- else:
- return False
diff --git a/cozy/ui/disk_element.py b/cozy/ui/disk_element.py
index 1b410a50..c183f0f9 100644
--- a/cozy/ui/disk_element.py
+++ b/cozy/ui/disk_element.py
@@ -1,30 +1,25 @@
from gi.repository import Gtk
+
class DiskElement(Gtk.Box):
"""
This class represents a small disk number header for the book overview track list.
"""
- disc_number = None
- container = None
def __init__(self, disc_number):
super().__init__()
- self.container = Gtk.Box()
+ self.add_css_class("dim-label")
- self.disc_number = disc_number
if disc_number > 1:
- self.container.set_margin_top(18)
+ self.set_margin_top(18)
+ self.set_margin_bottom(3)
+ self.set_margin_start(6)
+
+ image = Gtk.Image.new_from_icon_name("media-optical-cd-audio-symbolic")
+ self.append(image)
- self.container.set_margin_bottom(3)
- self.container.set_margin_left(5)
- self.container.set_orientation(Gtk.Orientation.HORIZONTAL)
- self.container.get_style_context().add_class("dim-label")
+ label = Gtk.Label(margin_start=5)
+ text = _("Disc") + " " + str(disc_number) # TODO: use formatted translation string here
+ label.set_markup(f"{text}")
+ self.append(label)
- image = Gtk.Image.new_from_icon_name("media-optical-cd-audio-symbolic", Gtk.IconSize.SMALL_TOOLBAR)
- self.container.add(image)
- label = Gtk.Label()
- label.set_margin_left(5)
- text = _("Disc") + " " + str(disc_number)
- label.set_markup("" + text + "")
- self.container.add(label)
- self.add(self.container)
diff --git a/cozy/ui/file_not_found_dialog.py b/cozy/ui/file_not_found_dialog.py
index bf5d5534..4beba83d 100644
--- a/cozy/ui/file_not_found_dialog.py
+++ b/cozy/ui/file_not_found_dialog.py
@@ -1,67 +1,64 @@
-import os
+from pathlib import Path
-from gi.repository import Gtk
+from gi.repository import Adw, Gio, GLib, Gtk
-import cozy.ui
from cozy.ext import inject
from cozy.media.importer import Importer
from cozy.model.chapter import Chapter
-from cozy.model.library import Library
-class FileNotFoundDialog():
+class FileNotFoundDialog(Adw.MessageDialog):
+ main_window = inject.attr("MainWindow")
_importer: Importer = inject.attr(Importer)
- _library: Library = inject.attr(Library)
-
def __init__(self, chapter: Chapter):
self.missing_chapter = chapter
- self.parent = cozy.ui.main_view.CozyUI()
- self.builder = Gtk.Builder.new_from_resource(
- "/com/github/geigi/cozy/file_not_found.ui")
- self.dialog = self.builder.get_object("dialog")
- self.dialog.set_modal(self.parent.window)
- self.builder.get_object("file_label").set_markup(
- "" + chapter.file + "")
-
- cancel_button = self.builder.get_object("cancel_button")
- cancel_button.connect("clicked", self.close)
- locate_button = self.builder.get_object("locate_button")
- locate_button.connect("clicked", self.locate)
-
- def show(self):
- self.dialog.show()
-
- def close(self, _):
- self.dialog.destroy()
-
- def locate(self, __):
- directory, filename = os.path.split(self.missing_chapter.file)
- dialog = Gtk.FileChooserDialog("Please locate the file " + filename, self.parent.window,
- Gtk.FileChooserAction.OPEN,
- (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
-
- filter = Gtk.FileFilter()
- filter.add_pattern(filename)
- filter.set_name(filename)
- dialog.add_filter(filter)
- path, file_extension = os.path.splitext(self.missing_chapter.file)
- filter = Gtk.FileFilter()
- filter.add_pattern("*" + file_extension)
- filter.set_name(file_extension + " files")
- dialog.add_filter(filter)
- filter = Gtk.FileFilter()
- filter.add_pattern("*")
- filter.set_name(_("All files"))
- dialog.add_filter(filter)
- dialog.set_local_only(False)
-
- response = dialog.run()
- if response == Gtk.ResponseType.OK:
- new_location = dialog.get_filename()
- self.missing_chapter.file = new_location
- self._importer.scan()
- self.dialog.destroy()
-
- dialog.destroy()
+
+ super().__init__(
+ heading=_("File not found"),
+ body=_("This file could not be found. Do you want to locate it manually?"),
+ default_response="locate",
+ close_response="cancel",
+ transient_for=self.main_window.window,
+ modal=True,
+ )
+
+ self.add_response("cancel", _("Cancel"))
+ self.add_response("locate", _("Locate"))
+ self.set_response_appearance("locate", Adw.ResponseAppearance.SUGGESTED)
+
+ label = Gtk.Label(label=chapter.file, margin_top=12)
+ label.add_css_class("monospace")
+ self.set_extra_child(label)
+
+ self.connect("response", self._on_locate)
+
+ def _on_locate(self, __, response):
+ if response == "locate":
+ file_dialog = Gtk.FileDialog(title=_("Locate Missing File"))
+
+ extension = Path(self.missing_chapter.file).suffix[1:]
+ current_extension_filter = Gtk.FileFilter(name=_("{ext} files").format(ext=extension))
+ current_extension_filter.add_suffix(extension)
+
+ audio_files_filter = Gtk.FileFilter(name=_("Audio files"))
+ audio_files_filter.add_mime_type("audio/*")
+
+ filters = Gio.ListStore.new(Gtk.FileFilter)
+ filters.append(current_extension_filter)
+ filters.append(audio_files_filter)
+
+ file_dialog.set_filters(filters)
+ file_dialog.set_default_filter(current_extension_filter)
+ file_dialog.open(self.main_window.window, None, self._file_dialog_open_callback)
+
+ def _file_dialog_open_callback(self, dialog, result):
+ try:
+ file = dialog.open_finish(result)
+ except GLib.GError:
+ pass
+ else:
+ if file is not None:
+ self.missing_chapter.file = file.get_path()
+ self._importer.scan()
+
diff --git a/cozy/ui/headerbar.py b/cozy/ui/headerbar.py
index 4d306c88..f5a01e21 100644
--- a/cozy/ui/headerbar.py
+++ b/cozy/ui/headerbar.py
@@ -6,11 +6,7 @@
from cozy.ui.widgets.progress_popover import ProgressPopover
from cozy.view_model.headerbar_view_model import HeaderbarViewModel, HeaderBarState
-gi.require_version('Gtk', '3.0')
-gi.require_version('Dazzle', '1.0')
-from gi.repository import Gtk, Handy
-from gi.repository.Handy import HeaderBar
-from gi.repository.Dazzle import ProgressMenuButton
+from gi.repository import Adw, Gtk
log = logging.getLogger("Headerbar")
@@ -18,35 +14,37 @@
@Gtk.Template.from_resource('/com/github/geigi/cozy/headerbar.ui')
-class Headerbar(HeaderBar):
+class Headerbar(Adw.Bin):
__gtype_name__ = "Headerbar"
+ headerbar: Adw.HeaderBar = Gtk.Template.Child()
+
+ show_sidebar_button: Gtk.ToggleButton = Gtk.Template.Child()
search_button: Gtk.MenuButton = Gtk.Template.Child()
menu_button: Gtk.MenuButton = Gtk.Template.Child()
- progress_menu_button: ProgressMenuButton = Gtk.Template.Child()
+ progress_menu_button: Gtk.MenuButton = Gtk.Template.Child()
+ progress_spinner: Gtk.Spinner = Gtk.Template.Child()
- back_button: Gtk.Button = Gtk.Template.Child()
- category_toolbar: Handy.ViewSwitcherTitle = Gtk.Template.Child()
+ view_switcher: Adw.ViewSwitcher = Gtk.Template.Child()
def __init__(self, main_window_builder: Gtk.Builder):
super().__init__()
- self._library_mobile_view_switcher: Handy.ViewSwitcherBar = main_window_builder.get_object(
- "library_mobile_view_switcher")
- self._library_mobile_revealer: Gtk.Revealer = main_window_builder.get_object("library_mobile_revealer")
- self._header_container: Gtk.Box = main_window_builder.get_object("header_container")
- self._header_container.pack_start(self, False, True, 0)
+ self.header_container: Adw.ToolbarView = main_window_builder.get_object("header_container")
+ self.header_container.add_top_bar(self)
+
+ self.mobile_view_switcher: Adw.ViewSwitcherBar = main_window_builder.get_object("mobile_view_switcher")
+ self.split_view: Adw.OverlaySplitView = main_window_builder.get_object("split_view")
- self._sort_stack: Gtk.Stack = main_window_builder.get_object("sort_stack")
- self.category_toolbar.set_stack(self._sort_stack)
- self._library_mobile_view_switcher.set_stack(self._sort_stack)
+ self.sort_stack: Adw.ViewStack = main_window_builder.get_object("sort_stack")
+ self.view_switcher.set_stack(self.sort_stack)
+ self.mobile_view_switcher.set_stack(self.sort_stack)
self.progress_popover = ProgressPopover()
self.progress_menu_button.set_popover(self.progress_popover)
self._headerbar_view_model: HeaderbarViewModel = inject.instance(HeaderbarViewModel)
- self._init_app_menu()
self._connect_view_model()
self._connect_widgets()
@@ -54,53 +52,47 @@ def _connect_view_model(self):
self._headerbar_view_model.bind_to("state", self._on_state_changed)
self._headerbar_view_model.bind_to("work_progress", self._on_work_progress_changed)
self._headerbar_view_model.bind_to("work_message", self._on_work_message_changed)
- self._headerbar_view_model.bind_to("can_navigate_back", self._on_can_navigate_back_changed)
- self._headerbar_view_model.bind_to("show_library_filter", self._on_show_library_filter_changed)
self._headerbar_view_model.bind_to("lock_ui", self._on_lock_ui_changed)
def _connect_widgets(self):
- self.back_button.connect("clicked", self._back_clicked)
- self.category_toolbar.connect("notify::title-visible", self._on_title_visible_changed)
+ self.split_view.connect("notify::show-sidebar", self._on_sidebar_toggle)
+ self.show_sidebar_button.connect("notify::active", self._on_sidebar_toggle)
+ self.mobile_view_switcher.connect("notify::reveal", self._on_mobile_view)
+ self.sort_stack.connect("notify::visible-child", self._on_sort_stack_changed)
+
+ def _on_sort_stack_changed(self, widget, _):
+ page = widget.props.visible_child_name
+
+ self.show_sidebar_button.set_visible(page != "recent")
+
+ def _on_mobile_view(self, widget, _):
+ if widget.props.reveal:
+ self.headerbar.set_title_widget(Adw.WindowTitle(title="Cozy"))
+ else:
+ self.headerbar.set_title_widget(self.view_switcher)
+
+ def _on_sidebar_toggle(self, widget, param):
+ show_sidebar = widget.get_property(param.name)
- def _init_app_menu(self):
- self.menu_builder = Gtk.Builder.new_from_resource("/com/github/geigi/cozy/titlebar_menu.ui")
- menu = self.menu_builder.get_object("titlebar_menu")
- self.menu_button.set_menu_model(menu)
+ if widget is self.show_sidebar_button:
+ self.split_view.set_show_sidebar(show_sidebar)
+ elif widget is self.split_view:
+ self.show_sidebar_button.set_active(show_sidebar)
def _on_state_changed(self):
if self._headerbar_view_model.state == HeaderBarState.PLAYING:
- progress_visible = False
- self.progress_menu_button.set_progress(0)
+ self.progress_menu_button.set_visible(False)
+ self.progress_popover.set_progress(0)
+ self.progress_spinner.stop()
else:
- progress_visible = True
-
- self.progress_menu_button.set_visible(progress_visible)
+ self.progress_menu_button.set_visible(True)
+ self.progress_spinner.start()
def _on_work_progress_changed(self):
- progress = self._headerbar_view_model.work_progress
- self.progress_menu_button.set_progress(progress)
- self.progress_popover.set_progress(progress)
+ self.progress_popover.set_progress(self._headerbar_view_model.work_progress)
def _on_work_message_changed(self):
self.progress_popover.set_message(self._headerbar_view_model.work_message)
- def _on_can_navigate_back_changed(self):
- self.back_button.set_visible(self._headerbar_view_model.can_navigate_back)
-
- def _on_show_library_filter_changed(self):
- self.category_toolbar.set_visible(self._headerbar_view_model.show_library_filter)
- self._reveal_mobile_library_filter(self.category_toolbar.get_title_visible())
-
- def _back_clicked(self, _):
- self._headerbar_view_model.navigate_back()
-
- def _on_title_visible_changed(self, widget, param):
- visible = widget.get_property(param.name)
- self._reveal_mobile_library_filter(visible)
-
- def _reveal_mobile_library_filter(self, reveal: bool):
- reveal_child = reveal and self._headerbar_view_model.show_library_filter
- self._library_mobile_revealer.set_reveal_child(reveal_child)
-
def _on_lock_ui_changed(self):
self.search_button.set_sensitive(not self._headerbar_view_model.lock_ui)
diff --git a/cozy/ui/import_failed_dialog.py b/cozy/ui/import_failed_dialog.py
index cc741270..7b98fa86 100644
--- a/cozy/ui/import_failed_dialog.py
+++ b/cozy/ui/import_failed_dialog.py
@@ -1,35 +1,59 @@
-from gi.repository import Gtk
+from gettext import gettext as _
-import cozy.ui
+from gi.repository import Adw, Gtk
+from cozy.ext import inject
-class ImportFailedDialog():
+
+HEADER = _("This can have multiple reasons:")
+POSSIBILITIES = "\n • ".join(( # yes, it is a hack, because \t would be too wide
+ "",
+ _("The audio format is not supported"),
+ _("The path or filename contains non utf-8 characters"),
+ _("The file(s) are no valid audio files"),
+ _("The file(s) are corrupt"),
+))
+
+message = HEADER + POSSIBILITIES
+
+
+class ImportFailedDialog(Adw.MessageDialog):
"""
Dialog that displays failed files on import.
"""
+ main_window = inject.attr("MainWindow")
+
+ def __init__(self, files: list[str]):
+ super().__init__(
+ heading=_("Some files could not be imported"),
+ default_response="cancel",
+ close_response="cancel",
+ transient_for=self.main_window.window,
+ modal=True,
+ )
+
+ self.add_response("cancel", _("Ok"))
+
+ box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=18)
+ body_label = Gtk.Label(label=message)
+
+ text_buffer = Gtk.TextBuffer(
+ text="\n".join(files).encode("utf-8", errors="replace").decode("utf-8")
+ )
+ text_view = Gtk.TextView(
+ buffer=text_buffer,
+ editable=False,
+ cursor_visible=False,
+ css_classes=["card", "failed-import-card", "monospace"]
+ )
+
+ scroller = Gtk.ScrolledWindow(
+ max_content_height=200,
+ propagate_natural_height=True,
+ child=text_view
+ )
+
+ box.append(body_label)
+ box.append(scroller)
+ self.set_extra_child(box)
- def __init__(self, files):
- self.parent = cozy.ui.main_view.CozyUI()
- self.builder = Gtk.Builder.new_from_resource(
- "/com/github/geigi/cozy/import_failed.ui")
- self.dialog = self.builder.get_object("dialog")
- self.dialog.set_modal(self.parent.window)
- self.text = self.builder.get_object("files_buffer")
-
- files_string = "\n".join(files)
- self.text.set_text(files_string.encode("utf-8", "replace").decode("utf-8"))
-
- locate_button = self.builder.get_object("ok_button")
- locate_button.connect("clicked", self.ok)
-
- def show(self):
- """
- show this dialog
- """
- self.dialog.show()
-
- def ok(self, button):
- """
- Close this dialog and destroy it.
- """
- self.dialog.destroy()
diff --git a/cozy/ui/info_banner.py b/cozy/ui/info_banner.py
deleted file mode 100644
index 5f3a986e..00000000
--- a/cozy/ui/info_banner.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import gi
-
-from gi.repository import Gtk
-
-from cozy.ext import inject
-
-
-class InfoBanner:
- _builder: Gtk.Builder = inject.attr("MainWindowBuilder")
-
- def __init__(self):
- super(InfoBanner, self).__init__()
-
- self._toast: Gtk.InfoBar = self._builder.get_object("error_info_bar")
- self._label: Gtk.Label = self._builder.get_object("error_message_label")
- self._toast.connect("response", self._on_response)
-
- def show(self, message: str):
- self._label.set_text(message)
- self._toast.set_revealed(True)
-
- def _on_response(self, _, __):
- self._toast.set_revealed(False)
\ No newline at end of file
diff --git a/cozy/ui/library_view.py b/cozy/ui/library_view.py
index 0695c398..3e530919 100644
--- a/cozy/ui/library_view.py
+++ b/cozy/ui/library_view.py
@@ -1,6 +1,7 @@
+import functools
from typing import Optional
-from gi.repository import Gtk, Handy
+from gi.repository import Gtk, Adw
from gi.repository.Gtk import Builder
from cozy.ext import inject
@@ -40,16 +41,16 @@ def __init__(self, builder: Builder):
def _get_ui_elements(self):
self._filter_stack: Gtk.Stack = self._builder.get_object("sort_stack")
self._main_stack: Gtk.Stack = self._builder.get_object("main_stack")
+ self._navigation_view: Adw.NavigationView = self._builder.get_object("navigation_view")
+ self._split_view: Adw.OverlaySplitView = self._builder.get_object("split_view")
self._book_box: Gtk.FlowBox = self._builder.get_object("book_box")
self._filter_stack_revealer: Gtk.Revealer = self._builder.get_object("sort_stack_revealer")
self._author_box: FilterListBox = self._builder.get_object("author_box")
self._reader_box: FilterListBox = self._builder.get_object("reader_box")
- self._library_leaflet: Handy.Leaflet = self._builder.get_object("library_leaflet")
self._book_stack: Gtk.Stack = self._builder.get_object("book_stack")
def _connect_ui_elements(self):
self._filter_stack.connect("notify::visible-child", self._on_sort_stack_changed)
- self._main_stack.connect("notify::visible-child", self._on_main_stack_changed)
self._book_box.set_sort_func(self._view_model.display_book_sort)
self._book_box.set_filter_func(self._view_model.display_book_filter)
@@ -64,7 +65,6 @@ def _connect_ui_elements(self):
def _connect_view_model(self):
self._view_model.bind_to("library_view_mode", self._on_library_view_mode_changed)
- self._view_model.bind_to("library_page", self._on_library_page_changed)
self._view_model.bind_to("authors", self.populate_author)
self._view_model.bind_to("readers", self.populate_reader)
self._view_model.bind_to("books", self.populate_book_box)
@@ -88,12 +88,6 @@ def _on_sort_stack_changed(self, widget, _):
self._view_model.library_view_mode = view_mode
- def _on_main_stack_changed(self, widget, _):
- page = widget.props.visible_child_name
-
- if page != MAIN_BOOK_PAGE:
- self._view_model.library_page = LibraryPage.NONE
-
def populate_book_box(self):
self._book_box.remove_all_children()
@@ -102,10 +96,7 @@ def populate_book_box(self):
book_element.connect("play-pause-clicked", self._play_book_clicked)
book_element.connect("open-book-overview", self._open_book_overview_clicked)
book_element.connect("book-removed", self._on_book_removed)
- book_element.show_all()
- self._book_box.add(book_element)
-
- self._book_box.show_all()
+ self._book_box.append(book_element)
def populate_author(self):
self._author_box.populate(self._view_model.authors)
@@ -139,20 +130,13 @@ def _on_library_view_mode_changed(self):
self._main_stack.props.visible_child_name = main_view_page
self._filter_stack.set_visible_child_name(visible_child_name)
self._book_stack.set_visible_child_name(books_view_page)
+ self._navigation_view.pop_to_tag("main")
if active_filter_box:
self._apply_selected_filter(active_filter_box.get_selected_row())
self._invalidate_filters()
- def _on_library_page_changed(self):
- page = self._view_model.library_page
-
- if page == LibraryPage.FILTER:
- self._library_leaflet.set_visible_child_name("filter")
- elif page == LibraryPage.BOOKS:
- self._library_leaflet.set_visible_child_name("books")
-
def _invalidate_filters(self):
self._book_box.invalidate_filter()
self._book_box.invalidate_sort()
@@ -166,7 +150,6 @@ def _apply_selected_filter(self, row):
def _on_filter_row_activated(self, _, row):
self._apply_selected_filter(row)
- self._view_model.library_page = LibraryPage.BOOKS
return True
@@ -181,17 +164,19 @@ def _play_book_clicked(self, _, book):
def _open_book_overview_clicked(self, _, book):
self._view_model.open_book_detail(book)
- self._view_model.library_page = LibraryPage.NONE
+
return True
def _on_book_removed(self, _, book):
- delete_from_library = True
- delete_files = False
-
if self._view_model.book_files_exist(book):
- dialog = DeleteBookView()
- delete_from_library = delete_files = dialog.get_delete_book()
- dialog.destroy()
+ DeleteBookView(self._on_book_removed_clicked, book).present()
+
+ def _on_book_removed_clicked(self, _, response, book):
+ if response != "delete":
+ return
+
+ delete_from_library = True
+ delete_files = True # TODO: maybe an option to not delete the files
if delete_files:
self._view_model.delete_book_files(book)
@@ -203,11 +188,14 @@ def _current_book_in_playback(self):
if self._connected_book_element:
self._connected_book_element.set_playing(False)
- self._connected_book_element = next((book_element
- for book_element
- in self._book_box.get_children()
- if book_element.book == self._view_model.current_book_in_playback),
- None)
+ self._connected_book_element = None
+
+ index = 0
+ while book_element := self._book_box.get_child_at_index(index):
+ if book_element.book == self._view_model.current_book_in_playback:
+ self._connected_book_element = book_element
+ break
+ index += 1
def _playing(self):
if self._connected_book_element:
diff --git a/cozy/ui/list_box_row_with_data.py b/cozy/ui/list_box_row_with_data.py
index fc1a227b..595a2e81 100644
--- a/cozy/ui/list_box_row_with_data.py
+++ b/cozy/ui/list_box_row_with_data.py
@@ -6,27 +6,23 @@ class ListBoxRowWithData(Gtk.ListBoxRow):
This class represents a listboxitem for an author/reader.
"""
LABEL_MARGIN = 8
- ROW_MARGIN = 4
def __init__(self, data, bold=False, **properties):
super().__init__(**properties)
self.data = data
- self.set_margin_top(self.ROW_MARGIN)
- self.set_margin_bottom(self.ROW_MARGIN)
- self.set_margin_start(self.ROW_MARGIN)
- self.set_margin_end(self.ROW_MARGIN)
+ self.set_margin_bottom(3)
- self.get_style_context().add_class("filter-list-box-row")
+ self.add_css_class("filter-list-box-row")
- label: Gtk.Label = Gtk.Label.new(data)
+ label = Gtk.Label.new(data)
if bold:
label.set_markup("" + data + "")
label.set_xalign(0.0)
label.set_margin_top(self.LABEL_MARGIN)
label.set_margin_bottom(self.LABEL_MARGIN)
- label.set_margin_start(7)
+ label.set_margin_start(6)
label.set_max_width_chars(30)
label.set_ellipsize(Pango.EllipsizeMode.END)
- self.add(label)
+ self.set_child(label)
self.set_tooltip_text(data)
diff --git a/cozy/ui/list_box_separator_row.py b/cozy/ui/list_box_separator_row.py
index b0ff68bc..af1eb593 100644
--- a/cozy/ui/list_box_separator_row.py
+++ b/cozy/ui/list_box_separator_row.py
@@ -9,6 +9,6 @@ class ListBoxSeparatorRow(Gtk.ListBoxRow):
def __init__(self):
super().__init__()
separator = Gtk.Separator()
- self.add(separator)
+ self.set_child(separator)
self.set_sensitive(False)
self.props.selectable = False
\ No newline at end of file
diff --git a/cozy/ui/main_view.py b/cozy/ui/main_view.py
index 22c7bf6e..e0b2e20a 100644
--- a/cozy/ui/main_view.py
+++ b/cozy/ui/main_view.py
@@ -3,7 +3,7 @@
import webbrowser
from threading import Thread
-from gi.repository import Gtk, Gio, Gdk, GLib
+from gi.repository import Adw, Gtk, Gio, Gdk, GLib, GObject
import cozy.control.filesystem_monitor as fs_monitor
import cozy.ext.inject as inject
@@ -62,7 +62,6 @@ def activate(self, library_view: LibraryView):
def startup(self):
self.__init_resources()
- self.__init_css()
def __init_resources(self):
"""
@@ -84,91 +83,48 @@ def __init_resources(self):
self.window: Gtk.Window = self.window_builder.get_object("app_window")
- def __init_css(self):
- """
- Initialize the main css files and providers.
- Add css classes to the default screen style context.
- """
- main_cssProviderFile = Gio.File.new_for_uri(
- "resource:///com/github/geigi/cozy/application.css")
- main_cssProvider = Gtk.CssProvider()
- main_cssProvider.load_from_file(main_cssProviderFile)
-
- if Gtk.get_minor_version() > 18:
- log.debug("Fanciest design possible")
- cssProviderFile = Gio.File.new_for_uri(
- "resource:///com/github/geigi/cozy/application_default.css")
- else:
- log.debug("Using legacy css file")
- cssProviderFile = Gio.File.new_for_uri(
- "resource:///com/github/geigi/cozy/application_legacy.css")
- cssProvider = Gtk.CssProvider()
- cssProvider.load_from_file(cssProviderFile)
-
- # add the bordered css class to the default screen for the borders around album art
- screen = Gdk.Screen.get_default()
- styleContext = Gtk.StyleContext()
- styleContext.add_provider_for_screen(
- screen, main_cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
- styleContext.add_provider_for_screen(
- screen, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
- styleContext.add_class("bordered")
-
def __init_window(self):
"""
- Add fields for all ui objects we need to access from code.
- Initialize everything we can't do from glade like events and other stuff.
+ Add fields for all UI objects we need to access from code.
+ Initialize everything we can't do from the UI files like events and other stuff.
"""
- log.info("Initialize main window")
+ log.info("Initializing main window")
self._restore_window_size()
+ self.window.set_title("Cozy")
self.window.set_application(self.app)
- self.window.show_all()
- self.window.present()
- self.window.connect("delete-event", self.on_close)
- self.window.connect("drag_data_received", self.__on_drag_data_received)
- self.window.connect("size-allocate", self._on_window_size_allocate)
- self.window.drag_dest_set(Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
- [Gtk.TargetEntry.new("text/uri-list", 0, 80)], Gdk.DragAction.COPY)
- self.window.title = "Cozy"
-
- self.book_box = self.window_builder.get_object("book_box")
+
+ self.window.connect("close-request", self.on_close)
+ self.window.connect("notify::default-width", self._on_window_size_allocate)
+ self.window.connect("notify::default-height", self._on_window_size_allocate)
+
+ self._drop_target = Gtk.DropTarget()
+ self._drop_target.set_gtypes([Gdk.FileList])
+ self._drop_target.set_actions(Gdk.DragAction.COPY)
+ self._drop_target.connect("enter", self._on_drag_enter)
+ self._drop_target.connect("leave", self._on_drag_leave)
+ self._drop_target.connect("drop", self._on_drag_data_received)
+ self.window.add_controller(self._drop_target)
+
self.main_stack: Gtk.Stack = self.window_builder.get_object("main_stack")
+ self.navigation_view: Adw.NavigationView = self.window_builder.get_object("navigation_view")
+ self.drop_revealer: Gtk.Revealer = self.window_builder.get_object("drop_revealer")
- self.no_media_file_chooser = self.window_builder.get_object(
- "no_media_file_chooser")
- self.no_media_file_chooser.connect(
- "clicked", self._open_audiobook_dir_selector)
+ self.no_media_file_chooser = self.window_builder.get_object("no_media_file_chooser")
+ self.no_media_file_chooser.connect("clicked", self._open_audiobook_dir_selector)
- # get about dialog
self.about_dialog = self.about_builder.get_object("about_dialog")
self.about_dialog.set_modal(self.window)
- self.about_dialog.connect("delete-event", self.hide_window)
+ self.about_dialog.connect("close-request", self.hide_window)
self.about_dialog.set_version(self.version)
- # shortcuts
- self.accel = Gtk.AccelGroup()
-
- try:
- about_close_button = self.about_builder.get_object(
- "button_box").get_children()[2]
-
- if about_close_button:
- about_close_button.connect(
- "clicked", self.__about_close_clicked)
- except Exception as e:
- log.info("Not connecting about close button.")
+ self._preferences = PreferencesView()
- self._preferences: PreferencesView = PreferencesView()
+ self.window.present()
def __init_actions(self):
"""
Init all app actions.
"""
- self.accel = Gtk.AccelGroup()
-
- help_action = Gio.SimpleAction.new("help", None)
- help_action.connect("activate", self.help)
- self.app.add_action(help_action)
about_action = Gio.SimpleAction.new("about", None)
about_action.connect("activate", self.about)
@@ -194,8 +150,9 @@ def __init_actions(self):
self.app.add_action(self.play_pause_action)
self.app.set_accels_for_action("app.play_pause", ["space"])
+ # NavigationView.pop-on-escape doesn't work in some cases, so this is a hack
back_action = Gio.SimpleAction.new("back", None)
- back_action.connect("activate", self.back)
+ back_action.connect("activate", lambda *_: self.navigation_view.pop())
self.app.add_action(back_action)
self.app.set_accels_for_action("app.back", ["Escape"])
@@ -215,12 +172,6 @@ def __init_components(self):
def get_object(self, name):
return self.window_builder.get_object(name)
- def help(self, action, parameter):
- """
- Show app help.
- """
- webbrowser.open("https://github.com/geigi/cozy/issues", new=2)
-
def quit(self, action, parameter):
"""
Quit app.
@@ -232,7 +183,25 @@ def about(self, action, parameter):
"""
Show about window.
"""
- self.about_dialog.show()
+ self.about_dialog.add_acknowledgement_section(
+ _("Patreon Supporters"),
+ ["Fred Warren", "Gabriel", "Hu Mann", "Josiah", "Oleksii Kriukov"]
+ )
+ self.about_dialog.add_acknowledgement_section(
+ _("m4b chapter support in mutagen"),
+ ("mweinelt",),
+ )
+ self.about_dialog.add_acknowledgement_section(
+ _("Open Source Projects"),
+ ("Lollypop music player https://gitlab.gnome.org/World/lollypop",),
+ )
+ self.about_dialog.add_legal_section(
+ "python-inject",
+ "© 2010 Ivan Korobkov",
+ Gtk.License.APACHE_2_0
+ )
+
+ self.about_dialog.present()
def show_prefs(self, action, parameter):
"""
@@ -266,7 +235,7 @@ def block_ui_buttons(self, block, scan=False):
if scan:
self.scan_action.set_enabled(sensitive)
self.hide_offline_action.set_enabled(sensitive)
- except:
+ except GLib.GError:
pass
def switch_to_playing(self):
@@ -274,15 +243,14 @@ def switch_to_playing(self):
Switch the UI state back to playing.
This enables all UI functionality for the user.
"""
- if self.main_stack.props.visible_child_name != "book_overview" and self.main_stack.props.visible_child_name != "no_media":
- self.main_stack.props.visible_child_name = "main"
+ if self.navigation_view.props.visible_page != "book_overview" and self.main_stack.props.visible_child_name != "no_media":
+ self.navigation_view.pop_to_page("main")
if self._player.loaded_book:
self.block_ui_buttons(False, True)
else:
# we want to only block the player controls
self.block_ui_buttons(False, True)
self.block_ui_buttons(True, False)
- self.window.props.window.set_cursor(None)
self.emit_event_main_thread("working", False)
def check_for_tracks(self):
@@ -298,25 +266,17 @@ def _open_audiobook_dir_selector(self, __):
if len(self._settings.storage_locations) > 0:
path = self._settings.default_location.path
- location_chooser: Gtk.FileChooserDialog = Gtk.FileChooserDialog(title=_("Set Audiobooks Directory"),
- parent=self.window,
- action=Gtk.FileChooserAction.SELECT_FOLDER)
- location_chooser.add_buttons(
- Gtk.STOCK_CANCEL,
- Gtk.ResponseType.CANCEL,
- Gtk.STOCK_OPEN,
- Gtk.ResponseType.OK,
- )
- location_chooser.set_select_multiple(False)
- location_chooser.set_current_folder(path)
- location_chooser.set_local_only(False)
- response = location_chooser.run()
-
- if response == Gtk.ResponseType.OK:
- audiobook_path = location_chooser.get_filename()
- self._set_audiobook_path(audiobook_path)
+ location_chooser = Gtk.FileDialog(title=_("Set Audiobooks Directory"))
+ location_chooser.select_folder(self.window, None, self._location_chooser_open_callback)
- location_chooser.destroy()
+ def _location_chooser_open_callback(self, dialog, result):
+ try:
+ file = dialog.select_folder_finish(result)
+ except GLib.GError:
+ pass
+ else:
+ if file is not None:
+ self._set_audiobook_path(file.get_path())
def scan(self, _, __):
thread = Thread(target=self._importer.scan, name="ScanMediaThread")
@@ -326,9 +286,6 @@ def auto_import(self):
if self.application_settings.autoscan:
self.scan(None, None)
- def back(self, action, parameter):
- self.emit_event("open_view", OpenView.LIBRARY)
-
def __on_hide_offline(self, action, value):
"""
Show/Hide offline books action handler.
@@ -336,14 +293,20 @@ def __on_hide_offline(self, action, value):
action.set_state(value)
self.application_settings.hide_offline = value.get_boolean()
- def __on_drag_data_received(self, widget, context, x, y, selection, target_type, timestamp):
- """
- We want to import the files that are dragged onto the window.
- inspired by https://stackoverflow.com/questions/24094186/drag-and-drop-file-example-in-pygobject
- """
- if target_type == 80:
- thread = Thread(target=self._files.copy, args=[selection], name="DragDropImportThread")
- thread.start()
+ def _on_drag_enter(self, *_):
+ self.drop_revealer.set_reveal_child(True)
+ self.main_stack.add_css_class("blurred")
+ return True
+
+ def _on_drag_leave(self, *_):
+ self.drop_revealer.set_reveal_child(False)
+ self.main_stack.remove_css_class("blurred")
+ return True
+
+ def _on_drag_data_received(self, widget, value, *_):
+ thread = Thread(target=self._files.copy, args=[value.get_files()], name="DnDImportThread")
+ thread.start()
+ return True
def _set_audiobook_path(self, path):
self._settings_view_model.add_first_storage_location(path)
@@ -387,8 +350,9 @@ def _restore_window_size(self):
else:
self.window.unmaximize()
- def _on_window_size_allocate(self, _, __):
- width, height = self.window.get_size()
+ def _on_window_size_allocate(self, *_):
+ width, height = self.window.get_default_size()
self.application_settings.window_width = width
self.application_settings.window_height = height
self.application_settings.window_maximize = self.window.is_maximized()
+
diff --git a/cozy/ui/media_controller.py b/cozy/ui/media_controller.py
index cfb668bd..b5c9a506 100644
--- a/cozy/ui/media_controller.py
+++ b/cozy/ui/media_controller.py
@@ -2,21 +2,156 @@
import gi
-from cozy.ui.media_controller_big import MediaControllerBig
-from cozy.ui.media_controller_small import MediaControllerSmall
+from cozy.control.artwork_cache import ArtworkCache
+from cozy.db.book import Book
+from cozy.ext import inject
+from cozy.ui.widgets.playback_speed_popover import PlaybackSpeedPopover
+from cozy.ui.widgets.seek_bar import SeekBar
+from cozy.ui.widgets.sleep_timer import SleepTimer
+from cozy.view_model.playback_control_view_model import PlaybackControlViewModel
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk, Handy
+from gi.repository import Adw, Gtk, Gdk
log = logging.getLogger("MediaController")
+COVER_SIZE = 46
+
+
+@Gtk.Template.from_resource('/com/github/geigi/cozy/media_controller.ui')
+class MediaController(Adw.BreakpointBin):
+ __gtype_name__ = "MediaController"
+
+ seek_bar_container: Gtk.Box = Gtk.Template.Child()
+
+ play_button: Gtk.Button = Gtk.Template.Child()
+ prev_button: Gtk.Button = Gtk.Template.Child()
+ next_button: Gtk.Button = Gtk.Template.Child()
+ volume_button: Gtk.ScaleButton = Gtk.Template.Child()
+
+ cover_img: Gtk.Image = Gtk.Template.Child()
+ title_label: Gtk.Label = Gtk.Template.Child()
+ subtitle_label: Gtk.Label = Gtk.Template.Child()
+
+ playback_speed_button: Gtk.MenuButton = Gtk.Template.Child()
+ timer_button: Gtk.MenuButton = Gtk.Template.Child()
+
+ timer_image: Gtk.Image = Gtk.Template.Child()
-class MediaController:
def __init__(self, main_window_builder: Gtk.Builder):
super().__init__()
- self._media_control_squeezer: Handy.Squeezer = main_window_builder.get_object("media_control_squeezer")
- self._media_controller_small: MediaControllerSmall = MediaControllerSmall()
- self._media_controller_big: MediaControllerBig = MediaControllerBig()
- self._media_control_squeezer.add(self._media_controller_big)
- self._media_control_squeezer.add(self._media_controller_small)
+ media_control_box: Gtk.Box = main_window_builder.get_object("media_control_box")
+ media_control_box.append(self)
+
+ self.seek_bar = SeekBar()
+ self.seek_bar_container.append(self.seek_bar)
+
+ self.sleep_timer: SleepTimer = SleepTimer(self.timer_image)
+ self.playback_speed_button.set_popover(PlaybackSpeedPopover())
+ self.timer_button.set_popover(self.sleep_timer)
+
+ self._playback_control_view_model: PlaybackControlViewModel = inject.instance(PlaybackControlViewModel)
+ self._artwork_cache: ArtworkCache = inject.instance(ArtworkCache)
+ self._connect_view_model()
+ self._connect_widgets()
+
+ self._on_book_changed()
+ self._on_lock_ui_changed()
+ self._on_length_changed()
+ self._on_position_changed()
+ self._on_volume_changed()
+
+ def _connect_view_model(self):
+ self._playback_control_view_model.bind_to("book", self._on_book_changed)
+ self._playback_control_view_model.bind_to("playing", self._on_play_changed)
+ self._playback_control_view_model.bind_to("length", self._on_length_changed)
+ self._playback_control_view_model.bind_to("position", self._on_position_changed)
+ self._playback_control_view_model.bind_to("lock_ui", self._on_lock_ui_changed)
+ self._playback_control_view_model.bind_to("volume", self._on_volume_changed)
+
+ def _connect_widgets(self):
+ self.play_button.connect("clicked", self._play_clicked)
+ self.prev_button.connect("clicked", self._rewind_clicked)
+ self.next_button.connect("clicked", self._forward_clicked)
+ self.volume_button.connect("value-changed", self._on_volume_button_changed)
+ self.seek_bar.connect("position-changed", self._on_seek_bar_position_changed)
+
+ self._cover_img_gesture = Gtk.GestureClick()
+ self._cover_img_gesture.connect("pressed", self._cover_clicked)
+ self.cover_img.add_controller(self._cover_img_gesture)
+
+ self.cover_img.set_cursor(Gdk.Cursor.new_from_name("pointer"))
+
+ def _set_cover_image(self, book: Book):
+ paintable = self._artwork_cache.get_cover_paintable(book, self.get_scale_factor(), COVER_SIZE)
+ if paintable:
+ self.cover_img.set_from_paintable(paintable)
+ else:
+ self.cover_img.set_from_icon_name("book-open-variant-symbolic")
+ self.cover_img.props.pixel_size = COVER_SIZE
+
+ def _on_book_changed(self) -> None:
+ book = self._playback_control_view_model.book
+ self._set_book(book)
+ self._show_media_information(bool(book))
+
+ def _show_media_information(self, visibility: bool) -> None:
+ self.title_label.set_visible(visibility)
+ self.subtitle_label.set_visible(visibility)
+ self.cover_img.set_visible(visibility)
+ self.seek_bar.visible = visibility
+
+ def _set_book(self, book: Book) -> None:
+ if book is not None:
+ self._set_cover_image(book)
+ self.title_label.set_text(book.name)
+ self.title_label.set_tooltip_text(book.name)
+ self.subtitle_label.set_text(book.current_chapter.name)
+ self.subtitle_label.set_tooltip_text(book.current_chapter.name)
+
+ def _on_play_changed(self):
+ if self._playback_control_view_model.playing:
+ self.play_button.set_icon_name("media-playback-pause-symbolic")
+ else:
+ self.play_button.set_icon_name("media-playback-start-symbolic")
+
+ def _on_position_changed(self):
+ position = self._playback_control_view_model.position
+ if position is not None:
+ self.seek_bar.position = position
+
+ def _on_length_changed(self):
+ length = self._playback_control_view_model.length
+ if length:
+ self.seek_bar.length = length
+
+ def _on_lock_ui_changed(self):
+ sensitive = not self._playback_control_view_model.lock_ui
+ self.seek_bar.sensitive = sensitive
+ self.prev_button.set_sensitive(sensitive)
+ self.next_button.set_sensitive(sensitive)
+ self.play_button.set_sensitive(sensitive)
+ self.volume_button.set_sensitive(sensitive)
+ self.playback_speed_button.set_sensitive(sensitive)
+ self.timer_button.set_sensitive(sensitive)
+
+ def _on_volume_changed(self):
+ self.volume_button.set_value(self._playback_control_view_model.volume)
+
+ def _play_clicked(self, *_):
+ self._playback_control_view_model.play_pause()
+
+ def _rewind_clicked(self, *_):
+ self._playback_control_view_model.rewind()
+
+ def _forward_clicked(self, *_):
+ self._playback_control_view_model.forward()
+
+ def _cover_clicked(self, *_):
+ self._playback_control_view_model.open_book_detail()
+
+ def _on_volume_button_changed(self, _, volume):
+ self._playback_control_view_model.volume = volume
+
+ def _on_seek_bar_position_changed(self, _, position):
+ self._playback_control_view_model.position = position
diff --git a/cozy/ui/media_controller_big.py b/cozy/ui/media_controller_big.py
deleted file mode 100644
index 89ef9e9a..00000000
--- a/cozy/ui/media_controller_big.py
+++ /dev/null
@@ -1,175 +0,0 @@
-import logging
-
-import gi
-
-from cozy.control.artwork_cache import ArtworkCache
-from cozy.db.book import Book
-from cozy.ext import inject
-from cozy.ui.widgets.playback_speed_popover import PlaybackSpeedPopover
-from cozy.ui.widgets.seek_bar import SeekBar
-from cozy.ui.widgets.sleep_timer import SleepTimer
-from cozy.view_model.playback_control_view_model import PlaybackControlViewModel
-
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk, Gdk
-
-log = logging.getLogger("Headerbar")
-
-COVER_SIZE = 46
-
-
-@Gtk.Template.from_resource('/com/github/geigi/cozy/media_controller_big.ui')
-class MediaControllerBig(Gtk.Box):
- __gtype_name__ = "MediaControllerBig"
-
- seek_bar_container: Gtk.Box = Gtk.Template.Child()
-
- play_button: Gtk.Button = Gtk.Template.Child()
- prev_button: Gtk.Button = Gtk.Template.Child()
- next_button: Gtk.Button = Gtk.Template.Child()
- volume_button: Gtk.VolumeButton = Gtk.Template.Child()
-
- cover_img: Gtk.Image = Gtk.Template.Child()
- cover_img_event_box: Gtk.EventBox = Gtk.Template.Child()
- title_label: Gtk.Label = Gtk.Template.Child()
- subtitle_label: Gtk.Label = Gtk.Template.Child()
-
- playback_speed_button: Gtk.MenuButton = Gtk.Template.Child()
- timer_button: Gtk.MenuButton = Gtk.Template.Child()
-
- timer_image: Gtk.Image = Gtk.Template.Child()
- play_img: Gtk.Image = Gtk.Template.Child()
-
- def __init__(self):
- super().__init__()
-
- self.seek_bar = SeekBar()
- self.seek_bar_container.add(self.seek_bar)
-
- self.sleep_timer: SleepTimer = SleepTimer(self.timer_image)
- self.playback_speed_button.set_popover(PlaybackSpeedPopover())
- self.timer_button.set_popover(self.sleep_timer)
-
- self._playback_control_view_model: PlaybackControlViewModel = inject.instance(PlaybackControlViewModel)
- self._artwork_cache: ArtworkCache = inject.instance(ArtworkCache)
- self._connect_view_model()
- self._connect_widgets()
-
- self._on_book_changed()
- self._on_lock_ui_changed()
- self._on_length_changed()
- self._on_position_changed()
- self._on_volume_changed()
-
- def _connect_view_model(self):
- self._playback_control_view_model.bind_to("book", self._on_book_changed)
- self._playback_control_view_model.bind_to("playing", self._on_play_changed)
- self._playback_control_view_model.bind_to("length", self._on_length_changed)
- self._playback_control_view_model.bind_to("position", self._on_position_changed)
- self._playback_control_view_model.bind_to("lock_ui", self._on_lock_ui_changed)
- self._playback_control_view_model.bind_to("volume", self._on_volume_changed)
-
- def _connect_widgets(self):
- self.play_button.connect("clicked", self._play_clicked)
- self.prev_button.connect("clicked", self._rewind_clicked)
- self.next_button.connect("clicked", self._forward_clicked)
- self.volume_button.connect("value-changed", self._on_volume_button_changed)
- self.seek_bar.connect("position-changed", self._on_seek_bar_position_changed)
- self.cover_img_event_box.connect("button-press-event", self._cover_clicked)
- self.cover_img_event_box.connect("enter-notify-event", self._on_cover_enter_notify)
- self.cover_img_event_box.connect("leave-notify-event", self._on_cover_leave_notify)
-
- def _set_cover_image(self, book: Book):
- pixbuf = self._artwork_cache.get_cover_pixbuf(book, self.get_scale_factor(), COVER_SIZE)
- if pixbuf:
- surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, self.get_scale_factor(), None)
- self.cover_img.set_from_surface(surface)
- else:
- self.cover_img.set_from_icon_name("book-open-variant-symbolic", Gtk.IconSize.DIALOG)
- self.cover_img.props.pixel_size = COVER_SIZE
-
- def _on_book_changed(self):
- book = self._playback_control_view_model.book
- if book:
- visibility = True
- self._set_book()
- else:
- visibility = False
-
- self._show_media_information(visibility)
-
- def _show_media_information(self, visibility):
- self.title_label.set_visible(visibility)
- self.subtitle_label.set_visible(visibility)
- self.cover_img.set_visible(visibility)
- self.seek_bar.visible = visibility
-
- def _set_book(self):
- book = self._playback_control_view_model.book
-
- self._set_cover_image(book)
- self.title_label.set_text(book.name)
- self.title_label.set_tooltip_text(book.name)
- self.subtitle_label.set_text(book.current_chapter.name)
- self.subtitle_label.set_tooltip_text(book.current_chapter.name)
-
- def _on_play_changed(self):
- playing = self._playback_control_view_model.playing
-
- play_button_img = "pause-symbolic" if playing else "play-symbolic"
- icon_size = 16 if playing else 20
- self.play_img.set_from_icon_name(play_button_img, Gtk.IconSize.LARGE_TOOLBAR)
- self.play_img.set_pixel_size(icon_size)
-
- def _on_position_changed(self):
- position = self._playback_control_view_model.position
- if position is not None:
- self.seek_bar.position = position
-
- def _on_length_changed(self):
- length = self._playback_control_view_model.length
- if length:
- self.seek_bar.length = length
-
- def _on_lock_ui_changed(self):
- sensitive = not self._playback_control_view_model.lock_ui
- self.seek_bar.sensitive = sensitive
- self.prev_button.set_sensitive(sensitive)
- self.next_button.set_sensitive(sensitive)
- self.play_button.set_sensitive(sensitive)
- self.volume_button.set_sensitive(sensitive)
- self.playback_speed_button.set_sensitive(sensitive)
- self.timer_button.set_sensitive(sensitive)
-
- def _on_volume_changed(self):
- self.volume_button.set_value(self._playback_control_view_model.volume)
-
- def _play_clicked(self, _):
- self._playback_control_view_model.play_pause()
-
- def _rewind_clicked(self, _):
- self._playback_control_view_model.rewind()
-
- def _forward_clicked(self, _):
- self._playback_control_view_model.forward()
-
- def _back_clicked(self, _):
- self._playback_control_view_model.navigate_back()
-
- def _cover_clicked(self, _, __):
- self._playback_control_view_model.open_book_detail()
-
- def _on_cover_enter_notify(self, widget: Gtk.Widget, __):
- try:
- widget.props.window.set_cursor(Gdk.Cursor(Gdk.CursorType.HAND2))
- except:
- log.error("Broken mouse theme, failed to set cursor.")
-
- def _on_cover_leave_notify(self, widget: Gtk.Widget, __):
- widget.props.window.set_cursor(None)
-
- def _on_volume_button_changed(self, _, volume):
- self._playback_control_view_model.volume = volume
-
- def _on_seek_bar_position_changed(self, _, position):
- self._playback_control_view_model.position = position
diff --git a/cozy/ui/media_controller_small.py b/cozy/ui/media_controller_small.py
deleted file mode 100644
index d375136c..00000000
--- a/cozy/ui/media_controller_small.py
+++ /dev/null
@@ -1,121 +0,0 @@
-import logging
-
-import gi
-
-from cozy.control.artwork_cache import ArtworkCache
-from cozy.db.book import Book
-from cozy.ext import inject
-from cozy.ui.widgets.playback_speed_popover import PlaybackSpeedPopover
-from cozy.view_model.playback_control_view_model import PlaybackControlViewModel
-
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk, Gdk
-
-log = logging.getLogger("Headerbar")
-
-COVER_SIZE = 46
-
-
-@Gtk.Template.from_resource('/com/github/geigi/cozy/media_controller_small.ui')
-class MediaControllerSmall(Gtk.Box):
- __gtype_name__ = "MediaControllerSmall"
-
- play_button: Gtk.Button = Gtk.Template.Child()
- prev_button: Gtk.Button = Gtk.Template.Child()
- next_button: Gtk.Button = Gtk.Template.Child()
-
- cover_img: Gtk.Image = Gtk.Template.Child()
- cover_img_event_box: Gtk.EventBox = Gtk.Template.Child()
-
- playback_speed_button: Gtk.MenuButton = Gtk.Template.Child()
-
- play_img: Gtk.Image = Gtk.Template.Child()
-
- def __init__(self):
- super().__init__()
-
- self.playback_speed_button.set_popover(PlaybackSpeedPopover())
-
- self._playback_control_view_model: PlaybackControlViewModel = inject.instance(PlaybackControlViewModel)
- self._artwork_cache: ArtworkCache = inject.instance(ArtworkCache)
- self._connect_view_model()
- self._connect_widgets()
-
- self._on_book_changed()
- self._on_lock_ui_changed()
-
- def _connect_view_model(self):
- self._playback_control_view_model.bind_to("book", self._on_book_changed)
- self._playback_control_view_model.bind_to("playing", self._on_play_changed)
- self._playback_control_view_model.bind_to("lock_ui", self._on_lock_ui_changed)
-
- def _connect_widgets(self):
- self.play_button.connect("clicked", self._play_clicked)
- self.prev_button.connect("clicked", self._rewind_clicked)
- self.next_button.connect("clicked", self._forward_clicked)
- self.cover_img_event_box.connect("button-press-event", self._cover_clicked)
- self.cover_img_event_box.connect("enter-notify-event", self._on_cover_enter_notify)
- self.cover_img_event_box.connect("leave-notify-event", self._on_cover_leave_notify)
-
- def _set_cover_image(self, book: Book):
- pixbuf = self._artwork_cache.get_cover_pixbuf(book, self.get_scale_factor(), COVER_SIZE)
- if pixbuf:
- surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, self.get_scale_factor(), None)
- self.cover_img.set_from_surface(surface)
- else:
- self.cover_img.set_from_icon_name("book-open-variant-symbolic", Gtk.IconSize.DIALOG)
- self.cover_img.props.pixel_size = COVER_SIZE
-
- def _on_book_changed(self):
- book = self._playback_control_view_model.book
- if book:
- visibility = True
- self._set_book()
- else:
- visibility = False
-
- self._show_media_information(visibility)
-
- def _show_media_information(self, visibility):
- self.cover_img.set_visible(visibility)
-
- def _set_book(self):
- book = self._playback_control_view_model.book
-
- self._set_cover_image(book)
-
- def _on_play_changed(self):
- playing = self._playback_control_view_model.playing
-
- play_button_img = "pause-symbolic" if playing else "play-symbolic"
- icon_size = 16 if playing else 20
- self.play_img.set_from_icon_name(play_button_img, Gtk.IconSize.LARGE_TOOLBAR)
- self.play_img.set_pixel_size(icon_size)
-
- def _on_lock_ui_changed(self):
- sensitive = not self._playback_control_view_model.lock_ui
- self.prev_button.set_sensitive(sensitive)
- self.next_button.set_sensitive(sensitive)
- self.play_button.set_sensitive(sensitive)
- self.playback_speed_button.set_sensitive(sensitive)
-
- def _play_clicked(self, _):
- self._playback_control_view_model.play_pause()
-
- def _rewind_clicked(self, _):
- self._playback_control_view_model.rewind()
-
- def _forward_clicked(self, _):
- self._playback_control_view_model.forward()
-
- def _cover_clicked(self, _, __):
- self._playback_control_view_model.open_book_detail()
-
- def _on_cover_enter_notify(self, widget: Gtk.Widget, __):
- try:
- widget.props.window.set_cursor(Gdk.Cursor(Gdk.CursorType.HAND2))
- except:
- log.error("Broken mouse theme, failed to set cursor.")
-
- def _on_cover_leave_notify(self, widget: Gtk.Widget, __):
- widget.props.window.set_cursor(None)
diff --git a/cozy/ui/preferences_view.py b/cozy/ui/preferences_view.py
index c8bcbded..709488a1 100644
--- a/cozy/ui/preferences_view.py
+++ b/cozy/ui/preferences_view.py
@@ -1,27 +1,27 @@
from gi.repository import Gtk
from cozy.view_model.settings_view_model import SettingsViewModel
import gi
-from gi.repository import Handy, Gio
+from gi.repository import Adw, Gio
from cozy.ext import inject
from cozy.ui.widgets.error_reporting import ErrorReporting
from cozy.ui.widgets.storage_list_box_row import StorageListBoxRow
-gi.require_version('Gtk', '3.0')
-
@Gtk.Template.from_resource('/com/github/geigi/cozy/preferences.ui')
-class PreferencesView(Handy.PreferencesWindow):
+class PreferencesView(Adw.PreferencesWindow):
__gtype_name__ = "PreferencesWindow"
+ main_window = inject.attr("MainWindow")
+
_glib_settings: Gio.Settings = inject.attr(Gio.Settings)
_view_model: SettingsViewModel = inject.attr(SettingsViewModel)
dark_mode_switch: Gtk.Switch = Gtk.Template.Child()
swap_author_reader_switch: Gtk.Switch = Gtk.Template.Child()
replay_switch: Gtk.Switch = Gtk.Template.Child()
- sleep_timer_fadeout_switch: Gtk.Switch = Gtk.Template.Child()
- sleep_timer_fadeout_row: Handy.ActionRow = Gtk.Template.Child()
+ sleep_timer_fadeout_switch: Adw.SwitchRow = Gtk.Template.Child()
+ fadeout_duration_spin_button: Adw.SpinRow = Gtk.Template.Child()
artwork_prefer_external_switch: Gtk.Switch = Gtk.Template.Child()
rewind_duration_adjustment: Gtk.Adjustment = Gtk.Template.Child()
@@ -34,22 +34,22 @@ class PreferencesView(Handy.PreferencesWindow):
external_storage_toggle_button: Gtk.ToggleButton = Gtk.Template.Child()
default_storage_button: Gtk.ToggleButton = Gtk.Template.Child()
- user_feedback_preference_row: Handy.PreferencesRow = Gtk.Template.Child()
+ user_feedback_preference_group: Adw.PreferencesRow = Gtk.Template.Child()
def __init__(self, **kwargs):
super().__init__(**kwargs)
error_reporting = ErrorReporting()
error_reporting.show_header(False)
- self.user_feedback_preference_row.add(error_reporting)
+ self.user_feedback_preference_group.add(error_reporting)
self._bind_settings()
self._bind_view_model()
- self.connect("delete-event", self._hide_window)
+ self.connect("close-request", self._hide_window)
- self.sleep_timer_fadeout_switch.connect("state-set", self._on_sleep_fadeout_switch_changed)
- self._on_sleep_fadeout_switch_changed(None, self.sleep_timer_fadeout_switch.get_active())
+ self.sleep_timer_fadeout_switch.connect("notify::active", self._on_sleep_fadeout_switch_changed)
+ self.fadeout_duration_spin_button.set_sensitive(self.sleep_timer_fadeout_switch.props.active)
self.storage_list_box.connect("row-selected", self._on_storage_box_changed)
@@ -58,6 +58,8 @@ def __init__(self, **kwargs):
self.external_button_handle_id = self.external_storage_toggle_button.connect("clicked", self._on_external_clicked)
self.default_storage_button.connect("clicked", self._on_default_storage_clicked)
+ self.set_transient_for(self.main_window.window)
+
self._init_storage_box()
def _bind_view_model(self):
@@ -86,8 +88,9 @@ def _bind_settings(self):
self._glib_settings.bind("prefer-external-cover", self.artwork_prefer_external_switch, "active",
Gio.SettingsBindFlags.DEFAULT)
- def _on_sleep_fadeout_switch_changed(self, _, state: bool):
- self.sleep_timer_fadeout_row.set_sensitive(state)
+ def _on_sleep_fadeout_switch_changed(self, widget, param):
+ state = widget.get_property(param.name)
+ self.fadeout_duration_spin_button.set_sensitive(state)
def _init_storage_box(self):
self.storage_list_box.remove_all_children()
@@ -95,7 +98,7 @@ def _init_storage_box(self):
for storage in self._view_model.storage_locations:
row = StorageListBoxRow(storage)
row.connect("location-changed", self._on_storage_location_changed)
- self.storage_list_box.add(row)
+ self.storage_list_box.append(row)
def _on_add_storage_clicked(self, _):
self._view_model.add_storage_location()
@@ -143,8 +146,7 @@ def _on_storage_location_changed(self, widget, new_location):
self._view_model.change_storage_location(widget.model, new_location)
def _refresh_storage_rows(self):
- for row in self.storage_list_box.get_children():
- row.refresh()
+ self._init_storage_box()
self._on_storage_box_changed(None, self.storage_list_box.get_selected_row())
@@ -158,6 +160,6 @@ def _on_lock_ui_changed(self):
self.default_storage_button.set_sensitive(sensitive)
self._on_storage_box_changed(None, self.storage_list_box.get_selected_row())
- def _hide_window(self, _, __):
+ def _hide_window(self, *_):
self.hide()
return True
diff --git a/cozy/ui/search_view.py b/cozy/ui/search_view.py
index e6d206c8..9d20a8ee 100644
--- a/cozy/ui/search_view.py
+++ b/cozy/ui/search_view.py
@@ -4,11 +4,8 @@
from cozy.ext import inject
from cozy.ui.widgets.search_results import BookSearchResult, ArtistSearchResult
-import gi
-
from cozy.view_model.search_view_model import SearchViewModel
-gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib
@@ -42,11 +39,10 @@ def __init__(self):
self.entry.connect("search-changed", self.__on_search_changed)
- if Gtk.get_minor_version() > 20:
- self.scroller.set_max_content_width(400)
- self.scroller.set_max_content_height(600)
- self.scroller.set_propagate_natural_height(True)
- self.scroller.set_propagate_natural_width(True)
+ self.scroller.set_max_content_width(400)
+ self.scroller.set_max_content_height(600)
+ self.scroller.set_propagate_natural_height(True)
+ self.scroller.set_propagate_natural_width(True)
self.search_thread = Thread(target=self.search, name="SearchThread")
self.search_thread_stop = threading.Event()
@@ -100,12 +96,6 @@ def search(self, user_search: str):
main_context.invoke_full(
GLib.PRIORITY_DEFAULT, self.stack.set_visible_child_name, "nothing")
- def close(self, object=None):
- if Gtk.get_minor_version() < 22:
- self.popover.hide()
- else:
- self.popover.popdown()
-
def __on_search_changed(self, sender):
self.search_thread_stop.set()
@@ -148,7 +138,7 @@ def __on_search_changed(self, sender):
def _on_search_open_changed(self):
if self.view_model.search_open == False:
- self.close()
+ self.popover.popdown()
def __on_book_search_finished(self, books):
if len(books) > 0:
@@ -161,7 +151,7 @@ def __on_book_search_finished(self, books):
return
book_result = BookSearchResult(book, self.view_model.jump_to_book)
- self.book_box.add(book_result)
+ self.book_box.append(book_result)
def __on_author_search_finished(self, authors):
if len(authors) > 0:
@@ -174,7 +164,7 @@ def __on_author_search_finished(self, authors):
return
author_result = ArtistSearchResult(self.view_model.jump_to_author, author, True)
- self.author_box.add(author_result)
+ self.author_box.append(author_result)
def __on_reader_search_finished(self, readers):
if len(readers) > 0:
@@ -187,6 +177,6 @@ def __on_reader_search_finished(self, readers):
return
reader_result = ArtistSearchResult(self.view_model.jump_to_reader, reader, False)
- self.reader_box.add(reader_result)
+ self.reader_box.append(reader_result)
self.popover.set_size_request(-1, -1)
diff --git a/cozy/ui/toaster.py b/cozy/ui/toaster.py
new file mode 100644
index 00000000..e3c20065
--- /dev/null
+++ b/cozy/ui/toaster.py
@@ -0,0 +1,16 @@
+from gi.repository import Adw, Gtk
+
+from cozy.ext import inject
+
+
+class ToastNotifier:
+ _builder: Gtk.Builder = inject.attr("MainWindowBuilder")
+
+ def __init__(self) -> None:
+ super().__init__()
+
+ self.overlay: Adw.ToastOverlay = self._builder.get_object("toast_overlay")
+
+ def show(self, message: str) -> None:
+ self.overlay.add_toast(Adw.Toast(title=message, timeout=2))
+
diff --git a/cozy/ui/warnings.py b/cozy/ui/warnings.py
deleted file mode 100644
index 66f699f2..00000000
--- a/cozy/ui/warnings.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from gettext import gettext
-
-import gi
-import cozy.ext.inject as inject
-
-from cozy.control.filesystem_monitor import FilesystemMonitor
-
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk
-
-
-class Warnings():
- _fs_monitor: FilesystemMonitor = inject.attr("FilesystemMonitor")
-
- def __init__(self, button: Gtk.MenuButton):
- self.button = button
-
- self.builder = Gtk.Builder.new_from_resource(
- "/com/github/geigi/cozy/warning_popover.ui")
-
- self.popover = self.builder.get_object("warning_popover")
- self.warning_container: Gtk.Box = self.builder.get_object("warning_container")
-
- self._fs_monitor.add_listener(self.__on_storage_changed)
-
- for storage in self._fs_monitor.get_offline_storages():
- self.append_text(gettext('{storage} is offline.').format(storage=storage))
-
- self.__hide_show_button()
-
- def get_popover(self):
- return self.popover
-
- def append_text(self, text):
- label = Gtk.Label()
- self.warning_container.add(label)
- label.set_visible(True)
- label.set_text(text)
-
- def __on_storage_changed(self, event, message):
- if event == "storage-offline":
- self.append_text(gettext('{storage} is offline.').format(storage=message))
- if event == "storage-online":
- for label in self.warning_container.get_children():
- if message in label.get_text():
- self.warning_container.remove(label)
-
- self.__hide_show_button()
-
- def __hide_show_button(self):
- if len(self.warning_container.get_children()) > 0:
- self.button.set_visible(True)
- else:
- self.button.set_visible(False)
\ No newline at end of file
diff --git a/cozy/ui/widgets/ScrollWrapper.py b/cozy/ui/widgets/ScrollWrapper.py
deleted file mode 100644
index 1deb1910..00000000
--- a/cozy/ui/widgets/ScrollWrapper.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import gi
-
-gi.require_version('Gtk', '3.0')
-
-from gi.repository import Gtk
-
-
-class ScrollWrapper(Gtk.ScrolledWindow):
- def __init__(self, child: Gtk.Widget, **kwargs):
- super().__init__(**kwargs)
- self.show()
- self.viewport = Gtk.Viewport()
- self.viewport.show()
-
- self.add(self.viewport)
- self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
- self.set_margin_start(20)
- self.set_margin_end(20)
-
- self.viewport.add(child)
diff --git a/cozy/ui/widgets/album_art.py b/cozy/ui/widgets/album_art.py
deleted file mode 100644
index 21d6c138..00000000
--- a/cozy/ui/widgets/album_art.py
+++ /dev/null
@@ -1,46 +0,0 @@
-import math
-
-import cairo
-from gi.repository import Gtk, GdkPixbuf, Gdk
-
-RADIUS = 5
-
-
-class AlbumArt(Gtk.DrawingArea):
- def __init__(self, **properties):
- super().__init__(**properties)
-
- self.art = None
-
- self.set_visible(True)
- self.connect("draw", self._on_draw)
-
- def set_art(self, art: GdkPixbuf):
- self.art = art
- scale = self.get_scale_factor()
- self.set_size_request(art.get_width() / scale, art.get_height() / scale)
- self.queue_draw()
-
- def _draw_rounded_path(self, context: cairo.Context):
- degrees = math.pi / 180.0
-
- context.new_sub_path()
- context.arc(self.width - RADIUS, RADIUS, RADIUS, -90 * degrees, 0 * degrees)
- context.arc(self.width - RADIUS, self.height - RADIUS, RADIUS, 0 * degrees, 90 * degrees)
- context.arc(RADIUS, self.height - RADIUS, RADIUS, 90 * degrees, 180 * degrees)
- context.arc(RADIUS, RADIUS, RADIUS, 180 * degrees, 270 * degrees)
- context.close_path()
-
- def _on_draw(self, area: Gtk.DrawingArea, context: cairo.Context):
- self.height = area.get_allocated_height()
- self.width = area.get_allocated_width()
-
- self._draw_rounded_path(context)
-
- if self.art:
- surface = Gdk.cairo_surface_create_from_pixbuf(self.art, self.get_scale_factor(), None)
- context.set_source_surface(surface, 0, 0)
- context.clip()
-
- context.paint()
- return False
diff --git a/cozy/ui/widgets/album_element.py b/cozy/ui/widgets/album_element.py
index c403114b..11a84a1d 100644
--- a/cozy/ui/widgets/album_element.py
+++ b/cozy/ui/widgets/album_element.py
@@ -4,14 +4,13 @@
import cairo
from cozy.control.artwork_cache import ArtworkCache
-from cozy.extensions.gtk_widget import set_hand_cursor, reset_cursor
from cozy.model.book import Book
from cozy.ext import inject
from gi.repository import Gtk, GObject, Gdk
ALBUM_ART_SIZE = 200
-PLAY_BUTTON_ICON_SIZE = Gtk.IconSize.SMALL_TOOLBAR
+PLAY_BUTTON_ICON_SIZE = Gtk.IconSize.NORMAL
STROKE_WIDTH = 3
log = logging.getLogger("album_element")
@@ -23,7 +22,6 @@ class AlbumElement(Gtk.Box):
artwork_cache: ArtworkCache = inject.attr(ArtworkCache)
- button_image: Gtk.Image = Gtk.Template.Child()
album_art_image: Gtk.Image = Gtk.Template.Child()
play_button: Gtk.Button = Gtk.Template.Child()
progress_drawing_area: Gtk.DrawingArea = Gtk.Template.Child()
@@ -35,44 +33,41 @@ def __init__(self, book: Book):
super().__init__()
self._book: Book = book
- pixbuf = self.artwork_cache.get_cover_pixbuf(book, self.get_scale_factor(), ALBUM_ART_SIZE)
+ paintable = self.artwork_cache.get_cover_paintable(book, 1, ALBUM_ART_SIZE)
- if pixbuf:
- surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, self.get_scale_factor(), None)
- self.album_art_image.set_from_surface(surface)
+ if paintable:
+ self.album_art_image.set_from_paintable(paintable)
+ self.album_art_image.set_size_request(ALBUM_ART_SIZE, ALBUM_ART_SIZE)
else:
- self.album_art_image.set_from_icon_name("book-open-variant-symbolic", Gtk.IconSize.DIALOG)
+ self.album_art_image.set_from_icon_name("book-open-variant-symbolic")
self.album_art_image.props.pixel_size = ALBUM_ART_SIZE
- self.set_size_request(ALBUM_ART_SIZE, ALBUM_ART_SIZE)
- self.play_button.connect("button-release-event", self._on_play_button_press)
+ self.play_button.connect("clicked", self._on_play_button_press)
- self.progress_drawing_area.connect("realize", lambda w: w.get_window().set_pass_through(True))
- self.progress_drawing_area.connect("draw", self._draw_progress)
- self.album_art_drawing_area.connect("draw", self._draw_album_hover)
- self.album_art_overlay_revealer.connect("enter-notify-event", self._on_revealer_enter_event)
- self.play_button_revealer.connect("enter-notify-event", self._on_revealer_enter_event)
+ # TODO: Just use CSS
+ #self.progress_drawing_area.connect("realize", lambda w: w.get_window().set_pass_through(True))
+ self.progress_drawing_area.set_draw_func(self._draw_progress)
+ #self.album_art_drawing_area.set_draw_func(self._draw_album_hover)
def set_playing(self, playing: bool):
if playing:
- self.button_image.set_from_icon_name("pause-symbolic", PLAY_BUTTON_ICON_SIZE)
+ self.play_button.set_icon_name("media-playback-pause-symbolic")
else:
- self.button_image.set_from_icon_name("play-symbolic", PLAY_BUTTON_ICON_SIZE)
+ self.play_button.set_icon_name("media-playback-start-symbolic")
def set_hover(self, hover: bool):
self.album_art_overlay_revealer.set_reveal_child(hover)
self.play_button_revealer.set_reveal_child(hover)
- def _on_play_button_press(self, _, __):
+ def _on_play_button_press(self, _):
self.emit("play-pause-clicked", self._book)
- return True
- def _draw_album_hover(self, area: Gtk.DrawingArea, context: cairo.Context):
+ def _draw_album_hover(self, area: Gtk.DrawingArea, context: cairo.Context, *_):
context.rectangle(0, 0, area.get_allocated_width(), area.get_allocated_height())
context.set_source_rgba(1, 1, 1, 0.15)
context.fill()
- def _draw_progress(self, area: Gtk.DrawingArea, context: cairo.Context):
+ def _draw_progress(self, area: Gtk.DrawingArea, context: cairo.Context, *_):
width = area.get_allocated_width()
height = area.get_allocated_height()
button_size = self.play_button.get_allocated_width()
@@ -101,13 +96,6 @@ def draw_background(self, area: Gtk.DrawingArea, context: cairo.Context):
def update_progress(self):
self.progress_drawing_area.queue_draw()
- def _on_revealer_enter_event(self, widget, _):
- # Somehow the GTK Revealer steals the mouse events from the parent
- # Maybe this is a bug in GTK but for now we have to handle hover in here as well
- self.set_hover(True)
- set_hand_cursor(widget)
- return True
-
GObject.type_register(AlbumElement)
GObject.signal_new('play-pause-clicked', AlbumElement, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT,
diff --git a/cozy/ui/widgets/book_element.py b/cozy/ui/widgets/book_element.py
index 4ee2795b..8bb61fe5 100644
--- a/cozy/ui/widgets/book_element.py
+++ b/cozy/ui/widgets/book_element.py
@@ -1,14 +1,8 @@
-import os
-import subprocess
+from gi.repository import Gtk, GObject, Gdk, Gio
-from gi.repository import Gtk, GObject, Gdk
-
-from cozy.extensions.gtk_widget import set_hand_cursor, reset_cursor
from cozy.model.book import Book
from cozy.ui.widgets.album_element import AlbumElement
-MAX_LABEL_LENGTH = 60
-
@Gtk.Template.from_resource('/com/github/geigi/cozy/book_element.ui')
class BookElement(Gtk.FlowBoxChild):
@@ -17,29 +11,51 @@ class BookElement(Gtk.FlowBoxChild):
name_label: Gtk.Label = Gtk.Template.Child()
author_label: Gtk.Label = Gtk.Template.Child()
container_box: Gtk.Box = Gtk.Template.Child()
- event_box: Gtk.Box = Gtk.Template.Child()
def __init__(self, book: Book):
- self.book: Book = book
-
super().__init__()
+ self.book = book
+ self.pressed = False
+
self.name_label.set_text(book.name)
self.name_label.set_tooltip_text(book.name)
self.author_label.set_text(book.author)
self.author_label.set_tooltip_text(book.author)
- self.art: AlbumElement = AlbumElement(self.book)
- self.context_menu = None
- self.pressed = False
-
- self.container_box.pack_start(self.art, False, True, 0)
+ self.art = AlbumElement(self.book)
self.art.connect("play-pause-clicked", self._on_album_art_press_event)
- self.event_box.connect("button-press-event", self._on_button_press_event)
- self.event_box.connect("button-release-event", self._on_button_release_event)
- self.event_box.connect("key-press-event", self._on_key_press_event)
- self.event_box.connect("enter-notify-event", self._on_cover_enter_notify)
- self.event_box.connect("leave-notify-event", self._on_cover_leave_notify)
+
+ self.container_box.prepend(self.art)
+ self.set_cursor(Gdk.Cursor.new_from_name("pointer"))
+
+ self._add_event_controllers()
+
+ def _add_event_controllers(self):
+ primary_button_gesture = Gtk.GestureClick(button=Gdk.BUTTON_PRIMARY)
+ # primary_button_gesture.connect("pressed", self._select_item)
+ primary_button_gesture.connect("released", self._open_book_overview)
+ self.container_box.add_controller(primary_button_gesture)
+
+ secondary_button_gesture = Gtk.GestureClick(button=Gdk.BUTTON_SECONDARY)
+ secondary_button_gesture.connect("released", self._show_context_menu)
+ self.container_box.add_controller(secondary_button_gesture)
+
+ # FIXME: When clicking on an album's play button in the recents view,
+ # it jumps to the first position, and GtkGestureLongPress thinks it's
+ # a long press gesture, although it's just an unfinished long press
+ long_press_gesture = Gtk.GestureLongPress()
+ long_press_gesture.connect("pressed", self._show_context_menu)
+ self.container_box.add_controller(long_press_gesture)
+
+ key_event_controller = Gtk.EventControllerKey()
+ key_event_controller.connect("key-pressed", self._on_key_press_event)
+ self.container_box.add_controller(key_event_controller)
+
+ motion_event_controller = Gtk.EventControllerMotion()
+ motion_event_controller.connect("enter", self._on_cover_enter_notify)
+ motion_event_controller.connect("leave", self._on_cover_leave_notify)
+ self.container_box.add_controller(motion_event_controller)
def set_playing(self, is_playing):
self.art.set_playing(is_playing)
@@ -48,82 +64,68 @@ def update_progress(self):
self.art.update_progress()
def _create_context_menu(self):
- menu = Gtk.Menu()
- read_item = Gtk.MenuItem(label=_("Mark as read"))
- read_item.connect("button-press-event", self._mark_as_read)
-
- jump_item = Gtk.MenuItem(label=_("Open in file browser"))
- jump_item.connect("button-press-event", self._jump_to_folder)
-
- rm_item = Gtk.MenuItem(label=_("Remove from library"))
- rm_item.connect("button-press-event", self._remove_book)
-
- menu.append(read_item)
- menu.append(jump_item)
- menu.append(Gtk.SeparatorMenuItem())
- menu.append(rm_item)
- menu.attach_to_widget(self)
- menu.show_all()
- return menu
+ menu_model = Gio.Menu()
- def _remove_book(self, _, __):
- if self.context_menu:
- self.context_menu.popdown()
+ self.install_action("book_element.mark_as_read", None, self._mark_as_read)
+ menu_model.append(_("Mark as read"), "book_element.mark_as_read")
+ self.install_action("book_element.jump_to_folder", None, self._jump_to_folder)
+ menu_model.append(_("Open in file browser"), "book_element.jump_to_folder")
+
+ self.install_action("book_element.remove_book", None, self._remove_book)
+ menu_model.append(_("Remove from library"), "book_element.remove_book")
+
+ menu = Gtk.PopoverMenu(menu_model=menu_model, has_arrow=False)
+ menu.set_parent(self.art)
+
+ return menu
+
+ def _remove_book(self, *_):
self.emit("book-removed", self.book)
- def _mark_as_read(self, _, __):
+ def _mark_as_read(self, *_):
self.book.position = -1
- def _jump_to_folder(self, _, __):
+ def _jump_to_folder(self, *_):
"""
Opens the folder containing this books files in the default file explorer.
"""
track = self.book.chapters[0]
- path = os.path.dirname(track.file)
- subprocess.Popen(['xdg-open', path])
-
- def _on_button_press_event(self, _, event):
- if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
- if self.context_menu is None:
- self.context_menu = self._create_context_menu()
- self.context_menu.popup(
- None, None, None, None, event.button, event.time)
- return True
- elif event.type == Gdk.EventType.BUTTON_PRESS and event.button == 1:
- if super().get_sensitive():
- self.pressed = True
- self.container_box.get_style_context().add_class("selected")
- elif event.type == Gdk.EventType.KEY_PRESS and event.keyval == Gdk.KEY_Return:
- if super().get_sensitive():
- self.emit("open-book-overview", self.book)
- return True
-
- def _on_button_release_event(self, _, event):
- if event.type == Gdk.EventType.BUTTON_RELEASE and event.button == 1 and self.pressed:
- self.pressed = False
- self.container_box.get_style_context().remove_class("selected")
- if super().get_sensitive():
- self.emit("open-book-overview", self.book)
- return True
-
- def _on_key_press_event(self, _, key):
- if key.keyval == Gdk.KEY_Return and super().get_sensitive():
+
+ file_launcher = Gtk.FileLauncher(file=Gio.File.new_for_path(track.file))
+ dummy_callback = lambda d, r: d.open_containing_folder_finish(r)
+ file_launcher.open_containing_folder(None, None, dummy_callback)
+
+ def _show_context_menu(self, gesture: Gtk.Gesture, *_):
+ gesture.set_state(Gtk.EventSequenceState.CLAIMED)
+
+ self._create_context_menu().popup()
+
+ def _select_item(self, gesture: Gtk.Gesture, *_):
+ if super().get_sensitive():
+ gesture.set_state(Gtk.EventSequenceState.CLAIMED)
+ self.pressed = True
+ self.container_box.add_css_class("selected")
+
+ def _open_book_overview(self, gesture, *_):
+ gesture.set_state(Gtk.EventSequenceState.CLAIMED)
+
+ self.pressed = False
+ self.container_box.remove_css_class("selected")
+ if super().get_sensitive():
self.emit("open-book-overview", self.book)
- return True
- def _on_cover_enter_notify(self, widget: Gtk.Widget, __):
- set_hand_cursor(widget)
+ def _on_key_press_event(self, keyval, *_):
+ if keyval == Gdk.KEY_Return and super().get_sensitive():
+ self.emit("open-book-overview", self.book)
+ def _on_cover_enter_notify(self, *_):
self.art.set_hover(True)
- return True
- def _on_cover_leave_notify(self, widget: Gtk.Widget, __):
- reset_cursor(widget)
+ def _on_cover_leave_notify(self, *_):
self.art.set_hover(False)
- return True
- def _on_album_art_press_event(self, _, __):
+ def _on_album_art_press_event(self, *_):
self.emit("play-pause-clicked", self.book)
@@ -134,3 +136,4 @@ def _on_album_art_press_event(self, _, __):
(GObject.TYPE_PYOBJECT,))
GObject.signal_new('book-removed', BookElement, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT,
(GObject.TYPE_PYOBJECT,))
+
diff --git a/cozy/ui/widgets/error_reporting.py b/cozy/ui/widgets/error_reporting.py
index 20b0f768..36103270 100644
--- a/cozy/ui/widgets/error_reporting.py
+++ b/cozy/ui/widgets/error_reporting.py
@@ -5,7 +5,6 @@
from cozy.application_settings import ApplicationSettings
from cozy.ext import inject
-gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
LEVELS = [
@@ -60,6 +59,7 @@ def show_header(self, show: bool):
def _load_report_level(self):
level = self.app_settings.report_level
self.verbose_adjustment.set_value(level + 1)
+ self._update_ui_texts(level)
def __init_scale(self):
for i in range(1, 5):
@@ -92,4 +92,5 @@ def _update_details(self, value):
def _on_app_setting_changed(self, event, _):
if event == "report-level":
- self._load_report_level()
\ No newline at end of file
+ self._load_report_level()
+
diff --git a/cozy/ui/widgets/filter_list_box.py b/cozy/ui/widgets/filter_list_box.py
index 10a72724..74e65ae4 100644
--- a/cozy/ui/widgets/filter_list_box.py
+++ b/cozy/ui/widgets/filter_list_box.py
@@ -1,7 +1,5 @@
from typing import List
-import gi
-gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from cozy.ui.list_box_row_with_data import ListBoxRowWithData
@@ -17,23 +15,22 @@ def __init__(self, **properties):
def populate(self, elements: List[str]):
self.remove_all_children()
- all_row = ListBoxRowWithData(_("All"), False)
+ all_row = ListBoxRowWithData(_("All"), True)
all_row.set_tooltip_text(_("Display all books"))
- self.add(all_row)
- self.add(ListBoxSeparatorRow())
+ self.append(all_row)
self.select_row(all_row)
for element in elements:
row = ListBoxRowWithData(element, False)
- self.add(row)
-
- self.show_all()
+ self.append(row)
def select_row_with_content(self, row_content: str):
- for row in self.get_children():
- if not isinstance(row, ListBoxRowWithData):
- continue
+ child = self.get_first_child()
+ while child:
+ next = child.get_next_sibling()
- if row.data == row_content:
- self.select_row(row)
+ if isinstance(child, ListBoxRowWithData) and child.data == row_content:
+ self.select_row(child)
break
+
+ child = next
diff --git a/cozy/ui/widgets/list_box_extensions.py b/cozy/ui/widgets/list_box_extensions.py
index 3d1d8ebf..82846727 100644
--- a/cozy/ui/widgets/list_box_extensions.py
+++ b/cozy/ui/widgets/list_box_extensions.py
@@ -3,15 +3,18 @@
def remove_all_children(self):
"""
- Removes all widgets from a gtk container.
+ Removes all widgets from a gtk widget.
"""
self.set_visible(False)
- childs = self.get_children()
- for element in childs:
- self.remove(element)
- element.destroy()
+
+ child = self.get_first_child()
+ while child:
+ next = child.get_next_sibling()
+ self.remove(child)
+ child = next
+
self.set_visible(True)
def extend_gtk_container():
- Gtk.Container.remove_all_children = remove_all_children
+ Gtk.Widget.remove_all_children = remove_all_children
diff --git a/cozy/ui/widgets/playback_speed_popover.py b/cozy/ui/widgets/playback_speed_popover.py
index 746bb185..2dd59847 100644
--- a/cozy/ui/widgets/playback_speed_popover.py
+++ b/cozy/ui/widgets/playback_speed_popover.py
@@ -1,9 +1,6 @@
-import gi
-
from cozy.ext import inject
from cozy.view_model.playback_speed_view_model import PlaybackSpeedViewModel
-gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
diff --git a/cozy/ui/widgets/progress_popover.py b/cozy/ui/widgets/progress_popover.py
index 2d90f4b8..8c5fe889 100644
--- a/cozy/ui/widgets/progress_popover.py
+++ b/cozy/ui/widgets/progress_popover.py
@@ -1,6 +1,3 @@
-import gi
-
-gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
@@ -19,3 +16,4 @@ def set_message(self, message: str):
def set_progress(self, progress: float):
self.progress_bar.set_fraction(progress)
+
diff --git a/cozy/ui/widgets/search_results.py b/cozy/ui/widgets/search_results.py
index 251f5d1c..ea1aa641 100644
--- a/cozy/ui/widgets/search_results.py
+++ b/cozy/ui/widgets/search_results.py
@@ -8,7 +8,7 @@
BOOK_ICON_SIZE = 40
-class SearchResult(Gtk.EventBox):
+class SearchResult(Gtk.Box):
"""
This class is the base class for all search result GUI object.
It features a GTK box that is highlighted when hovered.
@@ -20,10 +20,14 @@ def __init__(self, on_click, on_click_data):
self.on_click = on_click
self.on_click_data = on_click_data
- self.connect("enter-notify-event", self._on_enter_notify)
- self.connect("leave-notify-event", self._on_leave_notify)
- if on_click:
- self.connect("button-press-event", self.__on_clicked)
+ self._motion_event = Gtk.EventControllerMotion()
+ self._motion_event.connect("enter", self._on_enter_notify)
+ self._motion_event.connect("leave", self._on_leave_notify)
+ self.add_controller(self._motion_event)
+
+ self._primary_gesture = Gtk.GestureClick(button=Gdk.BUTTON_PRIMARY)
+ self._primary_gesture.connect("pressed", self.__on_clicked)
+ self.add_controller(self._primary_gesture)
self.props.margin_top = 2
self.props.margin_bottom = 2
@@ -35,23 +39,22 @@ def __init__(self, on_click, on_click_data):
self.box.set_halign(Gtk.Align.FILL)
self.box.set_valign(Gtk.Align.CENTER)
- def _on_enter_notify(self, widget, event):
+ def _on_enter_notify(self, widget, event, *_):
"""
On enter notify add css hover class
- :param widget: as Gtk.EventBox
+ :param widget: as Gtk.Box
:param event: as Gdk.Event
"""
- self.box.get_style_context().add_class("box_hover")
+ self.box.add_css_class("box_hover")
- def _on_leave_notify(self, widget, event):
+ def _on_leave_notify(self, widget):
"""
On leave notify remove css hover class
- :param widget: as Gtk.EventBox (can be None)
- :param event: as Gdk.Event (can be None)
+ :param widget: as Gtk.Box (can be None)
"""
- self.box.get_style_context().remove_class("box_hover")
+ self.box.remove_css_class("box_hover")
- def __on_clicked(self, widget, event):
+ def __on_clicked(self, widget, event, *_):
self.on_click(self.on_click_data)
@@ -75,17 +78,18 @@ def __init__(self, on_click, artist: str, is_author):
title_label.set_text(tools.shorten_string(artist, MAX_BOOK_LENGTH))
self.set_tooltip_text(_("Jump to reader ") + artist)
title_label.set_halign(Gtk.Align.START)
- title_label.props.margin = 4
+ title_label.props.margin_top = 4
+ title_label.props.margin_bottom = 4
+ title_label.props.margin_start = 4
+ title_label.props.margin_end = 5
title_label.props.hexpand = True
title_label.props.hexpand_set = True
- title_label.set_margin_right(5)
title_label.props.width_request = 100
title_label.props.xalign = 0.0
- title_label.set_line_wrap(True)
+ title_label.props.wrap = True
- self.box.add(title_label)
- self.add(self.box)
- self.show_all()
+ self.box.append(title_label)
+ self.append(self.box)
class BookSearchResult(SearchResult):
@@ -100,27 +104,28 @@ def __init__(self, book: Book, on_click):
self.set_tooltip_text(_("Play this book"))
scale = self.get_scale_factor()
- pixbuf = self._artwork_cache.get_cover_pixbuf(book, scale, BOOK_ICON_SIZE)
- if pixbuf:
- surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale, None)
- img = Gtk.Image.new_from_surface(surface)
+ paintable = self._artwork_cache.get_cover_paintable(book, scale, BOOK_ICON_SIZE)
+ if paintable:
+ img = Gtk.Image.new_from_paintable(paintable)
else:
- img = Gtk.Image.new_from_icon_name("book-open-variant-symbolic", Gtk.IconSize.MENU)
+ img = Gtk.Image.new_from_icon_name("book-open-variant-symbolic")
img.props.pixel_size = BOOK_ICON_SIZE
+
img.set_size_request(BOOK_ICON_SIZE, BOOK_ICON_SIZE)
title_label = Gtk.Label()
title_label.set_text(tools.shorten_string(book.name, MAX_BOOK_LENGTH))
title_label.set_halign(Gtk.Align.START)
- title_label.props.margin = 4
+ title_label.props.margin_top = 4
+ title_label.props.margin_bottom = 4
+ title_label.props.margin_start = 4
+ title_label.props.margin_end = 5
title_label.props.hexpand = True
title_label.props.hexpand_set = True
- title_label.set_margin_right(5)
title_label.props.width_request = 100
title_label.props.xalign = 0.0
- title_label.set_line_wrap(True)
+ title_label.props.wrap = True
- self.box.add(img)
- self.box.add(title_label)
- self.add(self.box)
- self.show_all()
+ self.box.append(img)
+ self.box.append(title_label)
+ self.append(self.box)
diff --git a/cozy/ui/widgets/seek_bar.py b/cozy/ui/widgets/seek_bar.py
index 29795918..31c9e5c6 100644
--- a/cozy/ui/widgets/seek_bar.py
+++ b/cozy/ui/widgets/seek_bar.py
@@ -1,11 +1,7 @@
-import gi
-from gi.repository import GObject, Gdk
+from gi.repository import Gdk, GObject, Gtk
from cozy.control.string_representation import seconds_to_str
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk
-
@Gtk.Template.from_resource('/com/github/geigi/cozy/seek_bar.ui')
class SeekBar(Gtk.Box):
@@ -14,7 +10,7 @@ class SeekBar(Gtk.Box):
progress_scale: Gtk.Scale = Gtk.Template.Child()
current_label: Gtk.Label = Gtk.Template.Child()
remaining_label: Gtk.Label = Gtk.Template.Child()
- remaining_event_box: Gtk.EventBox = Gtk.Template.Child()
+ remaining_event_box: Gtk.Box = Gtk.Template.Child()
def __init__(self, **kwargs):
super().__init__(**kwargs)
@@ -22,9 +18,16 @@ def __init__(self, **kwargs):
self._progress_scale_pressed = False
self.progress_scale.connect("value-changed", self._on_progress_scale_changed)
- self.progress_scale.connect("button-release-event", self._on_progress_scale_clicked)
- self.progress_scale.connect("button-press-event", self._on_progress_scale_press)
- self.progress_scale.connect("key-press-event", self._on_progress_key_pressed)
+
+ self._progress_scale_gesture = Gtk.GestureClick()
+ self._progress_scale_gesture.connect("pressed", self._on_progress_scale_press)
+ self._progress_scale_gesture.connect("end", self._on_progress_scale_release)
+ self.progress_scale.add_controller(self._progress_scale_gesture)
+
+ self._progress_scale_key = Gtk.EventControllerKey()
+ self._progress_scale_key.connect("key-pressed", self._on_progress_scale_press)
+ self._progress_scale_key.connect("key-released", self._on_progress_scale_release)
+ self.progress_scale.add_controller(self._progress_scale_key)
@property
def position(self) -> float:
@@ -72,7 +75,7 @@ def _on_progress_scale_changed(self, _):
self.current_label.set_markup("" + current_text + "")
self.remaining_label.set_markup("-" + remaining_text + "")
- def _on_progress_scale_clicked(self, _, __):
+ def _on_progress_scale_release(self, *_):
self._progress_scale_pressed = False
value = self.progress_scale.get_value()
self.emit("position-changed", value)
@@ -86,9 +89,8 @@ def _on_progress_key_pressed(self, _, event):
self.position = min(self.position + 30, max_value)
self.emit("position-changed", self.position)
- def _on_progress_scale_press(self, _, __):
+ def _on_progress_scale_press(self, *_):
self._progress_scale_pressed = True
-
return False
diff --git a/cozy/ui/widgets/sleep_timer.py b/cozy/ui/widgets/sleep_timer.py
index 4c49e79c..6d396db1 100644
--- a/cozy/ui/widgets/sleep_timer.py
+++ b/cozy/ui/widgets/sleep_timer.py
@@ -1,9 +1,6 @@
-import gi
-
from cozy.ext import inject
from cozy.view_model.sleep_timer_view_model import SleepTimerViewModel, SystemPowerControl
-gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
@@ -21,8 +18,8 @@ class SleepTimer(Gtk.Popover):
power_control_switch: Gtk.Switch = Gtk.Template.Child()
power_control_options: Gtk.Box = Gtk.Template.Child()
- system_shutdown_radiob: Gtk.RadioButton = Gtk.Template.Child()
- system_suspend_radiob: Gtk.RadioButton = Gtk.Template.Child()
+ system_shutdown_radiob: Gtk.CheckButton = Gtk.Template.Child()
+ system_suspend_radiob: Gtk.CheckButton = Gtk.Template.Child()
def __init__(self, timer_image: Gtk.Image):
super().__init__()
@@ -101,4 +98,5 @@ def _on_timer_enabled_changed(self):
else:
icon = "no-bed-symbolic"
- self._timer_image.set_from_icon_name(icon, Gtk.IconSize.BUTTON)
+ self._timer_image.set_from_icon_name(icon)
+
diff --git a/cozy/ui/widgets/storage_list_box_row.py b/cozy/ui/widgets/storage_list_box_row.py
index b630b07a..fcf97bd9 100644
--- a/cozy/ui/widgets/storage_list_box_row.py
+++ b/cozy/ui/widgets/storage_list_box_row.py
@@ -6,7 +6,7 @@
from cozy.ext import inject
from cozy.model.library import Library
from cozy.model.settings import Settings
-from gi.repository import Gtk, GObject
+from gi.repository import Gtk, GObject, Gio, GLib
log = logging.getLogger("settings")
@@ -15,6 +15,9 @@ class StorageListBoxRow(Gtk.ListBoxRow):
"""
This class represents a listboxitem for a storage location.
"""
+
+ main_window = inject.attr("MainWindow")
+
def __init__(self, model: Storage):
self._model = model
@@ -24,32 +27,29 @@ def __init__(self, model: Storage):
box.set_spacing(3)
box.set_halign(Gtk.Align.FILL)
box.set_valign(Gtk.Align.CENTER)
- box.set_margin_left(5)
- box.set_margin_right(6)
- box.set_margin_top(10)
- box.set_margin_bottom(10)
+ box.set_margin_start(6)
+ box.set_margin_end(6)
+ box.set_margin_top(12)
+ box.set_margin_bottom(12)
self.default_image = Gtk.Image()
- self.default_image.set_from_icon_name("emblem-default-symbolic", Gtk.IconSize.LARGE_TOOLBAR)
- self.default_image.set_margin_right(5)
+ self.default_image.set_from_icon_name("emblem-default-symbolic")
+ self.default_image.set_margin_end(5)
self.type_image = Gtk.Image()
self._set_drive_icon()
- self.location_chooser = Gtk.FileChooserButton()
- self.location_chooser.set_local_only(False)
- self.location_chooser.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
- if self._model.path != "":
- self.location_chooser.set_current_folder(self._model.path)
- self.location_chooser.set_halign(Gtk.Align.FILL)
- self.location_chooser.props.hexpand = True
- self.location_chooser.connect("file-set", self.__on_folder_changed)
- self.location_chooser.set_margin_right(6)
-
- box.add(self.type_image)
- box.add(self.location_chooser)
- box.add(self.default_image)
- self.add(box)
- self.show_all()
+ self.location_chooser = Gtk.Button()
+ self.location_label = Gtk.Label()
+ self.location_chooser.set_child(self.location_label)
+ self.location_chooser.set_margin_end(6)
+ self.location_chooser.connect("clicked", self._on_location_chooser_clicked)
+
+ self.location_label.set_text(model.path)
+
+ box.append(self.type_image)
+ box.append(self.location_chooser)
+ box.append(self.default_image)
+ self.set_child(box)
self._set_default_icon()
@property
@@ -60,8 +60,7 @@ def refresh(self):
self._set_drive_icon()
self._set_default_icon()
- def __on_folder_changed(self, widget):
- new_path = self.location_chooser.get_file().get_path()
+ def __on_folder_changed(self, new_path):
self.emit("location-changed", new_path)
def _set_drive_icon(self):
@@ -72,11 +71,30 @@ def _set_drive_icon(self):
icon_name = "drive-harddisk-symbolic"
self.type_image.set_tooltip_text(_("Internal drive"))
- self.type_image.set_from_icon_name(icon_name, Gtk.IconSize.LARGE_TOOLBAR)
- self.type_image.set_margin_right(5)
+ self.type_image.set_from_icon_name(icon_name)
+ self.type_image.set_margin_end(5)
def _set_default_icon(self):
self.default_image.set_visible(self._model.default)
+ def _on_location_chooser_clicked(self, *junk):
+ location_chooser = Gtk.FileDialog(title=_("Set Audiobooks Directory"))
+
+ if self._model.path != "":
+ folder = Gio.File.new_for_path(self._model.path)
+ location_chooser.set_initial_folder(folder)
+
+ location_chooser.select_folder(self.main_window.window, None, self._location_chooser_open_callback)
+
+ def _location_chooser_open_callback(self, dialog, result):
+ try:
+ file = dialog.select_folder_finish(result)
+ except GLib.GError:
+ pass
+ else:
+ if file is not None:
+ self.__on_folder_changed(file.get_path())
+
GObject.signal_new('location-changed', StorageListBoxRow, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT,
(GObject.TYPE_PYOBJECT,))
+
diff --git a/cozy/ui/widgets/welcome.py b/cozy/ui/widgets/welcome.py
index 5798fb3f..c2537653 100644
--- a/cozy/ui/widgets/welcome.py
+++ b/cozy/ui/widgets/welcome.py
@@ -1,11 +1,10 @@
import gi
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk
+from gi.repository import Adw, Gtk
@Gtk.Template.from_resource('/com/github/geigi/cozy/welcome.ui')
-class Welcome(Gtk.Box):
+class Welcome(Adw.Bin):
__gtype_name__ = "Welcome"
def __init__(self, **kwargs):
diff --git a/cozy/ui/widgets/whats_new_importer.py b/cozy/ui/widgets/whats_new_importer.py
index 7148f974..95113fd2 100644
--- a/cozy/ui/widgets/whats_new_importer.py
+++ b/cozy/ui/widgets/whats_new_importer.py
@@ -1,6 +1,3 @@
-import gi
-
-gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
diff --git a/cozy/ui/widgets/whats_new_library.py b/cozy/ui/widgets/whats_new_library.py
index ec548b1d..abea0bdd 100644
--- a/cozy/ui/widgets/whats_new_library.py
+++ b/cozy/ui/widgets/whats_new_library.py
@@ -1,6 +1,3 @@
-import gi
-
-gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
diff --git a/cozy/ui/widgets/whats_new_m4b.py b/cozy/ui/widgets/whats_new_m4b.py
index 0f72ec6d..50f4e1ee 100644
--- a/cozy/ui/widgets/whats_new_m4b.py
+++ b/cozy/ui/widgets/whats_new_m4b.py
@@ -1,6 +1,3 @@
-import gi
-
-gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
diff --git a/cozy/ui/widgets/whats_new_m4b_chapter.py b/cozy/ui/widgets/whats_new_m4b_chapter.py
index 23234fad..d0a33acf 100644
--- a/cozy/ui/widgets/whats_new_m4b_chapter.py
+++ b/cozy/ui/widgets/whats_new_m4b_chapter.py
@@ -1,6 +1,3 @@
-import gi
-
-gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
diff --git a/cozy/ui/widgets/whats_new_window.py b/cozy/ui/widgets/whats_new_window.py
index 1e80d1ef..0850609f 100644
--- a/cozy/ui/widgets/whats_new_window.py
+++ b/cozy/ui/widgets/whats_new_window.py
@@ -1,19 +1,16 @@
from typing import List
from packaging import version
-import gi
-
from cozy.application_settings import ApplicationSettings
from cozy.ext import inject
from cozy.ui.main_view import CozyUI
from cozy.version import __version__ as CozyVersion
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk, Handy
+from gi.repository import Gtk, Adw
@Gtk.Template(resource_path='/com/github/geigi/cozy/whats_new.ui')
-class WhatsNewWindow(Handy.Window):
+class WhatsNewWindow(Adw.Window):
__gtype_name__ = 'WhatsNew'
content_stack: Gtk.Stack = Gtk.Template.Child()
@@ -41,7 +38,7 @@ def __init__(self, **kwargs):
self.set_default_size(800, 550)
for widget in self.children:
- self.content_stack.add(widget)
+ self.content_stack.add_child(widget)
widget.set_visible(False)
self.children[0].set_visible(True)
diff --git a/cozy/view_model/app_view_model.py b/cozy/view_model/app_view_model.py
index d98a8b64..c9d02d3a 100644
--- a/cozy/view_model/app_view_model.py
+++ b/cozy/view_model/app_view_model.py
@@ -20,8 +20,3 @@ def view(self, new_value: View):
self._notify("view")
self.emit_event_main_thread("view", self._view)
- def navigate_back(self):
- if self.view == View.BOOK_DETAIL:
- self.view = View.LIBRARY_BOOKS
- elif self.view == View.LIBRARY_BOOKS:
- self.view = View.LIBRARY_FILTER
diff --git a/cozy/view_model/book_detail_view_model.py b/cozy/view_model/book_detail_view_model.py
index 9aa7d024..ede76aec 100644
--- a/cozy/view_model/book_detail_view_model.py
+++ b/cozy/view_model/book_detail_view_model.py
@@ -217,6 +217,3 @@ def _on_offline_cache_event(self, event, message):
def _on_app_setting_changed(self, event, _):
if event == "swap-author-reader":
self._notify("book")
-
- def navigate_back(self):
- self.emit_event(OpenView.BACK)
diff --git a/cozy/view_model/headerbar_view_model.py b/cozy/view_model/headerbar_view_model.py
index 564aea6e..dc20d622 100644
--- a/cozy/view_model/headerbar_view_model.py
+++ b/cozy/view_model/headerbar_view_model.py
@@ -52,23 +52,8 @@ def work_progress(self) -> float:
def work_message(self) -> str:
return self._work_message
- @property
- def can_navigate_back(self) -> bool:
- return self._view == View.BOOK_DETAIL or \
- self._view == View.LIBRARY_BOOKS
-
- @property
- def show_library_filter(self) -> bool:
- return self._view == View.LIBRARY or \
- self._view == View.LIBRARY_BOOKS or \
- self._view == View.LIBRARY_FILTER or \
- self._view == View.BOOK_DETAIL or \
- self._view == View.NO_MEDIA
-
def set_view(self, value: View):
self._view = value
- self._notify("can_navigate_back")
- self._notify("show_library_filter")
self._notify("lock_ui")
def _start_working(self, message: str):
@@ -120,5 +105,3 @@ def _on_offline_cache_event(self, event: str, message):
elif event == "finished":
self._stop_working()
- def navigate_back(self):
- self.emit_event(OpenView.BACK)
diff --git a/cozy/view_model/library_view_model.py b/cozy/view_model/library_view_model.py
index 38263155..612724e8 100644
--- a/cozy/view_model/library_view_model.py
+++ b/cozy/view_model/library_view_model.py
@@ -50,7 +50,6 @@ def __init__(self):
super(Observable, self).__init__()
self._library_view_mode: LibraryViewMode = LibraryViewMode.CURRENT
- self._library_page: LibraryPage = LibraryPage.NONE
self._selected_filter: str = _("All")
self._connect()
@@ -77,15 +76,6 @@ def library_view_mode(self, value):
self._notify("library_view_mode")
self.emit_event(OpenView.LIBRARY, None)
- @property
- def library_page(self) -> LibraryPage:
- return self._library_page
-
- @library_page.setter
- def library_page(self, value: LibraryPage):
- self._library_page = value
- self._notify("library_page")
-
@property
def selected_filter(self):
return self._selected_filter
@@ -211,8 +201,7 @@ def _on_importer_event(self, event, message):
self._notify("books-filter")
self._notify("library_view_mode")
if event == "import-failed":
- dialog = ImportFailedDialog(message)
- dialog.show()
+ ImportFailedDialog(message).show()
def _on_player_event(self, event, message):
if event == "play":
diff --git a/cozy/view_model/settings_view_model.py b/cozy/view_model/settings_view_model.py
index 3301811e..28251f8a 100644
--- a/cozy/view_model/settings_view_model.py
+++ b/cozy/view_model/settings_view_model.py
@@ -13,7 +13,7 @@
from cozy.media.importer import Importer
from cozy.model.settings import Settings
from cozy.report import reporter
-from gi.repository import Gtk
+from gi.repository import Gtk, Adw
@@ -34,6 +34,8 @@ def __init__(self):
self._lock_ui: bool = False
self._gtk_settings = Gtk.Settings.get_default()
+ self.style_manager = Adw.StyleManager.get_default()
+ self._set_dark_mode()
self._app_settings.add_listener(self._on_app_setting_changed)
@@ -119,8 +121,10 @@ def add_first_storage_location(self, path: str):
self._notify("storage_locations")
def _set_dark_mode(self):
- prefer_dark_mode = self._app_settings.dark_mode
- self._gtk_settings.set_property("gtk-application-prefer-dark-theme", prefer_dark_mode)
+ if self._app_settings.dark_mode:
+ self.style_manager.set_color_scheme(Adw.ColorScheme.PREFER_DARK)
+ else:
+ self.style_manager.set_color_scheme(Adw.ColorScheme.PREFER_LIGHT)
def _on_app_setting_changed(self, event: str, data):
if event == "dark-mode":
diff --git a/data/icons/hicolor/scalable/actions/library-symbolic.svg b/data/icons/hicolor/scalable/actions/library-symbolic.svg
new file mode 100644
index 00000000..4fdb7f87
--- /dev/null
+++ b/data/icons/hicolor/scalable/actions/library-symbolic.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/data/icons/hicolor/scalable/actions/pause-symbolic.svg b/data/icons/hicolor/scalable/actions/pause-symbolic.svg
deleted file mode 100644
index f2804257..00000000
--- a/data/icons/hicolor/scalable/actions/pause-symbolic.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
diff --git a/data/icons/hicolor/scalable/actions/play-symbolic.svg b/data/icons/hicolor/scalable/actions/play-symbolic.svg
deleted file mode 100644
index d8ea5926..00000000
--- a/data/icons/hicolor/scalable/actions/play-symbolic.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/data/ui/about.ui b/data/ui/about.ui
index 4691fac4..468226d9 100644
--- a/data/ui/about.ui
+++ b/data/ui/about.ui
@@ -1,22 +1,14 @@
-
-
-
diff --git a/data/ui/album_element.ui b/data/ui/album_element.ui
index 1b9b0046..5ed94ea6 100644
--- a/data/ui/album_element.ui
+++ b/data/ui/album_element.ui
@@ -1,150 +1,73 @@
-
-
-
- True
- False
- play-symbolic
- 2
-
-
+
- 200
- 200
- True
- False
vertical
- True
- False
- center
- center
- False
- False
-
+
- True
- False
- center
- center
- False
- False
-
+
- True
- False
- start
- False
- False
- gtk-missing-image
-
- -1
-
-
+
- True
- False
- GDK_POINTER_MOTION_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_STRUCTURE_MASK
crossfade
200
-
+
- True
- False
- False
- False
-
+
-
- 1
-
-
- -1
-
-
+
play_button_revealer
- True
- False
- GDK_POINTER_MOTION_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_STRUCTURE_MASK
end
end
- False
- False
+ 0
+ 0
crossfade
200
-
+
- True
- False
end
end
- 10
+ 10
10
- False
- False
-
+
40
40
- True
- True
- False
- False
- Play
+ 1
+ 0
+ Play
center
center
- False
- False
- button_image
+ media-playback-start-symbolic
-
- -1
-
-
+
+ 0
40
40
- True
- False
- GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_MOTION_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_FOCUS_CHANGE_MASK | GDK_STRUCTURE_MASK | GDK_PROPERTY_CHANGE_MASK | GDK_VISIBILITY_NOTIFY_MASK | GDK_PROXIMITY_IN_MASK | GDK_PROXIMITY_OUT_MASK | GDK_SUBSTRUCTURE_MASK | GDK_SCROLL_MASK | GDK_TOUCH_MASK | GDK_SMOOTH_SCROLL_MASK | GDK_TOUCHPAD_GESTURE_MASK | GDK_TABLET_PAD_MASK
center
center
- False
- False
-
- True
-
-
+
-
- 1
-
-
- False
- True
- 0
-
diff --git a/data/ui/application_default.css b/data/ui/application_default.css
deleted file mode 100644
index f5851aa6..00000000
--- a/data/ui/application_default.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.bold {
- font-weight: bold;
-}
-
-.semi-bold {
- font-weight: 600;
-}
-
-.bordered {
- border: 1px solid shade(@theme_bg_color, 0.8);
-}
-
-.h1 {
- font-size: 230%;
-}
-
-.h2 {
- font-size: 200%;
- font-weight: 200;
-}
-
-.h3 {
- font-size: 160%;
-}
-
-.h5 {
- font-size: 115%;
-}
-
-.monospace {
- font-family: monospace;
-}
-
diff --git a/data/ui/application_legacy.css b/data/ui/application_legacy.css
deleted file mode 100644
index 5c680ae3..00000000
--- a/data/ui/application_legacy.css
+++ /dev/null
@@ -1,12 +0,0 @@
-.bordered {
- border: 2px solid shade(@theme_bg_color, 0.85);
-}
-
-.h4 {
- font-weight: lighter;
- font-size: 110%;
-}
-
-.h5 {
- font-size: 115%;
-}
\ No newline at end of file
diff --git a/data/ui/book_detail.ui b/data/ui/book_detail.ui
index 0b6eb43a..df9a7838 100644
--- a/data/ui/book_detail.ui
+++ b/data/ui/book_detail.ui
@@ -1,569 +1,387 @@
-
-
-
-
- True
- False
+
+
- True
- False
vertical
- True
- True
+ true
center
- True
+ true
never
- False
- True
- True
-
+ true
+ true
+
- True
- False
- none
-
+
- True
- False
center
- 10
- 10
- 20
- 20
- True
- True
- 10
- 10
+ 12
+ 12
+ 12
+ 12
+ true
+ true
+ 12
+ 12
1
2
none
- True
- False
center
- center
-
-
- True
- False
+ start
+
+
250
250
- True
- False
vertical
- 5
+ 6
- True
- False
center
- 25
- vertical
+ 18
+ 256
+ 256
-
+
+
+
-
- False
- True
- 0
-
-
- True
- False
- 20
- vertical
- start
-
-
- True
- True
- True
- none
-
-
- True
- True
- play-symbolic
- 5
-
-
-
-
- True
- True
- 0
-
-
+
+ start
+ end
+ Book name
+ true
+ word-char
+ end
+ 25
+ 4
+ 0
+
+
+
+
+
+ start
+ start
+ 6
+ Book author
+ true
+ word-char
+ end
+ 30
+ 2
+ 0
+
-
- False
- True
- end
- 1
-
- True
- False
center
center
- 15
+ 12
5
- True
- False
end
center
- True
- False
download-symbolic
- 3
-
- False
- True
- 0
-
- True
- False
- 5
- 4
- Download
+ 5
+ 4
+ Download
-
- False
- True
- 1
-
-
- False
- True
- 0
-
- True
- True
+ true
start
center
-
- False
- True
- 1
-
-
- False
- True
- end
- 2
-
250
- True
- False
center
start
- True
+ true
+ 18
-
- False
- True
- end
- 2
-
-
- True
- False
- 20
- True
+ 18
+ true
4
20
- True
- False
start
- False
- Remaining
+ true
+ Remaining
+
+ 0
+ 3
+
-
- 0
- 3
-
- True
- False
- True
+ true
label
0
+
+ 1
+ 3
+
-
- 1
- 3
-
- True
- False
- True
+ true
label
0
+
+ 1
+ 2
+
-
- 1
- 2
-
- True
- False
- True
+ true
label
0
+
+ 1
+ 1
+
-
- 1
- 1
-
- True
- False
start
- False
- Total
+ Total
+
+ 0
+ 2
+
-
- 0
- 2
-
- True
- False
start
- False
- Last played
+ Last played
+
+ 0
+ 1
+
-
- 0
- 1
-
- True
- False
start
- False
- Published
+ Published
+
+ 0
+ 0
+
-
- 0
- 0
-
- True
- False
- True
+ true
label
0
+
+ 1
+ 0
+
-
- 1
- 0
-
-
- False
- True
- end
- 3
-
- True
- False
- Some or all files of this book cannot be found.
+ Some or all files of this book cannot be found.
center
- 20
4
- True
- False
info-symbolic
-
- False
- True
- 0
-
- True
- False
- unavailable
+ unavailable
-
- False
- True
- 1
-
-
- False
- True
- end
- 4
-
-
-
-
- True
- False
- start
- start
- 15
- Book author
- True
- word-char
- end
- 30
- 2
- 0
-
-
-
-
-
-
- False
- True
- end
- 6
-
-
- True
- False
- start
- end
- Book name
- True
- word-char
- end
- 25
- 4
- 0
-
-
-
-
+
+ 20
+ vertical
+
+
+ true
+ true
+ center
+ media-playback-start-symbolic
+
+
+
-
- False
- True
- end
- 8
-
-
+
- True
- False
- True
- True
-
-
- True
- False
+ true
+ true
+
+
500
350
- True
- False
-
- True
- True
- True
- True
- never
- never
- in
- True
- True
-
-
- True
- False
- none
-
-
- True
- False
- start
- 8
- 8
- 8
- 8
- vertical
- 4
-
-
-
+
+ chapters_wrapper
+ page0
+
+
+ true
+ true
+ true
+ never
+ never
+ true
+ true
+
+
+
+
+ start
+ 8
+ 8
+ 8
+ 8
+ vertical
+ 4
+
+
+
+
+
+
-
+
-
-
+
-
- chapters_wrapper
- page0
-
-
- True
- False
- center
- start
- 8
-
-
- True
- False
- True
-
-
- True
- False
- 0
-
-
-
-
- True
- False
- Loading chapters, please wait...
-
-
- False
- False
- 5
- 1
-
-
-
-
+
chapters_loader
chapters
- 1
-
+
+
+ center
+ start
+ 8
+
+
+ center
+ true
+
+
+
+
+ center
+ Loading chapters, please wait...
+
+
+
+
+
-
+
-
+
-
+
-
- False
- True
- 0
-
+
+
diff --git a/data/ui/book_element.ui b/data/ui/book_element.ui
index 8102da86..402d96c7 100644
--- a/data/ui/book_element.ui
+++ b/data/ui/book_element.ui
@@ -1,104 +1,59 @@
-
-
-
+
- True
- False
- GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_MOTION_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_FOCUS_CHANGE_MASK | GDK_STRUCTURE_MASK | GDK_PROPERTY_CHANGE_MASK | GDK_VISIBILITY_NOTIFY_MASK | GDK_PROXIMITY_IN_MASK | GDK_PROXIMITY_OUT_MASK | GDK_SUBSTRUCTURE_MASK | GDK_SCROLL_MASK | GDK_TOUCH_MASK | GDK_SMOOTH_SCROLL_MASK | GDK_TOUCHPAD_GESTURE_MASK | GDK_TABLET_PAD_MASK
- center
- start
- False
- False
-
-
- True
- False
- center
+ 150
+ 150
+
+
+ true
+ Open book overview
+ 6
+ 6
+ 12
+ 12
+ vertical
+ 1
start
- 300
- 250
-
- True
- False
- center
- start
- False
- False
-
-
- 200
- True
- True
- GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_MOTION_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_FOCUS_CHANGE_MASK | GDK_STRUCTURE_MASK | GDK_PROPERTY_CHANGE_MASK | GDK_VISIBILITY_NOTIFY_MASK | GDK_PROXIMITY_IN_MASK | GDK_PROXIMITY_OUT_MASK | GDK_SUBSTRUCTURE_MASK | GDK_SCROLL_MASK | GDK_TOUCH_MASK | GDK_SMOOTH_SCROLL_MASK | GDK_TOUCHPAD_GESTURE_MASK | GDK_TABLET_PAD_MASK
- Open book overview
- start
- start
- 10
- 10
- 10
- 10
- False
- False
- vertical
- 1
-
-
- True
- False
- end
- False
- False
- True
- end
- 30
- 0
- 0
-
-
-
- False
- True
- end
- 0
-
-
-
-
- True
- False
- end
- 15
- False
- False
- True
- end
- 20
- 0
- 0
-
-
-
- False
- True
- end
- 1
-
-
-
-
-
+
+ start
+ end
+ 15
+ false
+ false
+ true
+ end
+ 20
+ 0
+ 0
+
+
+
+ start
+ end
+ false
+ false
+ true
+ end
+ 30
+ 0
+ 0
+
+
+
+
-
+
diff --git a/data/ui/catalog/book_detail.xml b/data/ui/catalog/book_detail.xml
deleted file mode 100644
index 9740640a..00000000
--- a/data/ui/catalog/book_detail.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
- glade_python_init
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/data/ui/catalog/error_reporting.xml b/data/ui/catalog/error_reporting.xml
deleted file mode 100644
index 140d9857..00000000
--- a/data/ui/catalog/error_reporting.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
- glade_python_init
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/data/ui/catalog/filter_list_box.xml b/data/ui/catalog/filter_list_box.xml
deleted file mode 100644
index c8a4ac99..00000000
--- a/data/ui/catalog/filter_list_box.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
- glade_python_init
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/data/ui/catalog/seek_bar.xml b/data/ui/catalog/seek_bar.xml
deleted file mode 100644
index f02f3dac..00000000
--- a/data/ui/catalog/seek_bar.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
- glade_python_init
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/data/ui/chapter_element.ui b/data/ui/chapter_element.ui
index d8606ede..21137c26 100644
--- a/data/ui/chapter_element.ui
+++ b/data/ui/chapter_element.ui
@@ -1,74 +1,51 @@
-
-
-
- True
- False
+
+
+ Play this part
- True
- False
- 10
- 10
- 7
- 7
+ 12
+ 12
+ 6
+ 6
5
-
- True
- False
+
-
- True
- False
- GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_STRUCTURE_MASK
-
+
+ number
+
- True
- False
start
- 4
- 4
+ 3
0
0
-
- number
- page0
-
-
-
+
+
+
+
+
+ play_icon
+
- True
- False
start
center
- play-symbolic
+ media-playback-start-symbolic
+ 12
-
- play_icon
- page0
- 1
-
-
+
-
- False
- True
- 0
-
- True
- False
- True
+ true
end
50
0
@@ -77,27 +54,14 @@
-
- False
- True
- 1
-
- True
- False
end
right
0
0
-
- False
- True
- end
- 2
-
diff --git a/data/ui/db_migration_failed.ui b/data/ui/db_migration_failed.ui
deleted file mode 100644
index 07ade838..00000000
--- a/data/ui/db_migration_failed.ui
+++ /dev/null
@@ -1,164 +0,0 @@
-
-
-
-
-
- False
- popup
-
- False
- True
- center-on-parent
- 200
- True
- dialog
- True
- False
-
-
-
-
-
- False
- vertical
-
-
- False
- 10
- 10
-
-
- Close Cozy
- True
- True
- False
-
-
-
- True
- True
- 0
-
-
-
-
- Receive help on GitHub
- True
- True
- True
- 20
-
-
-
- True
- True
- 1
-
-
-
-
- False
- False
- 0
-
-
-
-
- True
- False
- center
- center
- 10
- 20
- 10
- 20
- 20
-
-
- True
- False
- 64
- dialog-warning
- 6
-
-
- False
- True
- 0
-
-
-
-
- True
- False
- center
- center
- vertical
- 20
-
-
- True
- False
- start
- start
- An error occured while updating the database
- True
- 45
- 0
-
-
-
- False
- False
- 0
-
-
-
-
- True
- False
- start
- start
- During an update of the database an error occurred and Cozy will not be able to startup.
-A backup of the database was created before the update and has been restored now.
-Until this issue is resolved please use version 0.9.5 of Cozy.
-You can help resolve this problem by reporting an issue on GitHub.
- True
- 70
- 0
-
-
- False
- False
- 1
-
-
-
-
- False
- False
- 1
-
-
-
-
- False
- False
- 1
-
-
-
-
-
- button1
- button2
-
-
-
diff --git a/data/ui/delete_book_dialog.ui b/data/ui/delete_book_dialog.ui
deleted file mode 100644
index 9d8bbae1..00000000
--- a/data/ui/delete_book_dialog.ui
+++ /dev/null
@@ -1,158 +0,0 @@
-
-
-
-
-
- False
- popup
-
- False
- True
- center-on-parent
- 200
- True
- dialog
- True
- False
-
-
-
-
-
- False
- vertical
-
-
- False
- 10
- 10
-
-
- Cancel
- True
- True
- True
- 20
-
-
- True
- True
- 0
-
-
-
-
- Delete Audiobook
- True
- True
- False
-
-
-
- True
- True
- 1
-
-
-
-
- False
- False
- 0
-
-
-
-
- True
- False
- center
- center
- 10
- 20
- 10
- 20
- 20
-
-
- True
- False
- 64
- dialog-warning
- 6
-
-
- False
- True
- 0
-
-
-
-
- True
- False
- center
- center
- vertical
- 20
-
-
- True
- False
- start
- start
- Are you sure you want to delete the selected audiobook?
- True
- 45
- 0
-
-
-
- False
- False
- 0
-
-
-
-
- True
- False
- start
- start
- The audiobook will be removed from your disk and from Cozy's library.
- True
- 70
- 0
-
-
- False
- False
- 1
-
-
-
-
- False
- False
- 1
-
-
-
-
- False
- False
- 1
-
-
-
-
-
- button2
- button1
-
-
-
diff --git a/data/ui/error_reporting.ui b/data/ui/error_reporting.ui
index 2a1a7fc6..fe418c72 100644
--- a/data/ui/error_reporting.ui
+++ b/data/ui/error_reporting.ui
@@ -1,8 +1,6 @@
-
-
-
+
1
5
@@ -12,70 +10,45 @@
1
- True
- False
vertical
10
+
-
- False
- True
- 0
-
- True
- False
none
- False
+ 0
+
- True
- False
- False
- False
-
+ 0
+ 0
+
- True
- False
10
10
10
@@ -84,146 +57,96 @@
10
- True
- False
center
vertical
10
- True
- False
start
- start
- You can help improve Cozy by contributing information in case of errors and crashes.
- True
+ center
+ You can help improve Cozy by contributing information in case of errors and crashes.
+ 1
0
-
- False
- False
- 0
-
- True
- False
- Contributing this information is optional and completely anonymous. We will never collect personal data, files you import or any information that could identify you.
- True
+ center
+ Contributing this information is optional and completely anonymous. We will never collect personal data, files you import or any information that could identify you.
+ 1
0
-
- False
- False
- 1
-
- True
- False
+ center
start
- Cozy is opensource and the user feedback source code can be inspected here:
- True
+ Cozy is opensource and the user feedback source code can be inspected here:
+ 1
0
-
- False
- False
- 2
-
-
- False
- True
- 0
-
- True
- False
- <a href="https://github.com/geigi/cozy/tree/master/cozy/report">Sourcecode on GitHub</a>
- True
- False
+ <a href="https://github.com/geigi/cozy/tree/master/cozy/report">Sourcecode on GitHub</a>
+ 1
-
- False
- True
- 1
-
-
+
- True
- False
- False
- False
-
+ 0
+ 0
+
- True
- False
5
5
-
+
- True
- False
- False
- False
-
+ 0
+ 0
+
- True
- True
+ 1
20
20
10
10
verbose_adjustment
- False
+ 0
0
0
- False
-
+
- True
- False
- False
- False
-
+ 0
+ 0
+
- True
- False
5
5
-
+
- True
- False
- False
- False
-
+ 0
+ 0
+
- True
- False
start
center
10
@@ -234,62 +157,37 @@
10
- True
- False
start
- Detailed error reporting with import errors
- True
+ Detailed error reporting with import errors
+ 1
0
-
- False
- True
- 0
-
- True
- False
start
start
- The following information will be sent in case of an error or crash:
- True
+ The following information will be sent in case of an error or crash:
+ 1
0
-
- False
- True
- 1
-
- True
- False
+ 1
start
start
- True
+ 1
-
- True
- True
- 2
-
-
+
-
- False
- True
- 2
-
diff --git a/data/ui/file_not_found.ui b/data/ui/file_not_found.ui
deleted file mode 100644
index 0f001d76..00000000
--- a/data/ui/file_not_found.ui
+++ /dev/null
@@ -1,147 +0,0 @@
-
-
-
-
-
- False
- dialog
-
-
- False
- vertical
- 2
-
-
- False
- end
-
-
- Cancel
- True
- True
- True
-
-
- True
- True
- 0
-
-
-
-
- Locate
- True
- True
- True
-
-
- True
- True
- 1
-
-
-
-
- False
- False
- 0
-
-
-
-
- True
- False
- True
-
-
- True
- False
- 5
- 10
- dialog-warning
- 6
-
-
- False
- True
- 0
-
-
-
-
- 300
- 100
- True
- False
- 6
- True
- vertical
-
-
- True
- False
- 5
- True
- True
- File not found
-
-
-
- False
- True
- 0
-
-
-
-
- True
- False
- center
- <tt>/home/ju/path/to/the/borken/file/that/is/maybe/very/very/long.mp3</tt>
- True
- True
- char
-
-
- False
- True
- 1
-
-
-
-
- True
- False
- center
- True
- This file could not be found. Do you want to locate it manually?
-
-
- False
- True
- 2
-
-
-
-
- False
- True
- 1
-
-
-
-
- False
- True
- 1
-
-
-
-
-
-
-
-
-
diff --git a/data/ui/gresource.xml b/data/ui/gresource.xml
index 51baa252..05828d20 100644
--- a/data/ui/gresource.xml
+++ b/data/ui/gresource.xml
@@ -1,31 +1,22 @@
- application.css
- application_legacy.css
- application_default.css
+ style.css
about.ui
album_element.ui
book_detail.ui
book_element.ui
chapter_element.ui
- db_migration_failed.ui
- delete_book_dialog.ui
error_reporting.ui
- file_not_found.ui
headerbar.ui
- import_failed.ui
main_window.ui
- media_controller_big.ui
- media_controller_small.ui
+ media_controller.ui
playback_speed_popover.ui
preferences.ui
progress_popover.ui
search_popover.ui
seek_bar.ui
timer_popover.ui
- titlebar_menu.ui
- warning_popover.ui
welcome.ui
whats_new.ui
whats_new_importer.ui
diff --git a/data/ui/headerbar.ui b/data/ui/headerbar.ui
index b62d6c49..408d2f72 100644
--- a/data/ui/headerbar.ui
+++ b/data/ui/headerbar.ui
@@ -1,125 +1,86 @@
-
-
-
-
- True
- False
- go-previous-symbolic
-
-
+
diff --git a/data/ui/import_failed.ui b/data/ui/import_failed.ui
deleted file mode 100644
index f7c31995..00000000
--- a/data/ui/import_failed.ui
+++ /dev/null
@@ -1,166 +0,0 @@
-
-
-
-
-
-
-
-
-
- False
- dialog
-
-
- False
- 5
- 5
- 5
- 5
- vertical
- 2
-
-
- False
- end
-
-
- Ok
- True
- True
- True
-
-
- True
- True
- 1
-
-
-
-
- False
- False
- 0
-
-
-
-
- True
- False
- True
-
-
- True
- False
- 5
- 10
- dialog-warning
- 6
-
-
- False
- True
- 0
-
-
-
-
- 300
- 100
- True
- False
- 6
- True
- vertical
-
-
- True
- False
- 5
- True
- True
- Some files could not be imported
-
-
-
- False
- True
- 0
-
-
-
-
- 400
- 100
- True
- True
- 10
- 10
- True
- True
- in
-
-
- True
- False
- True
-
-
- text-view
- True
- True
- False
- False
- files_buffer
-
-
-
-
-
-
- False
- True
- 1
-
-
-
-
- True
- False
- start
- 5
- True
- This can have multiple reasons:
-- The audio format is not supported
-- The path or filename contains non utf-8 characters
-- The file(s) are no valid audio files
-- The file(s) are corrupt
-
-
- False
- True
- 2
-
-
-
-
- False
- True
- 1
-
-
-
-
- False
- True
- 1
-
-
-
-
-
-
-
-
-
diff --git a/data/ui/main_window.ui b/data/ui/main_window.ui
index f4526a42..b6ad6048 100644
--- a/data/ui/main_window.ui
+++ b/data/ui/main_window.ui
@@ -1,579 +1,304 @@
-
-
-
-
- 0.25
- 2
- 1
- 0.05
- 0.10
-
-
+
+
main_window
- False
+ Cozy
com.github.geigi.cozy
+ 360
+ 294
-
-
-
- author
- Author
- account-symbolic
- 1
-
-
- 220
- True
- True
- never
- in
-
-
- True
- False
-
-
- True
- False
-
-
- List of readers
-
-
-
-
-
-
-
-
+
+ Book title
+ book_overview
+
+
+
-
- reader
- Reader
- microphone-symbolic
- 2
-
-
- filter
-
-
-
-
- True
- False
- True
- False
- False
- True
+
+
+
+ vertical
-
- True
- True
- True
- never
- in
-
-
- True
- False
-
-
- True
- False
- 10
- 10
- True
- True
- True
- 10
- 5
- 1
- 10
- none
-
-
- List of books
-
-
-
-
-
-
-
+
-
- books
-
-
- True
- False
- vertical
-
-
- True
- False
- center
- center
- 15
- 15
- True
- True
- Start exploring your library by switching to the Author or Reader view.
- True
- 0
- 0
-
-
-
- False
- True
- 0
-
-
+
+ false
+ true
-
- no_recent
- 1
-
-
- books
-
-
- main
- Author
-
-
-
-
- True
- False
- center
- center
- 40
- 40
- vertical
- 20
-
-
- True
- False
- 120
- com.github.geigi.cozy
+
+
+
+
+
+ import
+
+
+
+
-
- False
- True
- 0
-
-
-
- True
- False
- Stay tuned while Cozy is preparing your library…
- True
- 0
- 0
-
+
+
+ com.github.geigi.cozy
+ Importing
+ Stay tuned while Cozy is preparing your library…
-
- True
- True
- 1
-
-
+
-
- import
- page1
- 1
-
-
-
-
- True
- True
- never
- in
-
-
- True
- False
-
-
- True
- False
- 700
- 250
+
+
+
+
+
+ no_media
+
+
+
+
+
+
+
+ com.github.geigi.cozy
+ Let's get cozy
+ Select a folder, or drag audiobooks here to import them
+
+
+ center
-
- True
- False
- center
- center
- 20
- 20
- 40
- 40
- vertical
-
-
- True
- False
- start
- 10
- Import your Audiobooks
- True
- 0
-
-
-
- False
- True
- 0
-
-
-
-
- True
- False
- start
- 85
- Cozy automatically imports your audiobooks in one directory - your library
- True
- 0
-
-
-
- False
- True
- 1
-
-
-
-
- True
- False
- 0
- in
-
-
- True
- False
- none
-
-
- 100
- 80
- True
- False
- False
- False
- Drag & Drop
- input-mouse-symbolic
- Drag your audiobooks into cozy and they will be automatically imported
-
-
-
-
-
-
-
- True
- True
- False
- False
-
-
- True
- False
-
-
-
-
-
-
- 80
- True
- False
- Set Audiobooks Directory
- folder-symbolic
- Load audiobooks from a directory, network drive or an external disk
-
-
- Select
- True
- False
- False
- end
- center
-
-
-
-
-
-
-
-
-
-
-
- False
- True
- 2
-
-
+
+ Select folder
+
-
+
-
-
-
- no_media
- page0
- 2
-
-
-
-
-
- True
- True
- 0
-
-
-
-
- True
- False
-
-
- False
- True
- 1
-
-
-
-
- 46
- True
- False
- True
- crossfade
-
-
-
-
-
-
- False
- True
- 2
-
-
-
-
- True
- False
- slide-up
- 200
-
-
- True
- False
- True
- narrow
+
-
-
-
- False
- True
- 3
-
-
-
-
- -1
-
-
-
-
- True
- False
- center
- start
- error
- True
- False
-
-
- False
-
-
-
-
+
-
- False
- True
- end
- 1
-
-
-
-
- True
- False
- 10
- 10
- play-pause-symbolic
-
-
- True
- False
- 10
- 10
- pause-symbolic
- 3
-
-
- True
- False
- 10
- 10
- play-symbolic
-
-
- True
- False
- 4
- 4
- object-rotate-left-symbolic
+
diff --git a/data/ui/media_controller.ui b/data/ui/media_controller.ui
index b343d194..fac4dd60 100644
--- a/data/ui/media_controller.ui
+++ b/data/ui/media_controller.ui
@@ -1,609 +1,237 @@
-
-
-
-
- True
- False
- object-rotate-right-symbolic
-
-
- True
- False
- object-rotate-right-symbolic
-
-
- True
- False
- play-symbolic
-
-
- True
- False
- play-symbolic
-
-
- True
- False
- object-rotate-left-symbolic
-
-
- True
- False
- True
- True
- True
+
+
+ true
+ 350
+ 52
-
- True
- False
-
+
+ max-width: 750px
+ false
+ false
+ false
+ false
+
+
+
+
+ max-width: 550px
+ false
+ false
+ false
+ false
+ false
+ false
+
+
+
+
+
- True
- False
- start
- 6
+ center
+ false
+ 12
-
- True
- False
- end
- center
- 3
+
+ 12
+ 0
-
- True
- False
-
-
- True
- False
- True
- True
- Rewind
- center
- center
- True
- prev_img
-
-
- Rewind button
- Rewind playback
-
-
-
-
- False
- True
- 0
-
-
+
+ 52
+ end
+
+
+
+
+ true
+ Currently playing
+ center
+ vertical
+ 2
-
- 42
- True
- False
- True
- True
- Start playback
- center
+
+ start
center
- True
- play_img
-
-
- Play/Pause Button
- Start or pause the playback
-
-
+ end
+ 15
+ true
+ 20
+ 0
+
+
+
+
+ Title of currently playing book
+
+
-
- True
- True
- 1
-
-
- True
- False
- True
- True
- Forward
- center
+
+ start
center
- True
- next_img
-
-
- Forward button
- Forward Playback
-
-
+ end
+ 15
+ true
+ 20
+ 0
+
+ Title of the currently playing part
+
-
- False
- True
- 2
-
-
-
- True
- True
- 0
-
-
-
- False
- True
- 0
-
-
-
-
- True
- False
-
- 45
- 45
- True
- False
- center
- end
- gtk-missing-image
+
+ vertical
-
- False
- True
- 2
-
+
+
+
+
+ true
+ 12
+ 12
+ 12
-
- s
- True
- False
- Currently playing
- center
- vertical
+
+ center
+ 6
-
- title_book
- True
- False
- Harry Potter und der Stein der Weisen
- end
- 20
- True
- 0
-
-
- Booktitle
- Title of currently playing book
-
-
+
+ false
+ true
+ true
+ Rewind
+ center
+ center
+ object-rotate-left-symbolic
+
+ Rewind playback
+
-
- True
- True
- 0
-
-
- title_track
- True
- False
- Kapitel 01
- end
- 20
- True
- 0
-
-
- Part name
- Title of the currently playing part
-
-
+
+ 42
+ 42
+ false
+ true
+ true
+ Start playback
+ center
+ center
+ media-playback-start-symbolic
+
+ Start or pause the playback
+
-
- True
- True
- 1
-
-
-
- False
- True
- 3
-
-
-
-
- False
- True
- 0
-
-
-
-
- True
- False
- True
- 5
-
-
- True
- False
- Elapsed time
- end
- center
- <span font_features='tnum'>--:--</span>
- True
- True
-
-
- Time elapsed
- Elapsed time of current part
-
-
-
-
- False
- True
- 1
-
-
-
-
- 150
- True
- False
- True
- Jump to position in current chapter
- True
- on
- on
- False
- 0
- False
-
-
- Position slider
- Position of the current part in seconds
+
+
+ false
+ true
+ true
+ Forward
+ center
+ center
+ object-rotate-right-symbolic
+
+ Forward Playback
+
+
-
- True
- True
- 2
-
-
- True
- False
- center
-
-
- True
- False
- Remaining time
- start
- <span font_features='tnum'>--:--</span>
- True
- True
-
-
- Time remaining
- Remaining time of current part
-
-
-
-
+
+ true
-
- False
- True
- 3
-
-
- False
- True
- 1
-
-
-
- True
- False
+
+
+ center
center
+ 12
+ 12
3
-
- True
- False
- True
- False
- True
- Volume control
- button
+
+ Volume control
audio-volume-muted-symbolic
audio-volume-high-symbolic
audio-volume-low-symbolic
audio-volume-medium-symbolic
-
-
- True
- True
- center
- center
- none
-
-
-
-
- True
- True
- center
- center
- none
+
+
+ 0
+ 1
+ 1
+ 0.1
+ 0.2
-
+
+
-
- False
- True
- 1
-
-
-
- False
- True
- 3
-
-
-
-
- False
- True
- 2
-
-
-
-
-
-
- True
- False
- True
-
-
- True
- False
-
-
- 45
- 45
- True
- False
- start
- end
- gtk-missing-image
-
-
-
-
- False
- True
- 0
-
-
-
-
- True
- False
- center
- center
- 3
-
-
- True
- False
-
-
- True
- False
- True
- True
- Rewind
- center
- center
- True
- prev_img
-
-
- Rewind button
- Rewind playback
-
-
-
-
- False
- True
- 0
-
-
-
-
- 42
- True
- False
- True
- True
- Start playback
- center
- center
- True
- play_img
-
-
- Play/Pause Button
- Start or pause the playback
-
-
-
-
- True
- True
- 1
-
-
-
-
- True
- False
- True
- True
- Forward
- center
- center
- True
- next_img
-
-
- Forward button
- Forward Playback
-
-
-
-
- False
- True
- 2
-
-
+
+ Open the sleep timer popover
+
-
- True
- True
- 0
-
-
-
-
- True
- True
- 1
-
-
-
-
-
- False
- True
- end
- 2
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
- True
- False
- object-rotate-left-symbolic
-
diff --git a/data/ui/media_controller_big.ui b/data/ui/media_controller_big.ui
deleted file mode 100644
index 0f66e9c1..00000000
--- a/data/ui/media_controller_big.ui
+++ /dev/null
@@ -1,428 +0,0 @@
-
-
-
-
-
-
- True
- False
- 14
- object-rotate-right-symbolic
-
-
- True
- False
- 20
- play-symbolic
-
-
- True
- False
- 14
- object-rotate-left-symbolic
-
-
- True
- False
- 10
- 20
-
-
- True
- False
- center
- center
- False
- 15
-
-
- True
- False
- 220
- 220
-
-
- 220
- True
- False
- 10
-
-
- True
- False
- Open book
-
-
- 46
- 46
- True
- False
- center
- end
-
-
-
-
- False
- True
- 0
-
-
-
-
- s
- True
- False
- Currently playing
- center
- vertical
- 2
-
-
- title_book
- True
- False
- start
- center
- end
- 15
- True
- 20
- 0
-
-
-
-
-
- Booktitle
- Title of currently playing book
-
-
-
-
-
- False
- True
- 0
-
-
-
-
- title_track
- True
- False
- start
- center
- end
- 15
- True
- 20
- 0
-
-
- Part name
- Title of the currently playing part
-
-
-
-
-
- False
- True
- 1
-
-
-
-
- True
- True
- 1
-
-
-
-
- True
- False
- vertical
-
-
- False
- True
- 2
-
-
-
-
-
-
- False
- True
- 1
-
-
-
-
- True
- False
- end
- center
- 3
-
-
- True
- False
- 4
-
-
- 36
- 36
- True
- False
- True
- True
- Rewind
- center
- center
- True
- prev_img
- none
-
-
- Rewind button
- Rewind playback
-
-
-
-
-
- False
- True
- 0
-
-
-
-
- 36
- 36
- True
- False
- True
- True
- Start playback
- center
- center
- True
- play_img
- none
-
-
- Play/Pause Button
- Start or pause the playback
-
-
-
-
-
- True
- True
- 1
-
-
-
-
- 36
- 36
- True
- False
- True
- True
- Forward
- center
- center
- True
- next_img
- none
-
-
- Forward button
- Forward Playback
-
-
-
-
-
- False
- True
- 2
-
-
-
-
- True
- True
- 0
-
-
-
-
- False
- True
- 3
-
-
-
-
- False
- False
- 0
-
-
-
-
- True
- False
- True
-
-
- True
- True
- 2
-
-
-
-
- True
- False
- center
- 20
- 3
-
-
- 36
- 36
- True
- False
- True
- False
- True
- Volume control
- button
- audio-volume-muted-symbolic
-audio-volume-high-symbolic
-audio-volume-low-symbolic
-audio-volume-medium-symbolic
-
-
- True
- True
- center
- center
- none
-
-
-
-
- True
- True
- center
- center
- none
-
-
-
-
-
- False
- True
- 1
-
-
-
-
-
- False
- True
- 2
-
-
-
-
-
- False
- True
- 3
-
-
-
-
- False
- False
- end
- 2
-
-
-
-
diff --git a/data/ui/media_controller_small.ui b/data/ui/media_controller_small.ui
deleted file mode 100644
index e6f51c6c..00000000
--- a/data/ui/media_controller_small.ui
+++ /dev/null
@@ -1,198 +0,0 @@
-
-
-
-
-
-
- True
- False
- 14
- object-rotate-right-symbolic
-
-
- True
- False
- 20
- play-symbolic
-
-
- True
- False
- 14
- object-rotate-left-symbolic
-
-
- True
- False
- 10
- True
-
-
- True
- False
-
-
- 46
- 46
- True
- False
- start
- center
-
-
-
-
- False
- True
- 0
-
-
-
-
- True
- False
- center
- center
- 3
-
-
- True
- False
- 4
-
-
- 36
- 36
- True
- False
- True
- True
- Rewind
- center
- center
- True
- prev_img
-
-
- Rewind button
- Rewind playback
-
-
-
-
-
- False
- True
- 0
-
-
-
-
- 36
- 36
- True
- False
- True
- True
- Start playback
- center
- center
- True
- play_img
-
-
- Play/Pause Button
- Start or pause the playback
-
-
-
-
-
- True
- True
- 1
-
-
-
-
- 36
- 36
- True
- False
- True
- True
- Forward
- center
- center
- True
- next_img
-
-
- Forward button
- Forward Playback
-
-
-
-
-
- False
- True
- 2
-
-
-
-
- True
- True
- 0
-
-
-
-
- True
- True
- 1
-
-
-
-
-
- False
- True
- end
- 2
-
-
-
-
diff --git a/data/ui/playback_speed_popover.ui b/data/ui/playback_speed_popover.ui
index 5cc7a677..7965dee5 100644
--- a/data/ui/playback_speed_popover.ui
+++ b/data/ui/playback_speed_popover.ui
@@ -1,57 +1,39 @@
-
-
+
0.5
3.5
1
- 0.050000000000000003
- 0.10000000000000001
+ 0.05
+ 0.1
- 300
- False
-
+ 300
+
- True
- False
- True
- True
- 5
- 10
- 10
- 10
+ true
+ true
+ 6
+ 6
+ 6
+ 6
speed_value
- on
- on
- 5
- 1
- False
+ 5
+ 1
-
- True
- True
- 0
-
- True
- False
- 10
- <span font_features='tnum'>1.0 x</span>
- True
+ 12
+ <span font_features='tnum'>1.0 x</span>
+ true
-
- False
- True
- 1
-
-
+
+
diff --git a/data/ui/preferences.ui b/data/ui/preferences.ui
index 234d2dc5..20045d83 100644
--- a/data/ui/preferences.ui
+++ b/data/ui/preferences.ui
@@ -1,209 +1,95 @@
-
-
-
-
- True
- False
- list-add-symbolic
-
-
- True
- False
- checkmark-symbolic
-
-
- True
- False
- network-server-symbolic
-
+
- 121
+ 120
+ 15
5
10
5
- 121
+ 120
5
10
-
- True
- False
- list-remove-symbolic
-
5
- 121
+ 120
15
5
10
-
- False
- popup
- center-on-parent
- True
- center
+
-
- True
- False
+
settings-symbolic
- General
+ General
-
- True
- False
- Appearance
+
+ Appearance
-
- True
- False
- Dark Mode
- dark_mode_switch
-
-
- True
- False
- center
- center
-
-
+
+ Dark Mode
-
- True
- False
- Tags
+
+ Tags
-
- True
- False
- Swap author and reader
- swap_author_reader_switch
- Activate if author and reader are displayed the wrong way
-
-
- True
- False
- center
- center
-
-
+
+ Swap author and reader
+ Activate if author and reader are displayed the wrong way
-
- True
- False
- Playback
+
+ Playback
-
- True
- False
- Replay
- replay_switch
- Rewind 30 seconds of the current book when starting Cozy
-
-
- True
- False
- center
- center
-
-
+
+ Replay
+ Rewind 30 seconds of the current book when starting Cozy
-
- True
- False
- Rewind Duration
-
-
- True
- True
- center
- center
- digits
- rewind_duration_adjustment
- True
- True
- 15
-
-
+
+ Rewind Duration
+ true
+ rewind_duration_adjustment
+ true
+ true
-
- True
- False
- Forward Duration
-
-
- True
- True
- center
- center
- 15
- digits
- forward_duration_adjustment
- True
- True
- 15
-
-
+
+ Forward Duration
+ true
+ forward_duration_adjustment
+ true
+ true
-
- True
- False
- Sleep Timer
+
+ Sleep Timer
-
- True
- False
- Fadeout
- sleep_timer_fadeout_switch
-
-
- True
- False
- center
- center
-
-
+
+ Fadeout
-
- True
- False
- Fadeout duration
-
-
- True
- True
- center
- center
- 15
- digits
- fadeout_duration_adjustment
- True
- True
-
-
+
+ Fadeout duration
+ true
+ fadeout_duration_adjustment
+ true
+ true
@@ -211,231 +97,141 @@
-
- True
- False
+
harddisk-symbolic
- Storage
+ Storage
-
- True
- False
- Artwork
+
+ Artwork
-
- True
- False
- Prefer external images over embedded cover
- artwork_prefer_external_switch
- Always use images (cover.jpg, *.png, …) when available
-
-
- True
- False
- center
- center
-
-
+
+ Prefer external images over embedded cover
+ Always use images (cover.jpg, *.png, …) when available
-
- True
- False
- Storage locations
+
+ Storage locations
-
- True
- False
- False
- Storage locations
+
+ Storage locations
- True
- False
13
13
13
13
- True
+ true
vertical
250
- True
- False
- 0
- none
-
+
- True
- True
+ true
never
- in
-
+
- True
- False
-
+
- True
- False
- True
+ 1
-
+
-
+
-
+
-
- False
- True
- 0
-
-
+
24
- True
- False
- icons
+
-
- True
- False
- Add location
+
+ 24
+ 24
+ Add location
-
- 24
- 24
- True
- True
- False
- add_image
- none
- True
-
+
+ list-add-symbolic
+
-
- False
- False
-
-
- True
- False
- Remove location
+
+ 24
+ 24
+ false
+ Remove location
-
- 24
- 24
- True
- False
- True
- False
- remove_image
- none
- True
-
+
+ list-remove-symbolic
+
-
- False
- False
-
-
- True
- False
- False
+
+ vertical
+ True
+
-
- True
- False
-
-
- True
- False
- Toggle this storage location to be internal/external.
- end
+
+ External drive
+ 24
+ 24
+ false
+ Toggle this storage location to be internal/external.
-
- External drive
- 24
- 24
- True
- False
- True
- False
- external_image
- none
- True
-
+
+ network-server-symbolic
+
-
- False
- False
-
-
- True
- False
- Set as default storage location for new audiobooks
- end
+
+ Set as default
+ 24
+ 24
+ false
+ Set as default storage location for new audiobooks
-
- Set as default
- 24
- 24
- True
- False
- True
- False
- default_image
- none
- True
-
+
+ checkmark-symbolic
+
-
- False
- False
-
-
- False
- True
- 1
-
@@ -446,29 +242,16 @@
-
- True
- False
+
papyrus-vertical-symbolic
- Feedback
+ Feedback
-
- True
- False
- User feedback
-
-
- True
- False
- User Feedback
-
-
-
-
-
+
+ User feedback
+
diff --git a/data/ui/progress_popover.ui b/data/ui/progress_popover.ui
index 9d0cb704..e0838bdb 100644
--- a/data/ui/progress_popover.ui
+++ b/data/ui/progress_popover.ui
@@ -1,14 +1,10 @@
-
-
-
+
+
- False
-
- True
- False
+
15
15
15
@@ -17,35 +13,19 @@
250
- True
- False
vertical
10
- True
- False
end
0
-
- False
- True
- 0
-
- True
- False
0.5
- dfsg
+ dfsg
-
- False
- True
- 1
-
diff --git a/data/ui/search_popover.ui b/data/ui/search_popover.ui
index 7748193c..25f96856 100644
--- a/data/ui/search_popover.ui
+++ b/data/ui/search_popover.ui
@@ -1,319 +1,209 @@
-
-
+
- False
- True
-
+ 1
+
- True
- False
- True
+ 1
vertical
- True
- False
- True
+ 1
380
- True
- True
- Search
+ 1
+ Search
center
8
- True
- edit-find-symbolic
- False
- False
+ 1
-
+ Search your audiobook library
-
-
- Search box
- Search your audiobook library
-
-
-
- False
- True
- 0
-
-
- False
- True
- 0
-
- True
- False
- True
+ 1
crossfade
-
- True
- False
- 10
- 10
- True
- Which book are you looking for?
-
-
-
-
-
-
+
start
page2
-
+
+
+ 10
+ 10
+ 1
+ Which book are you looking for?
+
+
+
+
+
+
+
-
- True
- True
- 5
- 5
- 5
- True
- bottom-left
- 400
- 0
-
-
- True
- False
- none
-
-
- True
- False
- vertical
-
-
- False
- center
- 2
- 3
- Author
-
-
-
-
-
-
- False
- True
- 0
-
-
-
-
- True
- False
+
+ main
+
+
+ 1
+ 5
+ 5
+ 5
+ 1
+ bottom-left
+ 300
+ 200
+
+
+
+
vertical
-
+
+ 0
+ center
+ 2
+ 3
+ Author
+
+
+
+
+
-
-
- False
- True
- 1
-
-
-
-
- True
- False
- 5
-
-
- False
- True
- 2
-
-
-
-
- False
- center
- 2
- 3
- Book
-
-
-
-
-
-
- False
- True
- 3
-
-
-
-
- True
- False
- vertical
-
+
+ vertical
+
+
+
+
-
-
- False
- True
- 4
-
-
-
-
- True
- False
- 5
-
-
- False
- True
- 5
-
-
-
-
- False
- center
- 2
- 3
- Reader
-
-
-
-
-
-
- False
- True
- 6
-
-
-
-
- True
- False
- vertical
-
+
+ 5
+
-
-
- False
- True
- 7
-
-
-
-
- True
- False
- 5
-
-
- False
- True
- 8
-
-
-
-
- False
- center
- 2
- 3
- Part
-
-
-
-
-
-
- False
- True
- 9
-
-
-
-
- True
- False
- vertical
-
+
+ 0
+ center
+ 2
+ 3
+ Book
+
+
+
+
+
+
+
+
+ vertical
+
+
+
+
+
+
+
+ 5
+
+
+
+
+ 0
+ center
+ 2
+ 3
+ Reader
+
+
+
+
+
+
+
+
+ vertical
+
+
+
+
+
+
+
+ 5
+
+
+
+
+ 0
+ center
+ 2
+ 3
+ Part
+
+
+
+
+
+
+
+
+ vertical
+
+
+
+
-
- False
- True
- 10
-
-
+
-
+
-
+
-
- main
- page0
- 1
-
-
- True
- False
- 10
- 10
- True
- Nothing found :(
-
-
-
-
-
-
+
nothing
- page1
- 2
-
+
+
+ 10
+ 10
+ 1
+ Nothing found :(
+
+
+
+
+
+
+
-
- False
- True
- 1
-
-
+
diff --git a/data/ui/seek_bar.ui b/data/ui/seek_bar.ui
index 7926f26c..6873bf27 100644
--- a/data/ui/seek_bar.ui
+++ b/data/ui/seek_bar.ui
@@ -1,97 +1,60 @@
-
-
+
100
1
15
- True
- False
center
- True
+ true
5
- True
- False
- Elapsed time
+ Elapsed time
end
center
- <span font_features='tnum'>--:--</span>
- True
- True
-
-
- Time elapsed
- Elapsed time of current part
-
-
+ <span font_features='tnum'>--:--</span>
+ true
+ true
+
+ Elapsed time of current part
+
-
- False
- True
- 1
-
150
- True
- False
- True
- Jump to position in current chapter
+ false
+ true
+ Jump to position in current chapter
center
- True
+ true
seek_bar_adjustment
- on
- on
- False
+ false
0
- False
-
-
- Position slider
- Position of the current part in seconds
-
-
+
+ Position of the current part in seconds
+
-
- True
- True
- 2
-
-
- True
- False
+
center
- True
- False
- Remaining time
+ Remaining time
start
- <span font_features='tnum'>--:--</span>
- True
- True
-
-
- Time remaining
- Remaining time of current part
-
-
+ <span font_features='tnum'>--:--</span>
+ true
+ true
+
+ Remaining time of current part
+
-
- False
- True
- 3
-
diff --git a/data/ui/application.css b/data/ui/style.css
similarity index 66%
rename from data/ui/application.css
rename to data/ui/style.css
index 4b26a98d..89fbd3fc 100644
--- a/data/ui/application.css
+++ b/data/ui/style.css
@@ -42,11 +42,8 @@
color: white;
}
-.book_container {
- border-radius: 0.5rem;
+.book_card {
padding: 1rem;
- background-color: @theme_base_color;
- box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.02);
}
.selected {
@@ -55,8 +52,7 @@
}
.book_detail_art {
- box-shadow: 0 0.5em 0.6em rgba(0, 0, 0, 0.25);
- border-radius: 0.5rem;
+ border-radius: 12px;
}
.chapter_element {
@@ -83,23 +79,48 @@
}
.play_button {
- background: @theme_fg_color;
+ background-color: @theme_fg_color;
color: @theme_bg_color;
- transition: none;
}
-/*.play_button {*/
-/* background: @theme_selected_bg_color;*/
-/* color: @theme_selected_fg_color;*/
-/* transition: none;*/
-/*}*/
+.player_bar {
+ background: shade(@theme_bg_color, 0.95) 0%;
+}
+
+.bold {
+ font-weight: 900;
+}
-.play_button:disabled {
- background-color: transparent;
- color: @insensitive_fg_color;
- box-shadow: none;
+.semi-bold {
+ font-weight: 600;
+}
+
+.bordered {
+ border: 1px solid shade(@theme_bg_color, 0.8);
+}
+
+.monospace {
+ font-family: monospace;
+}
+
+.transparent_bg {
+ background: transparent;
+}
+
+.failed-import-card {
+ /*
+ 6px vertical padding is not ideal, because the text scrolls into
+ an invisible barrier, but it's nicer when no scrolling is happening
+ */
+ padding: 6px 12px;
+}
+
+.drag-overlay-status-page {
+ background-color: alpha(@accent_bg_color, 0.65);
+ color: @accent_fg_color;
+}
+
+.blurred {
+ filter: blur(6px);
}
-.player_bar {
- background: shade(@theme_bg_color, 0.95) 0%;
-}
\ No newline at end of file
diff --git a/data/ui/timer_popover.ui b/data/ui/timer_popover.ui
index 665966ff..65661a58 100644
--- a/data/ui/timer_popover.ui
+++ b/data/ui/timer_popover.ui
@@ -1,7 +1,6 @@
-
-
+
0
10
@@ -13,256 +12,143 @@
180
- False
-
+
270
- True
- False
- 10
- 10
+ 10
+ 10
15
20
vertical
10
- True
- False
205
- True
- True
- Timer duration
+ true
+ Timer duration
start
- 10
+ 10
timer_value
- False
- 120
- 5
+ true
+ 120
+ 5
0
- False
- right
-
-
- Timer duration slider
- Set the sleep timer duration in minutes
-
-
+ right
+
+ Set the sleep timer duration in minutes
+
+
+ 0
+ 0
+
-
- 0
- 0
-
- True
- False
end
center
- 6
- min
+ 6
+ min
+
+ 2
+ 0
+
-
- 2
- 0
-
25
- True
- False
center
vertical
- True
- False
end
center
-
- False
- True
- 0
-
+
+ 1
+ 0
+
-
- 1
- 0
-
-
- False
- True
- 0
-
- True
- False
- True
- False
+ true
start
- 10
- Stop after current chapter
+ 10
+ Stop after current chapter
-
- True
- True
- 0
-
- True
- True
+ true
end
center
-
- False
- True
- 1
-
-
- False
- True
- 1
-
-
- True
- False
-
-
- False
- True
- 2
-
+
- True
- False
- True
- False
+ true
start
- Enable system power control
+ Enable system power control
-
- True
- True
- 0
-
- True
- True
+ true
end
center
-
- False
- True
- 1
-
-
- False
- True
- 3
-
- True
- False
- False
+ false
- True
- False
- Type of the action when the timer finishes.
-"shutdown" will attempt to turn your system off (also known as power off)
-"suspend" will attempt to suspend your system (also known as sleep).
+ true
+ Type of the action when the timer finishes.
+"shutdown" will attempt to turn your system off (also known as power off)
+"suspend" will attempt to suspend your system (also known as sleep).
start
- System power action
-to perform
+ System power action to perform
-
- True
- True
- 0
-
- True
- False
vertical
-
- suspend
- True
- True
- False
- right
- True
- True
+
+ suspend
+ true
-
- False
- True
- 0
-
-
- shutdown
- True
- True
- False
- right
- True
+
+ shutdown
system_suspend_radiob
-
- False
- True
- 1
-
-
- False
- True
- 1
-
-
- False
- True
- 4
-
-
+
diff --git a/data/ui/titlebar_menu.ui b/data/ui/titlebar_menu.ui
deleted file mode 100644
index 7f89e030..00000000
--- a/data/ui/titlebar_menu.ui
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
diff --git a/data/ui/warning_popover.ui b/data/ui/warning_popover.ui
deleted file mode 100644
index 23626f47..00000000
--- a/data/ui/warning_popover.ui
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
- False
-
-
- True
- False
- 10
- 10
- 10
- 10
- vertical
- 5
-
-
-
-
-
-
-
diff --git a/data/ui/welcome.ui b/data/ui/welcome.ui
index 10e204d0..532cde76 100644
--- a/data/ui/welcome.ui
+++ b/data/ui/welcome.ui
@@ -1,57 +1,13 @@
-
-
-
- True
- False
- vertical
- 10
+
+
-
- True
- False
- True
- 200
+
com.github.geigi.cozy
- 6
+ Welcome!
+ Add your audiobooks and let's get cozy.
-
- False
- True
- 0
-
-
-
-
- True
- False
- Welcome!
- 1
-
-
-
- False
- True
- 1
-
-
-
-
- True
- False
- True
- Add your audiobooks and let's get cozy.
- True
- 0
-
-
- False
- True
- 2
-
diff --git a/data/ui/whats_new.ui b/data/ui/whats_new.ui
index a235c107..dda8ca65 100644
--- a/data/ui/whats_new.ui
+++ b/data/ui/whats_new.ui
@@ -1,64 +1,37 @@
-
-
-
-
- False
- popup
- Whats new?
- center-on-parent
+
+
+ Whats new?
650
400
- True
- dialog
- False
- True
- False
vertical
-
- True
- True
- True
- True
+ 1
+ 1
+ 1
never
- in
-
+
- True
- False
-
+
- True
- False
20
20
10
@@ -68,15 +41,10 @@
-
+
-
+
-
- False
- True
- 1
-
diff --git a/data/ui/whats_new_importer.ui b/data/ui/whats_new_importer.ui
index 100529bc..5dfd8471 100644
--- a/data/ui/whats_new_importer.ui
+++ b/data/ui/whats_new_importer.ui
@@ -1,132 +1,75 @@
-
-
+
- True
- False
- 20
- 20
- True
+ 20
+ 20
+ 1
vertical
10
- True
- False
- What's new in Cozy
+ What's new in Cozy
-
- False
- True
- 0
-
- True
- False
+ 1
20
160
com.github.geigi.cozy
-
- True
- True
- 1
-
- True
- False
+ 1
vertical
10
- True
- False
- A completely rewritten and far more reliable media importer.
- True
+ A completely rewritten and far more reliable media importer.
+ 1
-
- False
- True
- 0
-
- True
- False
center
20
vertical
10
- True
- False
start
- Did you experience audio files that couldn't be imported? Drag & Drop those files onto Cozy or use the application menu in the titlebar to rescan your audiobook directories!
+ Did you experience audio files that couldn't be imported? Drag & Drop those files onto Cozy or use the application menu in the titlebar to rescan your audiobook directories!
fill
- True
+ 1
-
- False
- True
- 0
-
- True
- False
start
- Supported media files currently are mp3, m4a, flac, ogg, opus and wav.
+ Supported media files currently are mp3, m4a, flac, ogg, opus and wav.
fill
- True
+ 1
-
- False
- True
- 1
-
- True
- False
start
- More to come in a later update.
+ More to come in a later update.
fill
- True
+ 1
-
- False
- True
- 2
-
-
- False
- True
- 1
-
-
- True
- True
- 5
-
diff --git a/data/ui/whats_new_library.ui b/data/ui/whats_new_library.ui
index 148bcf3b..3d699a37 100644
--- a/data/ui/whats_new_library.ui
+++ b/data/ui/whats_new_library.ui
@@ -1,132 +1,75 @@
-
-
+
- True
- False
- 20
- 20
- True
+ 20
+ 20
+ 1
vertical
10
- True
- False
- What's new in Cozy
+ What's new in Cozy
-
- False
- True
- 0
-
- True
- False
+ 1
20
160
com.github.geigi.cozy
-
- True
- True
- 1
-
- True
- False
+ 1
vertical
10
- True
- False
- An important change in library management
- True
+ An important change in library management
+ 1
-
- False
- True
- 0
-
- True
- False
center
20
vertical
10
- True
- False
start
- Previously every file which was imported in your library but couldn't be found anymore was removed from the library during a scan.
+ Previously every file which was imported in your library but couldn't be found anymore was removed from the library during a scan.
fill
- True
+ 1
-
- False
- True
- 0
-
- True
- False
start
- Now audiobooks are not removed from your library automatically anymore. This prevents accidentally loosing the progress of a audiobook when a file can't be found temporarily.
+ Now audiobooks are not removed from your library automatically anymore. This prevents accidentally loosing the progress of a audiobook when a file can't be found temporarily.
fill
- True
+ 1
-
- False
- True
- 1
-
- True
- False
start
- To remove an audiobook from the library simply right-click on it and choose the remove from library option.
+ To remove an audiobook from the library simply right-click on it and choose the remove from library option.
fill
- True
+ 1
-
- False
- True
- 2
-
-
- False
- True
- 1
-
-
- True
- True
- 5
-
diff --git a/data/ui/whats_new_m4b.ui b/data/ui/whats_new_m4b.ui
index 42b0e464..c97c53b4 100644
--- a/data/ui/whats_new_m4b.ui
+++ b/data/ui/whats_new_m4b.ui
@@ -1,132 +1,75 @@
-
-
+
- True
- False
- 20
- 20
- True
+ 20
+ 20
+ 1
vertical
10
- True
- False
- What's new in Cozy
+ What's new in Cozy
-
- False
- True
- 0
-
- True
- False
+ 1
20
160
com.github.geigi.cozy
-
- True
- True
- 1
-
- True
- False
+ 1
vertical
10
- True
- False
- Basic support for m4b audio books.
- True
+ Basic support for m4b audio books.
+ 1
-
- False
- True
- 0
-
- True
- False
center
20
vertical
10
- True
- False
start
- Many of you have been waiting for it: Support for m4b audio books! This version features basic support for m4b files without chapter support.
+ Many of you have been waiting for it: Support for m4b audio books! This version features basic support for m4b files without chapter support.
fill
- True
+ 1
-
- False
- True
- 0
-
- True
- False
start
- Drag & Drop your m4b files onto Cozy or use the application menu in the titlebar to rescan your audiobook directories.
+ Drag & Drop your m4b files onto Cozy or use the application menu in the titlebar to rescan your audiobook directories.
fill
- True
+ 1
-
- False
- True
- 1
-
- True
- False
start
- Chapter support will follow in a later update. Stay tuned!
+ Chapter support will follow in a later update. Stay tuned!
fill
- True
+ 1
-
- False
- True
- 2
-
-
- False
- True
- 1
-
-
- True
- True
- 5
-
diff --git a/data/ui/whats_new_m4b_chapter.ui b/data/ui/whats_new_m4b_chapter.ui
index b802b4b3..7d382344 100644
--- a/data/ui/whats_new_m4b_chapter.ui
+++ b/data/ui/whats_new_m4b_chapter.ui
@@ -1,132 +1,75 @@
-
-
+
- True
- False
- 20
- 20
- True
+ 20
+ 20
+ 1
vertical
10
- True
- False
- What's new in Cozy
+ What's new in Cozy
-
- False
- True
- 0
-
- True
- False
+ 1
20
160
com.github.geigi.cozy
-
- True
- True
- 1
-
- True
- False
+ 1
vertical
10
- True
- False
- Chapter support for m4b audio books.
- True
+ Chapter support for m4b audio books.
+ 1
-
- False
- True
- 0
-
- True
- False
center
20
vertical
10
- True
- False
start
- This version of Cozy features chapter support for m4b audio books!
+ This version of Cozy features chapter support for m4b audio books!
fill
- True
+ 1
-
- False
- True
- 0
-
- True
- False
start
- If you already have m4b files imported you'll need to start a scan of your library from the app menu.
+ If you already have m4b files imported you'll need to start a scan of your library from the app menu.
fill
- True
+ 1
-
- False
- True
- 1
-
- True
- False
start
- The chapters will then be detected.
+ The chapters will then be detected.
fill
- True
+ 1
-
- False
- True
- 2
-
-
- False
- True
- 1
-
-
- True
- True
- 5
-
diff --git a/main.py b/main.py
index befc3a08..766540a6 100755
--- a/main.py
+++ b/main.py
@@ -26,8 +26,11 @@
import traceback
import gi
-gi.require_version('Gtk', '3.0')
+gi.require_version('Gtk', '4.0')
+gi.require_version('Gdk', '4.0')
+gi.require_version('Adw', '1')
gi.require_version('Gst', '1.0')
+gi.require_version('GstPbutils', '1.0')
pkgdatadir = '@DATA_DIR@'
localedir = '@LOCALE_DIR@'
diff --git a/meson.build b/meson.build
index af74fc3a..36700911 100644
--- a/meson.build
+++ b/meson.build
@@ -13,7 +13,7 @@ else
endif
dependency('glib-2.0')
-dependency('libhandy-1', version: '>= 1.0.0')
+dependency('libadwaita-1', version: '>= 1.0.0')
# from https://github.com/AsavarTzeth/pulseeffects/blob/master/meson.build
# Support Debian non-standard python paths
diff --git a/test/conftest.py b/test/conftest.py
index 07959dc1..2aa48d09 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -3,6 +3,14 @@
import pytest
+import gi
+
+gi.require_version('Gtk', '4.0')
+gi.require_version('Gdk', '4.0')
+gi.require_version('Adw', '1')
+gi.require_version('Gst', '1.0')
+gi.require_version('GstPbutils', '1.0')
+
@pytest.fixture(scope='session', autouse=True)
def install_l10n():