Compare commits

...

27 commits

Author SHA1 Message Date
345e7697ff Bump version to 4.0.2 2026-01-10 16:35:46 +01:00
7d474598e3 Center non-square album covers on grid views (close #112) 2026-01-10 16:34:13 +01:00
9b29f7b274 Preserve aspect ratio of album covers in grid views (close #111) 2026-01-10 16:23:30 +01:00
0a109bc886 Fix handling and logging of thumbnail save failures 2026-01-10 16:04:43 +01:00
9311f9974a Do not try to convert default icon to GDK pixbuf (close #110) 2026-01-10 15:42:24 +01:00
08cd9dbe65 Set pixel size for “standalone” images (close #109) 2026-01-10 15:41:12 +01:00
099adbab8c Bump version to 4.0.1 2025-04-06 17:36:02 +02:00
3d91ab1b35 Scroll library to the beginning after loading items (close #108) 2025-04-06 17:33:16 +02:00
dce1c441a0 Fix READMe to call meson’s “setup” command explicitly 2025-04-06 16:55:05 +02:00
cd4f32e7f2 Fix alignment of tracks on Cover panel (close #106)
GTK 4 centers marks of the Scale widget even when the scale has a vertical
orientation. Unfortunately, the Scale widget does not provide a way to set the
alignment or to access the internal Label widget in any way. To left-align the
labels this commit add a method that traverses the all children of the songs
scale recursively and adjusts the alignment if it is a Label widget.
2025-04-06 16:52:10 +02:00
79b3111fb0 Fix reacting to window resizing (close #107)
Replace the handlers for the “default-width” and “default-height” with an
override to the virtual “size_allocate” method to reliably react to Window
resizing.
2025-04-06 16:03:39 +02:00
f3e3f920a5 Bump version to 4.0 2024-11-24 14:35:40 +01:00
84103229f1 Fix conditions using None (see #103) 2024-11-23 16:35:44 +01:00
ccf68deff7 Remove unused (commented out) code (see #103) 2024-11-23 16:35:19 +01:00
2b1edc715f Mark strings as Regular Expressions (see #103) 2024-11-23 16:29:35 +01:00
4586494f8d Pass version to application (see #103) 2024-11-23 16:26:13 +01:00
188dcb4ef8 fixup! Improve variable names to match Code Style Guide (see #103) 2024-11-03 10:56:25 +01:00
99958eabc9 Fix some comments and remove German ones 2024-11-03 10:55:52 +01:00
bcfd17a8ae Remove unused variables (see #103) 2024-11-03 10:54:41 +01:00
420946d7d4 Declare static methods with annotation (see #103) 2024-11-03 10:42:29 +01:00
54fe825acf Improve variable names to match Code Style Guide (see #103) 2024-05-28 12:03:07 +02:00
4ebfc12f0e Fix import statements and order (see #103) 2024-05-28 11:47:30 +02:00
a1f8b73590 Adjust line length to match Code Style Guide (see #103) 2024-05-28 11:47:30 +02:00
d2e1f6f5d8 Adjust blank lines to match Code Style Guide (see #103) 2024-05-28 11:47:30 +02:00
75b99e5820 Port UI to GTK 4 (close #85) 2024-05-23 13:02:51 +02:00
6ba8bc550f Bump version to 3.2.1 2023-02-28 13:18:07 +01:00
21bd0f5832 Fix setting sort type on Library panel at startup (close #95) 2023-02-28 12:55:26 +01:00
40 changed files with 2207 additions and 3959 deletions

View file

@ -13,7 +13,8 @@ Dependencies:
* [Python](http://www.python.org) 3
* [python-dateutil](https://pypi.org/project/python-dateutil/)
* [GTK](http://www.gtk.org) 3 (>= 3.22) ([python-gobject](https://live.gnome.org/PyGObject))
* [GTK](http://www.gtk.org) 4 (>= 4.18) ([python-gobject](https://live.gnome.org/PyGObject))
* [libadwaita](https://gnome.pages.gitlab.gnome.org/libadwaita/) (>= 1.2)
* [Avahi](http://www.avahi.org) (optional)
* [python-keyring](http://pypi.python.org/pypi/keyring) (optional)
* [meson](https://mesonbuild.com/) and [ninja](https://ninja-build.org/) (building)
@ -35,7 +36,7 @@ For testing the application and running it without (system-wide) installation,
donwload/clone the code, build it with the `--prefix` option and install it
with `ninja`:
$ meson --prefix $(pwd)/install build
$ meson setup --prefix $(pwd)/install build
$ ninja -C build
$ ninja -C build install

View file

@ -1,8 +1,20 @@
.bg-texture {
#content_stack {
box-shadow:inset 4px 4px 10px rgba(0,0,0,0.3);
background-image:url('noise-texture.png');
}
#port_spinner {
background:none;
margin-top:-13px;
}
#port_spinner text {
padding-left: 0;
}
#server_stack_sidebar {
background-color:alpha(@theme_bg_color, 1);
}
.no-bg {
background:none;
}
@ -18,13 +30,17 @@
font-weight:bold;
}
revealer.sidebar > * {
background-color:alpha(@theme_bg_color, 0.8);
box-shadow:0 0 10px @theme_bg_color;
margin-left:20px
window.fullscreen #cover_box {
background: black;
}
revealer.sidebar scale mark indicator {
#cover_info_revealer {
background-color:alpha(@theme_bg_color, 0.8);
box-shadow:0 0 10px @theme_bg_color;
margin-left:20px;
}
#cover_info_revealer scale mark indicator {
margin-right:5px;
}
@ -32,24 +48,13 @@ actionbar {
background-color:@theme_unfocused_bg_color;
}
/* Icon View in regular mode */
iconview.view:selected,
iconview.view:selected:focus {
background-color:@theme_selected_bg_color;
gridview child {
padding: 1px;
}
iconview.view:hover {
-gtk-icon-effect:highlight;
gridview.selection child {
opacity: 0.5;
}
/* Icon View in selection mode */
iconview.view.selection {
-gtk-icon-effect:dim;
}
iconview.view.selection:selected,
iconview.view.selection:selected:focus {
background-color:@theme_selected_bg_color;
-gtk-icon-effect:highlight;
}
iconview.view.selection:hover {
-gtk-icon-effect:none;
gridview.selection child:hover,
gridview.selection child:selected {
opacity: 1;
}

View file

@ -1,58 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.10"/>
<template class="McgAlbumHeaderbar" parent="GtkHeaderBar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child type="title">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="standalone_title">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Title</property>
<property name="selectable">True</property>
<attributes>
<attribute name="weight" value="bold"/>
<attribute name="scale" value="1"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="standalone_artist">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Artist</property>
<property name="selectable">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<requires lib="gtk+" version="4.8"/>
<requires lib="adw" version="1.2" />
<template class="McgAlbumHeaderbar" parent="AdwBin">
<child>
<object class="GtkButton">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_close_clicked" swapped="no"/>
<object class="GtkHeaderBar">
<child type="title">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="standalone_title">
<property name="selectable">True</property>
<attributes>
<attribute name="weight" value="bold"/>
<attribute name="scale" value="1"/>
</attributes>
</object>
</child>
<child>
<object class="GtkLabel" id="standalone_artist">
<property name="selectable">True</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">go-previous-symbolic</property>
<object class="GtkButton">
<property name="receives-default">True</property>
<signal name="clicked" handler="on_close_clicked" swapped="no"/>
<child>
<object class="GtkImage">
<property name="icon-name">go-previous-symbolic</property>
</object>
</child>
</object>
</child>
</object>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.12"/>
<requires lib="gtk+" version="4.8"/>
<requires lib="adw" version="1.2" />
<object class="GtkAdjustment" id="server-port-adjustment">
<property name="lower">1024</property>
<property name="upper">9999</property>
@ -9,174 +9,79 @@
<property name="step-increment">1</property>
<property name="page-increment">100</property>
</object>
<template class="McgConnectionPanel" parent="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<object class="GtkBox" id="toolbar">
<property name="orientation">horizontal</property>
<property name="spacing">6</property>
</object>
<template class="McgConnectionPanel" parent="AdwBin">
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<object class="AdwStatusPage">
<property name="title">Connect to MPD</property>
<child>
<!-- n-columns=3 n-rows=6 -->
<object class="GtkGrid">
<property name="width-request">500</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="column-spacing">5</property>
<property name="row-homogeneous">True</property>
<property name="column-homogeneous">True</property>
<object class="GtkBox">
<property name="hexpand">false</property>
<property name="halign">center</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<object class="GtkListBox" id="zeroconf_list">
<property name="hexpand">true</property>
<child type="placeholder">
<object class="GtkLabel">
<property name="label" translatable="yes">No service found</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkListBox">
<property name="hexpand">true</property>
<property name="selection-mode">none</property>
<style>
<class name="boxed-list"/>
</style>
<child>
<object class="GtkTreeView" id="zeroconf_list">
<property name="visible">True</property>
<property name="can-focus">True</property>
<signal name="focus-out-event" handler="on_zeroconf_list_outfocused" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection">
<signal name="changed" handler="on_service_selected" swapped="no"/>
<object class="AdwEntryRow" id="host_row">
<property name="title" translatable="yes">Host</property>
<property name="show-apply-button">true</property>
<signal name="apply" handler="on_host_entry_apply"/>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<style>
<class name="header"/>
</style>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Port</property>
<property name="halign">start</property>
<property name="hexpand">false</property>
<style>
<class name="subtitle"/>
</style>
</object>
</child>
<child>
<object class="GtkSpinButton" id="port_spinner">
<property name="name">port_spinner</property>
<property name="value">6600</property>
<property name="adjustment">server-port-adjustment</property>
<signal name="value-changed" handler="on_port_spinner_value_changed"/>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="orientation">vertical</property>
<object class="AdwPasswordEntryRow" id="password_row">
<property name="title" translatable="yes">Password</property>
<property name="show-apply-button">true</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
<property name="height">6</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="host_entry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="text">localhost</property>
<property name="placeholder-text" translatable="yes">Enter hostname or IP address</property>
<signal name="focus-out-event" handler="on_host_entry_outfocused" swapped="no"/>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="password_entry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="visibility">False</property>
<property name="placeholder-text" translatable="yes">Enter password or leave blank</property>
<property name="input-purpose">password</property>
<signal name="focus-out-event" handler="on_password_entry_outfocused" swapped="no"/>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">5</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="port_spinner">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="text">6600</property>
<property name="input-purpose">number</property>
<property name="adjustment">server-port-adjustment</property>
<property name="value">6600</property>
<signal name="value-changed" handler="on_port_spinner_value_changed" swapped="no"/>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Host:</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Port:</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Password:</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">4</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</template>
</interface>

View file

@ -1,187 +1,153 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.22"/>
<requires lib="gtk+" version="4.8"/>
<requires lib="adw" version="1.2" />
<object class="GtkBox" id="toolbar">
<property name="orientation">horizontal</property>
<property name="halign">end</property>
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="fullscreen_button">
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Show the cover in fullscreen mode</property>
<property name="action-name">win.toggle-fullscreen</property>
<child>
<object class="GtkImage">
<property name="icon-name">view-fullscreen-symbolic</property>
</object>
</child>
</object>
</child>
</object>
<template class="McgCoverPanel" parent="GtkOverlay">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkStack" id="cover_stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="visible-child">cover_default</property>
<child>
<object class="GtkSpinner" id="cover_spinner">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="name">cover-spinner</property>
</packing>
</child>
<child>
<object class="GtkImage" id="cover_default">
<property name="icon-name">image-x-generic-symbolic</property>
<property name="icon-size">large</property>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="cover_scroll">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="kinetic-scrolling">False</property>
<property name="overlay-scrolling">False</property>
<signal name="size-allocate" handler="on_cover_size_allocate" swapped="no"/>
<property name="hexpand">true</property>
<property name="halign">fill</property>
<property name="vexpand">true</property>
<property name="valign">fill</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<object class="GtkViewport" id="cover_box">
<property name="name">cover_box</property>
<property name="hexpand">true</property>
<property name="halign">fill</property>
<property name="vexpand">true</property>
<property name="valign">fill</property>
<child>
<object class="GtkEventBox" id="cover_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<signal name="button-press-event" handler="on_cover_box_pressed" swapped="no"/>
<child>
<object class="GtkImage" id="cover_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon_size">0</property>
</object>
</child>
<object class="GtkImage" id="cover_image">
<property name="hexpand">true</property>
<property name="halign">fill</property>
<property name="vexpand">true</property>
<property name="valign">fill</property>
<property name="icon-name">image-x-generic-symbolic</property>
<property name="icon-size">large</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">cover-scroll</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="index">-1</property>
</packing>
</child>
<child type="overlay">
<object class="GtkRevealer" id="info_revealer">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="transition-type">slide-right</property>
<property name="name">cover_info_revealer</property>
<style>
<class name="background"/>
</style>
<child>
<object class="GtkScrolledWindow" id="cover_info_scroll">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="halign">start</property>
<property name="vscrollbar-policy">never</property>
<property name="max-content-width">200</property>
<property name="propagate-natural-width">True</property>
<property name="name">cover_info_scroll</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="shadow-type">none</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="halign">start</property>
<property name="valign">fill</property>
<property name="vexpand">true</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="orientation">vertical</property>
<child>
<!-- n-columns=3 n-rows=3 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-bottom">5</property>
<property name="row-spacing">5</property>
<property name="column-homogeneous">True</property>
<style>
<class name="cover-labels"/>
</style>
<child>
<object class="GtkLabel" id="album_title_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label">Album</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="album_date_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label">Date</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="album_artist_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label">Artist</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">2</property>
</layout>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<style>
<class name="cover-labels"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">10</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScale" id="songs_scale">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="orientation">vertical</property>
<property name="halign">start</property>
<property name="valign">fill</property>
<property name="vexpand">true</property>
<property name="restrict-to-fill-level">False</property>
<property name="digits">0</property>
<property name="draw-value">False</property>
<signal name="button-press-event" handler="on_songs_start_change" swapped="no"/>
<signal name="button-release-event" handler="on_songs_change" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
@ -189,10 +155,6 @@
</child>
</object>
</child>
<style>
<class name="sidebar"/>
<class name="background"/>
</style>
</object>
</child>
</template>

View file

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.10"/>
<template class="McgCoverToolbar" parent="GtkButtonBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">6</property>
<property name="layout-style">end</property>
<child>
<object class="GtkButton" id="fullscreen_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Show the cover in fullscreen mode</property>
<property name="action-name">win.toggle-fullscreen</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">view-fullscreen-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
<property name="non-homogeneous">True</property>
</packing>
</child>
</template>
</interface>

View file

@ -1,75 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id="app-menu">
<section>
<item>
<attribute name="action">win.connect</attribute>
<attribute name="label" translatable="yes">Connect</attribute>
<attribute name="accel">&lt;Primary&gt;c</attribute>
</item>
<item>
<attribute name="action">win.play</attribute>
<attribute name="label" translatable="yes">Play</attribute>
<attribute name="accel">&lt;Primary&gt;p</attribute>
</item>
<item>
<attribute name="action">win.clear-playlist</attribute>
<attribute name="label" translatable="yes">Clear Playlist</attribute>
<attribute name="accel">&lt;Primary&gt;r</attribute>
</item>
<item>
<attribute name="action">win.toggle-fullscreen</attribute>
<attribute name="label" translatable="yes">Toggle Fullscreen</attribute>
<attribute name="accel">F11</attribute>
</item>
<item>
<attribute name="action">win.search-library</attribute>
<attribute name="label" translatable="yes">Search Library</attribute>
<attribute name="accel">&lt;Primary&gt;f</attribute>
</item>
</section>
<section>
<item>
<attribute name="action">win.panel</attribute>
<attribute name="label" translatable="yes">Connection</attribute>
<attribute name="target">0</attribute>
<attribute name="accel">&lt;Primary&gt;KP_1</attribute>
</item>
<item>
<attribute name="action">win.panel</attribute>
<attribute name="label" translatable="yes">Cover</attribute>
<attribute name="target">1</attribute>
<attribute name="accel">&lt;Primary&gt;KP_2</attribute>
</item>
<item>
<attribute name="action">win.panel</attribute>
<attribute name="label" translatable="yes">Playlist</attribute>
<attribute name="target">2</attribute>
<attribute name="accel">&lt;Primary&gt;KP_3</attribute>
</item>
<item>
<attribute name="action">win.panel</attribute>
<attribute name="label" translatable="yes">Library</attribute>
<attribute name="target">3</attribute>
<attribute name="accel">&lt;Primary&gt;KP_4</attribute>
</item>
</section>
<section>
<item>
<attribute name="action">win.show-help-overlay</attribute>
<attribute name="label" translatable="yes">Keyboard Shortcuts</attribute>
<attribute name="accel">&lt;Primary&gt;k</attribute>
</item>
<item>
<attribute name="action">app.info</attribute>
<attribute name="label" translatable="yes">Info</attribute>
<attribute name="accel">&lt;Primary&gt;i</attribute>
</item>
<item>
<attribute name="action">app.quit</attribute>
<attribute name="label" translatable="yes">Quit</attribute>
<attribute name="accel">&lt;Primary&gt;q</attribute>
</item>
</section>
</menu>
</interface>

View file

@ -1,46 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.10"/>
<template class="McgInfoDialog" parent="GtkAboutDialog">
<property name="can-focus">False</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window-position">center</property>
<property name="type-hint">dialog</property>
<property name="gravity">center</property>
<property name="program-name">CoverGrid</property>
<property name="version">3.2</property>
<property name="comments" translatable="yes">CoverGrid is a client for the Music Player Daemon, focusing on albums instead of single tracks.</property>
<property name="website">http://www.suruatoel.xyz/codes/mcg</property>
<property name="logo-icon-name">xyz.suruatoel.mcg</property>
<property name="license-type">gpl-3-0</property>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can-focus">False</property>
<property name="layout-style">end</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</template>
</interface>

View file

@ -1,303 +1,344 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.16"/>
<template class="McgLibraryPanel" parent="GtkStack">
<property name="visible">True</property>
<requires lib="gtk+" version="4.8"/>
<requires lib="adw" version="1.2" />
<object class="GtkAdjustment" id="grid_adjustment">
<property name="lower">100</property>
<property name="upper">1000</property>
<property name="value">150</property>
<property name="step-increment">1</property>
<property name="page-increment">10</property>
</object>
<object class="GtkPopover" id="toolbar_popover">
<property name="can-focus">False</property>
<property name="transition-type">slide-left-right</property>
<child>
<object class="GtkBox" id="panel_normal">
<property name="visible">True</property>
<property name="can-focus">False</property>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="halign">end</property>
<child>
<object class="GtkSearchBar" id="filter_bar">
<object class="GtkScale" id="grid_scale">
<property name="width-request">350</property>
<property name="visible">True</property>
<property name="app-paintable">True</property>
<property name="can-focus">False</property>
<signal name="notify" handler="on_filter_bar_notify" swapped="no"/>
<child>
<object class="GtkSearchEntry" id="filter_entry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="primary-icon-name">edit-find-symbolic</property>
<property name="primary-icon-activatable">False</property>
<property name="primary-icon-sensitive">False</property>
<property name="placeholder-text" translatable="yes">search library</property>
<signal name="search-changed" handler="on_filter_entry_changed" swapped="no"/>
</object>
</child>
<property name="can-focus">True</property>
<property name="adjustment">grid_adjustment</property>
<property name="restrict-to-fill-level">False</property>
<property name="fill-level">-1</property>
<property name="round-digits">0</property>
<property name="digits">0</property>
<property name="has-origin">False</property>
<signal name="value-changed" handler="on_grid_scale_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
<object class="GtkButton" id="library-toolbar-update">
<property name="label" translatable="yes">update library</property>
<signal name="clicked" handler="on_update_clicked" swapped="no"/>
</object>
</child>
<child>
<object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="transition-type">crossfade</property>
<object class="GtkBox" id="library-toolbar-sort">
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="progress_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<object class="GtkSeparator">
<property name="orientation">vertical</property>
<child type="center">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child type="center">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<child>
<object class="GtkImage" id="progress_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon_size">6</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkProgressBar" id="progress_bar">
<property name="width-request">200</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="pulse-step">0</property>
<property name="show-text">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="name">page1</property>
<property name="title">page1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scroll">
<object class="GtkLabel">
<property name="label" translatable="yes">Sort</property>
</object>
</child>
<child>
<object class="GtkCheckButton" id="sort_artist">
<property name="label" translatable="yes">sort by artist</property>
<property name="receives-default">False</property>
<property name="group">sort_year</property>
<signal name="toggled" handler="on_sort_toggled" swapped="no"/>
</object>
</child>
<child>
<object class="GtkCheckButton" id="sort_title">
<property name="label" translatable="yes">sort by title</property>
<property name="receives-default">False</property>
<property name="group">sort_year</property>
<signal name="toggled" handler="on_sort_toggled" swapped="no"/>
</object>
</child>
<child>
<object class="GtkCheckButton" id="sort_year">
<property name="label" translatable="yes">sort by year</property>
<property name="receives-default">False</property>
<signal name="toggled" handler="on_sort_toggled" swapped="no"/>
</object>
</child>
<child>
<object class="GtkCheckButton" id="sort_modified">
<property name="label" translatable="yes">sort by modification</property>
<property name="receives-default">False</property>
<property name="group">sort_year</property>
<signal name="toggled" handler="on_sort_toggled" swapped="no"/>
</object>
</child>
<child>
<object class="GtkCheckButton" id="toolbar_sort_order_button">
<property name="label" translatable="yes">sort library descending</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<child>
<object class="GtkIconView" id="library_grid">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin">0</property>
<property name="item-orientation">horizontal</property>
<property name="row-spacing">0</property>
<property name="column-spacing">0</property>
<property name="tooltip-column">1</property>
<property name="item-padding">0</property>
<property name="activate-on-single-click">True</property>
<signal name="item-activated" handler="on_library_grid_clicked" swapped="no"/>
<signal name="selection-changed" handler="on_library_grid_selection_changed" swapped="no"/>
<signal name="size-allocate" handler="on_resize" swapped="no"/>
<style>
<class name="no-bg"/>
</style>
</object>
</child>
<style>
<class name="no-bg"/>
</style>
</object>
<packing>
<property name="name">page0</property>
<property name="title">page0</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkRevealer" id="actionbar_revealer">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="transition-type">none</property>
<child>
<object class="GtkActionBar" id="library-actionbar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">cancel</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_selection_cancel_clicked" swapped="no"/>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">queue</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_selection_add_clicked" swapped="no"/>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">0</property>
</packing>
</child>
<property name="receives-default">False</property>
<property name="active">True</property>
<signal name="toggled" handler="on_sort_order_toggled" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="name">page0</property>
<property name="title">page0</property>
</packing>
</child>
</object>
<object class="GtkBox" id="toolbar">
<property name="orientation">horizontal</property>
<property name="halign">end</property>
<property name="spacing">6</property>
<child>
<object class="GtkToggleButton" id="toolbar_search_bar">
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Search the library</property>
<child>
<object class="GtkImage">
<property name="icon-name">system-search-symbolic</property>
</object>
</child>
<!--
<accelerator key="f" signal="activate" modifiers="GDK_CONTROL_MASK"/>
-->
</object>
</child>
<child>
<object class="GtkBox" id="panel_standalone">
<object class="GtkToggleButton" id="select_button">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Select multiple albums</property>
<signal name="toggled" handler="on_select_toggled" swapped="no"/>
<child>
<object class="GtkStack" id="standalone_stack">
<object class="GtkImage">
<property name="icon-name">object-select-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkMenuButton">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Settings and actions</property>
<property name="popover">toolbar_popover</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="transition-type">crossfade</property>
<property name="icon-name">open-menu-symbolic</property>
</object>
</child>
</object>
</child>
</object>
<template class="McgLibraryPanel" parent="AdwBin">
<child>
<object class="GtkStack" id="library_stack">
<property name="transition-type">slide-left-right</property>
<child>
<object class="GtkBox" id="panel_normal">
<property name="orientation">vertical</property>
<child>
<object class="GtkSpinner" id="standalone_spinner">
<property name="visible">True</property>
<property name="can-focus">False</property>
<object class="GtkSearchBar" id="filter_bar">
<property name="search-mode-enabled" bind-source="toolbar_search_bar" bind-property="active" bind-flags="sync-create"/>
<child>
<object class="GtkSearchEntry" id="filter_entry">
<property name="placeholder-text" translatable="yes">search library</property>
<signal name="search-changed" handler="on_filter_entry_changed" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="name">standalone-spinne</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="standalone_scroll">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="kinetic-scrolling">False</property>
<property name="overlay-scrolling">False</property>
<signal name="size-allocate" handler="on_standalone_scroll_size_allocate" swapped="no"/>
<object class="GtkStack" id="stack">
<property name="transition-type">crossfade</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<object class="GtkBox" id="progress_box">
<property name="orientation">vertical</property>
<property name="valign">center</property>
<child>
<object class="GtkImage" id="standalone_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">gtk-missing-image</property>
<property name="icon_size">6</property>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="halign">center</property>
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<child>
<object class="GtkImage" id="progress_image">
<property name="icon-size">large</property>
<property name="icon-name">image-x-generic-symbolic</property>
</object>
</child>
<child>
<object class="GtkProgressBar" id="progress_bar">
<property name="width-request">200</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="pulse-step">0</property>
<property name="show-text">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">standalone-scroll</property>
<property name="position">1</property>
</packing>
<child>
<object class="GtkScrolledWindow" id="scroll">
<property name="vexpand">true</property>
<child>
<object class="GtkGridView" id="library_grid">
<property name="orientation">vertical</property>
<property name="single_click_activate">true</property>
<signal name="activate" handler="on_library_grid_clicked"/>
<style>
<class name="no-bg"/>
</style>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes">
<![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="activatable">true</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="vexpand">true</property>
<property name="hexpand">true</property>
<child>
<object class="GtkPicture">
<property name="content-fit">contain</property>
<property name="can-shrink">false</property>
<property name="vexpand">true</property>
<property name="hexpand">true</property>
<binding name="tooltip-markup">
<lookup name="tooltip" type="GridItem">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
<binding name="paintable">
<lookup name="cover" type="GridItem">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</child>
</object>
</property>
</template>
</interface>
]]>
</property>
</object>
</property>
</object>
</child>
<style>
<class name="no-bg"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkRevealer" id="actionbar_revealer">
<property name="transition-type">slide-up</property>
<child>
<object class="GtkActionBar" id="actionbar">
<child type="end">
<object class="GtkButton">
<property name="label" translatable="yes">cancel</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_selection_cancel_clicked" swapped="no"/>
</object>
</child>
<child type="end">
<object class="GtkButton">
<property name="label" translatable="yes">queue</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_selection_add_clicked" swapped="no"/>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="panel_standalone">
<property name="orientation">vertical</property>
<child>
<object class="GtkStack" id="standalone_stack">
<property name="vexpand">true</property>
<property name="transition-type">crossfade</property>
<child>
<object class="GtkSpinner" id="standalone_spinner">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="standalone_scroll">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="kinetic-scrolling">False</property>
<property name="overlay-scrolling">False</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkImage" id="standalone_image">
<property name="icon-name">gtk-missing-image</property>
<property name="icon-size">large</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkActionBar" id="actionbar_standalone">
<child type="end">
<object class="GtkButton">
<property name="label" translatable="yes">play</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_standalone_play_clicked" swapped="no"/>
</object>
</child>
<child type="end">
<object class="GtkButton">
<property name="label" translatable="yes">queue</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_standalone_queue_clicked" swapped="no"/>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkActionBar" id="library-standalone-actionbar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">queue</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_standalone_queue_clicked" swapped="no"/>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">play</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_standalone_play_clicked" swapped="no"/>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="name">page1</property>
<property name="title">page1</property>
<property name="position">1</property>
</packing>
</child>
</template>
</interface>

View file

@ -1,248 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkAdjustment" id="grid_adjustment">
<property name="lower">100</property>
<property name="upper">1000</property>
<property name="value">150</property>
<property name="step-increment">1</property>
<property name="page-increment">10</property>
</object>
<object class="GtkPopover" id="toolbar_popover">
<property name="can-focus">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScale" id="grid_scale">
<property name="width-request">350</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="adjustment">grid_adjustment</property>
<property name="restrict-to-fill-level">False</property>
<property name="fill-level">-1</property>
<property name="round-digits">0</property>
<property name="digits">0</property>
<property name="has-origin">False</property>
<signal name="button-release-event" handler="on_grid_scale_changed" swapped="no"/>
<signal name="change-value" handler="on_grid_scale_change" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="library-toolbar-update">
<property name="label">gtk-refresh</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="relief">none</property>
<property name="use-stock">True</property>
<signal name="clicked" handler="on_update_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="library-toolbar-sort">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Sort</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="sort_artist">
<property name="label" translatable="yes">sort by artist</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
<property name="group">sort_year</property>
<signal name="toggled" handler="on_sort_toggled" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="sort_title">
<property name="label" translatable="yes">sort by title</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
<property name="group">sort_year</property>
<signal name="toggled" handler="on_sort_toggled" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="sort_year">
<property name="label" translatable="yes">sort by year</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
<signal name="toggled" handler="on_sort_toggled" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="sort_modified">
<property name="label" translatable="yes">sort by modification</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
<property name="group">sort_year</property>
<signal name="toggled" handler="on_sort_toggled" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="toolbar_sort_order_button">
<property name="label">gtk-sort-descending</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="use-stock">True</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
<signal name="toggled" handler="on_sort_order_toggled" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">6</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
<template class="McgLibraryToolbar" parent="GtkButtonBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">6</property>
<property name="layout-style">end</property>
<child>
<object class="GtkToggleButton" id="toolbar_search_bar">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Search the library</property>
<signal name="toggled" handler="on_search_toggled" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">system-search-symbolic</property>
</object>
</child>
<accelerator key="f" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
<property name="non-homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="select_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Select multiple albums</property>
<signal name="toggled" handler="on_select_toggled" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">object-select-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
<property name="non-homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkMenuButton">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Settings and actions</property>
<property name="popover">toolbar_popover</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">open-menu-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
<property name="non-homogeneous">True</property>
</packing>
</child>
</template>
</interface>

View file

@ -1,194 +1,182 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.16"/>
<template class="McgPlaylistPanel" parent="GtkStack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="transition-type">slide-left-right</property>
<requires lib="gtk+" version="4.8"/>
<requires lib="adw" version="1.2" />
<object class="GtkBox" id="toolbar">
<property name="orientation">horizontal</property>
<property name="halign">end</property>
<property name="spacing">6</property>
<child>
<object class="GtkBox" id="panel_normal">
<object class="GtkToggleButton" id="select_button">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Select multiple albums</property>
<signal name="toggled" handler="on_select_toggled" swapped="no"/>
<child>
<object class="GtkScrolledWindow">
<object class="GtkImage">
<property name="icon-name">object-select-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkButton" id="playlist_clear_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Clear the playlist</property>
<signal name="clicked" handler="on_clear_clicked" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="can-focus">False</property>
<property name="icon-name">edit-clear</property>
</object>
</child>
</object>
</child>
</object>
<template class="McgPlaylistPanel" parent="AdwBin">
<child>
<object class="GtkStack" id="playlist_stack">
<property name="transition-type">slide-left-right</property>
<child>
<object class="GtkBox" id="panel_normal">
<property name="orientation">vertical</property>
<child>
<object class="GtkIconView" id="playlist_grid">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin">0</property>
<property name="item-orientation">horizontal</property>
<property name="row-spacing">0</property>
<property name="column-spacing">0</property>
<property name="tooltip-column">1</property>
<property name="item-padding">0</property>
<property name="activate-on-single-click">True</property>
<signal name="item-activated" handler="on_playlist_grid_clicked" swapped="no"/>
<signal name="selection-changed" handler="on_playlist_grid_selection_changed" swapped="no"/>
<object class="GtkScrolledWindow">
<property name="vexpand">true</property>
<child>
<object class="GtkGridView" id="playlist_grid">
<property name="orientation">vertical</property>
<property name="single-click-activate">true</property>
<signal name="activate" handler="on_playlist_grid_clicked"/>
<style>
<class name="no-bg"/>
</style>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes">
<![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="activatable">true</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkPicture">
<property name="content-fit">contain</property>
<property name="can-shrink">false</property>
<binding name="tooltip-markup">
<lookup name="tooltip" type="GridItem">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
<binding name="paintable">
<lookup name="cover" type="GridItem">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</child>
</object>
</property>
</template>
</interface>
]]>
</property>
</object>
</property>
</object>
</child>
<style>
<class name="no-bg"/>
</style>
</object>
</child>
<style>
<class name="no-bg"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRevealer" id="actionbar_revealer">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="transition-type">slide-up</property>
<child>
<object class="GtkActionBar" id="actionbar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<object class="GtkRevealer" id="actionbar_revealer">
<property name="transition-type">slide-up</property>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">cancel</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_selection_cancel_clicked" swapped="no"/>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">remove</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_selection_remove_clicked" swapped="no"/>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="name">page2</property>
<property name="title">page2</property>
</packing>
</child>
<child>
<object class="GtkBox" id="panel_standalone">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkStack" id="standalone_stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkSpinner" id="standalone_spinner">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="name">standalone-spinne</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="standalone_scroll">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="kinetic-scrolling">False</property>
<property name="overlay-scrolling">False</property>
<signal name="size-allocate" handler="on_standalone_scroll_size_allocate" swapped="no"/>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkImage" id="standalone_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">gtk-missing-image</property>
<property name="icon_size">6</property>
<object class="GtkActionBar" id="actionbar">
<child type="end">
<object class="GtkButton">
<property name="label" translatable="yes">cancel</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_selection_cancel_clicked" swapped="no"/>
</object>
</child>
<child type="end">
<object class="GtkButton">
<property name="label" translatable="yes">remove</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_selection_remove_clicked" swapped="no"/>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">standalone-scroll</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkActionBar" id="actionbar_standalone">
<property name="visible">True</property>
<property name="can-focus">False</property>
<object class="GtkBox" id="panel_standalone">
<property name="orientation">vertical</property>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">remove</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_standalone_remove_clicked" swapped="no"/>
<object class="GtkStack" id="standalone_stack">
<property name="vexpand">true</property>
<child>
<object class="GtkSpinner" id="standalone_spinner">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="standalone_scroll">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="kinetic-scrolling">False</property>
<property name="overlay-scrolling">False</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkImage" id="standalone_image">
<property name="icon-name">gtk-missing-image</property>
<property name="icon-size">large</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">play</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_standalone_play_clicked" swapped="no"/>
<object class="GtkActionBar" id="actionbar_standalone">
<child type="end">
<object class="GtkButton">
<property name="label" translatable="yes">play</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_standalone_play_clicked" swapped="no"/>
</object>
</child>
<child type="end">
<object class="GtkButton">
<property name="label" translatable="yes">remove</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_standalone_remove_clicked" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="name">page1</property>
<property name="title">page1</property>
<property name="position">1</property>
</packing>
</child>
</template>
</interface>

View file

@ -1,55 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.10"/>
<template class="McgPlaylistToolbar" parent="GtkButtonBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">6</property>
<property name="layout-style">end</property>
<child>
<object class="GtkToggleButton" id="select_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Select multiple albums</property>
<signal name="toggled" handler="on_select_toggled" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">object-select-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
<property name="non-homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkButton" id="playlist_clear_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Clear the playlist</property>
<signal name="clicked" handler="on_clear_clicked" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">edit-clear</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
<property name="non-homogeneous">True</property>
</packing>
</child>
</template>
</interface>

View file

@ -1,547 +1,326 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.16"/>
<template class="McgServerPanel" parent="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<requires lib="gtk+" version="4.8"/>
<requires lib="adw" version="1.2" />
<object class="GtkBox" id="toolbar">
<property name="orientation">horizontal</property>
<property name="halign">end</property>
<child>
<object class="GtkStackSidebar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="stack">stack</property>
<object class="GtkToggleButton" id="sidebar_switcher">
<property name="icon-name">sidebar-show-symbolic</property>
<property name="active">true</property>
<property name="visible" bind-source="server_flap" bind-property="folded" bind-flags="sync-create"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<template class="McgServerPanel" parent="AdwBin">
<child>
<object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child type="center">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child type="center">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<object class="AdwFlap" id="server_flap">
<property name="flap-position">end</property>
<property name="reveal-flap" bind-source="sidebar_switcher" bind-property="active" bind-flags="sync-create|bidirectional" />
<property name="flap">
<object class="GtkStackSidebar">
<property name="stack">stack</property>
<property name="name">server_stack_sidebar</property>
</object>
</property>
<property name="separator">
<object class="GtkSeparator"/>
</property>
<property name="content">
<object class="GtkStack" id="stack">
<child>
<object class="GtkStackPage">
<property name="name">status</property>
<property name="title" translatable="yes">Status</property>
<property name="child">
<object class="AdwStatusPage">
<property name="icon-name">dialog-information-symbolic</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">dialog-information-symbolic</property>
<property name="icon_size">6</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<!-- n-columns=3 n-rows=4 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="row-spacing">2</property>
<property name="column-spacing">5</property>
<property name="hexpand">false</property>
<property name="halign">center</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="label" translatable="yes">File:</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="label" translatable="yes">Audio:</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="label" translatable="yes">Bitrate:</property>
<layout>
<property name="column">0</property>
<property name="row">2</property>
</layout>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="label" translatable="yes">Error:</property>
<layout>
<property name="column">0</property>
<property name="row">3</property>
</layout>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="status_bitrate">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">&lt;i&gt;none&lt;/i&gt;</property>
<property name="use-markup">True</property>
<property name="wrap">True</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="status_audio">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">&lt;i&gt;none&lt;/i&gt;</property>
<property name="use-markup">True</property>
<property name="wrap">True</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="status_file">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">&lt;i&gt;none&lt;/i&gt;</property>
<property name="use-markup">True</property>
<property name="wrap">True</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="status_audio">
<property name="halign">start</property>
<property name="label" translatable="yes">&lt;i&gt;none&lt;/i&gt;</property>
<property name="use-markup">True</property>
<property name="wrap">True</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="status_bitrate">
<property name="halign">start</property>
<property name="label" translatable="yes">&lt;i&gt;none&lt;/i&gt;</property>
<property name="use-markup">True</property>
<property name="wrap">True</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">2</property>
</layout>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="status_error">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">&lt;i&gt;none&lt;/i&gt;</property>
<property name="use-markup">True</property>
<property name="wrap">True</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">3</property>
</layout>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="name">page0</property>
<property name="title" translatable="yes">Status</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child type="center">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child type="center">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<object class="GtkStackPage">
<property name="name">stats</property>
<property name="title" translatable="yes">Statistics</property>
<property name="child">
<object class="AdwStatusPage">
<property name="icon-name">starred-symbolic</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">starred-symbolic</property>
<property name="icon_size">6</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<!-- n-columns=3 n-rows=7 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="row-spacing">2</property>
<property name="column-spacing">5</property>
<property name="column-homogeneous">true</property>
<property name="hexpand">false</property>
<property name="halign">center</property>
<child>
<object class="GtkLabel" id="stats_artists">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="justify">right</property>
<property name="selectable">True</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label" translatable="yes">Artists</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="stats_albums">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="justify">right</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label" translatable="yes">Albums</property>
<layout>
<property name="column">1</property>
<property name="row">1</property>
</layout>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="stats_songs">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="justify">right</property>
<layout>
<property name="column">0</property>
<property name="row">2</property>
</layout>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Albums</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Songs</property>
<layout>
<property name="column">1</property>
<property name="row">2</property>
</layout>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Artists</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="stats_dbplaytime">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="justify">right</property>
<layout>
<property name="column">0</property>
<property name="row">3</property>
</layout>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Seconds</property>
<layout>
<property name="column">1</property>
<property name="row">3</property>
</layout>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="stats_uptime">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="justify">right</property>
<object class="GtkLabel">
<layout>
<property name="column">0</property>
<property name="row">4</property>
<property name="column-span">2</property>
</layout>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="stats_playtime">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="justify">right</property>
<layout>
<property name="column">0</property>
<property name="row">5</property>
</layout>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">4</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Seconds played</property>
<layout>
<property name="column">1</property>
<property name="row">5</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="stats_uptime">
<property name="halign">end</property>
<property name="justify">right</property>
<layout>
<property name="column">0</property>
<property name="row">6</property>
</layout>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Seconds running</property>
<layout>
<property name="column">1</property>
<property name="row">6</property>
</layout>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">6</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="name">page1</property>
<property name="title" translatable="yes">Statistics</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child type="center">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child type="center">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">audio-speakers-symbolic</property>
<property name="icon_size">6</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<object class="GtkStackPage">
<property name="name">devices</property>
<property name="title" translatable="yes">Audio Devices</property>
<property name="child">
<object class="AdwStatusPage">
<property name="icon-name">audio-speakers-symbolic</property>
<child>
<object class="GtkListBox" id="output_devices">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">false</property>
<property name="halign">center</property>
<property name="selection-mode">none</property>
<style>
<class name="no-bg"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="name">page2</property>
<property name="title" translatable="yes">Audio Devices</property>
<property name="position">2</property>
</packing>
</child>
</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</template>
</interface>

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.10"/>
<template class="McgServerToolbar" parent="GtkButtonBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="layout-style">start</property>
<child>
<placeholder/>
</child>
</template>
</interface>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.18"/>
<requires lib="gtk+" version="4.8"/>
<requires lib="adw" version="1.2" />
<template class="McgShortcutsDialog" parent="GtkShortcutsWindow">
<property name="modal">True</property>
<property name="modal">1</property>
<child>
<object class="GtkShortcutsSection">
<property name="visible">1</property>
@ -11,53 +11,45 @@
<property name="max-height">10</property>
<child>
<object class="GtkShortcutsGroup">
<property name="visible">1</property>
<property name="title" translatable="yes">General</property>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;primary&gt;KP_1</property>
<property name="title" translatable="yes">Switch to the Connection panel</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;primary&gt;KP_2</property>
<property name="title" translatable="yes">Switch to the Cover panel</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;primary&gt;KP_3</property>
<property name="title" translatable="yes">Switch to the Playlist panel</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;primary&gt;KP_4</property>
<property name="title" translatable="yes">Switch to the Library panel</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;primary&gt;k</property>
<property name="title" translatable="yes">Show the keyboard shortcuts</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;primary&gt;i</property>
<property name="title" translatable="yes">Open the info dialog</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;primary&gt;q</property>
<property name="title" translatable="yes">Quit the application</property>
</object>
@ -66,25 +58,21 @@
</child>
<child>
<object class="GtkShortcutsGroup">
<property name="visible">1</property>
<property name="title" translatable="yes">Player</property>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;primary&gt;c</property>
<property name="title" translatable="yes">Connect or disconnect</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;primary&gt;p</property>
<property name="title" translatable="yes">Switch between play and pause</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;primary&gt;r</property>
<property name="title" translatable="yes">Clear the playlist</property>
</object>
@ -93,11 +81,9 @@
</child>
<child>
<object class="GtkShortcutsGroup">
<property name="visible">1</property>
<property name="title" translatable="yes">Cover Panel</property>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">F11</property>
<property name="title" translatable="yes">Show the cover in fullscreen mode</property>
</object>
@ -106,11 +92,9 @@
</child>
<child>
<object class="GtkShortcutsGroup">
<property name="visible">1</property>
<property name="title" translatable="yes">Library Panel</property>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;primary&gt;f</property>
<property name="title" translatable="yes">Search the library</property>
</object>

View file

@ -1,229 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<template class="McgAppWindow" parent="GtkApplicationWindow">
<property name="can-focus">False</property>
<property name="icon-name">xyz.suruatoel.mcg</property>
<signal name="size-allocate" handler="on_resize" swapped="no"/>
<signal name="window-state-event" handler="on_state" swapped="no"/>
<child>
<object class="GtkOverlay">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkStack" id="content_stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="transition-duration">100</property>
<property name="transition-type">crossfade</property>
<child>
<object class="GtkStack" id="panel_stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="name">page0</property>
<property name="title">page0</property>
</packing>
</child>
</object>
<packing>
<property name="index">-1</property>
</packing>
</child>
<child type="overlay">
<object class="GtkRevealer" id="info_revealer">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">start</property>
<child>
<object class="GtkInfoBar" id="info_bar">
<property name="can-focus">False</property>
<property name="valign">start</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="orientation">vertical</property>
<property name="baseline-position">top</property>
<property name="show-close-button">True</property>
<signal name="close" handler="on_info_bar_close" swapped="no"/>
<signal name="response" handler="on_info_bar_response" swapped="no"/>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="can-focus">False</property>
<child>
<object class="GtkLabel" id="info_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child type="titlebar">
<object class="GtkHeaderBar" id="headerbar">
<property name="name">headerbar</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="show-close-button">True</property>
<child type="title">
<object class="GtkStack" id="headerbar_title_stack">
<property name="name">headerbar-connection</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="transition-duration">100</property>
<property name="transition-type">crossfade</property>
<child>
<object class="GtkLabel" id="headerbar_connection_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Connect to MPD</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="name">page1</property>
<property name="title">page1</property>
</packing>
</child>
<child>
<object class="GtkStackSwitcher" id="headerbar_panel_switcher">
<property name="visible">True</property>
<property name="can-focus">False</property>
<requires lib="gtk+" version="4.8" />
<requires lib="adw" version="1.2" />
<template class="McgAppWindow" parent="AdwApplicationWindow">
<property name="content">
<object class="AdwToolbarView" id="toolbar_view">
<child type="top">
<object class="AdwHeaderBar" id="headerbar">
<property name="centering-policy">strict</property>
<property name="show_end_title_buttons">true</property>
<property name="title-widget">
<object class="AdwViewSwitcherTitle" id="headerbar_panel_switcher">
<property name="title">CoverGrid</property>
<property name="stack">panel_stack</property>
</object>
<packing>
<property name="name">page0</property>
<property name="title">page0</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="headerbar_button_connect">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="tooltip-text" translatable="yes">Connect or disconnect</property>
<signal name="notify::active" handler="on_headerbar_connection_active_notify" swapped="no"/>
<signal name="state-set" handler="on_headerbar_connection_state_set" swapped="no"/>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="headerbar_button_playpause">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Switch between play and pause</property>
<signal name="toggled" handler="on_headerbar_playpause_toggled" swapped="no"/>
</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">media-playback-start</property>
<object class="GtkSwitch" id="headerbar_button_connect">
<signal name="state-set" handler="on_headerbar_connection_state_set" swapped="no"/>
</object>
</child>
<child>
<object class="GtkToggleButton" id="headerbar_button_playpause">
<property name="tooltip-text" translatable="yes">Switch between play and pause</property>
<signal name="toggled" handler="on_headerbar_playpause_toggled" swapped="no"/>
<child>
<object class="GtkImage">
<property name="can-focus">False</property>
<property name="icon-name">media-playback-start</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkVolumeButton" id="headerbar_button_volume">
<signal name="value-changed" handler="on_headerbar_volume_changed" swapped="no"/>
</object>
</child>
<child type="end">
<object class="GtkStack" id="toolbar_stack">
</object>
</child>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkVolumeButton" id="headerbar_button_volume">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="focus-on-click">False</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Adjust the volume</property>
<property name="relief">none</property>
<property name="content">
<object class="GtkBox" id="content_box">
<property name="orientation">vertical</property>
<signal name="button-press-event" handler="on_headerbar_volume_press" swapped="no"/>
<signal name="button-release-event" handler="on_headerbar_volume_release" swapped="no"/>
<signal name="value-changed" handler="on_headerbar_volume_changed" swapped="no"/>
<child internal-child="plus_button">
<object class="GtkButton">
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="relief">none</property>
</object>
</child>
<child internal-child="minus_button">
<object class="GtkButton">
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="relief">none</property>
</object>
</child>
</object>
<packing>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkStack" id="toolbar_stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<placeholder/>
<object class="AdwToastOverlay" id="info_toast">
<child>
<object class="GtkStack" id="content_stack">
<property name="name">content_stack</property>
<property name="vexpand">true</property>
<child>
<object class="AdwViewStack" id="panel_stack">
<property name="vexpand">true</property>
<signal name="notify::visible-child" handler="on_stack_switched" swapped="no"/>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwViewSwitcherBar">
<property name="stack">panel_stack</property>
<binding name="reveal">
<lookup name="title-visible">headerbar_panel_switcher</lookup>
</binding>
</object>
</child>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">4</property>
</packing>
</child>
</property>
</object>
</child>
<style>
<class name="bg-texture"/>
</style>
</property>
</template>
</interface>

View file

@ -4,18 +4,12 @@
<file>gtk.css</file>
<file>noise-texture.png</file>
<file>ui/window.ui</file>
<file>ui/gtk.menu.ui</file>
<file>ui/info-dialog.ui</file>
<file>ui/shortcuts-dialog.ui</file>
<file>ui/connection-panel.ui</file>
<file>ui/album-headerbar.ui</file>
<file>ui/server-toolbar.ui</file>
<file>ui/server-panel.ui</file>
<file>ui/cover-toolbar.ui</file>
<file>ui/cover-panel.ui</file>
<file>ui/playlist-toolbar.ui</file>
<file>ui/playlist-panel.ui</file>
<file>ui/library-toolbar.ui</file>
<file>ui/library-panel.ui</file>
</gresource>
</gresources>

View file

@ -1,5 +1,5 @@
project('mcg',
version: '3.2',
version: '4.0.2',
meson_version: '>= 0.59.0',
default_options: [
'warning_level=2',

View file

@ -1,16 +1,9 @@
data/xyz.suruatoel.mcg.gschema.xml
data/ui/album-headerbar.ui
data/ui/connection-panel.ui
data/ui/cover-panel.ui
data/ui/cover-toolbar.ui
data/ui/gtk.menu.ui
data/ui/info-dialog.ui
data/ui/library-panel.ui
data/ui/library-toolbar.ui
data/ui/playlist-panel.ui
data/ui/playlist-toolbar.ui
data/ui/server-panel.ui
data/ui/server-toolbar.ui
data/ui/shortcuts-dialog.ui
data/ui/window.ui
src/albumheaderbar.py
@ -18,7 +11,6 @@ src/application.py
src/client.py
src/connectionpanel.py
src/coverpanel.py
src/infodialog.py
src/librarypanel.py
src/main.py
src/playlistpanel.py

BIN
po/de.mo

Binary file not shown.

431
po/de.po
View file

@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: CoverGrid (mcg)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-01-08 19:06+0100\n"
"PO-Revision-Date: 2023-01-08 19:07+0100\n"
"POT-Creation-Date: 2024-05-22 14:39+0200\n"
"PO-Revision-Date: 2024-05-22 14:39+0200\n"
"Last-Translator: coderkun <olli@suruatoel.xyz>\n"
"Language-Team: \n"
"Language: de_DE\n"
@ -11,357 +11,212 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.2.2\n"
"X-Generator: Poedit 3.4.2\n"
"X-Poedit-Basepath: ../../..\n"
"X-Poedit-SourceCharset: UTF-8\n"
#: data/xyz.suruatoel.mcg.gschema.xml:11
msgid "MPD host"
msgstr ""
#: data/ui/connection-panel.ui:29
msgid "No service found"
msgstr "Keine Dienste gefunden"
#: data/xyz.suruatoel.mcg.gschema.xml:12
msgid "MPD host to connect to"
msgstr ""
#: data/ui/connection-panel.ui:43
msgid "Host"
msgstr "Host"
#: data/xyz.suruatoel.mcg.gschema.xml:16
msgid "MPD port"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:17
msgid "MPD port to connect to"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:21
#, fuzzy
msgid "Connection state"
msgstr "Verbindung"
#: data/xyz.suruatoel.mcg.gschema.xml:22
msgid "State of last connection"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:26
msgid "Window width"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:27
msgid "The window width in pixels."
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:31
msgid "Window height"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:32
msgid "The window height in pixels."
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:36
msgid "Window maximized"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:37
msgid "Whether or not the window is in maximized state."
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:42
msgid "Last selected panel"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:43
msgid "The index of the last selected panel."
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:48
msgid "Size of library items"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:49
msgid "The size of items displayed in the library."
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:53
msgid "Sort criterium for library items"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:54
msgid "The sort criterium of items displayed in the library."
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:58
msgid "Sort type for library items"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:59
msgid "The sort type of items displayed in the library."
msgstr ""
#: data/ui/album-headerbar.ui:17
msgid "Title"
msgstr "Titel"
#: data/ui/album-headerbar.ui:34
msgid "Artist"
msgstr "Künstler"
#: data/ui/connection-panel.ui:56
msgid "Port"
msgstr "Port"
#: data/ui/connection-panel.ui:76
msgid "Enter hostname or IP address"
msgstr "Hostnamen oder IP-Adresse eingeben"
msgid "Password"
msgstr "Passwort"
#: data/ui/connection-panel.ui:89
msgid "Enter password or leave blank"
msgstr "Passwort eingeben oder leer lassen"
#: data/ui/connection-panel.ui:118
msgid "Host:"
msgstr "Host:"
#: data/ui/connection-panel.ui:130
msgid "Port:"
msgstr "Port:"
#: data/ui/connection-panel.ui:142
msgid "Password:"
msgstr "Passwort:"
#: data/ui/cover-toolbar.ui:15 data/ui/shortcuts-dialog.ui:102
#: data/ui/cover-panel.ui:12 data/ui/shortcuts-dialog.ui:88
msgid "Show the cover in fullscreen mode"
msgstr "Das Cover im Vollbildmodus anzeigen"
#: data/ui/gtk.menu.ui:7
msgid "Connect"
msgstr "Verbinden"
#: data/ui/library-panel.ui:34
msgid "update library"
msgstr "Die Bibliothek aktualisieren"
#: data/ui/gtk.menu.ui:12
msgid "Play"
msgstr "Abspielen"
#: data/ui/gtk.menu.ui:17
msgid "Clear Playlist"
msgstr "Playlist leeren"
#: data/ui/gtk.menu.ui:22
msgid "Toggle Fullscreen"
msgstr "Vollbild wechseln"
#: data/ui/gtk.menu.ui:27
msgid "Search Library"
msgstr "Bibliothek durchsuchen"
#: data/ui/gtk.menu.ui:34
msgid "Connection"
msgstr "Verbindung"
#: data/ui/gtk.menu.ui:40 src/window.py:113
msgid "Cover"
msgstr "Cover"
#: data/ui/gtk.menu.ui:46 src/window.py:114
msgid "Playlist"
msgstr "Wiedergabeliste"
#: data/ui/gtk.menu.ui:52 src/window.py:115
msgid "Library"
msgstr "Bibliothek"
#: data/ui/gtk.menu.ui:60
msgid "Keyboard Shortcuts"
msgstr "Tastenkombinationen"
#: data/ui/gtk.menu.ui:65
msgid "Info"
msgstr "Info"
#: data/ui/gtk.menu.ui:70
msgid "Quit"
msgstr "Beenden"
#: data/ui/info-dialog.ui:14
msgid ""
"CoverGrid is a client for the Music Player Daemon, focusing on albums "
"instead of single tracks."
msgstr ""
"CoverGrid ist ein ein Client für den Music Player Daemon, der sich auf Alben "
"anstellen von einzelnen Songs fokussiert."
#: data/ui/library-panel.ui:27
msgid "search library"
msgstr "Bibliothek durchsuchen"
#: data/ui/library-panel.ui:166 data/ui/playlist-panel.ui:57
msgid "cancel"
msgstr "abbrechen"
#: data/ui/library-panel.ui:179 data/ui/library-panel.ui:264
msgid "queue"
msgstr "einreihen"
#: data/ui/library-panel.ui:277 data/ui/playlist-panel.ui:168
msgid "play"
msgstr "abspielen"
#: data/ui/library-toolbar.ui:76
#: data/ui/library-panel.ui:48
msgid "Sort"
msgstr "Sortierung"
#: data/ui/library-toolbar.ui:86
#: data/ui/library-panel.ui:53
msgid "sort by artist"
msgstr "nach Künstler"
#: data/ui/library-toolbar.ui:102
#: data/ui/library-panel.ui:61
msgid "sort by title"
msgstr "nach Titel"
#: data/ui/library-toolbar.ui:118
#: data/ui/library-panel.ui:69
msgid "sort by year"
msgstr "nach Jahr"
#: data/ui/library-toolbar.ui:134
#: data/ui/library-panel.ui:76
msgid "sort by modification"
msgstr "nach Änderungsdatum"
#: data/ui/library-toolbar.ui:185 data/ui/shortcuts-dialog.ui:115
#: data/ui/library-panel.ui:84
msgid "sort library descending"
msgstr "absteigend sortieren"
#: data/ui/library-panel.ui:105 data/ui/shortcuts-dialog.ui:99
msgid "Search the library"
msgstr "Die Bibliothek durchsuchen"
#: data/ui/library-toolbar.ui:208 data/ui/playlist-toolbar.ui:15
#: data/ui/library-panel.ui:121 data/ui/playlist-panel.ui:14
msgid "Select multiple albums"
msgstr "Mehrere Alben auswählen"
#: data/ui/library-toolbar.ui:230
#: data/ui/library-panel.ui:135
msgid "Settings and actions"
msgstr "Einstellungen und Aktionen"
#: data/ui/playlist-panel.ui:70 data/ui/playlist-panel.ui:155
msgid "remove"
msgstr "entfernen"
#: data/ui/library-panel.ui:160
msgid "search library"
msgstr "Bibliothek durchsuchen"
#: data/ui/playlist-toolbar.ui:37 data/ui/shortcuts-dialog.ui:89
#: data/ui/library-panel.ui:263 data/ui/playlist-panel.ui:107
msgid "cancel"
msgstr "abbrechen"
#: data/ui/library-panel.ui:270 data/ui/library-panel.ui:327
msgid "queue"
msgstr "einreihen"
#: data/ui/library-panel.ui:320 data/ui/playlist-panel.ui:163
msgid "play"
msgstr "abspielen"
#: data/ui/playlist-panel.ui:28 data/ui/shortcuts-dialog.ui:77
msgid "Clear the playlist"
msgstr "Die Wiedergabeliste leeren"
#: data/ui/server-panel.ui:69
msgid "File:"
msgstr "Datei:"
#: data/ui/playlist-panel.ui:114 data/ui/playlist-panel.ui:170
msgid "remove"
msgstr "entfernen"
#: data/ui/server-panel.ui:82
msgid "Audio:"
msgstr "Audio:"
#: data/ui/server-panel.ui:95
msgid "Bitrate:"
msgstr "Bitrate:"
#: data/ui/server-panel.ui:108
msgid "Error:"
msgstr "Fehler:"
#: data/ui/server-panel.ui:120 data/ui/server-panel.ui:136
#: data/ui/server-panel.ui:152 data/ui/server-panel.ui:168
msgid "<i>none</i>"
msgstr "<i>nichts</i>"
#: data/ui/server-panel.ui:221
#: data/ui/server-panel.ui:35
msgid "Status"
msgstr "Status"
#: data/ui/server-panel.ui:301
msgid "Albums"
msgstr "Alben"
#: data/ui/server-panel.ui:49
msgid "File:"
msgstr "Datei:"
#: data/ui/server-panel.ui:313
msgid "Songs"
msgstr "Songs"
#: data/ui/server-panel.ui:60
msgid "Audio:"
msgstr "Audio:"
#: data/ui/server-panel.ui:325
msgid "Artists"
msgstr "Künstler"
#: data/ui/server-panel.ui:71
msgid "Bitrate:"
msgstr "Bitrate:"
#: data/ui/server-panel.ui:349
msgid "Seconds"
msgstr "Sekunden"
#: data/ui/server-panel.ui:82
msgid "Error:"
msgstr "Fehler:"
#: data/ui/server-panel.ui:396
msgid "Seconds played"
msgstr "Sekunden gespielt"
#: data/ui/server-panel.ui:92 data/ui/server-panel.ui:106
#: data/ui/server-panel.ui:120 data/ui/server-panel.ui:134
msgid "<i>none</i>"
msgstr "<i>nichts</i>"
#: data/ui/server-panel.ui:407
msgid "Seconds running"
msgstr "Sekunden laufend"
#: data/ui/server-panel.ui:465
#: data/ui/server-panel.ui:154
msgid "Statistics"
msgstr "Statistiken"
#: data/ui/server-panel.ui:535
#: data/ui/server-panel.ui:178
msgid "Artists"
msgstr "Künstler"
#: data/ui/server-panel.ui:198
msgid "Albums"
msgstr "Alben"
#: data/ui/server-panel.ui:218
msgid "Songs"
msgstr "Songs"
#: data/ui/server-panel.ui:238
msgid "Seconds"
msgstr "Sekunden"
#: data/ui/server-panel.ui:267
msgid "Seconds played"
msgstr "Sekunden gespielt"
#: data/ui/server-panel.ui:287
msgid "Seconds running"
msgstr "Sekunden laufend"
#: data/ui/server-panel.ui:303
msgid "Audio Devices"
msgstr "Audiogeräte"
#: data/ui/shortcuts-dialog.ui:15
#: data/ui/shortcuts-dialog.ui:14
msgid "General"
msgstr "Allgemein"
#: data/ui/shortcuts-dialog.ui:20
#: data/ui/shortcuts-dialog.ui:18
msgid "Switch to the Connection panel"
msgstr "Zum Verbindungspaneel wechseln"
#: data/ui/shortcuts-dialog.ui:27
#: data/ui/shortcuts-dialog.ui:24
msgid "Switch to the Cover panel"
msgstr "Zum Cover-Paneel wechseln"
#: data/ui/shortcuts-dialog.ui:34
#: data/ui/shortcuts-dialog.ui:30
msgid "Switch to the Playlist panel"
msgstr "Zum Wiedergabelistenpaneel wechseln"
#: data/ui/shortcuts-dialog.ui:41
#: data/ui/shortcuts-dialog.ui:36
msgid "Switch to the Library panel"
msgstr "Zum Bibliothekspaneel wechseln"
#: data/ui/shortcuts-dialog.ui:48
#: data/ui/shortcuts-dialog.ui:42
msgid "Show the keyboard shortcuts"
msgstr "Die Tastenkombinationen anzeigen (dieser Dialog)"
#: data/ui/shortcuts-dialog.ui:55
#: data/ui/shortcuts-dialog.ui:48
msgid "Open the info dialog"
msgstr "Den Infodialog öffnen"
#: data/ui/shortcuts-dialog.ui:62
#: data/ui/shortcuts-dialog.ui:54
msgid "Quit the application"
msgstr "Die Anwendung beenden"
#: data/ui/shortcuts-dialog.ui:70
#: data/ui/shortcuts-dialog.ui:61
msgid "Player"
msgstr "Wiedergabeprogramm"
#: data/ui/shortcuts-dialog.ui:75 data/ui/window.ui:139
#: data/ui/shortcuts-dialog.ui:65
msgid "Connect or disconnect"
msgstr "Die Verbindung herstellen oder trennen"
#: data/ui/shortcuts-dialog.ui:82 data/ui/window.ui:161
#: data/ui/shortcuts-dialog.ui:71 data/ui/window.ui:25
msgid "Switch between play and pause"
msgstr "Zwischen Abspielen und Pause wechseln"
#: data/ui/shortcuts-dialog.ui:97
#: data/ui/shortcuts-dialog.ui:84
msgid "Cover Panel"
msgstr "Cover-Paneel"
#: data/ui/shortcuts-dialog.ui:110
#: data/ui/shortcuts-dialog.ui:95
msgid "Library Panel"
msgstr "Bibliothekspaneel"
#: data/ui/window.ui:108
msgid "Connect to MPD"
msgstr "Zu MPD verbinden"
#: src/connectionpanel.py:51
msgid "use"
msgstr "verwenden"
#: data/ui/window.ui:181
msgid "Adjust the volume"
msgstr "Die Lautstärke anpassen"
#: src/librarypanel.py:421
#: src/librarypanel.py:291
msgid "Loading albums"
msgstr "Alben werden geladen"
#: src/librarypanel.py:521
#: src/librarypanel.py:379
msgid "Loading images"
msgstr "Bilder werden geladen"
@ -373,10 +228,74 @@ msgstr "{} mit {}"
msgid "{}:{} minutes"
msgstr "{}:{} Minuten"
#: src/window.py:112
#: src/window.py:114
msgid "Server"
msgstr "Server"
#: src/window.py:115
msgid "Cover"
msgstr "Cover"
#: src/window.py:116
msgid "Playlist"
msgstr "Wiedergabeliste"
#: src/window.py:117
msgid "Library"
msgstr "Bibliothek"
#, fuzzy
#~ msgid "Connection state"
#~ msgstr "Verbindung"
#~ msgid "Title"
#~ msgstr "Titel"
#~ msgid "Artist"
#~ msgstr "Künstler"
#~ msgid "Enter hostname or IP address"
#~ msgstr "Hostnamen oder IP-Adresse eingeben"
#~ msgid "Enter password or leave blank"
#~ msgstr "Passwort eingeben oder leer lassen"
#~ msgid "Connect"
#~ msgstr "Verbinden"
#~ msgid "Play"
#~ msgstr "Abspielen"
#~ msgid "Clear Playlist"
#~ msgstr "Playlist leeren"
#~ msgid "Toggle Fullscreen"
#~ msgstr "Vollbild wechseln"
#~ msgid "Search Library"
#~ msgstr "Bibliothek durchsuchen"
#~ msgid "Connection"
#~ msgstr "Verbindung"
#~ msgid "Keyboard Shortcuts"
#~ msgstr "Tastenkombinationen"
#~ msgid "Info"
#~ msgstr "Info"
#~ msgid "Quit"
#~ msgstr "Beenden"
#~ msgid "CoverGrid is a client for the Music Player Daemon, focusing on albums instead of single tracks."
#~ msgstr "CoverGrid ist ein ein Client für den Music Player Daemon, der sich auf Alben anstellen von einzelnen Songs fokussiert."
#~ msgid "Connect to MPD"
#~ msgstr "Zu MPD verbinden"
#~ msgid "Adjust the volume"
#~ msgstr "Die Lautstärke anpassen"
#~ msgid "Enter URL or local path"
#~ msgstr "URL oder lokalen Pfad eingeben"

BIN
po/en.mo

Binary file not shown.

431
po/en.po
View file

@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: CoverGrid (mcg)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-01-08 19:06+0100\n"
"PO-Revision-Date: 2023-01-08 19:07+0100\n"
"POT-Creation-Date: 2024-05-22 14:39+0200\n"
"PO-Revision-Date: 2024-05-22 14:39+0200\n"
"Last-Translator: coderkun <olli@suruatoel.xyz>\n"
"Language-Team: \n"
"Language: en\n"
@ -11,358 +11,213 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.2.2\n"
"X-Generator: Poedit 3.4.2\n"
"X-Poedit-Basepath: ../../..\n"
"X-Poedit-SearchPath-0: mcg\n"
"X-Poedit-SearchPath-1: data/ui\n"
#: data/xyz.suruatoel.mcg.gschema.xml:11
msgid "MPD host"
msgstr ""
#: data/ui/connection-panel.ui:29
msgid "No service found"
msgstr "No service found"
#: data/xyz.suruatoel.mcg.gschema.xml:12
msgid "MPD host to connect to"
msgstr ""
#: data/ui/connection-panel.ui:43
msgid "Host"
msgstr "Host"
#: data/xyz.suruatoel.mcg.gschema.xml:16
msgid "MPD port"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:17
msgid "MPD port to connect to"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:21
#, fuzzy
msgid "Connection state"
msgstr "Connection"
#: data/xyz.suruatoel.mcg.gschema.xml:22
msgid "State of last connection"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:26
msgid "Window width"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:27
msgid "The window width in pixels."
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:31
msgid "Window height"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:32
msgid "The window height in pixels."
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:36
msgid "Window maximized"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:37
msgid "Whether or not the window is in maximized state."
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:42
msgid "Last selected panel"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:43
msgid "The index of the last selected panel."
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:48
msgid "Size of library items"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:49
msgid "The size of items displayed in the library."
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:53
msgid "Sort criterium for library items"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:54
msgid "The sort criterium of items displayed in the library."
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:58
msgid "Sort type for library items"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:59
msgid "The sort type of items displayed in the library."
msgstr ""
#: data/ui/album-headerbar.ui:17
msgid "Title"
msgstr "Title"
#: data/ui/album-headerbar.ui:34
msgid "Artist"
msgstr "Artist"
#: data/ui/connection-panel.ui:56
msgid "Port"
msgstr "Port"
#: data/ui/connection-panel.ui:76
msgid "Enter hostname or IP address"
msgstr "Enter hostname or IP address"
msgid "Password"
msgstr "Password"
#: data/ui/connection-panel.ui:89
msgid "Enter password or leave blank"
msgstr "Enter password or leave blank"
#: data/ui/connection-panel.ui:118
msgid "Host:"
msgstr "Host:"
#: data/ui/connection-panel.ui:130
msgid "Port:"
msgstr "Port:"
#: data/ui/connection-panel.ui:142
msgid "Password:"
msgstr "Password:"
#: data/ui/cover-toolbar.ui:15 data/ui/shortcuts-dialog.ui:102
#: data/ui/cover-panel.ui:12 data/ui/shortcuts-dialog.ui:88
msgid "Show the cover in fullscreen mode"
msgstr "Show the cover in fullscreen mode"
#: data/ui/gtk.menu.ui:7
msgid "Connect"
msgstr "Connect"
#: data/ui/library-panel.ui:34
msgid "update library"
msgstr "update the library"
#: data/ui/gtk.menu.ui:12
msgid "Play"
msgstr "Play"
#: data/ui/gtk.menu.ui:17
msgid "Clear Playlist"
msgstr "Clear Playlist"
#: data/ui/gtk.menu.ui:22
msgid "Toggle Fullscreen"
msgstr "Toggle fullscreen"
#: data/ui/gtk.menu.ui:27
msgid "Search Library"
msgstr "Search Library"
#: data/ui/gtk.menu.ui:34
msgid "Connection"
msgstr "Connection"
#: data/ui/gtk.menu.ui:40 src/window.py:113
msgid "Cover"
msgstr "Cover"
#: data/ui/gtk.menu.ui:46 src/window.py:114
msgid "Playlist"
msgstr "Playlist"
#: data/ui/gtk.menu.ui:52 src/window.py:115
msgid "Library"
msgstr "Library"
#: data/ui/gtk.menu.ui:60
msgid "Keyboard Shortcuts"
msgstr "Keyboard Shortcuts"
#: data/ui/gtk.menu.ui:65
msgid "Info"
msgstr "Info"
#: data/ui/gtk.menu.ui:70
msgid "Quit"
msgstr "Quit"
#: data/ui/info-dialog.ui:14
msgid ""
"CoverGrid is a client for the Music Player Daemon, focusing on albums "
"instead of single tracks."
msgstr ""
"CoverGrid is a client for the Music Player Daemon, focusing on albums "
"instead of single tracks."
#: data/ui/library-panel.ui:27
msgid "search library"
msgstr "search library"
#: data/ui/library-panel.ui:166 data/ui/playlist-panel.ui:57
msgid "cancel"
msgstr "cancel"
#: data/ui/library-panel.ui:179 data/ui/library-panel.ui:264
msgid "queue"
msgstr "queue"
#: data/ui/library-panel.ui:277 data/ui/playlist-panel.ui:168
msgid "play"
msgstr "play"
#: data/ui/library-toolbar.ui:76
#: data/ui/library-panel.ui:48
msgid "Sort"
msgstr "Sort order"
#: data/ui/library-toolbar.ui:86
#: data/ui/library-panel.ui:53
msgid "sort by artist"
msgstr "by Artist"
#: data/ui/library-toolbar.ui:102
#: data/ui/library-panel.ui:61
msgid "sort by title"
msgstr "by Title"
#: data/ui/library-toolbar.ui:118
#: data/ui/library-panel.ui:69
msgid "sort by year"
msgstr "by Year"
#: data/ui/library-toolbar.ui:134
#: data/ui/library-panel.ui:76
msgid "sort by modification"
msgstr "by Modification Date"
#: data/ui/library-toolbar.ui:185 data/ui/shortcuts-dialog.ui:115
#: data/ui/library-panel.ui:84
msgid "sort library descending"
msgstr "sort descending"
#: data/ui/library-panel.ui:105 data/ui/shortcuts-dialog.ui:99
msgid "Search the library"
msgstr "Search the library"
#: data/ui/library-toolbar.ui:208 data/ui/playlist-toolbar.ui:15
#: data/ui/library-panel.ui:121 data/ui/playlist-panel.ui:14
msgid "Select multiple albums"
msgstr "Select multiple albums"
#: data/ui/library-toolbar.ui:230
#: data/ui/library-panel.ui:135
msgid "Settings and actions"
msgstr "Settings and actions"
#: data/ui/playlist-panel.ui:70 data/ui/playlist-panel.ui:155
msgid "remove"
msgstr "remove"
#: data/ui/library-panel.ui:160
msgid "search library"
msgstr "search library"
#: data/ui/playlist-toolbar.ui:37 data/ui/shortcuts-dialog.ui:89
#: data/ui/library-panel.ui:263 data/ui/playlist-panel.ui:107
msgid "cancel"
msgstr "cancel"
#: data/ui/library-panel.ui:270 data/ui/library-panel.ui:327
msgid "queue"
msgstr "queue"
#: data/ui/library-panel.ui:320 data/ui/playlist-panel.ui:163
msgid "play"
msgstr "play"
#: data/ui/playlist-panel.ui:28 data/ui/shortcuts-dialog.ui:77
msgid "Clear the playlist"
msgstr "Clear the playlist"
#: data/ui/server-panel.ui:69
msgid "File:"
msgstr "File:"
#: data/ui/playlist-panel.ui:114 data/ui/playlist-panel.ui:170
msgid "remove"
msgstr "remove"
#: data/ui/server-panel.ui:82
msgid "Audio:"
msgstr "Audio:"
#: data/ui/server-panel.ui:95
msgid "Bitrate:"
msgstr "Bitrate:"
#: data/ui/server-panel.ui:108
msgid "Error:"
msgstr "Error:"
#: data/ui/server-panel.ui:120 data/ui/server-panel.ui:136
#: data/ui/server-panel.ui:152 data/ui/server-panel.ui:168
msgid "<i>none</i>"
msgstr "<i>none</i>"
#: data/ui/server-panel.ui:221
#: data/ui/server-panel.ui:35
msgid "Status"
msgstr "Status"
#: data/ui/server-panel.ui:301
msgid "Albums"
msgstr "Albums"
#: data/ui/server-panel.ui:49
msgid "File:"
msgstr "File:"
#: data/ui/server-panel.ui:313
msgid "Songs"
msgstr "Songs"
#: data/ui/server-panel.ui:60
msgid "Audio:"
msgstr "Audio:"
#: data/ui/server-panel.ui:325
msgid "Artists"
msgstr "Artists"
#: data/ui/server-panel.ui:71
msgid "Bitrate:"
msgstr "Bitrate:"
#: data/ui/server-panel.ui:349
msgid "Seconds"
msgstr "Seconds"
#: data/ui/server-panel.ui:82
msgid "Error:"
msgstr "Error:"
#: data/ui/server-panel.ui:396
msgid "Seconds played"
msgstr "Seconds"
#: data/ui/server-panel.ui:92 data/ui/server-panel.ui:106
#: data/ui/server-panel.ui:120 data/ui/server-panel.ui:134
msgid "<i>none</i>"
msgstr "<i>none</i>"
#: data/ui/server-panel.ui:407
msgid "Seconds running"
msgstr "Seconds running"
#: data/ui/server-panel.ui:465
#: data/ui/server-panel.ui:154
msgid "Statistics"
msgstr "Statistics"
#: data/ui/server-panel.ui:535
#: data/ui/server-panel.ui:178
msgid "Artists"
msgstr "Artists"
#: data/ui/server-panel.ui:198
msgid "Albums"
msgstr "Albums"
#: data/ui/server-panel.ui:218
msgid "Songs"
msgstr "Songs"
#: data/ui/server-panel.ui:238
msgid "Seconds"
msgstr "Seconds"
#: data/ui/server-panel.ui:267
msgid "Seconds played"
msgstr "Seconds"
#: data/ui/server-panel.ui:287
msgid "Seconds running"
msgstr "Seconds running"
#: data/ui/server-panel.ui:303
msgid "Audio Devices"
msgstr "Audio Devices"
#: data/ui/shortcuts-dialog.ui:15
#: data/ui/shortcuts-dialog.ui:14
msgid "General"
msgstr "General"
#: data/ui/shortcuts-dialog.ui:20
#: data/ui/shortcuts-dialog.ui:18
msgid "Switch to the Connection panel"
msgstr "Switch to the Connection panel"
#: data/ui/shortcuts-dialog.ui:27
#: data/ui/shortcuts-dialog.ui:24
msgid "Switch to the Cover panel"
msgstr "Switch to the Cover panel"
#: data/ui/shortcuts-dialog.ui:34
#: data/ui/shortcuts-dialog.ui:30
msgid "Switch to the Playlist panel"
msgstr "Switch to the Playlist panel"
#: data/ui/shortcuts-dialog.ui:41
#: data/ui/shortcuts-dialog.ui:36
msgid "Switch to the Library panel"
msgstr "Switch to the Cover panel"
#: data/ui/shortcuts-dialog.ui:48
#: data/ui/shortcuts-dialog.ui:42
msgid "Show the keyboard shortcuts"
msgstr "Show the keyboard shortcuts (this dialog)"
#: data/ui/shortcuts-dialog.ui:55
#: data/ui/shortcuts-dialog.ui:48
msgid "Open the info dialog"
msgstr "Open the info dialog"
#: data/ui/shortcuts-dialog.ui:62
#: data/ui/shortcuts-dialog.ui:54
msgid "Quit the application"
msgstr "Quit the application"
#: data/ui/shortcuts-dialog.ui:70
#: data/ui/shortcuts-dialog.ui:61
msgid "Player"
msgstr "Player"
#: data/ui/shortcuts-dialog.ui:75 data/ui/window.ui:139
#: data/ui/shortcuts-dialog.ui:65
msgid "Connect or disconnect"
msgstr "Connect or disconnect"
#: data/ui/shortcuts-dialog.ui:82 data/ui/window.ui:161
#: data/ui/shortcuts-dialog.ui:71 data/ui/window.ui:25
msgid "Switch between play and pause"
msgstr "Switch between play and pause"
#: data/ui/shortcuts-dialog.ui:97
#: data/ui/shortcuts-dialog.ui:84
msgid "Cover Panel"
msgstr "Cover Panel"
#: data/ui/shortcuts-dialog.ui:110
#: data/ui/shortcuts-dialog.ui:95
msgid "Library Panel"
msgstr "Library Panel"
#: data/ui/window.ui:108
msgid "Connect to MPD"
msgstr "Connect to MPD"
#: src/connectionpanel.py:51
msgid "use"
msgstr "use"
#: data/ui/window.ui:181
msgid "Adjust the volume"
msgstr "Adjust the volume"
#: src/librarypanel.py:421
#: src/librarypanel.py:291
msgid "Loading albums"
msgstr "Loading albums"
#: src/librarypanel.py:521
#: src/librarypanel.py:379
msgid "Loading images"
msgstr "Loading images"
@ -374,10 +229,74 @@ msgstr "{} feat. {}"
msgid "{}:{} minutes"
msgstr "{}:{} minutes"
#: src/window.py:112
#: src/window.py:114
msgid "Server"
msgstr "Server"
#: src/window.py:115
msgid "Cover"
msgstr "Cover"
#: src/window.py:116
msgid "Playlist"
msgstr "Playlist"
#: src/window.py:117
msgid "Library"
msgstr "Library"
#, fuzzy
#~ msgid "Connection state"
#~ msgstr "Connection"
#~ msgid "Title"
#~ msgstr "Title"
#~ msgid "Artist"
#~ msgstr "Artist"
#~ msgid "Enter hostname or IP address"
#~ msgstr "Enter hostname or IP address"
#~ msgid "Enter password or leave blank"
#~ msgstr "Enter password or leave blank"
#~ msgid "Connect"
#~ msgstr "Connect"
#~ msgid "Play"
#~ msgstr "Play"
#~ msgid "Clear Playlist"
#~ msgstr "Clear Playlist"
#~ msgid "Toggle Fullscreen"
#~ msgstr "Toggle fullscreen"
#~ msgid "Search Library"
#~ msgstr "Search Library"
#~ msgid "Connection"
#~ msgstr "Connection"
#~ msgid "Keyboard Shortcuts"
#~ msgstr "Keyboard Shortcuts"
#~ msgid "Info"
#~ msgstr "Info"
#~ msgid "Quit"
#~ msgstr "Quit"
#~ msgid "CoverGrid is a client for the Music Player Daemon, focusing on albums instead of single tracks."
#~ msgstr "CoverGrid is a client for the Music Player Daemon, focusing on albums instead of single tracks."
#~ msgid "Connect to MPD"
#~ msgstr "Connect to MPD"
#~ msgid "Adjust the volume"
#~ msgstr "Adjust the volume"
#~ msgid "Enter URL or local path"
#~ msgstr "Enter URL or local path"

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: mcg\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-01-08 19:06+0100\n"
"POT-Creation-Date: 2024-05-22 14:39+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,350 +17,208 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: data/xyz.suruatoel.mcg.gschema.xml:11
msgid "MPD host"
#: data/ui/connection-panel.ui:29
msgid "No service found"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:12
msgid "MPD host to connect to"
#: data/ui/connection-panel.ui:43
msgid "Host"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:16
msgid "MPD port"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:17
msgid "MPD port to connect to"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:21
msgid "Connection state"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:22
msgid "State of last connection"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:26
msgid "Window width"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:27
msgid "The window width in pixels."
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:31
msgid "Window height"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:32
msgid "The window height in pixels."
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:36
msgid "Window maximized"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:37
msgid "Whether or not the window is in maximized state."
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:42
msgid "Last selected panel"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:43
msgid "The index of the last selected panel."
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:48
msgid "Size of library items"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:49
msgid "The size of items displayed in the library."
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:53
msgid "Sort criterium for library items"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:54
msgid "The sort criterium of items displayed in the library."
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:58
msgid "Sort type for library items"
msgstr ""
#: data/xyz.suruatoel.mcg.gschema.xml:59
msgid "The sort type of items displayed in the library."
msgstr ""
#: data/ui/album-headerbar.ui:17
msgid "Title"
msgstr ""
#: data/ui/album-headerbar.ui:34
msgid "Artist"
#: data/ui/connection-panel.ui:56
msgid "Port"
msgstr ""
#: data/ui/connection-panel.ui:76
msgid "Enter hostname or IP address"
msgid "Password"
msgstr ""
#: data/ui/connection-panel.ui:89
msgid "Enter password or leave blank"
msgstr ""
#: data/ui/connection-panel.ui:118
msgid "Host:"
msgstr ""
#: data/ui/connection-panel.ui:130
msgid "Port:"
msgstr ""
#: data/ui/connection-panel.ui:142
msgid "Password:"
msgstr ""
#: data/ui/cover-toolbar.ui:15 data/ui/shortcuts-dialog.ui:102
#: data/ui/cover-panel.ui:12 data/ui/shortcuts-dialog.ui:88
msgid "Show the cover in fullscreen mode"
msgstr ""
#: data/ui/gtk.menu.ui:7
msgid "Connect"
#: data/ui/library-panel.ui:34
msgid "update library"
msgstr ""
#: data/ui/gtk.menu.ui:12
msgid "Play"
msgstr ""
#: data/ui/gtk.menu.ui:17
msgid "Clear Playlist"
msgstr ""
#: data/ui/gtk.menu.ui:22
msgid "Toggle Fullscreen"
msgstr ""
#: data/ui/gtk.menu.ui:27
msgid "Search Library"
msgstr ""
#: data/ui/gtk.menu.ui:34
msgid "Connection"
msgstr ""
#: data/ui/gtk.menu.ui:40 src/window.py:113
msgid "Cover"
msgstr ""
#: data/ui/gtk.menu.ui:46 src/window.py:114
msgid "Playlist"
msgstr ""
#: data/ui/gtk.menu.ui:52 src/window.py:115
msgid "Library"
msgstr ""
#: data/ui/gtk.menu.ui:60
msgid "Keyboard Shortcuts"
msgstr ""
#: data/ui/gtk.menu.ui:65
msgid "Info"
msgstr ""
#: data/ui/gtk.menu.ui:70
msgid "Quit"
msgstr ""
#: data/ui/info-dialog.ui:14
msgid ""
"CoverGrid is a client for the Music Player Daemon, focusing on albums "
"instead of single tracks."
msgstr ""
#: data/ui/library-panel.ui:27
msgid "search library"
msgstr ""
#: data/ui/library-panel.ui:166 data/ui/playlist-panel.ui:57
msgid "cancel"
msgstr ""
#: data/ui/library-panel.ui:179 data/ui/library-panel.ui:264
msgid "queue"
msgstr ""
#: data/ui/library-panel.ui:277 data/ui/playlist-panel.ui:168
msgid "play"
msgstr ""
#: data/ui/library-toolbar.ui:76
#: data/ui/library-panel.ui:48
msgid "Sort"
msgstr ""
#: data/ui/library-toolbar.ui:86
#: data/ui/library-panel.ui:53
msgid "sort by artist"
msgstr ""
#: data/ui/library-toolbar.ui:102
#: data/ui/library-panel.ui:61
msgid "sort by title"
msgstr ""
#: data/ui/library-toolbar.ui:118
#: data/ui/library-panel.ui:69
msgid "sort by year"
msgstr ""
#: data/ui/library-toolbar.ui:134
#: data/ui/library-panel.ui:76
msgid "sort by modification"
msgstr ""
#: data/ui/library-toolbar.ui:185 data/ui/shortcuts-dialog.ui:115
#: data/ui/library-panel.ui:84
msgid "sort library descending"
msgstr ""
#: data/ui/library-panel.ui:105 data/ui/shortcuts-dialog.ui:99
msgid "Search the library"
msgstr ""
#: data/ui/library-toolbar.ui:208 data/ui/playlist-toolbar.ui:15
#: data/ui/library-panel.ui:121 data/ui/playlist-panel.ui:14
msgid "Select multiple albums"
msgstr ""
#: data/ui/library-toolbar.ui:230
#: data/ui/library-panel.ui:135
msgid "Settings and actions"
msgstr ""
#: data/ui/playlist-panel.ui:70 data/ui/playlist-panel.ui:155
msgid "remove"
#: data/ui/library-panel.ui:160
msgid "search library"
msgstr ""
#: data/ui/playlist-toolbar.ui:37 data/ui/shortcuts-dialog.ui:89
#: data/ui/library-panel.ui:263 data/ui/playlist-panel.ui:107
msgid "cancel"
msgstr ""
#: data/ui/library-panel.ui:270 data/ui/library-panel.ui:327
msgid "queue"
msgstr ""
#: data/ui/library-panel.ui:320 data/ui/playlist-panel.ui:163
msgid "play"
msgstr ""
#: data/ui/playlist-panel.ui:28 data/ui/shortcuts-dialog.ui:77
msgid "Clear the playlist"
msgstr ""
#: data/ui/server-panel.ui:69
msgid "File:"
#: data/ui/playlist-panel.ui:114 data/ui/playlist-panel.ui:170
msgid "remove"
msgstr ""
#: data/ui/server-panel.ui:82
msgid "Audio:"
msgstr ""
#: data/ui/server-panel.ui:95
msgid "Bitrate:"
msgstr ""
#: data/ui/server-panel.ui:108
msgid "Error:"
msgstr ""
#: data/ui/server-panel.ui:120 data/ui/server-panel.ui:136
#: data/ui/server-panel.ui:152 data/ui/server-panel.ui:168
msgid "<i>none</i>"
msgstr ""
#: data/ui/server-panel.ui:221
#: data/ui/server-panel.ui:35
msgid "Status"
msgstr ""
#: data/ui/server-panel.ui:301
msgid "Albums"
#: data/ui/server-panel.ui:49
msgid "File:"
msgstr ""
#: data/ui/server-panel.ui:313
msgid "Songs"
#: data/ui/server-panel.ui:60
msgid "Audio:"
msgstr ""
#: data/ui/server-panel.ui:325
msgid "Artists"
#: data/ui/server-panel.ui:71
msgid "Bitrate:"
msgstr ""
#: data/ui/server-panel.ui:349
msgid "Seconds"
#: data/ui/server-panel.ui:82
msgid "Error:"
msgstr ""
#: data/ui/server-panel.ui:396
msgid "Seconds played"
#: data/ui/server-panel.ui:92 data/ui/server-panel.ui:106
#: data/ui/server-panel.ui:120 data/ui/server-panel.ui:134
msgid "<i>none</i>"
msgstr ""
#: data/ui/server-panel.ui:407
msgid "Seconds running"
msgstr ""
#: data/ui/server-panel.ui:465
#: data/ui/server-panel.ui:154
msgid "Statistics"
msgstr ""
#: data/ui/server-panel.ui:535
#: data/ui/server-panel.ui:178
msgid "Artists"
msgstr ""
#: data/ui/server-panel.ui:198
msgid "Albums"
msgstr ""
#: data/ui/server-panel.ui:218
msgid "Songs"
msgstr ""
#: data/ui/server-panel.ui:238
msgid "Seconds"
msgstr ""
#: data/ui/server-panel.ui:267
msgid "Seconds played"
msgstr ""
#: data/ui/server-panel.ui:287
msgid "Seconds running"
msgstr ""
#: data/ui/server-panel.ui:303
msgid "Audio Devices"
msgstr ""
#: data/ui/shortcuts-dialog.ui:15
#: data/ui/shortcuts-dialog.ui:14
msgid "General"
msgstr ""
#: data/ui/shortcuts-dialog.ui:20
#: data/ui/shortcuts-dialog.ui:18
msgid "Switch to the Connection panel"
msgstr ""
#: data/ui/shortcuts-dialog.ui:27
#: data/ui/shortcuts-dialog.ui:24
msgid "Switch to the Cover panel"
msgstr ""
#: data/ui/shortcuts-dialog.ui:34
#: data/ui/shortcuts-dialog.ui:30
msgid "Switch to the Playlist panel"
msgstr ""
#: data/ui/shortcuts-dialog.ui:41
#: data/ui/shortcuts-dialog.ui:36
msgid "Switch to the Library panel"
msgstr ""
#: data/ui/shortcuts-dialog.ui:48
#: data/ui/shortcuts-dialog.ui:42
msgid "Show the keyboard shortcuts"
msgstr ""
#: data/ui/shortcuts-dialog.ui:55
#: data/ui/shortcuts-dialog.ui:48
msgid "Open the info dialog"
msgstr ""
#: data/ui/shortcuts-dialog.ui:62
#: data/ui/shortcuts-dialog.ui:54
msgid "Quit the application"
msgstr ""
#: data/ui/shortcuts-dialog.ui:70
#: data/ui/shortcuts-dialog.ui:61
msgid "Player"
msgstr ""
#: data/ui/shortcuts-dialog.ui:75 data/ui/window.ui:139
#: data/ui/shortcuts-dialog.ui:65
msgid "Connect or disconnect"
msgstr ""
#: data/ui/shortcuts-dialog.ui:82 data/ui/window.ui:161
#: data/ui/shortcuts-dialog.ui:71 data/ui/window.ui:25
msgid "Switch between play and pause"
msgstr ""
#: data/ui/shortcuts-dialog.ui:97
#: data/ui/shortcuts-dialog.ui:84
msgid "Cover Panel"
msgstr ""
#: data/ui/shortcuts-dialog.ui:110
#: data/ui/shortcuts-dialog.ui:95
msgid "Library Panel"
msgstr ""
#: data/ui/window.ui:108
msgid "Connect to MPD"
#: src/connectionpanel.py:51
msgid "use"
msgstr ""
#: data/ui/window.ui:181
msgid "Adjust the volume"
msgstr ""
#: src/librarypanel.py:421
#: src/librarypanel.py:291
msgid "Loading albums"
msgstr ""
#: src/librarypanel.py:521
#: src/librarypanel.py:379
msgid "Loading images"
msgstr ""
@ -372,6 +230,18 @@ msgstr ""
msgid "{}:{} minutes"
msgstr ""
#: src/window.py:112
#: src/window.py:114
msgid "Server"
msgstr ""
#: src/window.py:115
msgid "Cover"
msgstr ""
#: src/window.py:116
msgid "Playlist"
msgstr ""
#: src/window.py:117
msgid "Library"
msgstr ""

View file

@ -1,12 +1,8 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
# Set environment
srcdir = os.path.abspath(os.path.dirname(__file__))
datadir = os.path.join(srcdir, 'data')
@ -21,21 +17,3 @@ if os.path.exists(localedirdev):
# Set GSettings schema dir (if not set already)
if not os.environ.get('GSETTINGS_SCHEMA_DIR'):
os.environ['GSETTINGS_SCHEMA_DIR'] = datadirdev
class Environment:
"""Wrapper class to access environment settings."""
def get_srcdir():
return srcdir
def get_data(subdir):
return os.path.join(datadir, subdir)
def get_locale():
return localedir

View file

@ -1,35 +1,28 @@
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, GObject, Adw
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/album-headerbar.ui')
class AlbumHeaderbar(Gtk.HeaderBar):
class AlbumHeaderbar(Adw.Bin):
__gtype_name__ = 'McgAlbumHeaderbar'
__gsignals__ = {
'close': (GObject.SIGNAL_RUN_FIRST, None, ())
}
__gsignals__ = {'close': (GObject.SIGNAL_RUN_FIRST, None, ())}
# Widgets
standalone_title = Gtk.Template.Child()
standalone_artist = Gtk.Template.Child()
def __init__(self):
super().__init__()
@Gtk.Template.Callback()
def on_close_clicked(self, widget):
self.emit('close')
def set_album(self, album):
self.standalone_title.set_text(album.get_title())
self.standalone_artist.set_text(", ".join(album.get_albumartists()))

View file

@ -1,17 +1,12 @@
#!/usr/bin/env python3
import logging
import urllib
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gio, Gtk, Gdk, GLib
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gio, Gtk, Gdk, Adw
from .window import Window
from .infodialog import InfoDialog
class Application(Gtk.Application):
@ -19,16 +14,25 @@ class Application(Gtk.Application):
ID = 'xyz.suruatoel.mcg'
DOMAIN = 'mcg'
def __init__(self):
super().__init__(application_id=Application.ID, flags=Gio.ApplicationFlags.FLAGS_NONE)
def __init__(self, version):
super().__init__(application_id=Application.ID,
flags=Gio.ApplicationFlags.FLAGS_NONE)
self._window = None
self._info_dialog = None
self._version = version
self._verbosity = logging.WARNING
#self.create_action('quit', self.quit, ['<primary>q'])
#self.create_action('about', self.on_about_action)
#self.create_action('preferences', self.on_preferences_action)
self.set_accels_for_action('window.close', ['<primary>q'])
self.set_accels_for_action('win.show-help-overlay', ['<primary>k'])
self.set_accels_for_action('app.info', ['<primary>i'])
self.set_accels_for_action('win.connect', ['<primary>c'])
self.set_accels_for_action('win.play', ['<primary>p'])
self.set_accels_for_action('win.clear-playlist', ['<primary>r'])
self.set_accels_for_action('win.toggle-fullscreen', ['F11'])
self.set_accels_for_action('win.search-library', ['<primary>f'])
self.set_accels_for_action('win.panel("0")', ['<primary>KP_1'])
self.set_accels_for_action('win.panel("1")', ['<primary>KP_2'])
self.set_accels_for_action('win.panel("2")', ['<primary>KP_3'])
self.set_accels_for_action('win.panel("3")', ['<primary>KP_4'])
def do_startup(self):
Gtk.Application.do_startup(self)
@ -37,8 +41,7 @@ class Application(Gtk.Application):
self._set_default_settings()
self._load_css()
self._setup_actions()
self._load_appmenu()
self._setup_adw()
def do_activate(self):
Gtk.Application.do_activate(self)
@ -46,43 +49,40 @@ class Application(Gtk.Application):
self._window = Window(self, Application.TITLE, self._settings)
self._window.present()
def on_menu_info(self, action, value):
if not self._info_dialog:
self._info_dialog = InfoDialog()
self._info_dialog.run()
self._info_dialog.hide()
self._info_dialog = Adw.AboutDialog()
self._info_dialog.set_application_icon("xyz.suruatoel.mcg")
self._info_dialog.set_application_name("CoverGrid")
self._info_dialog.set_version(self._version)
self._info_dialog.set_comments(
"""CoverGrid is a client for the Music Player Daemon, focusing on \
albums instead of single tracks.""")
self._info_dialog.set_website("https://www.suruatoel.xyz/codes/mcg")
self._info_dialog.set_license_type(Gtk.License.GPL_3_0)
self._info_dialog.set_issue_url(
"https://git.suruatoel.xyz/coderkun/mcg")
self._info_dialog.present()
def on_menu_quit(self, action, value):
self.quit()
def _setup_logging(self):
logging.basicConfig(
level=self._verbosity,
format="%(asctime)s %(levelname)s: %(message)s"
)
logging.basicConfig(level=self._verbosity,
format="%(asctime)s %(levelname)s: %(message)s")
def _load_settings(self):
self._settings = Gio.Settings.new(Application.ID)
def _set_default_settings(self):
settings = Gtk.Settings.get_default()
settings.set_property('gtk-application-prefer-dark-theme', True)
style_manager = Adw.StyleManager.get_default()
style_manager.set_color_scheme(Adw.ColorScheme.PREFER_DARK)
def _load_css(self):
styleProvider = Gtk.CssProvider()
styleProvider.load_from_resource(self._get_resource_path('gtk.css'))
Gtk.StyleContext.add_provider_for_screen(
Gdk.Screen.get_default(),
styleProvider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
style_provider = Gtk.CssProvider()
style_provider.load_from_resource(self._get_resource_path('gtk.css'))
Gtk.StyleContext.add_provider_for_display(
Gdk.Display.get_default(), style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
def _setup_actions(self):
action = Gio.SimpleAction.new("info", None)
@ -92,13 +92,17 @@ class Application(Gtk.Application):
action.connect('activate', self.on_menu_quit)
self.add_action(action)
def _load_appmenu(self):
builder = Gtk.Builder()
builder.set_translation_domain(Application.DOMAIN)
builder.add_from_resource(self._get_resource_path('ui/gtk.menu.ui'))
self.set_app_menu(builder.get_object('app-menu'))
def _get_resource_path(self, path):
return "/{}/{}".format(Application.ID.replace('.', '/'), path)
def _setup_adw(self):
Adw.HeaderBar()
Adw.ToolbarView()
Adw.ViewSwitcherTitle()
Adw.ViewSwitcherBar()
Adw.ViewStackPage()
Adw.ToastOverlay()
Adw.StatusPage()
Adw.Flap()
Adw.EntryRow()
Adw.PasswordEntryRow()

File diff suppressed because it is too large Load diff

View file

@ -1,108 +1,89 @@
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
import locale
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, GObject, Adw
from mcg.zeroconf import ZeroconfProvider
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/connection-panel.ui')
class ConnectionPanel(Gtk.Box):
class ConnectionPanel(Adw.Bin):
__gtype_name__ = 'McgConnectionPanel'
__gsignals__ = {
'connection-changed': (GObject.SIGNAL_RUN_FIRST, None, (str, int, str))
}
# Widgets
toolbar = Gtk.Template.Child()
zeroconf_list = Gtk.Template.Child()
host_entry = Gtk.Template.Child()
host_row = Gtk.Template.Child()
port_spinner = Gtk.Template.Child()
password_entry = Gtk.Template.Child()
password_row = Gtk.Template.Child()
def __init__(self):
super().__init__()
self._services = Gtk.ListStore(str, str, int)
self._profile = None
# Zeroconf
self.zeroconf_list.set_model(self._services)
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn("Zeroconf", renderer, text=0)
self.zeroconf_list.append_column(column)
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Zeroconf provider
self._zeroconf_provider = ZeroconfProvider()
self._zeroconf_provider.connect_signal(ZeroconfProvider.SIGNAL_SERVICE_NEW, self.on_new_service)
self._zeroconf_provider.connect_signal(
ZeroconfProvider.SIGNAL_SERVICE_NEW, self.on_new_service)
def on_new_service(self, service):
name, host, port = service
self._services.append([name, host, port])
row_button = Gtk.Button()
row_button.set_label(locale.gettext("use"))
row_button.connect("clicked", self.on_service_selected, host, port)
row = Adw.ActionRow()
row.set_title(name)
row.set_subtitle("{} ({})".format(host, port))
row.add_suffix(row_button)
self.zeroconf_list.insert(row, -1)
def on_service_selected(self, widget, host, port):
self.set_host(host)
self.set_port(port)
@Gtk.Template.Callback()
def on_service_selected(self, selection):
model, treeiter = selection.get_selected()
if treeiter != None:
service = model[treeiter]
self.set_host(service[1])
self.set_port(service[2])
@Gtk.Template.Callback()
def on_zeroconf_list_outfocused(self, widget, event):
self.zeroconf_list.get_selection().unselect_all()
@Gtk.Template.Callback()
def on_host_entry_outfocused(self, widget, event):
def on_host_entry_apply(self, widget):
self._call_back()
@Gtk.Template.Callback()
def on_port_spinner_value_changed(self, widget):
self._call_back()
@Gtk.Template.Callback()
def on_password_entry_outfocused(self, widget, event):
self._call_back()
def set_host(self, host):
self.host_entry.set_text(host)
self.host_row.set_text(host)
def get_host(self):
return self.host_entry.get_text()
return self.host_row.get_text()
def set_port(self, port):
self.port_spinner.set_value(port)
def get_port(self):
return self.port_spinner.get_value_as_int()
def set_password(self, password):
if password is None:
password = ""
self.password_entry.set_text(password)
self.password_row.set_text(password)
def get_password(self):
if self.password_entry.get_text() == "":
if self.password_row.get_text() == "":
return None
else:
return self.password_entry.get_text()
def _call_back(self):
self.emit('connection-changed', self.get_host(), self.get_port(), self.get_password(),)
self.emit(
'connection-changed',
self.get_host(),
self.get_port(),
self.get_password(),
)

View file

@ -1,52 +1,30 @@
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
import logging
import math
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, Gdk, GObject, GdkPixbuf
from mcg.utils import Utils
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/cover-toolbar.ui')
class CoverToolbar(Gtk.ButtonBox):
__gtype_name__ = 'McgCoverToolbar'
__gsignals__ = {
'fullscreen': (GObject.SIGNAL_RUN_FIRST, None, ())
}
# Widgets
fullscreen_button = Gtk.Template.Child()
def __init__(self):
super().__init__()
def set_fullscreen_sensitive(self, sensitive):
self.fullscreen_button.set_sensitive(sensitive)
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/cover-panel.ui')
class CoverPanel(Gtk.Overlay):
__gtype_name__ = 'McgCoverPanel'
__gsignals__ = {
'toggle-fullscreen': (GObject.SIGNAL_RUN_FIRST, None, ()),
'set-song': (GObject.SIGNAL_RUN_FIRST, None, (int, int,)),
'albumart': (GObject.SIGNAL_RUN_FIRST, None, (str,))
'set-song': (GObject.SIGNAL_RUN_FIRST, None, (int, int, )),
'albumart': (GObject.SIGNAL_RUN_FIRST, None, (str, ))
}
# Widgets
# Toolbar
toolbar = Gtk.Template.Child()
fullscreen_button = Gtk.Template.Child()
# Cover
cover_stack = Gtk.Template.Child()
cover_spinner = Gtk.Template.Child()
cover_default = Gtk.Template.Child()
cover_scroll = Gtk.Template.Child()
cover_box = Gtk.Template.Child()
cover_image = Gtk.Template.Child()
@ -59,64 +37,60 @@ class CoverPanel(Gtk.Overlay):
# Songs
songs_scale = Gtk.Template.Child()
def __init__(self):
super().__init__()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._current_album = None
self._current_cover_album = None
self._cover_pixbuf = None
self._timer = None
self._properties = {}
self._icon_theme = Gtk.IconTheme.get_default()
self._icon_theme = Gtk.IconTheme.get_for_display(
Gdk.Display.get_default())
self._fullscreened = False
self._is_selected = False
self._current_size = None
self._cover_pixbuf = self._get_default_image()
# Widgets
self._toolbar = CoverToolbar()
self.cover_stack.set_visible_child(self.cover_scroll)
# Initial actions
GObject.idle_add(self._enable_tracklist)
# Click handler for image
click_controller = Gtk.GestureClick()
click_controller.connect('pressed', self.on_cover_box_pressed)
self.cover_box.add_controller(click_controller)
# Button controller for songs scale
button_controller = Gtk.GestureClick()
button_controller.connect('pressed', self.on_songs_scale_pressed)
button_controller.connect('unpaired-release',
self.on_songs_scale_released)
self.songs_scale.add_controller(button_controller)
def get_toolbar(self):
return self._toolbar
return self.toolbar
def set_selected(self, selected):
self._is_selected = selected
"""The cover panel does not use selections"""
pass
@Gtk.Template.Callback()
def on_cover_box_pressed(self, widget, event):
if self._current_album and event.type == Gdk.EventType._2BUTTON_PRESS:
def on_cover_box_pressed(self, widget, npress, x, y):
if self._current_album and npress == 2:
self.emit('toggle-fullscreen')
@Gtk.Template.Callback()
def on_cover_size_allocate(self, widget, allocation):
def set_width(self, width):
GObject.idle_add(self._resize_image)
self.cover_info_scroll.set_max_content_width(allocation.width // 2)
self.cover_info_scroll.set_max_content_width(width // 2)
@Gtk.Template.Callback()
def on_songs_start_change(self, widget, event):
def on_songs_scale_pressed(self, widget, npress, x, y):
if self._timer:
GObject.source_remove(self._timer)
self._timer = None
@Gtk.Template.Callback()
def on_songs_change(self, widget, event):
def on_songs_scale_released(self, widget, x, y, npress, sequence):
value = int(self.songs_scale.get_value())
time = self._current_album.get_length()
tracks = self._current_album.get_tracks()
pos = 0
for index in range(len(tracks)-1, -1, -1):
for index in range(len(tracks) - 1, -1, -1):
time = time - tracks[index].get_length()
pos = tracks[index].get_pos()
if time < value:
@ -124,13 +98,13 @@ class CoverPanel(Gtk.Overlay):
time = max(value - time - 1, 0)
self.emit('set-song', pos, time)
def set_album(self, album):
if album:
# Set labels
self.album_title_label.set_label(album.get_title())
self.album_date_label.set_label(', '.join(album.get_dates()))
self.album_artist_label.set_label(', '.join(album.get_albumartists()))
self.album_artist_label.set_label(', '.join(
album.get_albumartists()))
# Set tracks
self._set_tracks(album)
@ -144,8 +118,7 @@ class CoverPanel(Gtk.Overlay):
# Set current album
self._current_album = album
self._enable_tracklist()
self._toolbar.set_fullscreen_sensitive(self._current_album is not None)
self.fullscreen_button.set_sensitive(self._current_album is not None)
def set_play(self, pos, time):
if self._timer is not None:
@ -155,47 +128,42 @@ class CoverPanel(Gtk.Overlay):
for index in range(0, pos):
time = time + tracks[index].get_length()
self.songs_scale.set_value(time+1)
self.songs_scale.set_value(time + 1)
self._timer = GObject.timeout_add(1000, self._playing)
def set_pause(self):
if self._timer is not None:
GObject.source_remove(self._timer)
self._timer = None
def set_fullscreen(self, active):
if active:
self.info_revealer.set_reveal_child(False)
self.cover_box.override_background_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(0, 0, 0, 1))
GObject.idle_add(self._resize_image)
self._fullscreened = True
else:
self._fullscreened = False
self.info_revealer.set_reveal_child(True)
self.cover_box.override_background_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(0, 0, 0, 0))
if self._current_album:
self.info_revealer.set_reveal_child(True)
GObject.idle_add(self._resize_image)
def set_albumart(self, album, data):
if album == self._current_album:
if data:
# Load image and draw it
try:
self._cover_pixbuf = Utils.load_pixbuf(data)
except Exception as e:
except Exception:
self._logger.exception("Failed to set albumart")
self._cover_pixbuf = self._get_default_image()
self._cover_pixbuf = None
else:
# Reset image
self._cover_pixbuf = self._get_default_image()
self._cover_pixbuf = None
self._current_size = None
self._current_cover_album = album
# Show image
GObject.idle_add(self._show_image)
def _set_tracks(self, album):
self.songs_scale.clear_marks()
self.songs_scale.set_range(0, album.get_length())
@ -205,79 +173,84 @@ class CoverPanel(Gtk.Overlay):
if length > 0 and length < album.get_length():
cur_length = cur_length + 1
self.songs_scale.add_mark(
cur_length,
Gtk.PositionType.RIGHT,
GObject.markup_escape_text(
Utils.create_track_title(track)
)
)
cur_length, Gtk.PositionType.RIGHT,
GObject.markup_escape_text(Utils.create_track_title(track)))
length = length + track.get_length()
self.songs_scale.add_mark(
length,
Gtk.PositionType.RIGHT,
"{0[0]:02d}:{0[1]:02d} minutes".format(divmod(length, 60))
)
length, Gtk.PositionType.RIGHT,
"{0[0]:02d}:{0[1]:02d} minutes".format(divmod(length, 60)))
# Align marks
self._align_songs_scale_marks()
def _align_songs_scale_marks(self):
self._align_songs_scale_mark(self.songs_scale)
def _align_songs_scale_mark(self, widget):
child = widget.get_first_child()
while child:
if type(child) is Gtk.Label:
child.set_halign(Gtk.Align.START)
else:
self._align_songs_scale_mark(child)
child = child.get_next_sibling()
def _enable_tracklist(self):
if self._current_album:
# enable
self.info_revealer.set_reveal_child(True)
if not self._fullscreened:
self.info_revealer.set_reveal_child(True)
else:
# disable
self.info_revealer.set_reveal_child(False)
def _playing(self):
value = self.songs_scale.get_value() + 1
self.songs_scale.set_value(value)
return True
def _show_image(self):
self._resize_image()
self.cover_stack.set_visible_child(self.cover_scroll)
if self._cover_pixbuf:
self._resize_image()
self.cover_stack.set_visible_child(self.cover_scroll)
else:
self.cover_stack.set_visible_child(self.cover_default)
self.cover_spinner.stop()
def _resize_image(self):
"""Diese Methode skaliert das geladene Bild aus dem Pixelpuffer
auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse
"""
# Get size
size = self.cover_scroll.get_allocation()
size_width = self.cover_stack.get_size(Gtk.Orientation.HORIZONTAL)
size_height = self.cover_stack.get_size(Gtk.Orientation.HORIZONTAL)
# Abort if size is the same
if self._current_size and size.width == self._current_size.width and size.height == self._current_size.height:
return
self._current_size = size
if self._current_size:
current_width, current_height = self._current_size
if size_width == current_width and size_height == current_height:
return
self._current_size = (
size_width,
size_height,
)
# Get pixelbuffer
pixbuf = self._cover_pixbuf
# Check pixelbuffer
if pixbuf is None:
self.cover_default.set_pixel_size(min(size_width, size_height)/2)
return
# Skalierungswert für Breite und Höhe ermitteln
ratioW = float(size.width) / float(pixbuf.get_width())
ratioH = float(size.height) / float(pixbuf.get_height())
ratio_w = float(size_width) / float(pixbuf.get_width())
ratio_h = float(size_height) / float(pixbuf.get_height())
# Kleineren beider Skalierungswerte nehmen, nicht Hochskalieren
ratio = min(ratioW, ratioH)
ratio = min(ratio_w, ratio_h)
ratio = min(ratio, 1)
# Neue Breite und Höhe berechnen
width = int(math.floor(pixbuf.get_width()*ratio))
height = int(math.floor(pixbuf.get_height()*ratio))
width = int(math.floor(pixbuf.get_width() * ratio))
height = int(math.floor(pixbuf.get_height() * ratio))
if width <= 0 or height <= 0:
return
# Pixelpuffer auf Oberfläche zeichnen
self.cover_image.set_allocation(self.cover_scroll.get_allocation())
self.cover_image.set_from_pixbuf(pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER))
self.cover_image.set_from_pixbuf(
pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER))
self.cover_image.set_pixel_size(min(width, height))
self.cover_image.show()
def _get_default_image(self):
return self._icon_theme.load_icon(
Utils.STOCK_ICON_DEFAULT,
512,
Gtk.IconLookupFlags.FORCE_SVG & Gtk.IconLookupFlags.FORCE_SIZE
)

View file

@ -1,15 +0,0 @@
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
import logging
from gi.repository import Gtk, GdkPixbuf
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/info-dialog.ui')
class InfoDialog(Gtk.AboutDialog):
__gtype_name__ = 'McgInfoDialog'

View file

@ -1,37 +1,46 @@
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
import locale
import logging
import math
import threading
from gi.repository import Gtk, GObject, GdkPixbuf
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Gdk, GObject, GdkPixbuf, Gio, Adw
from mcg import client
from mcg.albumheaderbar import AlbumHeaderbar
from mcg.utils import SortOrder
from mcg.utils import Utils
from mcg.utils import GridItem
from mcg.utils import SearchFilter
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/library-toolbar.ui')
class LibraryToolbar(Gtk.ButtonBox):
__gtype_name__ = 'McgLibraryToolbar'
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/library-panel.ui')
class LibraryPanel(Adw.Bin):
__gtype_name__ = 'McgLibraryPanel'
__gsignals__ = {
'select': (GObject.SIGNAL_RUN_FIRST, None, (bool,)),
'toggle-search': (GObject.SIGNAL_RUN_FIRST, None, (bool,)),
'open-standalone': (GObject.SIGNAL_RUN_FIRST, None, ()),
'close-standalone': (GObject.SIGNAL_RUN_FIRST, None, ()),
'update': (GObject.SIGNAL_RUN_FIRST, None, ()),
'start-scale': (GObject.SIGNAL_RUN_FIRST, None, (int,)),
'end-scale': (GObject.SIGNAL_RUN_FIRST, None, (int,)),
'sort': (GObject.SIGNAL_RUN_FIRST, None, (int,)),
'sort-type': (GObject.SIGNAL_RUN_FIRST, None, (Gtk.SortType,))
'play': (GObject.SIGNAL_RUN_FIRST, None, (str, )),
'queue': (GObject.SIGNAL_RUN_FIRST, None, (str, )),
'queue-multiple':
(GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT, )),
'item-size-changed': (GObject.SIGNAL_RUN_FIRST, None, (int, )),
'sort-order-changed': (GObject.SIGNAL_RUN_FIRST, None, (int, )),
'sort-type-changed': (GObject.SIGNAL_RUN_FIRST, None, (bool, )),
'albumart': (GObject.SIGNAL_RUN_FIRST, None, (str, )),
}
# Widgets
library_stack = Gtk.Template.Child()
panel_normal = Gtk.Template.Child()
panel_standalone = Gtk.Template.Child()
actionbar_revealer = Gtk.Template.Child()
# Toolbar
toolbar = Gtk.Template.Child()
select_button = Gtk.Template.Child()
toolbar_search_bar = Gtk.Template.Child()
toolbar_popover = Gtk.Template.Child()
@ -41,107 +50,6 @@ class LibraryToolbar(Gtk.ButtonBox):
sort_year = Gtk.Template.Child()
sort_modified = Gtk.Template.Child()
grid_scale = Gtk.Template.Child()
def __init__(self, item_size):
super().__init__()
# Toolbar menu
self.grid_scale.set_value(item_size)
self._toolbar_sort_buttons = {
SortOrder.ARTIST: self.sort_artist,
SortOrder.TITLE: self.sort_title,
SortOrder.YEAR: self.sort_year,
SortOrder.MODIFIED: self.sort_modified
}
@Gtk.Template.Callback()
def on_select_toggled(self, widget):
self.emit('select', widget.get_active())
@Gtk.Template.Callback()
def on_search_toggled(self, widget):
self.emit('toggle-search', widget.get_active())
@Gtk.Template.Callback()
def on_update_clicked(self, widget):
self.emit('update')
@Gtk.Template.Callback()
def on_grid_scale_change(self, widget, scroll, value):
self.emit('start-scale', value)
@Gtk.Template.Callback()
def on_grid_scale_changed(self, widget, event):
self.emit('end-scale', self.grid_scale.get_value())
self.toolbar_popover.popdown()
@Gtk.Template.Callback()
def on_sort_toggled(self, widget):
if widget.get_active():
sort = [key for key, value in self._toolbar_sort_buttons.items() if value is widget][0]
self.emit('sort', sort)
@Gtk.Template.Callback()
def on_sort_order_toggled(self, button):
if button.get_active():
sort_type = Gtk.SortType.DESCENDING
else:
sort_type = Gtk.SortType.ASCENDING
self.emit('sort-type', sort_type)
def get_grid_scale(self):
return self.grid_scale
def is_search_active(self):
return self.toolbar_search_bar.get_active()
def set_search_active(self, active):
self.toolbar_search_bar.set_active(active)
def exit_selection(self):
self.select_button.set_active(False)
def set_sort_order(self, sort):
button = self._toolbar_sort_buttons[sort]
if button and not button.get_active():
button.set_active(True)
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/library-panel.ui')
class LibraryPanel(Gtk.Stack):
__gtype_name__ = 'McgLibraryPanel'
__gsignals__ = {
'open-standalone': (GObject.SIGNAL_RUN_FIRST, None, ()),
'close-standalone': (GObject.SIGNAL_RUN_FIRST, None, ()),
'update': (GObject.SIGNAL_RUN_FIRST, None, ()),
'play': (GObject.SIGNAL_RUN_FIRST, None, (str,)),
'queue': (GObject.SIGNAL_RUN_FIRST, None, (str,)),
'queue-multiple': (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
'item-size-changed': (GObject.SIGNAL_RUN_FIRST, None, (int,)),
'sort-order-changed': (GObject.SIGNAL_RUN_FIRST, None, (int,)),
'sort-type-changed': (GObject.SIGNAL_RUN_FIRST, None, (Gtk.SortType,)),
'albumart': (GObject.SIGNAL_RUN_FIRST, None, (str,))
}
# Widgets
panel_standalone = Gtk.Template.Child()
actionbar_revealer = Gtk.Template.Child()
# Filter/search bar
filter_bar = Gtk.Template.Child()
filter_entry = Gtk.Template.Child()
@ -153,169 +61,151 @@ class LibraryPanel(Gtk.Stack):
scroll = Gtk.Template.Child()
# Library Grid
library_grid = Gtk.Template.Child()
# Action bar (normal)
actionbar = Gtk.Template.Child()
actionbar_standalone = Gtk.Template.Child()
# Standalone Image
standalone_stack = Gtk.Template.Child()
standalone_spinner = Gtk.Template.Child()
standalone_scroll = Gtk.Template.Child()
standalone_image = Gtk.Template.Child()
def __init__(self, client):
super().__init__()
def __init__(self, client, **kwargs):
super().__init__(**kwargs)
self._logger = logging.getLogger(__name__)
self._client = client
self._buttons = {}
self._albums = None
self._host = "localhost"
self._filter_string = ""
self._item_size = 150
self._sort_order = SortOrder.YEAR
self._sort_type = Gtk.SortType.DESCENDING
self._grid_pixbufs = {}
self._grid_width = 0
self._old_ranges = {}
self._library_lock = threading.Lock()
self._library_stop = threading.Event()
self._icon_theme = Gtk.IconTheme.get_default()
self._icon_theme = Gtk.IconTheme.get_for_display(
Gdk.Display.get_default())
self._standalone_pixbuf = None
self._selected_albums = []
self._allocation = (0, 0)
self._is_selected = False
# Widgets
self._toolbar = LibraryToolbar(self._item_size)
self._toolbar.connect('select', self.on_toolbar_select)
self._toolbar.connect('toggle-search', self.on_toolbar_toggle_search)
self._toolbar.connect('update', self.on_toolbar_update)
self._toolbar.connect('start-scale', self.on_toolbar_scale)
self._toolbar.connect('end-scale', self.on_toolbar_scaled)
self._toolbar.connect('sort', self.on_toolbar_sort)
self._toolbar.connect('sort-type', self.on_toolbar_sort_type)
# Header bar
self._headerbar_standalone = AlbumHeaderbar()
self._headerbar_standalone.connect('close', self.on_standalone_close_clicked)
# Progress Bar
self.progress_image.set_from_pixbuf(self._get_default_image())
self._headerbar_standalone.connect('close',
self.on_standalone_close_clicked)
# Library Grid: Model
self._library_grid_model = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str)
self._library_grid_model.set_sort_func(2, self.compare_albums, self._sort_order)
self._library_grid_model.set_sort_column_id(2, self._sort_type)
self._library_grid_filter = self._library_grid_model.filter_new()
self._library_grid_filter.set_visible_func(self.on_filter_visible)
self._library_grid_model = Gio.ListStore()
self._library_grid_filter = Gtk.FilterListModel()
self._library_grid_filter.set_model(self._library_grid_model)
self._library_grid_selection_multi = Gtk.MultiSelection.new(
self._library_grid_filter)
self._library_grid_selection_single = Gtk.SingleSelection.new(
self._library_grid_filter)
# Library Grid
self.library_grid.set_model(self._library_grid_filter)
self.library_grid.set_pixbuf_column(0)
self.library_grid.set_text_column(-1)
self.library_grid.set_tooltip_column(1)
self.library_grid.set_model(self._library_grid_selection_single)
# Toolbar menu
self.grid_scale.set_value(self._item_size)
self._toolbar_sort_buttons = {
SortOrder.ARTIST: self.sort_artist,
SortOrder.TITLE: self.sort_title,
SortOrder.YEAR: self.sort_year,
SortOrder.MODIFIED: self.sort_modified
}
# Button controller for grid scale
button_controller = Gtk.GestureClick()
button_controller.connect('unpaired-release',
self.on_grid_scale_released)
self.grid_scale.add_controller(button_controller)
def get_headerbar_standalone(self):
return self._headerbar_standalone
def get_toolbar(self):
return self._toolbar
return self.toolbar
def set_selected(self, selected):
self._is_selected = selected
@Gtk.Template.Callback()
def on_resize(self, widget, event):
new_allocation = (widget.get_allocation().width, widget.get_allocation().height)
if new_allocation == self._allocation:
return
self._allocation = new_allocation
self._toolbar.get_grid_scale().clear_marks()
width = widget.get_allocation().width
lower = int(self._toolbar.get_grid_scale().get_adjustment().get_lower())
upper = int(self._toolbar.get_grid_scale().get_adjustment().get_upper())
countMin = max(int(width / upper), 1)
countMax = max(int(width / lower), 1)
for index in range(countMin, countMax):
pixel = int(width / index)
pixel = pixel - (2 * int(pixel / 100))
self._toolbar.get_grid_scale().add_mark(
pixel,
Gtk.PositionType.BOTTOM,
None
)
def on_toolbar_toggle_search(self, widget, active):
self.filter_bar.set_search_mode(active)
def on_toolbar_select(self, widget, active):
if active:
def on_select_toggled(self, widget):
if self.select_button.get_active():
self.actionbar_revealer.set_reveal_child(True)
self.library_grid.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
self.library_grid.get_style_context().add_class(Utils.CSS_SELECTION)
self.library_grid.set_model(self._library_grid_selection_multi)
self.library_grid.set_single_click_activate(False)
self.library_grid.get_style_context().add_class(
Utils.CSS_SELECTION)
else:
self.actionbar_revealer.set_reveal_child(False)
self.library_grid.set_selection_mode(Gtk.SelectionMode.SINGLE)
self.library_grid.get_style_context().remove_class(Utils.CSS_SELECTION)
def on_toolbar_update(self, widget):
self.emit('update')
def on_toolbar_scale(self, widget, value):
size = math.floor(value)
range = self._toolbar.get_grid_scale().get_adjustment()
if size < range.get_lower() or size > range.get_upper():
return
self._item_size = size
GObject.idle_add(self.library_grid.set_item_padding, size / 100)
GObject.idle_add(self._set_widget_grid_size, self.library_grid, size, True)
def on_toolbar_scaled(self, widget, value):
size = round(value)
range = self._toolbar.get_grid_scale().get_adjustment()
if size < range.get_lower() or size > range.get_upper():
return False
self.emit('item-size-changed', size)
self._redraw()
return False
def on_toolbar_sort(self, widget, sort):
self._change_sort(sort)
def on_toolbar_sort_type(self, widget, sort_type):
self._sort_type = sort_type
self._library_grid_model.set_sort_column_id(2, sort_type)
self.emit('sort-type-changed', sort_type)
self.library_grid.set_model(self._library_grid_selection_single)
self.library_grid.set_single_click_activate(True)
self.library_grid.get_style_context().remove_class(
Utils.CSS_SELECTION)
@Gtk.Template.Callback()
def on_filter_bar_notify(self, widget, value):
if self._toolbar.is_search_active() is not self.filter_bar.get_search_mode():
self._toolbar.set_search_active(self.filter_bar.get_search_mode())
def on_update_clicked(self, widget):
self.emit('update')
def on_grid_scale_released(self, widget, x, y, npress, sequence):
size = math.floor(self.grid_scale.get_value())
grid_range = self.grid_scale.get_adjustment()
if size < grid_range.get_lower() or size > grid_range.get_upper():
return
self._item_size = size
self.emit('item-size-changed', size)
self._redraw()
GObject.idle_add(self.toolbar_popover.popdown)
@Gtk.Template.Callback()
def on_grid_scale_changed(self, widget):
size = math.floor(self.grid_scale.get_value())
grid_range = widget.get_adjustment()
if size < grid_range.get_lower() or size > grid_range.get_upper():
return
self._set_widget_grid_size(self.library_grid, size, True)
@Gtk.Template.Callback()
def on_sort_toggled(self, widget):
if widget.get_active():
self._sort_order = [
key for key, value in self._toolbar_sort_buttons.items()
if value is widget
][0]
self._sort_grid_model()
self.emit('sort-order-changed', self._sort_order)
@Gtk.Template.Callback()
def on_sort_order_toggled(self, button):
if button.get_active():
self._sort_type = Gtk.SortType.DESCENDING
else:
self._sort_type = Gtk.SortType.ASCENDING
self._sort_grid_model()
self.emit('sort-type-changed', button.get_active())
def set_size(self, width, height):
self._set_marks()
self._resize_standalone_image()
@Gtk.Template.Callback()
def on_filter_entry_changed(self, widget):
self._filter_string = self.filter_entry.get_text()
GObject.idle_add(self._library_grid_filter.refilter)
self._library_grid_filter.set_filter(
SearchFilter(self.filter_entry.get_text()))
@Gtk.Template.Callback()
def on_library_grid_clicked(self, widget, path):
def on_library_grid_clicked(self, widget, position):
# Get selected album
path = self._library_grid_filter.convert_path_to_child_path(path)
iter = self._library_grid_model.get_iter(path)
id = self._library_grid_model.get_value(iter, 2)
album = self._albums[id]
item = self._library_grid_filter.get_item(position)
album = item.get_album()
album_id = album.get_id()
self._selected_albums = [album]
self.emit('albumart', id)
self.emit('albumart', album_id)
# Show standalone album
if widget.get_selection_mode() == Gtk.SelectionMode.SINGLE:
if widget.get_model() == self._library_grid_selection_single:
# Set labels
self._headerbar_standalone.set_album(album)
@ -326,166 +216,116 @@ class LibraryPanel(Gtk.Stack):
self.standalone_stack.set_visible_child(self.standalone_spinner)
self.standalone_spinner.start()
@Gtk.Template.Callback()
def on_library_grid_selection_changed(self, widget):
self._selected_albums = []
for path in widget.get_selected_items():
path = self._library_grid_filter.convert_path_to_child_path(path)
iter = self._library_grid_model.get_iter(path)
id = self._library_grid_model.get_value(iter, 2)
self._selected_albums.insert(0, self._albums[id])
def on_filter_visible(self, model, iter, data):
id = model.get_value(iter, 2)
if not id in self._albums.keys():
return
album = self._albums[id]
return album.filter(self._filter_string)
@Gtk.Template.Callback()
def on_selection_cancel_clicked(self, widget):
self._toolbar.exit_selection()
self.select_button.set_active(False)
@Gtk.Template.Callback()
def on_selection_add_clicked(self, widget):
ids = [album.get_id() for album in self._selected_albums]
self.emit('queue-multiple', ids)
self._toolbar.exit_selection()
@Gtk.Template.Callback()
def on_standalone_scroll_size_allocate(self, widget, allocation):
self._resize_standalone_image()
self.emit('queue-multiple', self._get_selected_albums())
self.select_button.set_active(False)
@Gtk.Template.Callback()
def on_standalone_play_clicked(self, widget):
self.emit('play', self._selected_albums[0].get_id())
self._close_standalone()
@Gtk.Template.Callback()
def on_standalone_queue_clicked(self, widget):
self.emit('queue', self._selected_albums[0].get_id())
self._close_standalone()
def on_standalone_close_clicked(self, widget):
self._close_standalone()
def show_search(self):
self.filter_bar.set_search_mode(True)
def set_item_size(self, item_size):
if self._item_size != item_size:
self._item_size = item_size
self._toolbar.get_grid_scale().set_value(item_size)
self.grid_scale.set_value(item_size)
self._redraw()
def get_item_size(self):
return self._item_size
def set_sort_order(self, sort):
if self._sort_order != sort:
self._toolbar.set_sort_order(sort)
self._sort_order = sort
self._library_grid_model.set_sort_func(2, self.compare_albums, self._sort_order)
def get_sort_order(self):
return self._sort_order
button = self._toolbar_sort_buttons[sort]
if button:
self._sort_order = [
key for key, value in self._toolbar_sort_buttons.items()
if value is button
][0]
if not button.get_active():
button.set_active(True)
self._sort_grid_model()
def set_sort_type(self, sort_type):
if self._sort_type != sort_type:
if sort_type:
sort_type_gtk = Gtk.SortType.DESCENDING
self.toolbar_sort_order_button.set_active(True)
else:
sort_type_gtk = Gtk.SortType.ASCENDING
self.toolbar_sort_order_button.set_active(False)
if self._sort_type != sort_type_gtk:
self._sort_type = sort_type_gtk
self._library_grid_model.set_sort_column_id(2, sort_type)
if sort_type:
sort_type_gtk = Gtk.SortType.DESCENDING
else:
sort_type_gtk = Gtk.SortType.ASCENDING
if sort_type_gtk != self._sort_type:
self._sort_type = sort_type_gtk
self.toolbar_sort_order_button.set_active(sort_type)
self._sort_grid_model()
def get_sort_type(self):
return (self._sort_type != Gtk.SortType.ASCENDING)
def init_albums(self):
self.progress_bar.set_text(locale.gettext("Loading albums"))
def load_albums(self):
self.progress_bar.pulse()
def set_albums(self, host, albums):
self._host = host
self._library_stop.set()
threading.Thread(target=self._set_albums, args=(host, albums, self._item_size,)).start()
threading.Thread(target=self._set_albums,
args=(
host,
albums,
self._item_size,
)).start()
def set_albumart(self, album, data):
if album in self._selected_albums:
self._standalone_pixbuf = None
if data:
# Load image and draw it
try:
self._standalone_pixbuf = Utils.load_pixbuf(data)
except Exception as e:
except Exception:
self._logger.exception("Failed to set albumart")
self._standalone_pixbuf = self._get_default_image()
else:
self._standalone_pixbuf = self._get_default_image()
# Show image
GObject.idle_add(self._show_image)
def _sort_grid_model(self):
GObject.idle_add(self._library_grid_model.sort,
self._grid_model_compare_func, self._sort_order,
self._sort_type)
def compare_albums(self, model, row1, row2, criterion):
id1 = model.get_value(row1, 2)
id2 = model.get_value(row2, 2)
if not id1 or not id2:
return
return client.MCGAlbum.compare(self._albums[id1], self._albums[id2], criterion)
def _grid_model_compare_func(self, item1, item2, criterion, order):
return client.MCGAlbum.compare(item1.get_album(), item2.get_album(),
criterion,
(order == Gtk.SortType.DESCENDING))
def stop_threads(self):
self._library_stop.set()
def _change_sort(self, sort):
self._sort_order = sort
self._library_grid_model.set_sort_func(2, self.compare_albums, sort)
self.emit('sort-order-changed', sort)
def _set_albums(self, host, albums, size):
if not self._is_selected and albums != self._albums:
GObject.idle_add(
self.get_parent().child_set_property,
self,
'needs-attention',
True
)
self._library_lock.acquire()
self._library_stop.clear()
self._albums = albums
stack_transition_type = self.stack.get_transition_type()
GObject.idle_add(self.stack.set_transition_type,
Gtk.StackTransitionType.NONE)
GObject.idle_add(self.stack.set_visible_child, self.progress_box)
GObject.idle_add(self.progress_bar.set_fraction, 0.0)
GObject.idle_add(self.library_grid.set_item_padding, size / 100)
self.library_grid.set_model(None)
self.library_grid.freeze_child_notify()
self._library_grid_model.clear()
GObject.idle_add(self.stack.set_transition_type, stack_transition_type)
GObject.idle_add(self._library_grid_model.remove_all)
i = 0
n = len(albums)
@ -493,157 +333,138 @@ class LibraryPanel(Gtk.Stack):
self._grid_pixbufs.clear()
for album_id in albums.keys():
album = albums[album_id]
grid_item = GridItem(album)
pixbuf = None
try:
pixbuf = Utils.load_thumbnail(cache, self._client, album, size)
except client.CommandException:
# Exception is handled by client
pass
except Exception as e:
except Exception:
self._logger.exception("Failed to load albumart")
if pixbuf is None:
pixbuf = self._icon_theme.load_icon(
Utils.STOCK_ICON_DEFAULT,
self._item_size,
Gtk.IconLookupFlags.FORCE_SVG & Gtk.IconLookupFlags.FORCE_SIZE
)
if pixbuf is not None:
icon = self._get_default_icon(self._item_size, self._item_size)
grid_item.set_icon(icon)
else:
self._grid_pixbufs[album.get_id()] = pixbuf
self._library_grid_model.append([
pixbuf,
GObject.markup_escape_text("\n".join([
album.get_title(),
', '.join(album.get_dates()),
Utils.create_artists_label(album),
Utils.create_length_label(album)
])),
album_id
])
grid_item.set_cover(pixbuf)
GObject.idle_add(self._library_grid_model.append, grid_item)
i += 1
GObject.idle_add(self.progress_bar.set_fraction, i/n)
GObject.idle_add(self.progress_bar.set_text, locale.gettext("Loading images"))
if self._library_stop.is_set():
self._library_lock.release()
return
GObject.idle_add(self.progress_bar.set_fraction, i / n)
GObject.idle_add(self.progress_bar.set_text,
locale.gettext("Loading images"))
self.library_grid.set_model(self._library_grid_filter)
self.library_grid.thaw_child_notify()
self.library_grid.set_item_width(-1)
self._library_lock.release()
self.stack.set_visible_child(self.scroll)
GObject.idle_add(self.stack.set_visible_child, self.scroll)
self._sort_grid_model()
GObject.idle_add(self.library_grid.scroll_to, 0, Gtk.ListScrollFlags.NONE, None)
def _set_widget_grid_size(self, grid_widget, size, vertical):
self._library_stop.set()
threading.Thread(target=self._set_widget_grid_size_thread, args=(grid_widget, size, vertical,)).start()
threading.Thread(target=self._set_widget_grid_size_thread,
args=(
grid_widget,
size,
vertical,
)).start()
def _set_widget_grid_size_thread(self, grid_widget, size, vertical):
self._library_lock.acquire()
self._library_stop.clear()
grid_filter = grid_widget.get_model()
grid_model = grid_filter.get_model()
# get old_range
grid_widget_id = id(grid_widget)
if grid_widget_id not in self._old_ranges or self._old_ranges[grid_widget_id] is None:
self._old_ranges[grid_widget_id] = range(0, len(grid_filter))
old_range = self._old_ranges[grid_widget_id]
old_start = len(old_range) > 0 and old_range[0] or 0
old_end = len(old_range) > 0 and old_range[len(old_range)-1] + 1 or 0
# calculate visible range
w = (grid_widget.get_allocation().width // size) + (vertical and 0 or 1)
h = (grid_widget.get_allocation().height // size) + (vertical and 1 or 0)
c = w * h
vis_range = grid_widget.get_visible_range()
if vis_range is None:
if size == self._item_size:
self._library_lock.release()
return
(vis_start,), (vis_end,) = vis_range
vis_end = min(vis_start + c, len(grid_filter))
vis_range = range(vis_start, vis_end)
# set pixbuf
cur_start = min(old_start, vis_start)
cur_end = max(old_end, vis_end)
cur_range = range(cur_start, cur_end)
for index in cur_range:
iter = grid_filter.convert_iter_to_child_iter(grid_filter[index].iter)
if index in vis_range:
album_id = grid_model.get_value(iter, 2)
pixbuf = self._grid_pixbufs[album_id]
pixbuf = pixbuf.scale_simple(size, size, GdkPixbuf.InterpType.NEAREST)
for i in range(self._library_grid_model.get_n_items()):
grid_item = self._library_grid_model.get_item(i)
album_id = grid_item.get_album().get_id()
pixbuf = self._grid_pixbufs[album_id]
if pixbuf is not None:
pixbuf = pixbuf.scale_simple(size, size,
GdkPixbuf.InterpType.NEAREST)
else:
pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, 1, 1)
grid_model.set_value(iter, 0, pixbuf)
pixbuf = self._icon_theme.lookup_icon(
Utils.STOCK_ICON_DEFAULT, None, size, size,
Gtk.TextDirection.LTR, Gtk.IconLookupFlags.FORCE_SYMBOLIC)
GObject.idle_add(grid_item.set_cover, pixbuf)
if self._library_stop.is_set():
self._library_lock.release()
return
self._old_ranges[grid_widget_id] = vis_range
grid_widget.set_item_width(size)
self._library_lock.release()
def _show_image(self):
self._resize_standalone_image()
self.standalone_stack.set_visible_child(self.standalone_scroll)
self.standalone_spinner.stop()
def _redraw(self):
if self._albums is not None:
self.set_albums(self._host, self._albums)
def _set_marks(self):
width = self.scroll.get_width()
if width == self._grid_width:
return
self._grid_width = width
self.grid_scale.clear_marks()
lower = int(self.grid_scale.get_adjustment().get_lower())
upper = int(self.grid_scale.get_adjustment().get_upper())
count_min = max(int(width / upper), 1)
count_max = max(int(width / lower), 1)
for index in range(count_min, count_max):
pixel = int(width / index)
pixel = pixel - (2 * int(pixel / 100))
self.grid_scale.add_mark(pixel, Gtk.PositionType.BOTTOM, None)
def _open_standalone(self):
self.set_visible_child(self.panel_standalone)
self.library_stack.set_visible_child(self.panel_standalone)
self.emit('open-standalone')
def _close_standalone(self):
self.set_visible_child(self.get_children()[0])
self.library_stack.set_visible_child(self.panel_normal)
self.emit('close-standalone')
def _resize_standalone_image(self):
"""Diese Methode skaliert das geladene Bild aus dem Pixelpuffer
auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse
"""
# Get size
size_width = self.standalone_stack.get_width()
size_height = self.standalone_stack.get_height()
# Get pixelbuffer
pixbuf = self._standalone_pixbuf
size = self.standalone_scroll.get_allocation()
# Check pixelbuffer
if pixbuf is None:
icon = self._get_default_icon(size_width, size_height)
self.standalone_image.set_from_paintable(icon)
self.standalone_image.set_pixel_size(min(size_width, size_height)/2)
return
# Skalierungswert für Breite und Höhe ermitteln
ratioW = float(size.width) / float(pixbuf.get_width())
ratioH = float(size.height) / float(pixbuf.get_height())
# Kleineren beider Skalierungswerte nehmen, nicht Hochskalieren
ratio = min(ratioW, ratioH)
ratio = min(ratio, 1)
# Neue Breite und Höhe berechnen
width = int(math.floor(pixbuf.get_width()*ratio))
height = int(math.floor(pixbuf.get_height()*ratio))
(width, height) = Utils.calculate_size(pixbuf.get_width(),
pixbuf.get_height(), size_width,
size_height)
if width <= 0 or height <= 0:
return
# Pixelpuffer auf Oberfläche zeichnen
self.standalone_image.set_allocation(self.standalone_scroll.get_allocation())
self.standalone_image.set_from_pixbuf(pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER))
self.standalone_image.set_from_pixbuf(
pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER))
self.standalone_image.set_pixel_size(min(width, height))
self.standalone_image.show()
def _get_default_icon(self, width, height):
return self._icon_theme.lookup_icon(Utils.STOCK_ICON_DEFAULT, None,
width, height,
Gtk.TextDirection.LTR,
Gtk.IconLookupFlags.FORCE_SYMBOLIC)
def _get_default_image(self):
return self._icon_theme.load_icon(
Utils.STOCK_ICON_DEFAULT,
512,
Gtk.IconLookupFlags.FORCE_SVG & Gtk.IconLookupFlags.FORCE_SIZE
)
def _get_selected_albums(self):
albums = []
for i in range(self.library_grid.get_model().get_n_items()):
if self.library_grid.get_model().is_selected(i):
albums.append(self.library_grid.get_model().get_item(
i).get_album().get_id())
return albums

View file

@ -1,11 +1,8 @@
import sys
import gi
gi.require_version('Gtk', '3.0')
from .application import Application
def main(version):
app = Application()
app = Application(version)
return app.run(sys.argv)

View file

@ -25,7 +25,6 @@ mcg_sources = [
'client.py',
'connectionpanel.py',
'coverpanel.py',
'infodialog.py',
'librarypanel.py',
'playlistpanel.py',
'serverpanel.py',

View file

@ -1,72 +1,42 @@
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
import logging
import math
import threading
from gi.repository import Gtk, GObject, GdkPixbuf
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Gdk, Gio, GObject, GdkPixbuf, Adw
from mcg import client
from mcg.albumheaderbar import AlbumHeaderbar
from mcg.utils import Utils
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/playlist-toolbar.ui')
class PlaylistToolbar(Gtk.ButtonBox):
__gtype_name__ = 'McgPlaylistToolbar'
__gsignals__ = {
'select': (GObject.SIGNAL_RUN_FIRST, None, (bool,)),
'clear-playlist': (GObject.SIGNAL_RUN_FIRST, None, ())
}
# Widgets
playlist_clear_button = Gtk.Template.Child()
select_button = Gtk.Template.Child()
def __init__(self):
super().__init__()
@Gtk.Template.Callback()
def on_select_toggled(self, widget):
self.emit('select', widget.get_active())
@Gtk.Template.Callback()
def on_clear_clicked(self, widget):
if widget is self.playlist_clear_button:
self.emit('clear-playlist')
def exit_selection(self):
self.select_button.set_active(False)
from mcg.utils import GridItem
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/playlist-panel.ui')
class PlaylistPanel(Gtk.Stack):
class PlaylistPanel(Adw.Bin):
__gtype_name__ = 'McgPlaylistPanel'
__gsignals__ = {
'open-standalone': (GObject.SIGNAL_RUN_FIRST, None, ()),
'close-standalone': (GObject.SIGNAL_RUN_FIRST, None, ()),
'clear-playlist': (GObject.SIGNAL_RUN_FIRST, None, ()),
'remove-album': (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
'remove-multiple-albums': (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
'play': (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
'albumart': (GObject.SIGNAL_RUN_FIRST, None, (str,))
'remove-album':
(GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT, )),
'remove-multiple-albums':
(GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT, )),
'play': (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT, )),
'albumart': (GObject.SIGNAL_RUN_FIRST, None, (str, )),
}
# Widgets
playlist_stack = Gtk.Template.Child()
panel_normal = Gtk.Template.Child()
panel_standalone = Gtk.Template.Child()
actionbar_revealer = Gtk.Template.Child()
# Toolbar
toolbar = Gtk.Template.Child()
playlist_clear_button = Gtk.Template.Child()
select_button = Gtk.Template.Child()
# Playlist Grid
playlist_grid = Gtk.Template.Child()
# Action bar (normal)
@ -78,9 +48,8 @@ class PlaylistPanel(Gtk.Stack):
standalone_scroll = Gtk.Template.Child()
standalone_image = Gtk.Template.Child()
def __init__(self, client):
GObject.GObject.__init__(self)
def __init__(self, client, **kwargs):
super().__init__(**kwargs)
self._client = client
self._host = None
self._item_size = 150
@ -88,65 +57,65 @@ class PlaylistPanel(Gtk.Stack):
self._playlist_albums = None
self._playlist_lock = threading.Lock()
self._playlist_stop = threading.Event()
self._icon_theme = Gtk.IconTheme.get_default()
self._icon_theme = Gtk.IconTheme.get_for_display(
Gdk.Display.get_default())
self._standalone_pixbuf = None
self._selected_albums = []
self._is_selected = False
# Widgets
self._toolbar = PlaylistToolbar()
self._toolbar.connect('select', self.on_toolbar_select)
self._toolbar.connect('clear-playlist', self.on_toolbar_clear)
# Header bar
self._headerbar_standalone = AlbumHeaderbar()
self._headerbar_standalone.connect('close', self.on_headerbar_close_clicked)
self._headerbar_standalone.connect('close',
self.on_headerbar_close_clicked)
# Playlist Grid: Model
self._playlist_grid_model = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str)
self._playlist_grid_model = Gio.ListStore()
self._playlist_grid_selection_multi = Gtk.MultiSelection.new(
self._playlist_grid_model)
self._playlist_grid_selection_single = Gtk.SingleSelection.new(
self._playlist_grid_model)
# Playlist Grid
self.playlist_grid.set_model(self._playlist_grid_model)
self.playlist_grid.set_pixbuf_column(0)
self.playlist_grid.set_text_column(-1)
self.playlist_grid.set_tooltip_column(1)
self.playlist_grid.set_model(self._playlist_grid_selection_single)
def get_headerbar_standalone(self):
return self._headerbar_standalone
def get_toolbar(self):
return self._toolbar
return self.toolbar
def set_selected(self, selected):
self._is_selected = selected
def on_toolbar_select(self, widget, active):
if active:
@Gtk.Template.Callback()
def on_select_toggled(self, widget):
if self.select_button.get_active():
self.actionbar_revealer.set_reveal_child(True)
self.playlist_grid.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
self.playlist_grid.get_style_context().add_class(Utils.CSS_SELECTION)
self.playlist_grid.set_model(self._playlist_grid_selection_multi)
self.playlist_grid.set_single_click_activate(False)
self.playlist_grid.get_style_context().add_class(
Utils.CSS_SELECTION)
else:
self.actionbar_revealer.set_reveal_child(False)
self.playlist_grid.set_selection_mode(Gtk.SelectionMode.SINGLE)
self.playlist_grid.get_style_context().remove_class(Utils.CSS_SELECTION)
def on_toolbar_clear(self, widget):
self.emit('clear-playlist')
self.playlist_grid.set_model(self._playlist_grid_selection_single)
self.playlist_grid.set_single_click_activate(True)
self.playlist_grid.get_style_context().remove_class(
Utils.CSS_SELECTION)
@Gtk.Template.Callback()
def on_playlist_grid_clicked(self, widget, path):
def on_clear_clicked(self, widget):
self.emit('clear-playlist')
@Gtk.Template.Callback()
def on_playlist_grid_clicked(self, widget, position):
# Get selected album
iter = self._playlist_grid_model.get_iter(path)
hash = self._playlist_grid_model.get_value(iter, 2)
album = self._playlist_albums[hash]
item = self._playlist_grid_model.get_item(position)
album = item.get_album()
album_id = album.get_id()
self._selected_albums = [album]
self.emit('albumart', hash)
self.emit('albumart', album_id)
# Show standalone album
if widget.get_selection_mode() == Gtk.SelectionMode.SINGLE:
if widget.get_model() == self._playlist_grid_selection_single:
# Set labels
self._headerbar_standalone.set_album(album)
@ -157,104 +126,77 @@ class PlaylistPanel(Gtk.Stack):
self.standalone_stack.set_visible_child(self.standalone_spinner)
self.standalone_spinner.start()
@Gtk.Template.Callback()
def on_playlist_grid_selection_changed(self, widget):
self._selected_albums = []
for path in widget.get_selected_items():
iter = self._playlist_grid_model.get_iter(path)
hash = self._playlist_grid_model.get_value(iter, 2)
self._selected_albums.append(self._playlist_albums[hash])
@Gtk.Template.Callback()
def on_selection_cancel_clicked(self, widget):
self._toolbar.exit_selection()
self.select_button.set_active(False)
@Gtk.Template.Callback()
def on_selection_remove_clicked(self, widget):
self.emit('remove-multiple-albums', self._selected_albums)
self._toolbar.exit_selection()
@Gtk.Template.Callback()
def on_standalone_scroll_size_allocate(self, widget, allocation):
self._resize_standalone_image()
self.emit('remove-multiple-albums', self._get_selected_albums())
self.select_button.set_active(False)
def on_headerbar_close_clicked(self, widget):
self._close_standalone()
@Gtk.Template.Callback()
def on_standalone_remove_clicked(self, widget):
self.emit('remove-album', self._selected_albums[0])
self.emit('remove-album', self._get_selected_albums()[0])
self._close_standalone()
@Gtk.Template.Callback()
def on_standalone_play_clicked(self, widget):
self.emit('play', self._selected_albums[0])
self.emit('play', self._get_selected_albums()[0])
self._close_standalone()
def set_size(self, width, height):
self._resize_standalone_image()
def set_item_size(self, item_size):
if self._item_size != item_size:
self._item_size = item_size
self._redraw()
def get_item_size(self):
return self._item_size
def set_playlist(self, host, playlist):
self._host = host
self._playlist_stop.set()
threading.Thread(target=self._set_playlist, args=(host, playlist, self._item_size,)).start()
threading.Thread(target=self._set_playlist,
args=(
host,
playlist,
self._item_size,
)).start()
def set_albumart(self, album, data):
if album in self._selected_albums:
self._standalone_pixbuf = None
if data:
# Load image and draw it
try:
self._standalone_pixbuf = Utils.load_pixbuf(data)
except Exception as e:
except Exception:
self._logger.exception("Failed to set albumart")
self._cover_pixbuf = self._get_default_image()
else:
self._cover_pixbuf = self._get_default_image()
# Show image
GObject.idle_add(self._show_image)
def stop_threads(self):
self._playlist_stop.set()
def _set_playlist(self, host, playlist, size):
if not self._is_selected and self._playlist != playlist:
GObject.idle_add(
self.get_parent().child_set_property,
self,
'needs-attention',
True
)
self._playlist_lock.acquire()
self._playlist_stop.clear()
self._playlist = playlist
self._playlist_albums = {}
for album in playlist:
self._playlist_albums[album.get_id()] = album
self.playlist_grid.set_model(None)
self.playlist_grid.freeze_child_notify()
self._playlist_grid_model.clear()
GObject.idle_add(self.playlist_grid.set_item_padding, size / 100)
self._playlist_grid_model.remove_all()
cache = client.MCGCache(host, size)
for album in playlist:
grid_item = GridItem(album)
pixbuf = None
# Load albumart thumbnail
try:
@ -265,89 +207,72 @@ class PlaylistPanel(Gtk.Stack):
except Exception:
self._logger.exception("Failed to load albumart")
if pixbuf is None:
pixbuf = self._icon_theme.load_icon(
Utils.STOCK_ICON_DEFAULT,
self._item_size,
Gtk.IconLookupFlags.FORCE_SVG & Gtk.IconLookupFlags.FORCE_SIZE
)
if pixbuf is not None:
self._playlist_grid_model.append([
pixbuf,
GObject.markup_escape_text("\n".join([
album.get_title(),
', '.join(album.get_dates()),
Utils.create_artists_label(album),
Utils.create_length_label(album)
])),
album.get_id()
])
icon = self._get_default_icon(self._item_size, self._item_size)
grid_item.set_icon(icon)
else:
grid_item.set_cover(pixbuf)
GObject.idle_add(self._playlist_grid_model.append, grid_item)
if self._playlist_stop.is_set():
self._playlist_lock.release()
return
self.playlist_grid.set_model(self._playlist_grid_model)
self.playlist_grid.thaw_child_notify()
# TODO why set_columns()?
#self.playlist_grid.set_columns(len(playlist))
self.playlist_grid.set_model(self._playlist_grid_selection_single)
self._playlist_lock.release()
def _show_image(self):
self._resize_standalone_image()
self.standalone_stack.set_visible_child(self.standalone_scroll)
self.standalone_spinner.stop()
def _redraw(self):
if self._playlist is not None:
self.set_playlist(self._host, self._playlist)
def _open_standalone(self):
self.set_visible_child(self.panel_standalone)
self.playlist_stack.set_visible_child(self.panel_standalone)
self.emit('open-standalone')
def _close_standalone(self):
self.set_visible_child(self.get_children()[0])
self.playlist_stack.set_visible_child(self.panel_normal)
self.emit('close-standalone')
def _resize_standalone_image(self):
"""Diese Methode skaliert das geladene Bild aus dem Pixelpuffer
auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse
"""
# Get size
size_width = self.standalone_stack.get_width()
size_height = self.standalone_stack.get_height()
# Get pixelbuffer
pixbuf = self._standalone_pixbuf
size = self.standalone_scroll.get_allocation()
# Check pixelbuffer
if pixbuf is None:
icon = self._get_default_icon(size_width, size_height)
self.standalone_image.set_from_paintable(icon)
self.standalone_image.set_pixel_size(min(size_width, size_height)/2)
return
# Skalierungswert für Breite und Höhe ermitteln
ratioW = float(size.width) / float(pixbuf.get_width())
ratioH = float(size.height) / float(pixbuf.get_height())
# Kleineren beider Skalierungswerte nehmen, nicht Hochskalieren
ratio = min(ratioW, ratioH)
ratio = min(ratio, 1)
# Neue Breite und Höhe berechnen
width = int(math.floor(pixbuf.get_width()*ratio))
height = int(math.floor(pixbuf.get_height()*ratio))
(width, height) = Utils.calculate_size(pixbuf.get_width(),
pixbuf.get_height(), size_width,
size_height)
if width <= 0 or height <= 0:
return
# Pixelpuffer auf Oberfläche zeichnen
self.standalone_image.set_allocation(self.standalone_scroll.get_allocation())
self.standalone_image.set_from_pixbuf(pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER))
self.standalone_image.set_from_pixbuf(
pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER))
self.standalone_image.set_pixel_size(min(width, height))
self.standalone_image.show()
def _get_default_icon(self, width, height):
return self._icon_theme.lookup_icon(Utils.STOCK_ICON_DEFAULT, None,
width, height,
Gtk.TextDirection.LTR,
Gtk.IconLookupFlags.FORCE_SYMBOLIC)
def _get_default_image(self):
return self._icon_theme.load_icon(
Utils.STOCK_ICON_DEFAULT,
512,
Gtk.IconLookupFlags.FORCE_SVG & Gtk.IconLookupFlags.FORCE_SIZE
)
def _get_selected_albums(self):
albums = []
for i in range(self.playlist_grid.get_model().get_n_items()):
if self.playlist_grid.get_model().is_selected(i):
albums.append(
self.playlist_grid.get_model().get_item(i).get_album())
return albums

View file

@ -1,33 +1,24 @@
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/server-toolbar.ui')
class ServerToolbar(Gtk.ButtonBox):
__gtype_name__ = 'McgServerToolbar'
def __init__(self):
super().__init__()
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Adw, GObject
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/server-panel.ui')
class ServerPanel(Gtk.Box):
class ServerPanel(Adw.Bin):
__gtype_name__ = 'McgServerPanel'
__gsignals__ = {
'change-output-device': (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT,bool,)),
'change-output-device': (GObject.SIGNAL_RUN_FIRST, None, (
GObject.TYPE_PYOBJECT,
bool,
)),
}
# Widgets
toolbar = Gtk.Template.Child()
# Status widgets
status_file = Gtk.Template.Child()
status_audio = Gtk.Template.Child()
@ -40,33 +31,27 @@ class ServerPanel(Gtk.Box):
stats_dbplaytime = Gtk.Template.Child()
stats_playtime = Gtk.Template.Child()
stats_uptime = Gtk.Template.Child()
# Audio ouptut devices widgets
# Audio output devices widgets
output_devices = Gtk.Template.Child()
def __init__(self):
super().__init__()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._none_label = ""
self._output_buttons = {}
self._is_selected = False
# Widgets
self._toolbar = ServerToolbar()
self._none_label = self.status_file.get_label()
def set_selected(self, selected):
self._is_selected = selected
def get_toolbar(self):
return self._toolbar
return self.toolbar
def on_output_device_toggled(self, widget, device):
self.emit('change-output-device', device, widget.get_active())
def set_status(self, file, audio, bitrate, error):
if file:
file = GObject.markup_escape_text(file)
@ -74,10 +59,11 @@ class ServerPanel(Gtk.Box):
file = self._none_label
self.status_file.set_markup(file)
# Audio information
if audio:
if audio:
parts = audio.split(":")
if len(parts) == 3:
audio = "{}Hz, {}bit, {}channels".format(parts[0], parts[1], parts[2])
audio = "{}Hz, {}bit, {}channels".format(
parts[0], parts[1], parts[2])
else:
audio = self._none_label
self.status_audio.set_markup(audio)
@ -94,7 +80,6 @@ class ServerPanel(Gtk.Box):
error = self._none_label
self.status_error.set_markup(error)
def set_stats(self, artists, albums, songs, dbplaytime, playtime, uptime):
self.stats_artists.set_text(str(artists))
self.stats_albums.set_text(str(albums))
@ -103,7 +88,6 @@ class ServerPanel(Gtk.Box):
self.stats_playtime.set_text(str(playtime))
self.stats_uptime.set_text(str(uptime))
def set_output_devices(self, devices):
device_ids = []
@ -112,18 +96,20 @@ class ServerPanel(Gtk.Box):
device_ids.append(device.get_id())
if device.get_id() in self._output_buttons.keys():
self._output_buttons[device.get_id()].freeze_notify()
self._output_buttons[device.get_id()].set_active(device.is_enabled())
self._output_buttons[device.get_id()].set_active(
device.is_enabled())
self._output_buttons[device.get_id()].thaw_notify()
else:
button = Gtk.CheckButton(device.get_name())
button = Gtk.CheckButton.new_with_label(device.get_name())
if device.is_enabled():
button.set_active(True)
handler = button.connect('toggled', self.on_output_device_toggled, device)
button.connect('toggled', self.on_output_device_toggled,
device)
self.output_devices.insert(button, -1)
self._output_buttons[device.get_id()] = button
self.output_devices.show_all()
# Remove devices
for id in self._output_buttons.keys():
if id not in device_ids:
self.output_devices.remove(self._output_buttons[id].get_parent())
self.output_devices.remove(
self._output_buttons[id].get_parent())

View file

@ -1,18 +1,15 @@
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/shortcuts-dialog.ui')
class ShortcutsDialog(Gtk.ShortcutsWindow):
__gtype_name__ = 'McgShortcutsDialog'
def __init__(self):
super().__init__()

View file

@ -1,23 +1,21 @@
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
import hashlib
import math
import locale
import logging
import os
import urllib
from gi.repository import GdkPixbuf
gi.require_version('Gtk', '4.0')
from gi.repository import Gdk, GdkPixbuf, GObject, Gtk
class Utils:
CSS_SELECTION = 'selection'
STOCK_ICON_DEFAULT = 'image-x-generic-symbolic'
@staticmethod
def load_pixbuf(data):
loader = GdkPixbuf.PixbufLoader()
try:
@ -26,7 +24,7 @@ class Utils:
loader.close()
return loader.get_pixbuf()
@staticmethod
def load_thumbnail(cache, client, album, size):
cache_url = cache.create_filename(album)
pixbuf = None
@ -39,38 +37,43 @@ class Utils:
if albumart:
pixbuf = Utils.load_pixbuf(albumart)
if pixbuf is not None:
pixbuf = pixbuf.scale_simple(size, size, GdkPixbuf.InterpType.HYPER)
pixbuf.savev(cache_url, 'jpeg', [], [])
(width, height) = Utils.calculate_size(pixbuf.get_width(),
pixbuf.get_height(),
size, size)
pixbuf = pixbuf.scale_simple(width, height,
GdkPixbuf.InterpType.HYPER)
try:
pixbuf.savev(cache_url, 'jpeg', [], [])
except Exception as e:
logger = logging.getLogger(__name__)
logger.warning("Failed to save thumbnail for album\"%s\": "
"%s", album.get_title(), e)
return pixbuf
@staticmethod
def create_artists_label(album):
label = ', '.join(album.get_albumartists())
if album.get_artists():
label = locale.gettext("{} feat. {}").format(
label,
", ".join(album.get_artists())
)
label, ", ".join(album.get_artists()))
return label
@staticmethod
def create_length_label(album):
minutes = album.get_length() // 60
seconds = album.get_length() - minutes * 60
return locale.gettext("{}:{} minutes").format(minutes, seconds)
@staticmethod
def create_track_title(track):
title = track.get_title()
if track.get_artists():
title = locale.gettext("{} feat. {}").format(
title,
", ".join(track.get_artists())
)
title, ", ".join(track.get_artists()))
return title
@staticmethod
def generate_id(values):
if type(values) is not list:
values = [values]
@ -79,7 +82,18 @@ class Utils:
m.update(value.encode('utf-8'))
return m.hexdigest()
@staticmethod
def calculate_size(src_width, src_height, dest_width, dest_height):
ratio_w = float(dest_width) / float(src_width)
ratio_h = float(dest_height) / float(src_height)
ratio = min(min(ratio_w, ratio_h), 1)
if ratio == 1:
return (src_width, src_height)
width = int(math.floor(src_width * ratio))
height = int(math.floor(src_height * ratio))
return (width, height)
class SortOrder:
@ -87,3 +101,38 @@ class SortOrder:
TITLE = 1
YEAR = 2
MODIFIED = 3
class GridItem(GObject.GObject):
__gtype_name__ = "GridItem"
tooltip = GObject.Property(type=str, default=None)
cover = GObject.Property(type=Gdk.Paintable, default=None)
def __init__(self, album):
super().__init__()
self._album = album
self.tooltip = GObject.markup_escape_text("\n".join([
album.get_title(), ', '.join(album.get_dates()),
Utils.create_artists_label(album),
Utils.create_length_label(album)
]))
def get_album(self):
return self._album
def set_cover(self, cover):
self.cover = Gdk.Texture.new_for_pixbuf(cover)
def set_icon(self, icon):
self.cover = icon
class SearchFilter(Gtk.Filter):
def __init__(self, search_string):
super().__init__()
self._search_string = search_string
def do_match(self, grid_item):
return grid_item.get_album().filter(self._search_string)

View file

@ -1,18 +1,17 @@
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
try:
import keyring
use_keyring = True
except:
except ImportError:
use_keyring = False
use_keyring = False
import locale
import logging
from gi.repository import Gtk, Gdk, GObject, GLib, Gio
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Adw, Gdk, GObject, GLib, Gio
from . import client
from .shortcutsdialog import ShortcutsDialog
from .connectionpanel import ConnectionPanel
@ -23,27 +22,22 @@ from .librarypanel import LibraryPanel
from .zeroconf import ZeroconfProvider
class WindowState(GObject.Object):
WIDTH = 'width'
HEIGHT = 'height'
IS_MAXIMIZED = 'is_maximized'
IS_FULLSCREENED = 'is_fullscreened'
PROP_WIDTH = 'width'
PROP_HEIGHT = 'height'
PROP_MAXIMIZED = 'is_maximized'
PROP_FULLSCREENED = 'is_fullscreened'
width = GObject.Property(type=int, default=800)
height = GObject.Property(type=int, default=600)
is_maximized = GObject.Property(type=bool, default=False)
is_fullscreened = GObject.Property(type=bool, default=False)
def __init__(self):
super().__init__()
@Gtk.Template(resource_path='/xyz/suruatoel/mcg/ui/window.ui')
class Window(Gtk.ApplicationWindow):
class Window(Adw.ApplicationWindow):
__gtype_name__ = 'McgAppWindow'
SETTING_HOST = 'host'
SETTING_PORT = 'port'
@ -55,36 +49,34 @@ class Window(Gtk.ApplicationWindow):
SETTING_ITEM_SIZE = 'item-size'
SETTING_SORT_ORDER = 'sort-order'
SETTING_SORT_TYPE = 'sort-type'
_CUSTOM_STARTUP_COMPLETE = 'startup-complete'
# Widgets
toolbar_view = Gtk.Template.Child()
content_stack = Gtk.Template.Child()
panel_stack = Gtk.Template.Child()
toolbar_stack = Gtk.Template.Child()
# Headerbar
headerbar = Gtk.Template.Child()
headerbar_title_stack = Gtk.Template.Child()
headerbar_panel_switcher = Gtk.Template.Child()
headerbar_connection_label = Gtk.Template.Child()
headerbar_button_connect = Gtk.Template.Child()
headerbar_button_playpause = Gtk.Template.Child()
headerbar_button_volume = Gtk.Template.Child()
# Infobar
info_revealer = Gtk.Template.Child()
info_bar = Gtk.Template.Child()
info_label = Gtk.Template.Child()
info_toast = Gtk.Template.Child()
def __init__(self, app, title, settings):
super().__init__()
def __init__(self, app, title, settings, **kwargs):
super().__init__(**kwargs)
self.set_application(app)
self.set_title(title)
self._settings = settings
self._panels = []
self._mcg = client.Client()
self._state = WindowState()
self._changing_volume = False
self._setting_volume = False
self._headerbar_connection_button_active = True
self._headerbar_playpause_button_active = True
self._width = 0
self._height = 0
# Help/Shortcuts dialog
self.set_help_overlay(ShortcutsDialog())
@ -99,209 +91,241 @@ class Window(Gtk.ApplicationWindow):
self._panels.append(self._cover_panel)
# Playlist panel
self._playlist_panel = PlaylistPanel(self._mcg)
self._playlist_panel.connect('open-standalone', self.on_panel_open_standalone)
self._playlist_panel.connect('close-standalone', self.on_panel_close_standalone)
self._playlist_panel.connect('open-standalone',
self.on_panel_open_standalone)
self._playlist_panel.connect('close-standalone',
self.on_panel_close_standalone)
self._panels.append(self._playlist_panel)
# Library panel
self._library_panel = LibraryPanel(self._mcg)
self._library_panel.connect('open-standalone', self.on_panel_open_standalone)
self._library_panel.connect('close-standalone', self.on_panel_close_standalone)
self._library_panel.connect('open-standalone',
self.on_panel_open_standalone)
self._library_panel.connect('close-standalone',
self.on_panel_close_standalone)
self._panels.append(self._library_panel)
# Stack
self.content_stack.add(self._connection_panel)
self.panel_stack.add_titled(self._server_panel, 'server-panel', locale.gettext("Server"))
self.panel_stack.add_titled(self._cover_panel, 'cover-panel', locale.gettext("Cover"))
self.panel_stack.add_titled(self._playlist_panel, 'playlist-panel', locale.gettext("Playlist"))
self.panel_stack.add_titled(self._library_panel, 'library-panel', locale.gettext("Library"))
# Header
self._playlist_panel.get_headerbar_standalone().connect('close', self.on_panel_close_standalone)
self._library_panel.get_headerbar_standalone().connect('close', self.on_panel_close_standalone)
self.content_stack.add_child(self._connection_panel)
self.panel_stack.add_titled_with_icon(self._server_panel,
'server-panel',
locale.gettext("Server"),
"network-wired-symbolic")
self.panel_stack.add_titled_with_icon(self._cover_panel, 'cover-panel',
locale.gettext("Cover"),
"image-x-generic-symbolic")
self.panel_stack.add_titled_with_icon(self._playlist_panel,
'playlist-panel',
locale.gettext("Playlist"),
"view-list-symbolic")
self.panel_stack.add_titled_with_icon(self._library_panel,
'library-panel',
locale.gettext("Library"),
"emblem-music-symbolic")
# Toolbar stack
self.toolbar_stack.add(self._server_panel.get_toolbar())
self.toolbar_stack.add(self._cover_panel.get_toolbar())
self.toolbar_stack.add(self._playlist_panel.get_toolbar())
self.toolbar_stack.add(self._library_panel.get_toolbar())
self.toolbar_stack.add_child(self._server_panel.get_toolbar())
self.toolbar_stack.add_child(self._cover_panel.get_toolbar())
self.toolbar_stack.add_child(self._playlist_panel.get_toolbar())
self.toolbar_stack.add_child(self._library_panel.get_toolbar())
# Properties
self._set_headerbar_sensitive(False, False)
self._connection_panel.set_host(self._settings.get_string(Window.SETTING_HOST))
self._connection_panel.set_port(self._settings.get_int(Window.SETTING_PORT))
self._connection_panel.set_host(
self._settings.get_string(Window.SETTING_HOST))
self._connection_panel.set_port(
self._settings.get_int(Window.SETTING_PORT))
if use_keyring:
self._connection_panel.set_password(keyring.get_password(ZeroconfProvider.KEYRING_SYSTEM, ZeroconfProvider.KEYRING_USERNAME))
self._playlist_panel.set_item_size(self._settings.get_int(Window.SETTING_ITEM_SIZE))
self._library_panel.set_item_size(self._settings.get_int(Window.SETTING_ITEM_SIZE))
self._library_panel.set_sort_order(self._settings.get_enum(Window.SETTING_SORT_ORDER))
self._library_panel.set_sort_type(self._settings.get_boolean(Window.SETTING_SORT_TYPE))
self._connection_panel.set_password(
keyring.get_password(ZeroconfProvider.KEYRING_SYSTEM,
ZeroconfProvider.KEYRING_USERNAME))
self._playlist_panel.set_item_size(
self._settings.get_int(Window.SETTING_ITEM_SIZE))
self._library_panel.set_item_size(
self._settings.get_int(Window.SETTING_ITEM_SIZE))
self._library_panel.set_sort_order(
self._settings.get_enum(Window.SETTING_SORT_ORDER))
self._library_panel.set_sort_type(
self._settings.get_boolean(Window.SETTING_SORT_TYPE))
# Signals
self._connection_panel.connect('connection-changed', self.on_connection_panel_connection_changed)
self.panel_stack.connect('notify::visible-child', self.on_stack_switched)
self._server_panel.connect('change-output-device', self.on_server_panel_output_device_changed)
self._cover_panel.connect('toggle-fullscreen', self.on_cover_panel_toggle_fullscreen)
self.connect("notify::maximized", self.on_maximized)
self.connect("notify::fullscreened", self.on_fullscreened)
self._connection_panel.connect(
'connection-changed', self.on_connection_panel_connection_changed)
self.panel_stack.connect('notify::visible-child',
self.on_stack_switched)
self._server_panel.connect('change-output-device',
self.on_server_panel_output_device_changed)
self._cover_panel.connect('toggle-fullscreen',
self.on_cover_panel_toggle_fullscreen)
self._cover_panel.connect('set-song', self.on_cover_panel_set_song)
self._cover_panel.connect('albumart', self.on_cover_panel_albumart)
self._playlist_panel.connect('clear-playlist', self.on_playlist_panel_clear_playlist)
self._playlist_panel.connect('remove-album', self.on_playlist_panel_remove)
self._playlist_panel.connect('remove-multiple-albums', self.on_playlist_panel_remove_multiple)
self._playlist_panel.connect('clear-playlist',
self.on_playlist_panel_clear_playlist)
self._playlist_panel.connect('remove-album',
self.on_playlist_panel_remove)
self._playlist_panel.connect('remove-multiple-albums',
self.on_playlist_panel_remove_multiple)
self._playlist_panel.connect('play', self.on_playlist_panel_play)
self._playlist_panel.connect('albumart', self.on_playlist_panel_albumart)
self._playlist_panel.connect('albumart',
self.on_playlist_panel_albumart)
self._library_panel.connect('update', self.on_library_panel_update)
self._library_panel.connect('play', self.on_library_panel_play)
self._library_panel.connect('queue', self.on_library_panel_queue)
self._library_panel.connect('queue-multiple', self.on_library_panel_queue_multiple)
self._library_panel.connect('item-size-changed', self.on_library_panel_item_size_changed)
self._library_panel.connect('sort-order-changed', self.on_library_panel_sort_order_changed)
self._library_panel.connect('sort-type-changed', self.on_library_panel_sort_type_changed)
self._library_panel.connect('queue-multiple',
self.on_library_panel_queue_multiple)
self._library_panel.connect('item-size-changed',
self.on_library_panel_item_size_changed)
self._library_panel.connect('sort-order-changed',
self.on_library_panel_sort_order_changed)
self._library_panel.connect('sort-type-changed',
self.on_library_panel_sort_type_changed)
self._library_panel.connect('albumart', self.on_library_panel_albumart)
self._mcg.connect_signal(client.Client.SIGNAL_CONNECTION, self.on_mcg_connect)
self._mcg.connect_signal(client.Client.SIGNAL_STATUS, self.on_mcg_status)
self._mcg.connect_signal(client.Client.SIGNAL_CONNECTION,
self.on_mcg_connect)
self._mcg.connect_signal(client.Client.SIGNAL_STATUS,
self.on_mcg_status)
self._mcg.connect_signal(client.Client.SIGNAL_STATS, self.on_mcg_stats)
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_OUTPUT_DEVICES, self.on_mcg_load_output_devices)
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_PLAYLIST, self.on_mcg_load_playlist)
self._mcg.connect_signal(client.Client.SIGNAL_PULSE_ALBUMS, self.on_mcg_pulse_albums)
self._mcg.connect_signal(client.Client.SIGNAL_INIT_ALBUMS, self.on_mcg_init_albums)
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_ALBUMS, self.on_mcg_load_albums)
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_ALBUMART, self.on_mcg_load_albumart)
self._mcg.connect_signal(client.Client.SIGNAL_CUSTOM, self.on_mcg_custom)
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_OUTPUT_DEVICES,
self.on_mcg_load_output_devices)
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_PLAYLIST,
self.on_mcg_load_playlist)
self._mcg.connect_signal(client.Client.SIGNAL_PULSE_ALBUMS,
self.on_mcg_pulse_albums)
self._mcg.connect_signal(client.Client.SIGNAL_INIT_ALBUMS,
self.on_mcg_init_albums)
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_ALBUMS,
self.on_mcg_load_albums)
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_ALBUMART,
self.on_mcg_load_albumart)
self._mcg.connect_signal(client.Client.SIGNAL_ERROR, self.on_mcg_error)
self._settings.connect('changed::'+Window.SETTING_PANEL, self.on_settings_panel_changed)
self._settings.connect('changed::'+Window.SETTING_ITEM_SIZE, self.on_settings_item_size_changed)
self._settings.connect('changed::'+Window.SETTING_SORT_ORDER, self.on_settings_sort_order_changed)
self._settings.connect('changed::'+Window.SETTING_SORT_TYPE, self.on_settings_sort_type_changed)
self._settings.bind(Window.SETTING_WINDOW_WIDTH, self._state, WindowState.WIDTH, Gio.SettingsBindFlags.DEFAULT)
self._settings.bind(Window.SETTING_WINDOW_HEIGHT, self._state, WindowState.HEIGHT, Gio.SettingsBindFlags.DEFAULT)
self._settings.bind(Window.SETTING_WINDOW_MAXIMIZED, self._state, WindowState.IS_MAXIMIZED, Gio.SettingsBindFlags.DEFAULT)
self._settings.connect('changed::' + Window.SETTING_PANEL,
self.on_settings_panel_changed)
self._settings.connect('changed::' + Window.SETTING_ITEM_SIZE,
self.on_settings_item_size_changed)
self._settings.connect('changed::' + Window.SETTING_SORT_ORDER,
self.on_settings_sort_order_changed)
self._settings.connect('changed::' + Window.SETTING_SORT_TYPE,
self.on_settings_sort_type_changed)
self._settings.bind(Window.SETTING_WINDOW_WIDTH, self._state,
WindowState.PROP_WIDTH,
Gio.SettingsBindFlags.DEFAULT)
self._settings.bind(Window.SETTING_WINDOW_HEIGHT, self._state,
WindowState.PROP_HEIGHT,
Gio.SettingsBindFlags.DEFAULT)
self._settings.bind(Window.SETTING_WINDOW_MAXIMIZED, self._state,
WindowState.PROP_MAXIMIZED,
Gio.SettingsBindFlags.DEFAULT)
# Actions
self.set_default_size(self._state.width, self._state.height)
if self._state.get_property(WindowState.IS_MAXIMIZED):
if self._state.get_property(WindowState.PROP_MAXIMIZED):
self.maximize()
self.show_all()
self.content_stack.set_visible_child(self._connection_panel)
if self._settings.get_boolean(Window.SETTING_CONNECTED):
self._connect()
# Menu actions
self._connect_action = Gio.SimpleAction.new_stateful("connect", None, GLib.Variant.new_boolean(False))
self._connect_action = Gio.SimpleAction.new_stateful(
"connect", None, GLib.Variant.new_boolean(False))
self._connect_action.connect('change-state', self.on_menu_connect)
self.add_action(self._connect_action)
self._play_action = Gio.SimpleAction.new_stateful("play", None, GLib.Variant.new_boolean(False))
self._play_action = Gio.SimpleAction.new_stateful(
"play", None, GLib.Variant.new_boolean(False))
self._play_action.set_enabled(False)
self._play_action.connect('change-state', self.on_menu_play)
self.add_action(self._play_action)
self._clear_playlist_action = Gio.SimpleAction.new("clear-playlist", None)
self._clear_playlist_action = Gio.SimpleAction.new(
"clear-playlist", None)
self._clear_playlist_action.set_enabled(False)
self._clear_playlist_action.connect('activate', self.on_menu_clear_playlist)
self._clear_playlist_action.connect('activate',
self.on_menu_clear_playlist)
self.add_action(self._clear_playlist_action)
panel_variant = GLib.Variant.new_string("0")
self._panel_action = Gio.SimpleAction.new_stateful("panel", panel_variant.get_type(), panel_variant)
self._panel_action = Gio.SimpleAction.new_stateful(
"panel", panel_variant.get_type(), panel_variant)
self._panel_action.set_enabled(False)
self._panel_action.connect('change-state', self.on_menu_panel)
self.add_action(self._panel_action)
self._toggle_fullscreen_action = Gio.SimpleAction.new("toggle-fullscreen", None)
self._toggle_fullscreen_action = Gio.SimpleAction.new(
"toggle-fullscreen", None)
self._toggle_fullscreen_action.set_enabled(True)
self._toggle_fullscreen_action.connect('activate', self.on_menu_toggle_fullscreen)
self._toggle_fullscreen_action.connect('activate',
self.on_menu_toggle_fullscreen)
self.add_action(self._toggle_fullscreen_action)
self._search_library_action = Gio.SimpleAction.new("search-library", None)
self._search_library_action = Gio.SimpleAction.new(
"search-library", None)
self._search_library_action.set_enabled(True)
self._search_library_action.connect('activate', self.on_menu_search_library)
self._search_library_action.connect('activate',
self.on_menu_search_library)
self.add_action(self._search_library_action)
def do_size_allocate(self, width, height, baseline):
Gtk.ApplicationWindow().do_size_allocate(self, width, height, baseline)
if self._width == width and self._height == height:
return
self._width = width
self._height = height
if width > 0:
self._cover_panel.set_width(width)
if not self._state.get_property(WindowState.PROP_MAXIMIZED):
self._state.set_property(WindowState.PROP_WIDTH, width)
self._state.set_property(WindowState.PROP_HEIGHT, height)
GObject.idle_add(self._playlist_panel.set_size, width, height)
GObject.idle_add(self._library_panel.set_size, width, height)
# Menu callbacks
def on_menu_connect(self, action, value):
self._connect()
def on_menu_play(self, action, value):
self._mcg.playpause()
def on_menu_clear_playlist(self, action, value):
self._mcg.clear_playlist()
def on_menu_panel(self, action, value):
action.set_state(value)
self.panel_stack.set_visible_child(self._panels[int(value.get_string())])
self.panel_stack.set_visible_child(self._panels[int(
value.get_string())])
def on_menu_toggle_fullscreen(self, action, value):
self.panel_stack.set_visible_child(self._cover_panel)
if not self._state.get_property(WindowState.IS_FULLSCREENED):
if not self._state.get_property(WindowState.PROP_FULLSCREENED):
self.fullscreen()
else:
self.unfullscreen()
def on_menu_search_library(self, action, value):
self.panel_stack.set_visible_child(self._library_panel)
self.panel_stack.set_visible_child(self.library_panel_page)
self._library_panel.show_search()
# Window callbacks
@Gtk.Template.Callback()
def on_resize(self, widget, event):
if not self._state.get_property(WindowState.IS_MAXIMIZED):
size = self.get_size()
self._state.set_property(WindowState.WIDTH, size.width)
self._state.set_property(WindowState.HEIGHT, size.height)
@Gtk.Template.Callback()
def on_state(self, widget, state):
self._state.set_property(WindowState.IS_MAXIMIZED, (state.new_window_state & Gdk.WindowState.MAXIMIZED > 0))
self._fullscreen((state.new_window_state & Gdk.WindowState.FULLSCREEN > 0))
def on_maximized(self, widget, maximized):
self._state.set_property(WindowState.PROP_MAXIMIZED, maximized is True)
def on_fullscreened(self, widget, fullscreened):
self._fullscreen(self.is_fullscreen())
# HeaderBar callbacks
@Gtk.Template.Callback()
def on_headerbar_connection_active_notify(self, widget, status):
self._connect()
@Gtk.Template.Callback()
def on_headerbar_connection_state_set(self, widget, state):
return True
@Gtk.Template.Callback()
def on_headerbar_volume_press(self, widget, active):
self._changing_volume = active
@Gtk.Template.Callback()
def on_headerbar_volume_release(self, widget, active):
self._changing_volume = active
@Gtk.Template.Callback()
def on_headerbar_playpause_toggled(self, widget):
self._mcg.playpause()
self._mcg.get_status()
if self._headerbar_connection_button_active:
self._connect()
@Gtk.Template.Callback()
def on_headerbar_volume_changed(self, widget, value):
if not self._setting_volume:
self._mcg.set_volume(int(value*100))
# Infobar callback
self._mcg.set_volume(int(value * 100))
@Gtk.Template.Callback()
def on_info_bar_close(self, *args):
self.info_revealer.set_reveal_child(False)
@Gtk.Template.Callback()
def on_info_bar_response(self, widget, response):
self.info_revealer.set_reveal_child(False)
def on_headerbar_playpause_toggled(self, widget):
if self._headerbar_playpause_button_active:
self._mcg.playpause()
self._mcg.get_status()
# Panel callbacks
@ -311,103 +335,86 @@ class Window(Gtk.ApplicationWindow):
self._set_menu_visible_panel()
for panel in self._panels:
panel.set_selected(panel == self.panel_stack.get_visible_child())
GObject.idle_add(
self.panel_stack.child_set_property,
self.panel_stack.get_visible_child(),
'needs-attention',
False
)
def on_panel_open_standalone(self, panel):
self.set_titlebar(panel.get_headerbar_standalone())
self.toolbar_view.add_top_bar(panel.get_headerbar_standalone())
self.toolbar_view.remove(self.headerbar)
def on_panel_close_standalone(self, panel):
self.toolbar_view.add_top_bar(self.headerbar)
self.toolbar_view.remove(panel.get_headerbar_standalone())
def on_panel_close_standalone(self, headerbar):
self.set_titlebar(self.headerbar)
def on_connection_panel_connection_changed(self, widget, host, port, password):
def on_connection_panel_connection_changed(self, widget, host, port,
password):
self._settings.set_string(Window.SETTING_HOST, host)
self._settings.set_int(Window.SETTING_PORT, port)
if use_keyring:
if password:
keyring.set_password(ZeroconfProvider.KEYRING_SYSTEM, ZeroconfProvider.KEYRING_USERNAME, password)
keyring.set_password(ZeroconfProvider.KEYRING_SYSTEM,
ZeroconfProvider.KEYRING_USERNAME,
password)
else:
if keyring.get_password(ZeroconfProvider.KEYRING_SYSTEM, ZeroconfProvider.KEYRING_USERNAME):
keyring.delete_password(ZeroconfProvider.KEYRING_SYSTEM, ZeroconfProvider.KEYRING_USERNAME)
if keyring.get_password(ZeroconfProvider.KEYRING_SYSTEM,
ZeroconfProvider.KEYRING_USERNAME):
keyring.delete_password(ZeroconfProvider.KEYRING_SYSTEM,
ZeroconfProvider.KEYRING_USERNAME)
def on_playlist_panel_clear_playlist(self, widget):
self._mcg.clear_playlist()
def on_playlist_panel_remove(self, widget, album):
self._mcg.remove_album_from_playlist(album)
def on_playlist_panel_remove_multiple(self, widget, albums):
self._mcg.remove_albums_from_playlist(albums)
def on_playlist_panel_play(self, widget, album):
self._mcg.play_album_from_playlist(album)
def on_playlist_panel_albumart(self, widget, album):
self._mcg.get_albumart(album)
def on_server_panel_output_device_changed(self, widget, device, enabled):
self._mcg.enable_output_device(device, enabled)
def on_cover_panel_toggle_fullscreen(self, widget):
if not self._state.get_property(WindowState.IS_FULLSCREENED):
if not self._state.get_property(WindowState.PROP_FULLSCREENED):
self.fullscreen()
else:
self.unfullscreen()
def on_cover_panel_set_song(self, widget, pos, time):
self.