diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 57fa85c7f..6f931c4f7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: - name: Install Dependencies run: | apt update - apt install -y libgnome-desktop-3-dev libgranite-dev libgtk-3-dev libplank-dev libswitchboard-2.0-dev libgexiv2-dev meson valac + apt install -y libgranite-7-dev libgtk-4-dev libadwaita-1-dev libswitchboard-3-dev libgexiv2-dev meson valac - name: Build env: DESTDIR: out diff --git a/README.md b/README.md index 6d37d1488..ccd580172 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,11 @@ You'll need the following dependencies: * gnome-settings-daemon-dev -* libswitchboard-2.0-dev -* libgnome-desktop-3-dev +* libswitchboard-3-dev * libgee-0.8-dev * libgexiv2-dev -* libgtk-3-dev (>= 3.22) -* libplank-dev -* libgranite-dev +* libgtk-4-dev +* libgranite-7-dev * meson * valac diff --git a/data/Check.css b/data/Check.css index 6c755c857..9f58c21a8 100644 --- a/data/Check.css +++ b/data/Check.css @@ -1,4 +1,23 @@ -radio { +/* +* Copyright 2022 elementary, Inc. (https://elementary.io) +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public +* License as published by the Free Software Foundation; either +* version 2 of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public +* License along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301 USA. +*/ + +check { min-height: 20px; min-width: 20px; -gtk-icon-transform: scale(0.6); diff --git a/data/RemoveButton.css b/data/RemoveButton.css new file mode 100644 index 000000000..21cdbca12 --- /dev/null +++ b/data/RemoveButton.css @@ -0,0 +1,22 @@ +/* +* Copyright 2022 elementary, Inc. (https://elementary.io) +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public +* License as published by the Free Software Foundation; either +* version 2 of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public +* License along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301 USA. +*/ + +.remove-button { + transition: background-color 0s; +} diff --git a/data/appearance-styles.css b/data/appearance-styles.css new file mode 100644 index 000000000..6a2c0369e --- /dev/null +++ b/data/appearance-styles.css @@ -0,0 +1,46 @@ +/* +* Copyright 2020 elementary, Inc. (https://elementary.io) +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public +* License as published by the Free Software Foundation; either +* version 2 of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public +* License along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301 USA. +*/ + +checkbutton .card { + background-position: center; + background-repeat: no-repeat; + background-size: 112px 80px, cover; + min-width: 112px; + min-height: 80px; +} + +checkbutton .card.prefer-default { + background-image: + url("resource:///io/elementary/switchboard/plug/pantheon-shell/appearance-default.svg"), + linear-gradient( + to bottom, + alpha(@accent_color_300, 0.1), + alpha(@accent_color_500, 0.1) + ); +} + +checkbutton .card.prefer-dark { + background-image: + url("resource:///io/elementary/switchboard/plug/pantheon-shell/appearance-dark.svg"), + linear-gradient( + to bottom, + alpha(@accent_color_300, 0.1), + alpha(@accent_color_500, 0.1) + ); +} diff --git a/data/icons.gresource.xml b/data/icons.gresource.xml index dd1fd2811..12371cadb 100644 --- a/data/icons.gresource.xml +++ b/data/icons.gresource.xml @@ -10,6 +10,8 @@ appearance-default.svg appearance-dark.svg Check.css + RemoveButton.css plug.css + appearance-styles.css diff --git a/data/plug.css b/data/plug.css index e5b343a48..2a82f61d8 100644 --- a/data/plug.css +++ b/data/plug.css @@ -26,8 +26,8 @@ ), linear-gradient( to bottom, - alpha (@accent_color_500, 0.1), - alpha (@accent_color_500, 0.1) + alpha(@accent_color_500, 0.1), + alpha(@accent_color_500, 0.1) ); background-repeat: no-repeat; background-size: 48px 48px, cover; @@ -58,29 +58,29 @@ } .wallpaper-container .card { - border-radius: 1px; - border: none; + margin: 9px; + min-height: 128px; } .wallpaper-container .card:checked { box-shadow: - 0 0 0 4px alpha (@text_color, 0.2), - 0 0 0 1px alpha (#000, 0.05), - 0 3px 3px alpha (#000, 0.22); + 0 0 0 4px alpha(@text_color, 0.2), + 0 0 0 1px alpha(#000, 0.05), + 0 3px 3px alpha(#000, 0.22); } .wallpaper-container:focus .card { box-shadow: 0 0 0 4px @accent_color, - 0 0 0 1px alpha (#000, 0.05), - 0 3px 3px alpha (#000, 0.22); + 0 0 0 1px alpha(#000, 0.05), + 0 3px 3px alpha(#000, 0.22); } radiobutton .card { background-image: linear-gradient( to bottom, - alpha (@accent_color_300, 0.1), - alpha (@accent_color_500, 0.1) + alpha(@accent_color_300, 0.1), + alpha(@accent_color_500, 0.1) ); } diff --git a/meson.build b/meson.build index 96bd07b43..64693d186 100644 --- a/meson.build +++ b/meson.build @@ -26,10 +26,10 @@ add_project_arguments( gio_dep = dependency('gio-2.0') glib_dep = dependency('glib-2.0') gobject_dep = dependency('gobject-2.0') -granite_dep = dependency('granite', version: '>=6.0.0') -gtk_dep = dependency('gtk+-3.0', version: '>= 3.22') -hdy_dep = dependency ('libhandy-1') -plank_dep = dependency('plank', version: '>=0.10.9') + +granite_dep = dependency('granite-7') +gtk_dep = dependency('gtk4') +adw_dep = dependency ('libadwaita-1') posix_dep = meson.get_compiler('vala').find_library('posix') plug_resources = gnome.compile_resources( diff --git a/po/POTFILES b/po/POTFILES index c0b6b5220..e33f42161 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -1,4 +1,3 @@ -src/IOHelper.vala src/Plug.vala src/ThumbnailGenerator.vala src/Translations.vala @@ -7,6 +6,9 @@ src/Views/Dock.vala src/Views/Multitasking.vala src/Views/Text.vala src/Views/Wallpaper.vala +src/Widgets/GenericContainer.vala +src/Widgets/ImageContainer.vala src/Widgets/SolidColorContainer.vala -src/Widgets/WallpaperContainer.vala +src/Widgets/UriContainer.vala +src/Widgets/XMLContainer.vala set-wallpaper-contract/set-wallpaper.vala diff --git a/set-wallpaper-contract/set-wallpaper.vala b/set-wallpaper-contract/set-wallpaper.vala index 6aa24c911..57d5b85ae 100644 --- a/set-wallpaper-contract/set-wallpaper.vala +++ b/set-wallpaper-contract/set-wallpaper.vala @@ -173,7 +173,6 @@ namespace SetWallpaperContractor { } public static int main (string[] args) { - Gtk.init (ref args); AccountsServiceUser? accounts_service = null; try { @@ -238,20 +237,25 @@ namespace SetWallpaperContractor { }; dialog.add_button (_("Create Slideshow"), Gtk.ResponseType.OK); dialog.set_default_response (Gtk.ResponseType.OK); - dialog.custom_bin.add (duration); - dialog.show_all (); + dialog.custom_bin.append (duration); + dialog.present (); delay_value_changed (duration, dialog.secondary_label); duration.value_changed.connect (() => delay_value_changed (duration, dialog.secondary_label)); - if (dialog.run () == Gtk.ResponseType.OK) { - dialog.destroy (); + int return_val = 1; + dialog.response.connect ((id) => { + if (id == Gtk.ResponseType.OK) { + dialog.destroy (); - var path = folder.get_child (SLIDESHOW_FILENAME).get_path (); - update_slideshow (path, files, delay_value); - return 0; - } + var path = folder.get_child (SLIDESHOW_FILENAME).get_path (); + update_slideshow (path, files, delay_value); + return_val = 0; + } else { + return_val = 1; + } + }); - return 1; + return return_val; } } diff --git a/src/Config.vala.in b/src/Config.vala.in index e29e292f6..995886c21 100644 --- a/src/Config.vala.in +++ b/src/Config.vala.in @@ -1,5 +1,4 @@ namespace Constants { - public const string PLANKDATADIR = "@PLANKDATADIR@"; public const string GETTEXT_PACKAGE = "@GETTEXT_PACKAGE@"; public const string LOCALEDIR = "@LOCALEDIR@"; } diff --git a/src/IOHelper.vala b/src/IOHelper.vala deleted file mode 100644 index 6c33e4887..000000000 --- a/src/IOHelper.vala +++ /dev/null @@ -1,42 +0,0 @@ -/*- - * Copyright (c) 2017-2018 elementary LLC. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -public class PantheonShell.IOHelper : GLib.Object { - private const string[] ACCEPTED_TYPES = { - "image/jpeg", - "image/png", - "image/tiff", - "image/svg+xml", - "image/gif" - }; - - // Check if the filename has a picture file extension. - public static bool is_valid_file_type (GLib.FileInfo file_info) { - // Check for correct file type, don't try to load directories and such - if (file_info.get_file_type () != GLib.FileType.REGULAR) { - return false; - } - - foreach (var type in ACCEPTED_TYPES) { - if (GLib.ContentType.equals (file_info.get_content_type (), type)) { - return true; - } - } - - return false; - } -} diff --git a/src/Plug.vala b/src/Plug.vala index 9216b8d86..5aad30dd1 100644 --- a/src/Plug.vala +++ b/src/Plug.vala @@ -20,7 +20,7 @@ public class PantheonShell.Plug : Switchboard.Plug { private Gtk.Stack stack; - private Gtk.Grid main_grid; + private Gtk.Box main_box; private Wallpaper wallpaper_view; @@ -38,7 +38,7 @@ public class PantheonShell.Plug : Switchboard.Plug { var provider = new Gtk.CssProvider (); provider.load_from_resource ("/io/elementary/switchboard/plug/pantheon-shell/plug.css"); - Gtk.StyleContext.add_provider_for_screen (Gdk.Screen.get_default (), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + Gtk.StyleContext.add_provider_for_display (Gdk.Display.get_default (), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); // DEPRECATED settings.set ("desktop/wallpaper", "wallpaper"); @@ -53,10 +53,10 @@ public class PantheonShell.Plug : Switchboard.Plug { } public override Gtk.Widget get_widget () { - if (main_grid == null) { - main_grid = new Gtk.Grid (); + if (main_box == null) { + main_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); - wallpaper_view = new Wallpaper (this); + wallpaper_view = new Wallpaper (); var multitasking = new Multitasking (); var appearance = new Appearance (); @@ -74,22 +74,32 @@ public class PantheonShell.Plug : Switchboard.Plug { stack.add_titled (multitasking, "multitasking", _("Multitasking")); - var stack_switcher = new Gtk.StackSwitcher (); - stack_switcher.stack = stack; - stack_switcher.halign = Gtk.Align.CENTER; - stack_switcher.homogeneous = true; - stack_switcher.margin = 24; + var stack_switcher = new Gtk.StackSwitcher () { + stack = stack, + halign = Gtk.Align.CENTER, + margin_start = 24, + margin_end = 24, + margin_top = 24, + margin_bottom = 24 + }; + + var size_group = new Gtk.SizeGroup (Gtk.SizeGroupMode.HORIZONTAL); + var switcher_toggles = stack_switcher.observe_children (); + if (size_group.get_widgets ().length () == 0) { + for (var index = 0; index < switcher_toggles.get_n_items (); index++) { + size_group.add_widget ((Gtk.ToggleButton) switcher_toggles.get_item (index)); + } + } - main_grid.attach (stack_switcher, 0, 0, 1, 1); - main_grid.attach (stack, 0, 1, 1, 1); - main_grid.show_all (); + main_box.append (stack_switcher); + main_box.append (stack); } - return main_grid; + return main_box; } public override void shown () { - wallpaper_view.update_wallpaper_folder (); + wallpaper_view.load_wallpapers (); } public override void hidden () { diff --git a/src/Views/Appearance.vala b/src/Views/Appearance.vala index 0b91e51f3..36e286045 100644 --- a/src/Views/Appearance.vala +++ b/src/Views/Appearance.vala @@ -18,7 +18,7 @@ * */ -public class PantheonShell.Appearance : Gtk.Box { +public class PantheonShell.Appearance : Gtk.Widget { private const string INTERFACE_SCHEMA = "org.gnome.desktop.interface"; private const string STYLESHEET_KEY = "gtk-theme"; private const string STYLESHEET_PREFIX = "io.elementary.stylesheet."; @@ -58,105 +58,114 @@ public class PantheonShell.Appearance : Gtk.Box { return "cocoa"; case GRAY: return "slate"; + case NO_PREFERENCE: + return "no preference"; } return "auto"; } } + static construct { + set_layout_manager_type (typeof (Gtk.BinLayout)); + } + construct { var dark_label = new Granite.HeaderLabel (_("Style")); - var prefer_default_image = new Gtk.Image.from_resource ("/io/elementary/switchboard/plug/pantheon-shell/appearance-default.svg"); + var css_provider = new Gtk.CssProvider (); + css_provider.load_from_resource ("/io/elementary/switchboard/plug/pantheon-shell/appearance-styles.css"); + + var prefer_default_radio = new Gtk.CheckButton () { + halign = Gtk.Align.START + }; + prefer_default_radio.add_css_class ("image-button"); var prefer_default_card = new Gtk.Grid () { - margin = 6, + margin_top = 6, + margin_bottom = 6, + margin_end = 6, margin_start = 12 }; - prefer_default_card.add (prefer_default_image); - - unowned Gtk.StyleContext prefer_default_card_context = prefer_default_card.get_style_context (); - prefer_default_card_context.add_class (Granite.STYLE_CLASS_CARD); - prefer_default_card_context.add_class (Granite.STYLE_CLASS_ROUNDED); + prefer_default_card.add_css_class (Granite.STYLE_CLASS_CARD); + prefer_default_card.add_css_class (Granite.STYLE_CLASS_ROUNDED); + prefer_default_card.add_css_class ("prefer-default"); + prefer_default_card.get_style_context ().add_provider (css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); var prefer_default_grid = new Gtk.Grid () { row_spacing = 6 }; prefer_default_grid.attach (prefer_default_card, 0, 0); prefer_default_grid.attach (new Gtk.Label (_("Default")), 0, 1); + prefer_default_grid.set_parent (prefer_default_radio); - var prefer_default_radio = new Gtk.RadioButton (null) { - halign = Gtk.Align.START + var prefer_dark_radio = new Gtk.CheckButton () { + halign = Gtk.Align.START, + hexpand = true, + group = prefer_default_radio }; - prefer_default_radio.get_style_context ().add_class ("image-button"); - prefer_default_radio.add (prefer_default_grid); - - var prefer_dark_image = new Gtk.Image.from_resource ("/io/elementary/switchboard/plug/pantheon-shell/appearance-dark.svg"); + prefer_dark_radio.add_css_class ("image-button"); var prefer_dark_card = new Gtk.Grid () { - margin = 6, + margin_top = 6, + margin_bottom = 6, + margin_end = 6, margin_start = 12 }; - prefer_dark_card.add (prefer_dark_image); - - unowned Gtk.StyleContext prefer_dark_card_context = prefer_dark_card.get_style_context (); - prefer_dark_card_context.add_class (Granite.STYLE_CLASS_CARD); - prefer_dark_card_context.add_class (Granite.STYLE_CLASS_ROUNDED); + prefer_dark_card.add_css_class (Granite.STYLE_CLASS_CARD); + prefer_dark_card.add_css_class (Granite.STYLE_CLASS_ROUNDED); + prefer_dark_card.add_css_class ("prefer-dark"); + prefer_dark_card.get_style_context ().add_provider (css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); var prefer_dark_grid = new Gtk.Grid () { row_spacing = 6 }; prefer_dark_grid.attach (prefer_dark_card, 0, 0); prefer_dark_grid.attach (new Gtk.Label (_("Dark")), 0, 1); - - var prefer_dark_radio = new Gtk.RadioButton.from_widget (prefer_default_radio) { - halign = Gtk.Align.START, - hexpand = true - }; - prefer_dark_radio.get_style_context ().add_class ("image-button"); - prefer_dark_radio.add (prefer_dark_grid); + prefer_dark_grid.set_parent (prefer_dark_radio); var prefer_style_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12); - prefer_style_box.add (prefer_default_radio); - prefer_style_box.add (prefer_dark_radio); + prefer_style_box.append (prefer_default_radio); + prefer_style_box.append (prefer_dark_radio); var dark_info = new Gtk.Label (_("Preferred visual style for system components. Apps may also choose to follow this preference.")) { wrap = true, xalign = 0 }; - dark_info.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL); + dark_info.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); var schedule_label = new Granite.HeaderLabel (_("Schedule")); - var schedule_disabled_radio = new Gtk.RadioButton.with_label (null, _("Disabled")) { + var schedule_disabled_radio = new Gtk.CheckButton.with_label (_("Disabled")) { margin_bottom = 3 }; - var schedule_sunset_radio = new Gtk.RadioButton.with_label_from_widget ( - schedule_disabled_radio, - _("Sunset to Sunrise") - ); + var schedule_sunset_radio = new Gtk.CheckButton.with_label (_("Sunset to Sunrise")) { + group = schedule_disabled_radio + }; var from_label = new Gtk.Label (_("From:")); - var from_time = new Granite.Widgets.TimePicker () { + var from_time = new Granite.TimePicker () { hexpand = true, margin_end = 6 }; var to_label = new Gtk.Label (_("To:")); - var to_time = new Granite.Widgets.TimePicker () { + var to_time = new Granite.TimePicker () { hexpand = true }; var schedule_manual_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6); - schedule_manual_box.add (from_label); - schedule_manual_box.add (from_time); - schedule_manual_box.add (to_label); - schedule_manual_box.add (to_time); + schedule_manual_box.append (from_label); + schedule_manual_box.append (from_time); + schedule_manual_box.append (to_label); + schedule_manual_box.append (to_time); - var schedule_manual_radio = new Gtk.RadioButton.from_widget (schedule_disabled_radio) ; + var schedule_manual_radio = new Gtk.CheckButton () { + group = schedule_disabled_radio + }; Pantheon.AccountsService? pantheon_act = null; @@ -271,24 +280,23 @@ public class PantheonShell.Appearance : Gtk.Box { pantheon_act.prefers_color_scheme = Granite.Settings.ColorScheme.DARK; }); - /* Connect to focus_in_event so that this is only triggered - * through user interaction, not if scheduling changes the selection - */ - prefer_default_radio.focus_in_event.connect (() => { + var prefer_default_radio_controller = new Gtk.EventControllerFocus (); + prefer_default_radio_controller.enter.connect (() => { // Check if selection changed if (pantheon_act.prefers_color_scheme != Granite.Settings.ColorScheme.NO_PREFERENCE) { schedule_disabled_radio.active = true; } - return Gdk.EVENT_PROPAGATE; }); + prefer_default_radio.add_controller (prefer_default_radio_controller); - prefer_dark_radio.focus_in_event.connect (() => { + var prefer_dark_radio_controller = new Gtk.EventControllerFocus (); + prefer_dark_radio_controller.enter.connect (() => { // Check if selection changed if (pantheon_act.prefers_color_scheme != Granite.Settings.ColorScheme.DARK) { schedule_disabled_radio.active = true; } - return Gdk.EVENT_PROPAGATE; }); + prefer_dark_radio.add_controller (prefer_dark_radio_controller); ((GLib.DBusProxy) pantheon_act).g_properties_changed.connect ((changed, invalid) => { var color_scheme = changed.lookup_value ("PrefersColorScheme", new VariantType ("i")); @@ -359,44 +367,48 @@ public class PantheonShell.Appearance : Gtk.Box { var auto_button = new PrefersAccentColorButton (pantheon_act, AccentColor.NO_PREFERENCE, blueberry_button); auto_button.tooltip_text = _("Automatic based on wallpaper"); - var accent_grid = new Gtk.Grid (); - accent_grid.column_spacing = 6; - accent_grid.add (blueberry_button); - accent_grid.add (mint_button); - accent_grid.add (lime_button); - accent_grid.add (banana_button); - accent_grid.add (orange_button); - accent_grid.add (strawberry_button); - accent_grid.add (bubblegum_button); - accent_grid.add (grape_button); - accent_grid.add (cocoa_button); - accent_grid.add (slate_button); - accent_grid.add (auto_button); + var accent_grid = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6); + accent_grid.append (blueberry_button); + accent_grid.append (mint_button); + accent_grid.append (lime_button); + accent_grid.append (banana_button); + accent_grid.append (orange_button); + accent_grid.append (strawberry_button); + accent_grid.append (bubblegum_button); + accent_grid.append (grape_button); + accent_grid.append (cocoa_button); + accent_grid.append (slate_button); + accent_grid.append (auto_button); var accent_info = new Gtk.Label (_("Used across the system by default. Apps can always use their own accent color.")) { xalign = 0, wrap = true }; - accent_info.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL); + accent_info.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); grid.attach (accent_label, 0, 7, 2); grid.attach (accent_info, 0, 8, 2); grid.attach (accent_grid, 0, 9, 2); } - var clamp = new Hdy.Clamp (); - clamp.add (grid); + var clamp = new Adw.Clamp () { + child = grid + }; - add (clamp); + clamp.set_parent (this); } - private class PrefersAccentColorButton : Gtk.RadioButton { + ~Appearance () { + this.get_last_child ().unparent (); + } + + private class PrefersAccentColorButton : Gtk.CheckButton { public AccentColor color { get; construct; } public Pantheon.AccountsService? pantheon_act { get; construct; default = null; } private static GLib.Settings interface_settings; - public PrefersAccentColorButton (Pantheon.AccountsService? pantheon_act, AccentColor color, Gtk.RadioButton? group_member = null) { + public PrefersAccentColorButton (Pantheon.AccountsService? pantheon_act, AccentColor color, Gtk.CheckButton? group_member = null) { Object ( pantheon_act: pantheon_act, color: color, @@ -411,9 +423,12 @@ public class PantheonShell.Appearance : Gtk.Box { } construct { - unowned Gtk.StyleContext context = get_style_context (); - context.add_class (Granite.STYLE_CLASS_COLOR_BUTTON); - context.add_class (color.to_string ()); + add_css_class (Granite.STYLE_CLASS_COLOR_BUTTON); + if (color == AccentColor.NO_PREFERENCE) { + add_css_class ("auto"); + } else { + add_css_class (color.to_string ()); + } realize.connect (() => { active = color == pantheon_act.prefers_accent_color; diff --git a/src/Views/Dock.vala b/src/Views/Dock.vala index f4d8885ae..d9effb47c 100644 --- a/src/Views/Dock.vala +++ b/src/Views/Dock.vala @@ -17,53 +17,88 @@ * Boston, MA 02110-1301 USA */ -public class PantheonShell.Dock : Gtk.Box { +public class PantheonShell.Dock : Gtk.Widget { private const string PANEL_SCHEMA = "io.elementary.desktop.wingpanel"; private const string TRANSLUCENCY_KEY = "use-transparency"; private Gtk.Label primary_monitor_label; private Gtk.Switch primary_monitor; private Gtk.Label monitor_label; - private Gtk.ComboBoxText monitor; - private Plank.DockPreferences dock_preferences; + private Gtk.ComboBoxText monitor_combo; + private Settings dock_preferences; + + private enum PlankHideTypes { + NONE, + INTELLIGENT, + AUTO, + DODGE_MAXIMIZED, + WINDOW_DODGE, + DODGE_ACTIVE + } + + static construct { + set_layout_manager_type (typeof (Gtk.BinLayout)); + } construct { var dock_header = new Granite.HeaderLabel (_("Dock")); - weak Gtk.IconTheme default_theme = Gtk.IconTheme.get_default (); + weak Gtk.IconTheme default_theme = Gtk.IconTheme.get_for_display (Gdk.Display.get_default ()); default_theme.add_resource_path ("/io/elementary/switchboard/plug/pantheon-shell"); - var icon_size_32 = new Gtk.RadioButton (null); - icon_size_32.image = new Gtk.Image.from_icon_name ("application-default-icon-symbolic", Gtk.IconSize.DND); - icon_size_32.tooltip_text = _("Small"); + var icon_size_32 = new Gtk.CheckButton () { + tooltip_text = _("Small") + }; + icon_size_32.add_css_class ("image-button"); + var icon_size_32_image = new Gtk.Image.from_icon_name ("application-default-icon-symbolic") { + pixel_size = 32 + }; + icon_size_32_image.set_parent (icon_size_32); - var icon_size_48 = new Gtk.RadioButton.from_widget (icon_size_32); - icon_size_48.image = new Gtk.Image.from_icon_name ("application-default-icon-symbolic", Gtk.IconSize.DIALOG); - icon_size_48.tooltip_text = _("Default"); + var icon_size_48 = new Gtk.CheckButton () { + tooltip_text = _("Default") + }; + icon_size_48.add_css_class ("image-button"); + icon_size_48.group = icon_size_32; + var icon_size_48_image = new Gtk.Image.from_icon_name ("application-default-icon-symbolic") { + pixel_size = 48 + }; + icon_size_48_image.set_parent (icon_size_48); - var image_64 = new Gtk.Image (); - image_64.icon_name = "application-default-icon-symbolic"; - image_64.pixel_size = 64; + var icon_size_64 = new Gtk.CheckButton () { + tooltip_text = _("Large"), + group = icon_size_32 + }; + icon_size_64.add_css_class ("image-button"); + var image_64 = new Gtk.Image () { + icon_name = "application-default-icon-symbolic", + pixel_size = 64 + }; + image_64.set_parent (icon_size_64); - var icon_size_64 = new Gtk.RadioButton.from_widget (icon_size_32); - icon_size_64.image = image_64; - icon_size_64.tooltip_text = _("Large"); + var icon_size_unsupported = new Gtk.CheckButton () { + group = icon_size_32 + }; - var icon_size_unsupported = new Gtk.RadioButton.from_widget (icon_size_32); + var icon_size_grid = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 24); + icon_size_grid.append (icon_size_32); + icon_size_grid.append (icon_size_48); + icon_size_grid.append (icon_size_64); - var icon_size_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 24); - icon_size_box.add (icon_size_32); - icon_size_box.add (icon_size_48); - icon_size_box.add (icon_size_64); + var schema_id = "net.launchpad.plank.dock.settings"; + var schema = GLib.SettingsSchemaSource.get_default ().lookup (schema_id, true); + if (schema == null) { + error ("GSettingsSchema '%s' not found", schema_id); + } - Plank.Paths.initialize ("plank", Constants.PLANKDATADIR); - dock_preferences = new Plank.DockPreferences ("dock1"); + dock_preferences = new Settings.full (schema, null, "/net/launchpad/plank/docks/dock1/"); - var pressure_switch = new Gtk.Switch (); - pressure_switch.halign = Gtk.Align.START; - pressure_switch.valign = Gtk.Align.CENTER; + var pressure_switch = new Gtk.Switch () { + halign = Gtk.Align.START, + valign = Gtk.Align.CENTER + }; - dock_preferences.bind_property ("PressureReveal", pressure_switch, "active", GLib.BindingFlags.SYNC_CREATE | GLib.BindingFlags.BIDIRECTIONAL); + dock_preferences.bind ("pressure-reveal", pressure_switch, "active", GLib.SettingsBindFlags.DEFAULT); var hide_mode = new Gtk.ComboBoxText () { hexpand = true @@ -73,83 +108,87 @@ public class PantheonShell.Dock : Gtk.Box { hide_mode.append_text (_("Any window overlaps the dock")); hide_mode.append_text (_("Not being used")); - Plank.HideType[] hide_mode_ids = {Plank.HideType.DODGE_MAXIMIZED, Plank.HideType.INTELLIGENT, Plank.HideType.WINDOW_DODGE, Plank.HideType.AUTO}; + PlankHideTypes[] hide_mode_ids = {PlankHideTypes.DODGE_MAXIMIZED, PlankHideTypes.INTELLIGENT, PlankHideTypes.WINDOW_DODGE, PlankHideTypes.AUTO}; - var hide_switch = new Gtk.Switch (); - hide_switch.halign = Gtk.Align.START; - hide_switch.valign = Gtk.Align.CENTER; + var hide_switch = new Gtk.Switch () { + halign = Gtk.Align.START, + valign = Gtk.Align.CENTER + }; - var hide_none = (dock_preferences.HideMode != Plank.HideType.NONE); + var hide_none = (dock_preferences.get_enum ("hide-mode") != PlankHideTypes.NONE); hide_switch.active = hide_none; if (hide_none) { - for (int i = 0; i < hide_mode_ids.length; i++) { - if (hide_mode_ids[i] == dock_preferences.HideMode) + for (var i = 0; i < hide_mode_ids.length; i++) { + if (hide_mode_ids[i] == dock_preferences.get_enum ("hide-mode")) { hide_mode.active = i; + } } } else { hide_mode.sensitive = false; } - hide_mode.changed.connect (() => { - dock_preferences.HideMode = hide_mode_ids[hide_mode.active]; - }); - hide_switch.bind_property ("active", pressure_switch, "sensitive", BindingFlags.SYNC_CREATE); hide_switch.bind_property ("active", hide_mode, "sensitive", BindingFlags.DEFAULT); + hide_mode.changed.connect (() => { + dock_preferences.set_enum ("hide-mode", hide_mode_ids[hide_mode.active]); + }); + hide_switch.notify["active"].connect (() => { if (hide_switch.active) { - dock_preferences.HideMode = hide_mode_ids[hide_mode.active]; + dock_preferences.set_enum ("hide-mode", hide_mode_ids[hide_mode.active]); } else { - dock_preferences.HideMode = Plank.HideType.NONE; + dock_preferences.set_enum ("hide-mode", PlankHideTypes.NONE); } }); - monitor = new Gtk.ComboBoxText (); + monitor_combo = new Gtk.ComboBoxText (); - primary_monitor_label = new Gtk.Label (_("Primary display:")); - primary_monitor_label.halign = Gtk.Align.END; - primary_monitor_label.no_show_all = true; + primary_monitor_label = new Gtk.Label (_("Primary display:")) { + halign = Gtk.Align.END + }; - monitor_label = new Gtk.Label (_("Display:")); - monitor_label.no_show_all = true; - monitor_label.halign = Gtk.Align.END; + monitor_label = new Gtk.Label (_("Display:")) { + halign = Gtk.Align.END + }; primary_monitor = new Gtk.Switch (); - primary_monitor.no_show_all = true; primary_monitor.notify["active"].connect (() => { if (primary_monitor.active == true) { - dock_preferences.Monitor = ""; + dock_preferences.set_string ("monitor", ""); monitor_label.sensitive = false; - monitor.sensitive = false; + monitor_combo.sensitive = false; } else { var plug_names = get_monitor_plug_names (get_display ()); - if (plug_names.length > monitor.active) - dock_preferences.Monitor = plug_names[monitor.active]; + if (plug_names.length > monitor_combo.active) { + dock_preferences.set_string ("monitor", plug_names[monitor_combo.active]); + } + monitor_label.sensitive = true; - monitor.sensitive = true; + monitor_combo.sensitive = true; } }); - primary_monitor.active = (dock_preferences.Monitor == ""); + primary_monitor.active = (dock_preferences.get_string ("monitor") == ""); - monitor.notify["active"].connect (() => { - if (monitor.active >= 0 && primary_monitor.active == false) { + monitor_combo.notify["active"].connect (() => { + if (monitor_combo.active >= 0 && primary_monitor.active == false) { var plug_names = get_monitor_plug_names (get_display ()); - if (plug_names.length > monitor.active) - dock_preferences.Monitor = plug_names[monitor.active]; + if (plug_names.length > monitor_combo.active) + dock_preferences.set_string ("monitor", plug_names[monitor_combo.active]); } }); - get_screen ().monitors_changed.connect (() => {check_for_screens ();}); - - var icon_label = new Gtk.Label (_("Icon size:")); - icon_label.halign = Gtk.Align.END; - var hide_label = new Gtk.Label (_("Hide when:")); - hide_label.halign = Gtk.Align.END; + var icon_label = new Gtk.Label (_("Icon size:")) { + halign = Gtk.Align.END + }; + var hide_label = new Gtk.Label (_("Hide when:")) { + halign = Gtk.Align.END + }; var primary_monitor_grid = new Gtk.Grid (); - primary_monitor_grid.add (primary_monitor); - var pressure_label = new Gtk.Label (_("Pressure reveal:")); - pressure_label.halign = Gtk.Align.END; + primary_monitor_grid.attach (primary_monitor, 0, 0); + var pressure_label = new Gtk.Label (_("Pressure reveal:")) { + halign = Gtk.Align.END + }; var panel_header = new Granite.HeaderLabel (_("Panel")) { margin_top = 12 @@ -172,28 +211,35 @@ public class PantheonShell.Dock : Gtk.Box { }; grid.attach (dock_header, 0, 0, 3); grid.attach (icon_label, 0, 1); - grid.attach (icon_size_box, 1, 1, 2); + grid.attach (icon_size_grid, 1, 1, 2); grid.attach (hide_label, 0, 2); grid.attach (hide_mode, 1, 2); grid.attach (hide_switch, 2, 2); grid.attach (primary_monitor_label, 0, 3); grid.attach (primary_monitor_grid, 1, 3); grid.attach (monitor_label, 0, 4); - grid.attach (monitor, 1, 4); + grid.attach (monitor_combo, 1, 4); grid.attach (pressure_label, 0, 5); grid.attach (pressure_switch, 1, 5); grid.attach (panel_header, 0, 6, 3); grid.attach (translucency_label, 0, 7); grid.attach (translucency_switch, 1, 7); - var clamp = new Hdy.Clamp (); - clamp.add (grid); + var clamp = new Adw.Clamp () { + child = grid + }; - add (clamp); + clamp.set_parent (this); - check_for_screens (); + var display = get_display (); + var monitors_list = display.get_monitors (); + monitors_list.items_changed.connect (() => { + check_for_screens (monitors_list); + }); - switch (dock_preferences.IconSize) { + check_for_screens (monitors_list); + + switch (dock_preferences.get_int ("icon-size")) { case 32: icon_size_32.active = true; break; @@ -209,94 +255,82 @@ public class PantheonShell.Dock : Gtk.Box { } icon_size_32.toggled.connect (() => { - dock_preferences.IconSize = 32; + dock_preferences.set_int ("icon-size", 32); }); icon_size_48.toggled.connect (() => { - dock_preferences.IconSize = 48; + dock_preferences.set_int ("icon-size", 48); }); icon_size_64.toggled.connect (() => { - dock_preferences.IconSize = 64; + dock_preferences.set_int ("icon-size", 64); }); var panel_settings = new GLib.Settings (PANEL_SCHEMA); panel_settings.bind (TRANSLUCENCY_KEY, translucency_switch, "active", SettingsBindFlags.DEFAULT); } - private void check_for_screens () { - int i = 0; + private void check_for_screens (ListModel monitors) { + int index = 0; int primary_screen = 0; - var default_display = get_display (); - var default_screen = get_screen (); - monitor.remove_all (); - try { - var screen = new Gnome.RRScreen (default_screen); - for (i = 0; i < default_display.get_n_monitors () ; i++) { - var monitor_plug_name = default_display.get_monitor (i).model; - - if (monitor_plug_name != null) { - unowned Gnome.RROutput output = screen.get_output_by_name (monitor_plug_name); - if (output != null && output.get_display_name () != null && output.get_display_name () != "") { - monitor.append_text (output.get_display_name ()); - if (output.get_is_primary () == true) { - primary_screen = i; - } - continue; - } - } + monitor_combo.remove_all (); - monitor.append_text (_("Monitor %d").printf (i + 1) ); - } - } catch (Error e) { - critical (e.message); - for (i = 0; i < default_display.get_n_monitors () ; i ++) { - monitor.append_text (_("Display %d").printf (i + 1)); + // TODO: get primary display + + for (index = 0; index < monitors.get_n_items (); index++) { + var monitor = (Gdk.Monitor) monitors.get_item (index); + if (monitor.connector != null || monitor.connector != "") { + monitor_combo.append_text (monitor.connector); + } else { + monitor_combo.insert_text (index + 1, "Display %d"); } } - if (i <= 1) { + if (index <= 1) { primary_monitor_label.hide (); primary_monitor.hide (); monitor_label.hide (); - monitor.no_show_all = true; - monitor.hide (); + monitor_combo.hide (); } else { - if (dock_preferences.Monitor != "") { - monitor.active = find_monitor_number (get_display (), dock_preferences.Monitor); + if (dock_preferences.get_string ("monitor") != "") { + monitor_combo.active = find_monitor_number (get_display (), dock_preferences.get_string ("monitor")); } else { - monitor.active = primary_screen; + monitor_combo.active = primary_screen; } primary_monitor_label.show (); primary_monitor.show (); monitor_label.show (); - monitor.show (); + monitor_combo.show (); } } static string[] get_monitor_plug_names (Gdk.Display display) { - int n_monitors = display.get_n_monitors (); + var monitors = display.get_monitors (); + var n_monitors = monitors.get_n_items (); var result = new string[n_monitors]; for (int i = 0; i < n_monitors; i++) { - result[i] = display.get_monitor (i).model; + result[i] = ((Gdk.Monitor) monitors.get_item (i)).model; } return result; } static int find_monitor_number (Gdk.Display display, string plug_name) { - int n_monitors = display.get_n_monitors (); + var monitors = display.get_monitors (); - for (int i = 0; i < n_monitors; i++) { - var monitor = display.get_monitor (i); + for (int i = 0; i < monitors.get_n_items (); i++) { + var monitor = (Gdk.Monitor) monitors.get_item (i); var name = monitor.get_model (); if (plug_name == name) return i; } - return display.get_n_monitors (); + return (int) monitors.get_n_items (); } + ~Dock () { + this.get_last_child ().unparent (); + } } diff --git a/src/Views/Multitasking.vala b/src/Views/Multitasking.vala index 230bdbf91..ca3ed9b51 100644 --- a/src/Views/Multitasking.vala +++ b/src/Views/Multitasking.vala @@ -19,11 +19,15 @@ * Authored by: Tom Beckmann */ -public class PantheonShell.Multitasking : Gtk.Box { +public class PantheonShell.Multitasking : Gtk.Widget { private GLib.Settings behavior_settings; private const string ANIMATIONS_SCHEMA = "org.pantheon.desktop.gala.animations"; private const string ANIMATIONS_KEY = "enable-animations"; + static construct { + set_layout_manager_type (typeof (Gtk.BinLayout)); + } + construct { var hotcorner_title = new Gtk.Label (_("When the pointer enters a display corner")) { halign = Gtk.Align.START, @@ -44,12 +48,12 @@ public class PantheonShell.Multitasking : Gtk.Box { var fullscreen_checkbutton = new Gtk.CheckButton.with_label (_("When entering fullscreen")); var maximize_checkbutton = new Gtk.CheckButton.with_label (_("When maximizing")); - var checkbutton_grid = new Gtk.Grid () { - column_spacing = 12, + var checkbutton_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12) { + margin_top = 12, margin_bottom = 12 }; - checkbutton_grid.add (fullscreen_checkbutton); - checkbutton_grid.add (maximize_checkbutton); + checkbutton_box.append (fullscreen_checkbutton); + checkbutton_box.append (maximize_checkbutton); var animations_label = new Gtk.Label (_("Window animations:")) { halign = Gtk.Align.END @@ -73,19 +77,20 @@ public class PantheonShell.Multitasking : Gtk.Box { grid.attach (bottomleft, 0, 3, 2); grid.attach (bottomright, 0, 4, 2); grid.attach (workspaces_label, 0, 6, 2); - grid.attach (checkbutton_grid, 0, 7, 2); + grid.attach (checkbutton_box, 0, 7, 2); grid.attach (animations_label, 0, 8); grid.attach (animations_switch, 1, 8); - var clamp = new Hdy.Clamp (); - clamp.add (grid); + var clamp = new Adw.Clamp () { + child = grid + }; - var scrolled = new Gtk.ScrolledWindow (null, null) { - hscrollbar_policy = Gtk.PolicyType.NEVER + var scrolled = new Gtk.ScrolledWindow () { + hscrollbar_policy = Gtk.PolicyType.NEVER, + child = clamp }; - scrolled.add (clamp); - add (scrolled); + scrolled.set_parent (this); var animations_settings = new GLib.Settings (ANIMATIONS_SCHEMA); animations_settings.bind (ANIMATIONS_KEY, animations_switch, "active", SettingsBindFlags.DEFAULT); @@ -95,6 +100,10 @@ public class PantheonShell.Multitasking : Gtk.Box { behavior_settings.bind ("move-maximized-workspace", maximize_checkbutton, "active", GLib.SettingsBindFlags.DEFAULT); } + ~Multitasking () { + this.get_last_child ().unparent (); + } + private class HotcornerControl : Gtk.Grid { public string label { get; construct; } public string position { get; construct; } @@ -146,9 +155,9 @@ public class PantheonShell.Multitasking : Gtk.Box { var command_revealer = new Gtk.Revealer () { margin_top = 6, - transition_type = Gtk.RevealerTransitionType.SLIDE_DOWN + transition_type = Gtk.RevealerTransitionType.SLIDE_DOWN, + child = command_entry }; - command_revealer.add (command_entry); margin_bottom = 12; column_spacing = 12; diff --git a/src/Views/Text.vala b/src/Views/Text.vala index 8d088e6a0..08de31bf1 100644 --- a/src/Views/Text.vala +++ b/src/Views/Text.vala @@ -18,7 +18,7 @@ * */ -public class PantheonShell.Text : Gtk.Box { +public class PantheonShell.Text : Gtk.Widget { private const string DYSLEXIA_KEY = "dyslexia-friendly-support"; private const string FONT_KEY = "font-name"; private const string DOCUMENT_FONT_KEY = "document-font-name"; @@ -30,6 +30,10 @@ public class PantheonShell.Text : Gtk.Box { private uint scale_timeout; + static construct { + set_layout_manager_type (typeof (Gtk.BinLayout)); + } + construct { var size_label = new Gtk.Label (_("Size:")) { halign = Gtk.Align.END @@ -62,7 +66,7 @@ public class PantheonShell.Text : Gtk.Box { wrap = true, xalign = 0 }; - dyslexia_font_description_label.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL); + dyslexia_font_description_label.get_style_context ().add_class (Granite.STYLE_CLASS_DIM_LABEL); var grid = new Gtk.Grid () { column_spacing = 12, @@ -78,10 +82,11 @@ public class PantheonShell.Text : Gtk.Box { grid.attach (dyslexia_font_switch, 1, 1); grid.attach (dyslexia_font_description_label, 1, 2, 2); - var clamp = new Hdy.Clamp (); - clamp.add (grid); + var clamp = new Adw.Clamp () { + child = grid + }; - add (clamp); + clamp.set_parent (this); var interface_settings = new Settings ("org.gnome.desktop.interface"); interface_settings.bind ("text-scaling-factor", size_adjustment, "value", SettingsBindFlags.GET); @@ -118,4 +123,8 @@ public class PantheonShell.Text : Gtk.Box { return Gdk.EVENT_PROPAGATE; }); } + + ~Text () { + this.get_last_child ().unparent (); + } } diff --git a/src/Views/Wallpaper.vala b/src/Views/Wallpaper.vala index eac24b515..f87d51d95 100644 --- a/src/Views/Wallpaper.vala +++ b/src/Views/Wallpaper.vala @@ -1,5 +1,5 @@ -/*- - * Copyright (c) 2015-2016 elementary LLC. +/* + * Copyright 2015-2022 elementary, Inc. (https://elementary.io) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,12 +22,7 @@ interface PantheonShell.AccountsServiceUser : Object { public abstract string background_file { owned get; set; } } -public class PantheonShell.Wallpaper : Gtk.Grid { - public enum ColumnType { - ICON, - NAME - } - +public class PantheonShell.Wallpaper : Gtk.Box { private const string [] REQUIRED_FILE_ATTRS = { FileAttribute.STANDARD_NAME, FileAttribute.STANDARD_TYPE, @@ -39,11 +34,18 @@ public class PantheonShell.Wallpaper : Gtk.Grid { FileAttribute.THUMBNAIL_IS_VALID }; - public Switchboard.Plug plug { get; construct set; } - private GLib.Settings settings; + private const string [] ALLOWED_MIMETYPES = { + "image/jpeg", + "image/png", + "image/tiff", + "image/svg+xml", + "image/gif" + }; + + private static GLib.Settings settings; - //Instance of the AccountsServices-Interface for this user - private AccountsServiceUser? accountsservice = null; + // Instance of the AccountsServices-Interface for this user + private static AccountsServiceUser? accountsservice = null; private Gtk.ScrolledWindow wallpaper_scrolled_window; private Gtk.FlowBox wallpaper_view; @@ -51,271 +53,241 @@ public class PantheonShell.Wallpaper : Gtk.Grid { private Gtk.ComboBoxText combo; private Gtk.ColorButton color_button; - private WallpaperContainer active_wallpaper = null; + private GenericContainer? previous_wallpaper { get; set; default = null; } + private SolidColorContainer solid_color = null; - private WallpaperContainer wallpaper_for_removal = null; + private UriContainer? wallpaper_for_removal = null; private Cancellable last_cancellable; - private string current_wallpaper_path; - private bool prevent_update_mode = false; // When restoring the combo state, don't trigger the update. - private bool finished; // Shows that we got or wallpapers together - - public Wallpaper (Switchboard.Plug _plug) { - Object (plug: _plug); - } - - construct { + static construct { settings = new GLib.Settings ("org.gnome.desktop.background"); // DBus connection needed in update_wallpaper for // passing the wallpaper-information to accountsservice. - try { - int uid = (int)Posix.getuid (); - accountsservice = Bus.get_proxy_sync (BusType.SYSTEM, - "org.freedesktop.Accounts", - "/org/freedesktop/Accounts/User%i".printf (uid)); - } catch (Error e) { + try { + int uid = (int) Posix.getuid (); + accountsservice = Bus.get_proxy_sync ( + BusType.SYSTEM, + "org.freedesktop.Accounts", + "/org/freedesktop/Accounts/User%i".printf (uid) + ); + } catch (IOError e) { warning (e.message); } + } + construct { var separator = new Gtk.Separator (Gtk.Orientation.HORIZONTAL); - wallpaper_view = new Gtk.FlowBox (); - wallpaper_view.activate_on_single_click = true; - wallpaper_view.get_style_context ().add_class (Gtk.STYLE_CLASS_VIEW); - wallpaper_view.homogeneous = true; - wallpaper_view.selection_mode = Gtk.SelectionMode.SINGLE; - wallpaper_view.child_activated.connect (update_checked_wallpaper); - wallpaper_view.set_sort_func (wallpapers_sort_function); + var drop_target = new Gtk.DropTarget (typeof (Gdk.FileList), Gdk.DragAction.COPY); - var color = settings.get_string ("primary-color"); - create_solid_color_container (color); - - Gtk.TargetEntry e = {"text/uri-list", 0, 0}; - wallpaper_view.drag_data_received.connect (on_drag_data_received); - Gtk.drag_dest_set (wallpaper_view, Gtk.DestDefaults.ALL, {e}, Gdk.DragAction.COPY); - - wallpaper_scrolled_window = new Gtk.ScrolledWindow (null, null); - wallpaper_scrolled_window.expand = true; - wallpaper_scrolled_window.add (wallpaper_view); - - view_overlay = new Gtk.Overlay (); - view_overlay.add (wallpaper_scrolled_window); - - var add_wallpaper_button = new Gtk.Button.with_label (_("Import Photo…")); - add_wallpaper_button.margin = 12; - - combo = new Gtk.ComboBoxText (); - combo.valign = Gtk.Align.CENTER; + wallpaper_view = new Gtk.FlowBox () { + activate_on_single_click = true, + selection_mode = Gtk.SelectionMode.SINGLE, + homogeneous = true, + row_spacing = 18, + column_spacing = 18 + }; + wallpaper_view.add_css_class (Granite.STYLE_CLASS_VIEW); + wallpaper_view.set_sort_func (wallpapers_sort_function); + wallpaper_view.add_controller (drop_target); + + wallpaper_scrolled_window = new Gtk.ScrolledWindow () { + hexpand = true, + vexpand = true, + child = wallpaper_view + }; + + view_overlay = new Gtk.Overlay () { + child = wallpaper_scrolled_window + }; + + var import_button = new Gtk.Button.with_label (_("Import Photo…")) { + margin_start = 12, + margin_end = 12, + margin_top = 12, + margin_bottom = 12 + }; + + combo = new Gtk.ComboBoxText () { + valign = Gtk.Align.CENTER + }; combo.append ("centered", _("Centered")); combo.append ("zoom", _("Zoom")); combo.append ("spanned", _("Spanned")); - combo.changed.connect (update_mode); - Gdk.RGBA rgba_color = {}; - if (!rgba_color.parse (color)) { - rgba_color = { 1, 1, 1, 1 }; - } - - color_button = new Gtk.ColorButton (); - color_button.margin = 12; - color_button.margin_start = 0; - color_button.rgba = rgba_color; - color_button.color_set.connect (update_color); + color_button = new Gtk.ColorButton () { + margin_start = 6, + margin_end = 12, + margin_top = 12, + margin_bottom = 12 + }; var size_group = new Gtk.SizeGroup (Gtk.SizeGroupMode.HORIZONTAL); - size_group.add_widget (add_wallpaper_button); + size_group.add_widget (import_button); size_group.add_widget (combo); size_group.add_widget (color_button); - load_settings (); - var actionbar = new Gtk.ActionBar (); - actionbar.get_style_context ().add_class (Gtk.STYLE_CLASS_INLINE_TOOLBAR); - actionbar.pack_start (add_wallpaper_button); + actionbar.add_css_class ("inline-toolbar"); + actionbar.pack_start (import_button); actionbar.pack_end (color_button); actionbar.pack_end (combo); - attach (separator, 0, 0, 1, 1); - attach (view_overlay, 0, 1, 1, 1); - attach (actionbar, 0, 2, 1, 1); - - add_wallpaper_button.clicked.connect (show_wallpaper_chooser); - } - - private void show_wallpaper_chooser () { - var filter = new Gtk.FileFilter (); - filter.add_mime_type ("image/*"); + orientation = Gtk.Orientation.VERTICAL; + append (separator); + append (view_overlay); + append (actionbar); - var preview_area = new Granite.AsyncImage (false); - preview_area.pixel_size = 256; - preview_area.margin_end = 12; + load_settings (); - var chooser = new Gtk.FileChooserNative ( - _("Import Photo"), null, Gtk.FileChooserAction.OPEN, - _("Import"), - _("Cancel") - ); - chooser.filter = filter; - chooser.select_multiple = true; - chooser.set_preview_widget (preview_area); - - chooser.update_preview.connect (() => { - string uri = chooser.get_preview_uri (); - - if (uri != null && uri.has_prefix ("file://") == true) { - var file = GLib.File.new_for_uri (uri); - preview_area.set_from_gicon_async.begin (new FileIcon (file), 256); - preview_area.show (); - } else { - preview_area.hide (); - } + // connect signals + drop_target.on_drop.connect (on_drag_data_received); + wallpaper_view.child_activated.connect (update_checked_wallpaper); + import_button.clicked.connect (show_wallpaper_chooser); + combo.changed.connect (() => { + settings.set_string ("picture-options", combo.get_active_id ()); + }); + color_button.color_set.connect (() => { + settings.set_string ("primary-color", color_button.rgba.to_string ()); + wallpaper_view.child_activated (solid_color); }); - - if (chooser.run () == Gtk.ResponseType.ACCEPT) { - SList uris = chooser.get_uris (); - foreach (unowned string uri in uris) { - var file = GLib.File.new_for_uri (uri); - string local_uri = uri; - var dest = copy_for_library (file); - if (dest != null) { - local_uri = dest.get_uri (); - } - - add_wallpaper_from_file (file, local_uri); - } - } - - chooser.destroy (); } private void load_settings () { // TODO: need to store the previous state, before changing to none // when a solid color is selected, because the combobox doesn't know // about it anymore. The previous state should be loaded instead here. - string picture_options = settings.get_string ("picture-options"); + + var picture_options = settings.get_string ("picture-options"); if (picture_options == "none") { - combo.set_sensitive (false); + combo.sensitive = false; picture_options = "zoom"; } - prevent_update_mode = true; - combo.set_active_id (picture_options); + combo.active_id = picture_options; - current_wallpaper_path = settings.get_string ("picture-uri"); + // load color button + var color = settings.get_string ("primary-color"); + Gdk.RGBA rgba_color = {}; + if (!rgba_color.parse (color)) { + rgba_color = { 1, 1, 1, 1 }; + } + color_button.rgba = rgba_color; } - /* - * This integrates with LightDM - */ - private void update_accountsservice () { - var file = File.new_for_uri (current_wallpaper_path); - string uri = file.get_uri (); - string path = file.get_path (); - - bool path_has_prefix_bg_dir = false; - foreach (unowned string directory in get_bg_directories ()) { - if (path.has_prefix (directory)) { - path_has_prefix_bg_dir = true; - break; - } + private void show_wallpaper_chooser () { + var filter = new Gtk.FileFilter (); + foreach (var type in ALLOWED_MIMETYPES) { + filter.add_mime_type (type); } - if (!path_has_prefix_bg_dir) { - var local_file = copy_for_library (file); - if (local_file != null) { - uri = local_file.get_uri (); - } - } + var chooser = new Gtk.FileChooserNative ( + _("Import Photo"), + (Gtk.Window) get_root (), + Gtk.FileChooserAction.OPEN, + _("Import"), + _("Cancel") + ) { + filter = filter, + select_multiple = true, + modal = true + }; + + chooser.response.connect ((id) => { + chooser.destroy (); + if (id == Gtk.ResponseType.ACCEPT) { + var files = chooser.get_files (); + for (var iter = 0; iter < files.get_n_items (); iter++) { + var file = (File) files.get_item (iter); + var local_uri = file.get_uri (); + var dest = copy_for_library (file); + if (dest != null) { + local_uri = dest.get_uri (); + } - var greeter_file = copy_for_greeter (file); - if (greeter_file != null) { - path = greeter_file.get_path (); - } + add_wallpaper_from_uri (local_uri); + } + } + }); - settings.set_string ("picture-uri", uri); - accountsservice.background_file = path; + chooser.show (); } - private void update_checked_wallpaper (Gtk.FlowBox box, Gtk.FlowBoxChild child) { - var children = (WallpaperContainer) wallpaper_view.get_selected_children ().data; + private void update_checked_wallpaper (Gtk.FlowBoxChild _selected_child) { + // We don't do gradient backgrounds, reset the key that might interfere + settings.reset ("color-shading-type"); - if (!(children is SolidColorContainer)) { - current_wallpaper_path = children.uri; - update_accountsservice (); + if (previous_wallpaper != null) { + previous_wallpaper.checked = false; + } - if (active_wallpaper == solid_color) { - combo.set_sensitive (true); - settings.set_string ("picture-options", combo.get_active_id ()); - } + var selected_child = (GenericContainer) _selected_child; + previous_wallpaper = selected_child; - } else { - set_combo_disabled_if_necessary (); - settings.set_string ("primary-color", solid_color.color); - } + selected_child.checked = true; - // We don't do gradient backgrounds, reset the key that might interfere - settings.reset ("color-shading-type"); + if (selected_child is SolidColorContainer) { + combo.sensitive = false; - children.checked = true; + settings.set_string ("picture-options", "none"); + } else if (selected_child is UriContainer) { + combo.sensitive = true; - if (active_wallpaper != null && active_wallpaper != children) { - active_wallpaper.checked = false; + settings.set_string ("picture-uri", ((UriContainer) selected_child).uri); + settings.set_string ("picture-options", combo.active_id); + update_accountsservice (); } - - active_wallpaper = children; } - private void update_color () { - if (finished) { - set_combo_disabled_if_necessary (); - create_solid_color_container (color_button.rgba.to_string ()); - wallpaper_view.add (solid_color); - wallpaper_view.select_child (solid_color); - - if (active_wallpaper != null) { - active_wallpaper.checked = false; - } + /* + * This integrates with LightDM + */ + private void update_accountsservice () { + var file = File.new_for_uri (settings.get_string ("picture-uri")); + var path = file.get_path (); - active_wallpaper = solid_color; - active_wallpaper.checked = true; - settings.set_string ("primary-color", solid_color.color); + var greeter_file = copy_for_greeter (file); + if (greeter_file != null) { + path = greeter_file.get_path (); } + + accountsservice.background_file = path; } - private void update_mode () { - if (!prevent_update_mode) { - settings.set_string ("picture-options", combo.get_active_id ()); + public void load_wallpapers () { + clean_wallpapers (); + + load_wallpapers_from_folders.begin ((obj, res) => { + solid_color = new SolidColorContainer (); + wallpaper_view.append (solid_color); - // Changing the mode, while a solid color is selected, change focus to the - // wallpaper tile. - if (active_wallpaper == solid_color) { - active_wallpaper.checked = false; - - foreach (var child in wallpaper_view.get_children ()) { - var container = (WallpaperContainer) child; - if (container.uri == current_wallpaper_path) { - container.checked = true; - wallpaper_view.select_child (container); - active_wallpaper = container; - break; + // Select current wallpaper + if (settings.get_string ("picture-options") == "none") { + wallpaper_view.child_activated (solid_color); + } else { + var children = wallpaper_view.observe_children (); + for (var i = 0; i < children.get_n_items (); i++) { + var child = (GenericContainer) children.get_item (i); + if (child is UriContainer && settings.get_string ("picture-uri") == ((UriContainer) child).uri) { + wallpaper_view.child_activated (child); } } } - } else { - prevent_update_mode = false; - } + }); } - private void set_combo_disabled_if_necessary () { - if (active_wallpaper != solid_color) { - combo.set_sensitive (false); - settings.set_string ("picture-options", "none"); + private void clean_wallpapers () { + var child = wallpaper_view.get_first_child (); + while (child != null) { + wallpaper_view.remove (child); + child.destroy (); + child = wallpaper_view.get_first_child (); } } - public void update_wallpaper_folder () { + private async void load_wallpapers_from_folders () { if (last_cancellable != null) { last_cancellable.cancel (); } @@ -323,29 +295,26 @@ public class PantheonShell.Wallpaper : Gtk.Grid { var cancellable = new Cancellable (); last_cancellable = cancellable; - clean_wallpapers (); - foreach (unowned string directory in get_bg_directories ()) { - load_wallpapers.begin (directory, cancellable); + yield load_wallpapers_from_folder (directory); } } - private async void load_wallpapers (string basefolder, Cancellable cancellable, bool toplevel_folder = true) { - if (cancellable.is_cancelled ()) { + private async void load_wallpapers_from_folder (string folder) { + if (last_cancellable.is_cancelled ()) { return; } - var directory = File.new_for_path (basefolder); - + var directory = File.new_for_path (folder); try { // Enumerator object that will let us read through the wallpapers asynchronously var attrs = string.joinv (",", REQUIRED_FILE_ATTRS); - var e = yield directory.enumerate_children_async (attrs, 0, Priority.DEFAULT); - FileInfo file_info; + var e = yield directory.enumerate_children_async (attrs, FileQueryInfoFlags.NONE, Priority.DEFAULT); + FileInfo? file_info = null; // Loop through and add each wallpaper in the batch while ((file_info = e.next_file ()) != null) { - if (cancellable.is_cancelled ()) { + if (last_cancellable.is_cancelled ()) { ThumbnailGenerator.get_default ().dequeue_all (); return; } @@ -357,70 +326,30 @@ public class PantheonShell.Wallpaper : Gtk.Grid { if (file_info.get_file_type () == FileType.DIRECTORY) { // Spawn off another loader for the subdirectory var subdir = directory.resolve_relative_path (file_info.get_name ()); - yield load_wallpapers (subdir.get_path (), cancellable, false); - continue; - } else if (!IOHelper.is_valid_file_type (file_info)) { - // Skip non-picture files + yield load_wallpapers_from_folder (subdir.get_path ()); continue; } var file = directory.resolve_relative_path (file_info.get_name ()); - string uri = file.get_uri (); + var uri = file.get_uri (); - add_wallpaper_from_file (file, uri); + add_wallpaper_from_uri (uri); } - - if (toplevel_folder) { - create_solid_color_container (color_button.rgba.to_string ()); - wallpaper_view.add (solid_color); - finished = true; - - if (settings.get_string ("picture-options") == "none") { - wallpaper_view.select_child (solid_color); - solid_color.checked = true; - active_wallpaper = solid_color; - } - - if (active_wallpaper != null) { - Gtk.Allocation alloc; - active_wallpaper.get_allocation (out alloc); - wallpaper_scrolled_window.get_vadjustment ().value = alloc.y; - } - } - } catch (Error err) { - if (!(err is IOError.NOT_FOUND)) { - warning (err.message); + } catch (Error e) { + if (!(e is IOError.NOT_FOUND)) { + warning (e.message); } } } - private void create_solid_color_container (string color) { - if (solid_color != null) { - wallpaper_view.unselect_child (solid_color); - wallpaper_view.remove (solid_color); - solid_color.destroy (); - } - - solid_color = new SolidColorContainer (color); - solid_color.show_all (); - } - - private void clean_wallpapers () { - foreach (var child in wallpaper_view.get_children ()) { - child.destroy (); - } - - solid_color = null; - } - private static string get_local_bg_directory () { - return Path.build_filename (Environment.get_user_data_dir (), "backgrounds") + "/"; + return Path.build_filename (Environment.get_user_data_dir (), "backgrounds"); } private static string[] get_system_bg_directories () { string[] directories = {}; foreach (unowned string data_dir in Environment.get_system_data_dirs ()) { - var system_background_dir = Path.build_filename (data_dir, "backgrounds") + "/"; + var system_background_dir = Path.build_filename (data_dir, "backgrounds"); if (FileUtils.test (system_background_dir, FileTest.EXISTS)) { debug ("Found system background directory: %s", system_background_dir); directories += system_background_dir; @@ -450,10 +379,9 @@ public class PantheonShell.Wallpaper : Gtk.Grid { private static File? copy_for_library (File source) { File? dest = null; - string local_bg_directory = get_local_bg_directory (); + var local_bg_directory = get_local_bg_directory (); try { - File folder = File.new_for_path (local_bg_directory); - folder.make_directory_with_parents (); + File.new_for_path (local_bg_directory).make_directory_with_parents (); } catch (Error e) { if (e is GLib.IOError.EXISTS) { debug ("Local background directory already exists"); @@ -477,6 +405,7 @@ public class PantheonShell.Wallpaper : Gtk.Grid { private static File? copy_for_greeter (File source) { File? dest = null; + try { string greeter_data_dir = Path.build_filename (Environment.get_variable ("XDG_GREETER_DATA_DIR"), "wallpaper"); if (greeter_data_dir == "") { @@ -506,85 +435,134 @@ public class PantheonShell.Wallpaper : Gtk.Grid { return dest; } - private void on_drag_data_received (Gtk.Widget widget, Gdk.DragContext ctx, int x, int y, Gtk.SelectionData sel, uint information, uint timestamp) { - if (sel.get_length () > 0) { - try { - var file = File.new_for_uri (sel.get_uris ()[0]); - var info = file.query_info (string.joinv (",", REQUIRED_FILE_ATTRS), 0); + private bool on_drag_data_received (Value val, double x, double y) { + var file_list = (Gdk.FileList) val; + foreach (var file in file_list.get_files ()) { + var local_uri = file.get_uri (); + var dest = copy_for_library (file); + if (dest != null) { + local_uri = dest.get_uri (); + } - if (!IOHelper.is_valid_file_type (info)) { - Gtk.drag_finish (ctx, false, false, timestamp); - return; - } + add_wallpaper_from_uri (local_uri); + } - string local_uri = file.get_uri (); - var dest = copy_for_library (file); - if (dest != null) { - local_uri = dest.get_uri (); - } + return true; + } - add_wallpaper_from_file (file, local_uri); + private void add_wallpaper_from_uri (string uri) { + var file = File.new_for_uri (uri); - Gtk.drag_finish (ctx, true, false, timestamp); - } catch (Error e) { - warning (e.message); - } + string? mime_type; + try { + var file_info = file.query_info (FileAttribute.STANDARD_CONTENT_TYPE, FileQueryInfoFlags.NONE, null); + mime_type = file_info.get_content_type ().to_ascii (); + } catch (Error e) { + warning ("Could not get mime type for file \"%s\": %s", uri, e.message); + return; } - Gtk.drag_finish (ctx, false, false, timestamp); - return; - } + if (!(mime_type in ALLOWED_MIMETYPES)) { + warning ("File with not allowed mimetype: %s, %s", uri, mime_type); + return; + } - private void add_wallpaper_from_file (GLib.File file, string uri) { // don't load 'removed' wallpaper on plug reload if (wallpaper_for_removal != null && wallpaper_for_removal.uri == uri) { return; } - try { - var info = file.query_info (string.joinv (",", REQUIRED_FILE_ATTRS), 0); - var thumb_path = info.get_attribute_as_string (FileAttribute.THUMBNAIL_PATH); - var thumb_valid = info.get_attribute_boolean (FileAttribute.THUMBNAIL_IS_VALID); - var wallpaper = new WallpaperContainer (uri, thumb_path, thumb_valid); - wallpaper_view.add (wallpaper); + UriContainer wallpaper; + if (mime_type.has_prefix ("image/")) { + wallpaper = new ImageContainer (uri); - wallpaper.show_all (); + // TODO: https://github.com/elementary/switchboard-plug-pantheon-shell/issues/296 + // } else if (mime_type == "application/xml") { + // wallpaper = new XMLContainer (uri); + // ... - wallpaper.trash.connect (() => { - send_undo_toast (); - mark_for_removal (wallpaper); - }); + } else { + // Fixes Use of possibly unassigned local variable `wallpaper' + // However, the file should never get here since it's been filtered before + warning ("Filtered file %s of unknown type %s", uri, mime_type); + return; + } - // Select the wallpaper if it is the current wallpaper - if (current_wallpaper_path.has_suffix (uri) && settings.get_string ("picture-options") != "none") { - this.wallpaper_view.select_child (wallpaper); - // Set the widget activated without activating it - wallpaper.checked = true; - active_wallpaper = wallpaper; + wallpaper_view.append (wallpaper); + + wallpaper.trash.connect (() => { + send_undo_toast (); + mark_for_removal (wallpaper); + }); + } + + private void send_undo_toast () { + var children = view_overlay.observe_children (); + for (int i = 0; i < children.get_n_items (); i++) { + var child = (Gtk.Widget) children.get_item (i); + if (child is Granite.Toast) { + view_overlay.remove_overlay (child); + child.destroy (); } - } catch (Error e) { - critical ("Unable to add wallpaper: %s", e.message); } - wallpaper_view.invalidate_sort (); + if (wallpaper_for_removal != null) { + confirm_removal (); + } + + var toast = new Granite.Toast (_("Wallpaper Deleted")); + toast.set_default_action (_("Undo")); + + toast.default_action.connect (() => { + undo_removal (); + }); + + var toast_revealer = (Gtk.Revealer) toast.get_first_child (); + toast_revealer.notify["reveal-child"].connect (() => { + // give some time for undo action to emit + Idle.add (() => { + if (!toast_revealer.reveal_child && wallpaper_for_removal != null) { + confirm_removal (); + } + return Source.REMOVE; + }); + }); + + view_overlay.add_overlay (toast); + view_overlay.set_measure_overlay (toast, true); + toast.send_notification (); } - public void cancel_thumbnail_generation () { - if (last_cancellable != null) { - last_cancellable.cancel (); - } + private void mark_for_removal (UriContainer wallpaper) { + wallpaper_for_removal = wallpaper; + wallpaper.hide (); + } + + private void confirm_removal () { + var wallpaper_file = File.new_for_uri (wallpaper_for_removal.uri); + wallpaper_file.trash_async.begin (); + wallpaper_for_removal.destroy (); + wallpaper_for_removal = null; + } + + private void undo_removal () { + wallpaper_for_removal.show (); + wallpaper_for_removal = null; } private int wallpapers_sort_function (Gtk.FlowBoxChild _child1, Gtk.FlowBoxChild _child2) { - var child1 = (WallpaperContainer) _child1; - var child2 = (WallpaperContainer) _child2; - var uri1 = child1.uri; - var uri2 = child2.uri; + var child1 = (GenericContainer) _child1; + var child2 = (GenericContainer) _child2; - if (uri1 == null || uri2 == null) { - return 0; + if (child1 is SolidColorContainer) { + return 1; + } else if (child2 is SolidColorContainer) { + return -1; } + var uri1 = ((UriContainer) child1).uri; + var uri2 = ((UriContainer) child2).uri; + var uri1_is_system = false; var uri2_is_system = false; foreach (var bg_dir in get_system_bg_directories ()) { @@ -600,8 +578,8 @@ public class PantheonShell.Wallpaper : Gtk.Grid { return -1; } - var child1_date = child1.creation_date; - var child2_date = child2.creation_date; + var child1_date = ((UriContainer) child1).creation_date; + var child2_date = ((UriContainer) child2).creation_date; // sort by filename if creation dates are equal if (child1_date == child2_date) { @@ -616,49 +594,9 @@ public class PantheonShell.Wallpaper : Gtk.Grid { } } - private void send_undo_toast () { - foreach (weak Gtk.Widget child in view_overlay.get_children ()) { - if (child is Granite.Widgets.Toast) { - child.destroy (); - } - } - - if (wallpaper_for_removal != null) { - confirm_removal (); + public void cancel_thumbnail_generation () { + if (last_cancellable != null) { + last_cancellable.cancel (); } - - var toast = new Granite.Widgets.Toast (_("Wallpaper Deleted")); - toast.set_default_action (_("Undo")); - toast.show_all (); - - toast.default_action.connect (() => { - undo_removal (); - }); - - toast.notify["child-revealed"].connect (() => { - if (!toast.child_revealed && wallpaper_for_removal != null) { - confirm_removal (); - } - }); - - view_overlay.add_overlay (toast); - toast.send_notification (); - } - - private void mark_for_removal (WallpaperContainer wallpaper) { - wallpaper_view.remove (wallpaper); - wallpaper_for_removal = wallpaper; - } - - private void confirm_removal () { - var wallpaper_file = File.new_for_uri (wallpaper_for_removal.uri); - wallpaper_file.trash_async.begin (); - wallpaper_for_removal.destroy (); - wallpaper_for_removal = null; - } - - private void undo_removal () { - wallpaper_view.add (wallpaper_for_removal); - wallpaper_for_removal = null; } } diff --git a/src/Widgets/GenericContainer.vala b/src/Widgets/GenericContainer.vala new file mode 100644 index 000000000..b81a5c724 --- /dev/null +++ b/src/Widgets/GenericContainer.vala @@ -0,0 +1,139 @@ +/*- + * Copyright 2022 elementary, Inc. (https://elementary.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Authored by: Erasmo Marín + * + */ + +public class PantheonShell.GenericContainer : Gtk.FlowBoxChild { + public signal void trash (); + + protected const int THUMB_WIDTH = 162; + protected const int THUMB_HEIGHT = 100; + + private static Gtk.CssProvider check_css_provider; + private static Gtk.CssProvider move_to_trash_provider; + private static Gtk.CheckButton check_group; // used for turning CheckButtons into RadioButtons + + protected Gtk.Box card_box; + protected Gtk.Picture image; + private Gtk.Revealer check_revealer; + protected Gtk.Overlay overlay; + protected Gtk.Box context_menu_box; + protected Gtk.Popover context_menu; + protected Gtk.Button move_to_trash_button; + + public bool checked { + get { + return check_revealer.reveal_child; + } + set { + check_revealer.reveal_child = value; + if (value) { + card_box.set_state_flags (Gtk.StateFlags.CHECKED, false); + } else { + card_box.unset_state_flags (Gtk.StateFlags.CHECKED); + } + } + } + + static construct { + check_css_provider = new Gtk.CssProvider (); + check_css_provider.load_from_resource ("/io/elementary/switchboard/plug/pantheon-shell/Check.css"); + + move_to_trash_provider = new Gtk.CssProvider (); + move_to_trash_provider.load_from_resource ("/io/elementary/switchboard/plug/pantheon-shell/RemoveButton.css"); + + check_group = new Gtk.CheckButton (); + } + + construct { + image = new Gtk.Picture () { + can_shrink = true, + keep_aspect_ratio = false + }; + + card_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); + card_box.append (image); + card_box.add_css_class (Granite.STYLE_CLASS_CARD); + card_box.add_css_class (Granite.STYLE_CLASS_ROUNDED); + + var check = new Gtk.CheckButton () { + halign = Gtk.Align.START, + valign = Gtk.Align.START, + focusable = false, + active = true, + group = check_group + }; + check.get_style_context ().add_provider (check_css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER); + + check_revealer = new Gtk.Revealer () { + transition_type = Gtk.RevealerTransitionType.CROSSFADE, + child = check + }; + + overlay = new Gtk.Overlay () { + child = card_box, + halign = Gtk.Align.CENTER + }; + overlay.add_overlay (check_revealer); + + var overlay_event_controller = new Gtk.GestureClick () { + button = Gdk.BUTTON_SECONDARY + }; + overlay.add_controller (overlay_event_controller); + + add_css_class ("wallpaper-container"); + child = overlay; + + // Context menu + move_to_trash_button = new Gtk.Button () { + child = new Granite.AccelLabel (_("Remove")), + sensitive = false + }; + move_to_trash_button.get_style_context ().add_provider (move_to_trash_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + move_to_trash_button.add_css_class (Granite.STYLE_CLASS_MENUITEM); + // remove background-color transition so it looks identical to menu item + move_to_trash_button.add_css_class ("remove-button"); + + context_menu_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0) { + margin_top = 3, + margin_bottom = 3 + }; + context_menu_box.append (move_to_trash_button); + + context_menu = new Gtk.Popover () { + child = context_menu_box, + autohide = true + }; + context_menu.set_parent (overlay); + + // signals + check.notify["active"].connect (() => { + check.active = true; + }); + + overlay_event_controller.pressed.connect ((n_press, x, y) => { + var rect = Gdk.Rectangle (); + rect = {(int) x, (int) y, 1, 1}; + + context_menu.pointing_to = rect; + context_menu.popup (); + }); + + move_to_trash_button.clicked.connect (() => trash ()); + } +} diff --git a/src/Widgets/ImageContainer.vala b/src/Widgets/ImageContainer.vala new file mode 100644 index 000000000..5c87df97a --- /dev/null +++ b/src/Widgets/ImageContainer.vala @@ -0,0 +1,103 @@ +/*- + * Copyright 2015-2022 elementary, Inc. (https://elementary.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Authored by: Erasmo Marín + * + */ + +public class PantheonShell.ImageContainer : UriContainer { + private bool thumb_valid { get; set; default = false; } + private string? thumb_path { get; set; default = null; } + + public ImageContainer (string uri) { + Object (uri: uri); + } + + construct { + // load file info + var file = File.new_for_uri (uri); + try { + var info = file.query_info ("*", FileQueryInfoFlags.NONE); + load_thumb_info (info); + move_to_trash_button.sensitive = info.get_attribute_boolean (GLib.FileAttribute.ACCESS_CAN_DELETE); + } catch (Error e) { + critical (e.message); + } + + // load thumb + if (thumb_valid && thumb_path != null) { + update_thumb.begin (); + } else { + generate_and_load_thumb (); + } + } + + private void load_thumb_info (FileInfo info) { + thumb_valid = info.get_attribute_boolean (FileAttribute.THUMBNAIL_IS_VALID); + thumb_path = info.get_attribute_as_string (FileAttribute.THUMBNAIL_PATH); + } + + private async void update_thumb () { + if (!thumb_valid || thumb_path == null) { + return; + } + + image.set_filename (thumb_path); + + load_artist_tooltip (); + } + + private void generate_and_load_thumb () { + var scale = get_style_context ().get_scale (); + ThumbnailGenerator.get_default ().get_thumbnail (uri, THUMB_WIDTH * scale, () => { + try { + var file = File.new_for_uri (uri); + var info = file.query_info ( + string.join (",", FileAttribute.THUMBNAIL_PATH, FileAttribute.THUMBNAIL_IS_VALID), + FileQueryInfoFlags.NONE + ); + load_thumb_info (info); + update_thumb.begin (); + } catch (Error e) { + warning ("Error loading thumbnail for \"%s\": %s", uri, e.message); + } + }); + } + + private void load_artist_tooltip () { + var metadata = new GExiv2.Metadata (); + try { + var path = Filename.from_uri (uri); + metadata.open_path (path); + } catch (Error e) { + warning ("Error parsing exif metadata of \"%s\": %s", uri, e.message); + return; + } + + if (metadata.has_exif ()) { + string? artist_name = null; + try { + artist_name = metadata.try_get_tag_string ("Exif.Image.Artist"); + } catch (Error e) { + warning (e.message); + } + + if (artist_name != null) { + set_tooltip_text (_("Artist: %s").printf (artist_name)); + } + } + } +} diff --git a/src/Widgets/SolidColorContainer.vala b/src/Widgets/SolidColorContainer.vala index ba14817be..8f62884ca 100644 --- a/src/Widgets/SolidColorContainer.vala +++ b/src/Widgets/SolidColorContainer.vala @@ -1,5 +1,5 @@ /*- - * Copyright (c) 2015-2016 elementary LLC. + * Copyright 2015-2022 elementary, Inc. (https://elementary.io) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,27 +18,28 @@ * */ -public class PantheonShell.SolidColorContainer : WallpaperContainer { - public string color { get; construct; } - public Gdk.RGBA rgba { - get { - Gdk.RGBA rgba = {}; - rgba.parse (color); +public class PantheonShell.SolidColorContainer : GenericContainer { + private static GLib.Settings settings; - return rgba; - } + static construct { + settings = new GLib.Settings ("org.gnome.desktop.background"); } - public SolidColorContainer (string color_value) { - Object (color: color_value); + construct { + fill_thumb (); + settings.changed["primary-color"].connect (fill_thumb); } - construct { + private void fill_thumb () { + Gdk.RGBA rgba = {}; + rgba.parse (settings.get_string ("primary-color")); + var thumb = new Gdk.Pixbuf (Gdk.Colorspace.RGB, false, 8, THUMB_WIDTH, THUMB_HEIGHT); thumb.fill (rgba_to_pixel (rgba)); + image.set_pixbuf (thumb); } // Borrowed from - // https://github.com/GNOME/california/blob/master/src/util/util-gfx.vala + // https://gitlab.gnome.org/Archive/california/-/blob/master/src/util/util-gfx.vala private static uint32 rgba_to_pixel (Gdk.RGBA rgba) { return (uint32) fp_to_uint8 (rgba.red) << 24 | (uint32) fp_to_uint8 (rgba.green) << 16 diff --git a/src/Widgets/UriContainer.vala b/src/Widgets/UriContainer.vala new file mode 100644 index 000000000..bef45e6ee --- /dev/null +++ b/src/Widgets/UriContainer.vala @@ -0,0 +1,37 @@ +/*- + * Copyright 2022 elementary, Inc. (https://elementary.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +public class PantheonShell.UriContainer : GenericContainer { + public string uri { get; construct; } + + public uint64 creation_date { get; set; default = 0; } // in unix time + + public UriContainer (string uri) { + Object (uri: uri); + } + + construct { + var file = File.new_for_uri (uri); + try { + var info = file.query_info (FileAttribute.TIME_CREATED, FileQueryInfoFlags.NONE); + creation_date = info.get_attribute_uint64 (GLib.FileAttribute.TIME_CREATED); + } catch (Error e) { + critical (e.message); + } + } +} diff --git a/src/Widgets/WallpaperContainer.vala b/src/Widgets/WallpaperContainer.vala deleted file mode 100644 index ee7d6192c..000000000 --- a/src/Widgets/WallpaperContainer.vala +++ /dev/null @@ -1,217 +0,0 @@ -/*- - * Copyright 2015-2022 elementary, Inc. (https://elementary.io) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Authored by: Erasmo Marín - * - */ - -public class PantheonShell.WallpaperContainer : Gtk.FlowBoxChild { - public signal void trash (); - - private const int THUMB_WIDTH = 162; - private const int THUMB_HEIGHT = 100; - - private Gtk.Grid card_box; - private Gtk.Menu context_menu; - private Gtk.Revealer check_revealer; - private Granite.AsyncImage image; - - public string? thumb_path { get; construct set; } - public bool thumb_valid { get; construct; } - public string uri { get; construct; } - public Gdk.Pixbuf thumb { get; set; } - public uint64 creation_date = 0; - - private int scale; - - public bool checked { - get { - return Gtk.StateFlags.CHECKED in get_state_flags (); - } set { - if (value) { - card_box.set_state_flags (Gtk.StateFlags.CHECKED, false); - check_revealer.reveal_child = true; - } else { - card_box.unset_state_flags (Gtk.StateFlags.CHECKED); - check_revealer.reveal_child = false; - } - - queue_draw (); - } - } - - public bool selected { - get { - return Gtk.StateFlags.SELECTED in get_state_flags (); - } set { - if (value) { - set_state_flags (Gtk.StateFlags.SELECTED, false); - } else { - unset_state_flags (Gtk.StateFlags.SELECTED); - } - - queue_draw (); - } - } - - public WallpaperContainer (string uri, string? thumb_path, bool thumb_valid) { - Object (uri: uri, thumb_path: thumb_path, thumb_valid: thumb_valid); - } - - construct { - var style_context = get_style_context (); - style_context.add_class ("wallpaper-container"); - - scale = style_context.get_scale (); - - height_request = THUMB_HEIGHT + 18; - width_request = THUMB_WIDTH + 18; - - image = new Granite.AsyncImage (); - image.halign = Gtk.Align.CENTER; - image.valign = Gtk.Align.CENTER; - image.get_style_context ().set_scale (1); - - // We need an extra grid to not apply a scale == 1 to the "card" style. - card_box = new Gtk.Grid (); - card_box.get_style_context ().add_class ("card"); - card_box.add (image); - card_box.margin = 9; - - var check_provider = new Gtk.CssProvider (); - check_provider.load_from_resource ("/io/elementary/switchboard/plug/pantheon-shell/Check.css"); - - var check = new Gtk.RadioButton (null) { - halign = Gtk.Align.START, - valign = Gtk.Align.START, - can_focus = false - }; - check.get_style_context ().add_provider (check_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER); - - check_revealer = new Gtk.Revealer (); - check_revealer.transition_type = Gtk.RevealerTransitionType.CROSSFADE; - check_revealer.add (check); - - var overlay = new Gtk.Overlay (); - overlay.add (card_box); - overlay.add_overlay (check_revealer); - - var event_box = new Gtk.EventBox (); - event_box.add (overlay); - - halign = Gtk.Align.CENTER; - valign = Gtk.Align.CENTER; - margin = 6; - add (event_box); - - if (uri != null) { - var move_to_trash = new Gtk.MenuItem.with_label (_("Remove")); - move_to_trash.activate.connect (() => trash ()); - - var file = File.new_for_uri (uri); - try { - var info = file.query_info ("*", FileQueryInfoFlags.NONE); - creation_date = info.get_attribute_uint64 (GLib.FileAttribute.TIME_CREATED); - move_to_trash.sensitive = info.get_attribute_boolean (GLib.FileAttribute.ACCESS_CAN_DELETE); - } catch (Error e) { - critical (e.message); - } - - context_menu = new Gtk.Menu (); - context_menu.append (move_to_trash); - context_menu.show_all (); - } - - activate.connect (() => { - checked = true; - }); - - event_box.button_press_event.connect (show_context_menu); - - try { - if (uri != null) { - if (thumb_path != null && thumb_valid) { - update_thumb.begin (); - } else { - generate_and_load_thumb (); - } - } else { - thumb = new Gdk.Pixbuf (Gdk.Colorspace.RGB, false, 8, THUMB_WIDTH * scale, THUMB_HEIGHT * scale); - image.gicon = thumb; - } - } catch (Error e) { - critical ("Failed to load wallpaper thumbnail: %s", e.message); - return; - } - } - - private void generate_and_load_thumb () { - ThumbnailGenerator.get_default ().get_thumbnail (uri, THUMB_WIDTH * scale, () => { - try { - var file = File.new_for_uri (uri); - var info = file.query_info (FileAttribute.THUMBNAIL_PATH + "," + FileAttribute.THUMBNAIL_IS_VALID, 0); - thumb_path = info.get_attribute_as_string (FileAttribute.THUMBNAIL_PATH); - update_thumb.begin (); - } catch (Error e) { - warning ("Error loading thumbnail for '%s': %s", uri, e.message); - } - }); - } - - private void load_artist_tooltip () { - if (uri != null) { - string path = ""; - GExiv2.Metadata metadata; - try { - path = Filename.from_uri (uri); - metadata = new GExiv2.Metadata (); - metadata.open_path (path); - } catch (Error e) { - warning ("Error parsing exif metadata of \"%s\": %s", path, e.message); - return; - } - - if (metadata.has_exif ()) { - var artist_name = metadata.get_tag_string ("Exif.Image.Artist"); - if (artist_name != null) { - set_tooltip_text (_("Artist: %s").printf (artist_name)); - } - } - } - } - - private bool show_context_menu (Gtk.Widget sender, Gdk.EventButton evt) { - if (evt.type == Gdk.EventType.BUTTON_PRESS && evt.button == 3) { - context_menu.popup_at_pointer (null); - return Gdk.EVENT_STOP; - } - return Gdk.EVENT_PROPAGATE; - } - - private async void update_thumb () { - if (thumb_path == null) { - return; - } - - try { - yield image.set_from_file_async (File.new_for_path (thumb_path), THUMB_WIDTH, THUMB_HEIGHT, false); - } catch (Error e) { - warning (e.message); - } - - load_artist_tooltip (); - } -} diff --git a/src/Widgets/XMLContainer.vala b/src/Widgets/XMLContainer.vala new file mode 100644 index 000000000..9fffef156 --- /dev/null +++ b/src/Widgets/XMLContainer.vala @@ -0,0 +1,32 @@ +/*- + * Copyright 2022 elementary, Inc. (https://elementary.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +public class PantheonShell.XMLContainer : UriContainer { + public XMLContainer (string uri) { + Object (uri: uri); + } + + construct { + // FIXME: https://github.com/elementary/switchboard-plug-pantheon-shell/issues/296 + // parse_file (); + // create_collage (); + // create_overlay_icon (); + // add_tooltip (); + // ... + } +} diff --git a/src/meson.build b/src/meson.build index 285bcd3af..26e5055ec 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,5 +1,4 @@ plug_files = files( - 'IOHelper.vala', 'PantheonAccountsServicePlugin.vala', 'Plug.vala', 'ThumbnailGenerator.vala', @@ -8,17 +7,17 @@ plug_files = files( 'Views/Multitasking.vala', 'Views/Text.vala', 'Views/Wallpaper.vala', + 'Widgets/GenericContainer.vala', + 'Widgets/ImageContainer.vala', 'Widgets/SolidColorContainer.vala', - 'Widgets/WallpaperContainer.vala', + 'Widgets/UriContainer.vala', + 'Widgets/XMLContainer.vala', ) -switchboard_dep = dependency('switchboard-2.0') +switchboard_dep = dependency('switchboard-3') switchboard_plugsdir = switchboard_dep.get_pkgconfig_variable('plugsdir', define_variable: ['libdir', libdir]) -plank_datadir = plank_dep.get_pkgconfig_variable('pkgdatadir') - configuration = configuration_data() -configuration.set('PLANKDATADIR', plank_datadir) configuration.set('LOCALEDIR', join_paths(get_option('prefix'), get_option('localedir'))) configuration.set('GETTEXT_PACKAGE', meson.project_name() + '-plug') @@ -39,10 +38,8 @@ shared_module( gobject_dep, granite_dep, gtk_dep, - hdy_dep, + adw_dep, dependency('gexiv2'), - dependency('gnome-desktop-3.0'), - plank_dep, posix_dep, switchboard_dep ],