diff options
| author | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
|---|---|---|
| committer | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
| commit | 70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 (patch) | |
| tree | ab1123d4067c1b086dd6faa7ee4ea643236b565a /raveos-theme/gnome/theme-data/extensions/installed/burn-my-windows@schneegans.github.com/prefs.js | |
| parent | 5d94c0a7d44a2255b81815a52a7056a94a39842d (diff) | |
| download | RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.tar.gz RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.zip | |
Replaced file structures for themes in the correct format
Diffstat (limited to 'raveos-theme/gnome/theme-data/extensions/installed/burn-my-windows@schneegans.github.com/prefs.js')
| -rw-r--r-- | raveos-theme/gnome/theme-data/extensions/installed/burn-my-windows@schneegans.github.com/prefs.js | 941 |
1 files changed, 0 insertions, 941 deletions
diff --git a/raveos-theme/gnome/theme-data/extensions/installed/burn-my-windows@schneegans.github.com/prefs.js b/raveos-theme/gnome/theme-data/extensions/installed/burn-my-windows@schneegans.github.com/prefs.js deleted file mode 100644 index 8476964..0000000 --- a/raveos-theme/gnome/theme-data/extensions/installed/burn-my-windows@schneegans.github.com/prefs.js +++ /dev/null @@ -1,941 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// ) ( // -// ( /( ( ( ) ( ( ( ( )\ ) ( ( // -// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( // -// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ // -// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) // -// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< // -// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ // -// |__/ // -////////////////////////////////////////////////////////////////////////////////////////// - -// SPDX-FileCopyrightText: Simon Schneegans <code@simonschneegans.de> -// SPDX-License-Identifier: GPL-3.0-or-later - -'use strict'; - -import Gio from 'gi://Gio'; -import Gtk from 'gi://Gtk'; -import GLib from 'gi://GLib'; -import Gdk from 'gi://Gdk'; -import Adw from 'gi://Adw'; - -import * as utils from './src/utils.js'; -import {ProfileManager} from './src/ProfileManager.js'; - -import Apparition from './src/effects/Apparition.js'; -import AuraGlow from './src/effects/AuraGlow.js'; -import BrokenGlass from './src/effects/BrokenGlass.js'; -import Doom from './src/effects/Doom.js'; -import EnergizeA from './src/effects/EnergizeA.js'; -import EnergizeB from './src/effects/EnergizeB.js'; -import Fire from './src/effects/Fire.js'; -import Focus from './src/effects/Focus.js'; -import Glide from './src/effects/Glide.js'; -import Glitch from './src/effects/Glitch.js'; -import Hexagon from './src/effects/Hexagon.js'; -import Incinerate from './src/effects/Incinerate.js'; -import Matrix from './src/effects/Matrix.js'; -import Mushroom from './src/effects/Mushroom.js'; -import PaintBrush from './src/effects/PaintBrush.js'; -import Pixelate from './src/effects/Pixelate.js'; -import PixelWheel from './src/effects/PixelWheel.js'; -import PixelWipe from './src/effects/PixelWipe.js'; -import Portal from './src/effects/Portal.js'; -import RGBWarp from './src/effects/RGBWarp.js'; -import SnapOfDisintegration from './src/effects/SnapOfDisintegration.js'; -import TeamRocket from './src/effects/TeamRocket.js'; -import TRexAttack from './src/effects/TRexAttack.js'; -import TVEffect from './src/effects/TVEffect.js'; -import TVGlitch from './src/effects/TVGlitch.js'; -import Wisps from './src/effects/Wisps.js'; - - -import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; - -// Currently, the extension supports only one set of UI files. In the past, there were -// three different sets for GTK3, GTK4, and Adwaita. This method returns the name of the -// current UI directory. It's still here since it might be useful in the future. -function getUIDir() { - return 'adw'; -} - -////////////////////////////////////////////////////////////////////////////////////////// -// The preferences dialog is organized in pages, each of which is loaded from a // -// separate ui file. There's one page with general options, all other paged are loaded // -// from the respective effects. // -////////////////////////////////////////////////////////////////////////////////////////// - -export default class BurnMyWindowsPreferences extends ExtensionPreferences { - - // ------------------------------------------------------------ constructor / destructor - - fillPreferencesWindow(window) { - window.set_default_size(650, 750); - - // New effects must be registered here and in extension.js. - this._ALL_EFFECTS = [ - Apparition, AuraGlow, BrokenGlass, - Doom, EnergizeA, EnergizeB, - Fire, Focus, Glide, - Glitch, Hexagon, Incinerate, - Matrix, Mushroom, PaintBrush, - Pixelate, PixelWheel, PixelWipe, - Portal, RGBWarp, SnapOfDisintegration, - TeamRocket, TRexAttack, TVEffect, - TVGlitch, Wisps, - ]; - - - // Load all of our resources. - this._resources = - Gio.Resource.load(this.path + '/resources/burn-my-windows.gresource'); - Gio.resources_register(this._resources); - - // Make sure custom icons are found. - Gtk.IconTheme.get_for_display(Gdk.Display.get_default()).add_resource_path('/img'); - - // Load the general user interface files. - this._builder = new Gtk.Builder(); - this._builder.add_from_resource(`/ui/common/menus.ui`); - this._builder.add_from_resource(`/ui/${getUIDir()}/prefs.ui`); - - - // Store a reference to the general settings object. - this._settings = this.getSettings(); - - // This tracks all available effect profiles. - this._profileManager = new ProfileManager(this.metadata); - - // This is used to track all connections to properties of the current effect profile. - // Whenever the current effect profile changes, all connections are disconnected. - this._profileConnections = []; - - // Load the current effect profile. If there is none, we create a default profile. - this._settings.connect('changed::active-profile', () => { - this._loadActiveProfile(); - }); - - // Check whether the power profiles daemon is available - if not, we hide the - // corresponding settings row. - let hasPowerProfiles = false; - try { - const PowerProfilesProxy = Gio.DBusProxy.makeProxyWrapper( - utils.getStringResource('/interfaces/net.hadess.PowerProfiles.xml')); - let powerProfilesProxy = new PowerProfilesProxy( - Gio.DBus.system, 'net.hadess.PowerProfiles', '/net/hadess/PowerProfiles'); - - hasPowerProfiles = powerProfilesProxy.get_name_owner() != null; - } catch (e) { - // Maybe the service is masked... - } - - - let powerProfileRow = this._builder.get_object('profile-power-profile'); - powerProfileRow.set_visible(hasPowerProfiles); - - // Wire up the window picker of the profile editor. Whenever the app-chooser button is - // clicked, we call a method of the extension which will initiate the window picking. - this._builder.get_object('profile-choose-app-button').connect('clicked', () => { - Gio.DBus.session.call('org.gnome.Shell', - '/org/gnome/shell/extensions/BurnMyWindows', - 'org.gnome.shell.extensions.BurnMyWindows', 'PickWindow', - null, null, Gio.DBusCallFlags.NO_AUTO_START, -1, null, null); - }); - - // If the window picking was successful, this D-Bus signal will be emitted. If a - // window was picked, we add it to the list of window name. - this._dbusConnection = Gio.DBus.session.signal_subscribe( - 'org.gnome.Shell', 'org.gnome.shell.extensions.BurnMyWindows', 'WindowPicked', - '/org/gnome/shell/extensions/BurnMyWindows', null, Gio.DBusSignalFlags.NONE, - (conn, sender, obj_path, iface, signal, params) => { - const val = params.get_child_value(0).get_string()[0]; - if (val != 'window-not-found') { - const entry = this._builder.get_object('profile-app'); - - // Split the current value at each |, trim spaces, and remove empty components. - let apps = entry.text.split('|').map(item => item.trim()).filter(v => v != ''); - - // If the list is empty, we simply use the name of the picked window. - if (apps.length == 0) { - entry.text = val; - } else { - - // Else we append the new application name, remove any duplicates, and sort - // the resulting list alphabetically. - apps.push(val); - apps = [...new Set(apps)].sort(); - entry.text = apps.join(' | '); - } - } - }); - - - // This is our top-level widget which we will return later. - this._widget = this._builder.get_object('general-prefs'); - - // Add the functionality to the choose-all and choose-none buttons. - this._builder.get_object('choose-all-effects-button').connect('clicked', () => { - this._ALL_EFFECTS.forEach(effect => { - this.getProfileSettings().set_boolean(`${effect.getNick()}-enable-effect`, true); - }); - }); - - this._builder.get_object('choose-no-effects-button').connect('clicked', () => { - this._ALL_EFFECTS.forEach(effect => { - this.getProfileSettings().set_boolean(`${effect.getNick()}-enable-effect`, false); - }); - }); - - // search for effect feature - this._searchEntry = this._builder.get_object('search_entry'); - this._searchEntry.connect('search-changed', () => { - const query = this._searchEntry.get_text().toLowerCase(); - - // If only one effect is left, we will expand it automatically. - let lastRow = null; - let numRows = 0; - - this._effectRows.forEach(row => { - row.set_expanded(false); - - if (query === '') { - row.show(); // Show all effects if query is empty - } else { - // Show or hide each effect based on query match - const showEffect = row.name.toLowerCase().includes(query); - - if (showEffect) { - row.show(); - lastRow = row; - numRows++; - } else { - row.hide(); - } - } - }); - - // If only one effect is left, we expand it automatically. - if (numRows === 1) { - lastRow.set_expanded(true); - } - }); - - // Then add a preferences group for the effect expander rows. - const group = this._builder.get_object('effects-group'); - - // This stores all expander rows for the effects. We use this to implement the - // accordion-like behavior of the effect settings. - this._effectRows = []; - - // Now add all the rows. - this._ALL_EFFECTS.forEach(effect => { - const [minMajor, minMinor] = effect.getMinShellVersion(); - if (utils.shellVersionIsAtLeast(minMajor, minMinor)) { - - const uiFile = `/ui/${getUIDir()}/${effect.getNick()}.ui`; - - // Is there a better way to test for the existence of a resource file? - let hasPrefs = false; - try { - Gio.resources_get_info(uiFile, 0); - hasPrefs = true; - } catch (e) { - // Nothing todo, there - } - - // Add the settings page to the builder. - if (hasPrefs) { - this._builder.add_from_resource(uiFile); - } - - // The preview button. - let previewButton = Gtk.Button.new_from_icon_name('bmw-preview-symbolic'); - previewButton.get_style_context().add_class('circular'); - previewButton.get_style_context().add_class('flat'); - previewButton.set_tooltip_text(_('Preview this effect')); - previewButton.set_valign(Gtk.Align.CENTER); - previewButton.connect('clicked', () => { - this._previewEffect(effect); - }); - - // The toggle button for enabling and disabling the effect. - const button = Gtk.Switch.new(); - button.set_valign(Gtk.Align.CENTER); - this._builder.expose_object(`${effect.getNick()}-enable-effect`, button); - - let row; - if (hasPrefs) { - - // Add the effect's preferences (if any). - row = this._builder.get_object(`${effect.getNick()}-prefs`); - } else { - row = Adw.ActionRow.new(); - } - - // On older versions of Adw (e.g. on GNOME Shell <43), the set_use_markup() does - // not yet exist. - if (row.set_use_markup) { - row.set_title('<b>' + effect.getLabel() + '</b>'); - row.set_use_markup(true); - } else { - row.set_title(effect.getLabel()); - } - - // this is the fix for the merge issue when an effect doesn't have a discription - row.name = effect.getLabel(); - - - // Un-expand any previously expanded effect row. This way we ensure that there - // is only one expanded row at any time. - if (hasPrefs) { - row.connect('notify::expanded', currentRow => { - if (currentRow.get_expanded()) { - this._effectRows.forEach(row => { - if (row != currentRow) { - row.set_expanded(false); - } - }); - } - }); - this._effectRows.push(row); - row.add_action(button); - } else { - row.add_suffix(button); - } - - row.add_prefix(previewButton); - - group.add(row); - } - }); - - - // Some things can only be done once the widget is shown as we do not have access to - // the toplevel widget before. - this._widget.connect('realize', (widget) => { - const window = widget.get_root(); - - // Show the version number in the title bar. - window.set_title(`Burn-My-Windows ${this.metadata.version}`); - - this._loadActiveProfile(); - this._updateProfileMenu(); - - // Show an Adw.Toast or a Gtk.InfoBar whenever Burn-My-Windows was updated. We use a - // small timeout so that it is not shown instantaneously. - const lastVersion = this._settings.get_int('last-prefs-version'); - if (lastVersion < this.metadata.version) { - this._showUpdateInfoTimeout = - GLib.timeout_add(GLib.PRIORITY_DEFAULT, 2000, () => { - this._showUpdateInfoTimeout = 0; - - const toast = Adw.Toast.new(_('Burn-My-Windows has been updated!')); - toast.set_button_label(_('View Changelog')); - toast.set_action_name('prefs.changelog'); - toast.set_timeout(0); - - toast.connect( - 'dismissed', - () => this._settings.set_int('last-prefs-version', this.metadata.version)); - - window.add_toast(toast); - - return false; - }); - } - - // Count the number of times the user has opened the preferences window. Every now - // and then, we show a dialog asking the user to support the extension. We connect - // to the notify::visible signal to ensure that the dialog can be a modal dialog. - window.connect('notify::visible', (window) => { - // Do not show the dialog when the window is hidden. - if (!window.get_visible()) { - return; - } - - // Do not show the dialog when the user has disabled it. - if (!this._settings.get_boolean('show-support-dialog')) { - return; - } - - const count = this._settings.get_int('prefs-open-count') + 1; - this._settings.set_int('prefs-open-count', count); - - // Show the dialog every 10th time. - if (count % 10 == 0) { - const dialog = this._createMessageDialog( - '❤️ Do you love Burn-My-Windows?', - `Even the smallest donation can have a big impact! If just one of ten Burn-My-Windows users donated $1 per month, I could dedicate myself to creating awesome open-source projects full-time! - -Ko-fi: <a href='https://ko-fi.com/schneegans'>https://ko-fi.com/schneegans</a> -GitHub: <a href='https://github.com/sponsors/schneegans'>https://github.com/sponsors/schneegans</a>`, - window, [ - { - label: 'Do not show this again!', - destructive: true, - default: false, - action: () => { - this._settings.set_boolean('show-support-dialog', false); - } - }, - { - label: 'Remind me later.', - destructive: false, - default: true, - } - ]); - - dialog.show(); - } - }); - - // Populate the menu with actions. - const group = Gio.SimpleActionGroup.new(); - window.insert_action_group('prefs', group); - - // Add the main menu to the title bar. - { - // Add the menu button to the title bar. - const menu = this._builder.get_object('menu-button'); - - // Starting with GNOME Shell 42, we have to hack our way through the widget tree - // of the Adw.PreferencesWindow... - const header = this._findWidgetByType(window.get_content(), Adw.HeaderBar); - header.pack_start(menu); - header.set_title_widget(this._builder.get_object('profile-button')); - - // GNOME Shell extensions are forced to use a AdwPreferencesWindow. This already - // includes a Gtk.ScrolledWindow as well as an Adw.Clamp. For the profile - // editing, we want to use an Adw.Flap which reveals itself from the top. This - // needs to be inserted in the widget hierarchy above the Adw.Clamp, else it - // would look ugly. Therefore, we fiddle around with the internal widgets... - const flap = this._builder.get_object('profile-editor-flap'); - const clamp = this._findWidgetByType(window.get_content(), Adw.Clamp); - const viewport = clamp.get_parent(); - viewport.get_parent().set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER); - viewport.set_child(flap); - - const scrolledWindow = Gtk.ScrolledWindow.new(); - scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); - scrolledWindow.set_propagate_natural_height(true); - scrolledWindow.set_vexpand(true); - scrolledWindow.set_child(clamp); - - flap.set_content(scrolledWindow); - - const addURIAction = (name, uri) => { - const action = Gio.SimpleAction.new(name, null); - action.connect('activate', () => Gtk.show_uri(null, uri, Gdk.CURRENT_TIME)); - group.add_action(action); - }; - - // clang-format off - addURIAction('homepage', 'https://github.com/Schneegans/Burn-My-Windows'); - addURIAction('bugs', 'https://github.com/Schneegans/Burn-My-Windows/issues'); - addURIAction('new-effect', 'https://github.com/Schneegans/Burn-My-Windows/blob/main/docs/how-to-create-new-effects.md'); - addURIAction('translate', 'https://hosted.weblate.org/engage/burn-my-windows/'); - addURIAction('donate-kofi', 'https://ko-fi.com/schneegans'); - addURIAction('donate-github', 'https://github.com/sponsors/Schneegans'); - addURIAction('donate-paypal', 'https://www.paypal.me/simonschneegans'); - addURIAction('donate-crypto', 'https://schneegans.cb.id'); - addURIAction('show-sponsors', 'https://schneegans.github.io/sponsors'); - addURIAction('wallpapers', 'https://github.com/Schneegans/ai-wallpapers'); - addURIAction('profile-dir', `file://${GLib.get_user_config_dir()}/burn-my-windows/profiles`); - // clang-format on - - // The changelog action is a bit different, as it may get activated when the - // view-changelog button is pressed which is shown in an Adw.Toast or a - // Gtk.InfoBar whenever Burn-My-Windows gets updated. To ensure that the - // notification is shown only once, we have to store the current version of - // Burn-My-Windows in the settings. - const changelogAction = Gio.SimpleAction.new('changelog', null); - changelogAction.connect('activate', () => { - Gtk.show_uri( - null, - 'https://github.com/Schneegans/Burn-My-Windows/blob/main/docs/changelog.md', - Gdk.CURRENT_TIME); - this._settings.set_int('last-prefs-version', this.metadata.version) - }); - group.add_action(changelogAction); - - // Add the about dialog. - const aboutAction = Gio.SimpleAction.new('about', null); - aboutAction.connect('activate', () => { - // The JSON report format from weblate is a bit weird. Here we extract all - // unique names from the translation report. - const translators = new Set(); - this._getJSONResource('/credits/translators.json').forEach(i => { - for (const j of Object.values(i)) { - j.forEach(k => translators.add(k.full_name)); - } - }); - - const dialog = new Adw.AboutWindow({transient_for: window, modal: true}); - dialog.set_application_icon('burn-my-windows-symbolic'); - dialog.set_application_name('Burn-My-Windows'); - dialog.set_version(`${this.metadata.version}`); - dialog.set_developer_name('Simon Schneegans'); - dialog.set_issue_url('https://github.com/Schneegans/Burn-My-Windows/issues'); - dialog.set_translator_credits([...translators].join('\n')); - dialog.set_copyright('© 2023 Simon Schneegans'); - dialog.set_website('https://github.com/Schneegans/Burn-My-Windows'); - dialog.set_license_type(Gtk.License.GPL_3_0); - - dialog.show(); - }); - - group.add_action(aboutAction); - } - - // Setup all the profile-related actions. - { - // Allow switching the current profile. - const activeProfileAction = this._settings.create_action('active-profile'); - group.add_action(activeProfileAction); - - // This action creates a new profile and makes it the active one. - const newProfileAction = Gio.SimpleAction.new('profile-new', null); - newProfileAction.connect('activate', () => { - const profile = this._profileManager.createProfile(); - this._settings.set_string('active-profile', profile.path); - - // Start editing the profile. - this._builder.get_object('edit-profile-button').active = true; - - // The number of profiles has changed, so we have to update the profile - // selection menu. - this._updateProfileMenu(); - }); - group.add_action(newProfileAction); - - // This action shows a confirmation dialog. Upon user approval, the currently - // active profile is deleted. We use the more beautiful Adw.MessageDialog if it is - // available (usually on GNOME 43 and beyond). - const deleteProfileDialog = this._createMessageDialog( - _('Delete this Profile?'), - _('The current effect profile with all its effect settings will be permanently lost.'), - window, [ - { - label: _('Cancel'), - destructive: false, - default: true, - }, - { - label: _('Delete'), - destructive: true, - default: false, - action: () => { - // Stop editing the current profile. - this._builder.get_object('edit-profile-button').active = false; - - // Delete the currently active profile. - this._profileManager.deleteProfile(this._activeProfile.path); - - // Select another one. - this._settings.set_string('active-profile', - this._profileManager.getProfiles()[0].path); - - // The number of profiles has changed, so we have to update the profile - // selection menu. - this._updateProfileMenu(); - } - } - ]); - - // This action deletes the currently active profile. - const deleteProfileAction = Gio.SimpleAction.new('profile-delete', null); - deleteProfileAction.connect('activate', () => { - deleteProfileDialog.show(); - }); - group.add_action(deleteProfileAction); - } - }); - - // As we do not have something like a destructor, we just listen for the destroy - // signal of our main widget. - this._widget.connect('destroy', () => { - // Unregister our resources. - Gio.resources_unregister(this._resources); - - // Disconnect from the window-picker API. - Gio.DBus.session.signal_unsubscribe(this._dbusConnection); - - // If the settings dialog got closed too quickly, this may not have been shown. - if (this._showUpdateInfoTimeout > 0) { - GLib.source_remove(this._showUpdateInfoTimeout); - } - }); - - window.add(this._widget); - } - - // -------------------------------------------------------------------- public interface - - // Returns the internally used Gtk.Builder. Effects can use this to modify the UI of the - // preferences dialog. - getBuilder() { - return this._builder; - } - - // Returns a Gio.Settings object for the current effect profile. - getProfileSettings() { - return this._activeProfile.settings; - } - - // Connects an Adw.ComboRow (or anything else which has an 'selected' property) to a - // settings key. It also binds the corresponding reset button. - bindComboRow(settingsKey) { - this._bind(settingsKey, 'selected'); - } - - // Connects a Gtk.ComboBox (or anything else which has an 'active' property) to a - // settings key. It also binds the corresponding reset button. - bindComboBox(settingsKey) { - this._bind(settingsKey, 'active'); - } - - // Connects a Gtk.Entry (or anything else which has an 'text' property) to a - // settings key. It also binds the corresponding reset button. - bindEntry(settingsKey) { - this._bind(settingsKey, 'text'); - } - - // Connects a Gtk.Adjustment (or anything else which has a 'value' property) to a - // settings key. It also binds the corresponding reset button. - bindAdjustment(settingsKey) { - this._bind(settingsKey, 'value'); - } - - // Connects a Gtk.Switch (or anything else which has an 'active' property) to a settings - // key. It also binds the corresponding reset button. - bindSwitch(settingsKey) { - this._bind(settingsKey, 'active'); - } - - // Colors are stored as strings like 'rgb(1, 0.5, 0)'. As Gio.Settings.bind_with_mapping - // is not available yet, we need to do the color conversion manually. It also binds the - // corresponding reset button. - bindColorButton(settingsKey) { - - const button = this._builder.get_object(settingsKey); - - if (button) { - - // Update the settings when the color is modified. - if (!button._isConnected) { - button.connect('color-set', () => { - this.getProfileSettings().set_string(settingsKey, - button.get_rgba().to_string()); - }); - - button._isConnected = true; - } - - // Update the button state when the settings change. - const settingSignalHandler = () => { - const rgba = new Gdk.RGBA(); - rgba.parse(this.getProfileSettings().get_string(settingsKey)); - button.rgba = rgba; - }; - - this._connectProfileSetting('changed::' + settingsKey, settingSignalHandler); - - // Initialize the button with the state in the settings. - settingSignalHandler(); - } - - this._bindResetButton(settingsKey); - } - - // ----------------------------------------------------------------------- private stuff - - // This loads all settings from the profile given in the settings key 'active-profile'. - _loadActiveProfile() { - - // Disconnect any connection to the previously active profile. - this._disconnectProfileSettings(); - - // Find the active profile in the list of all profiles. - const allProfiles = this._profileManager.getProfiles(); - const path = this._settings.get_string('active-profile'); - this._activeProfile = allProfiles.find(p => p.path == path); - - // If, for whatever reason, it is not found, select the first one. Setting the - // settings key will trigger this method again. - if (!this._activeProfile) { - this._activeProfile = allProfiles[0]; - this._settings.set_string('active-profile', this._activeProfile.path); - return; - } - - // Connect all profile options. - const updateProfileButtonMenuIfChanged = (settingsKey) => { - this._connectProfileSetting('changed::' + settingsKey, () => { - this._updateProfileButton(); - this._updateProfileMenu(); - }); - }; - - updateProfileButtonMenuIfChanged('profile-app'); - updateProfileButtonMenuIfChanged('profile-animation-type'); - updateProfileButtonMenuIfChanged('profile-window-type'); - updateProfileButtonMenuIfChanged('profile-color-scheme'); - updateProfileButtonMenuIfChanged('profile-power-mode'); - updateProfileButtonMenuIfChanged('profile-power-profile'); - updateProfileButtonMenuIfChanged('profile-high-priority'); - - this.bindEntry('profile-app'); - this.bindComboRow('profile-animation-type'); - this.bindComboRow('profile-window-type'); - this.bindComboRow('profile-color-scheme'); - this.bindComboRow('profile-power-mode'); - this.bindComboRow('profile-power-profile'); - this.bindSwitch('profile-high-priority'); - - // Connect all effect settings. - this._ALL_EFFECTS.forEach(effect => { - const [minMajor, minMinor] = effect.getMinShellVersion(); - if (utils.shellVersionIsAtLeast(minMajor, minMinor)) { - this.bindSwitch(`${effect.getNick()}-enable-effect`); - effect.bindPreferences(this); - } - }); - - // Update the label in the profile-select button. - this._updateProfileButton(); - } - - // This updates the label of the profile-selection button according to the currently - // active profile. It's called whenever the active profile is changed or whenever a - // configuration option of the profile is changed. - _updateProfileButton() { - this._builder.get_object('choose-profile-button').label = - this._getProfileName(this.getProfileSettings()); - } - - // This updates the list of available profiles in the dropdown of the profile-selection - // button. It's called whenever one of the configuration option of the currently active - // profile is changed, or whenever a profile is added / removed. - _updateProfileMenu() { - - // First, clear the menu section. - const profileSection = this._builder.get_object('profile-section'); - profileSection.remove_all(); - - // Then add an entry for each available profile. - this._profileManager.getProfiles().forEach(profile => { - const label = this._getProfileName(profile.settings); - const item = Gio.MenuItem.new(label, 'prefs.active-profile'); - item.set_action_and_target_value('prefs.active-profile', - GLib.Variant.new_string(profile.path)); - profileSection.append_item(item); - }); - } - - // Profiles are named according to their configuration. This method returns a localized - // string describing the settings of the profile. - _getProfileName(settings) { - let items = []; - - // If an app is configured, use it as first component for the profile's name. - const app = settings.get_string('profile-app'); - if (app != '') { - items.push(app); - } - - // Now add components to the name for each non-default profile option. Make sure that - // these are the same strings as used in the UI files! - const addComponent = (settingsKey, options) => { - const option = settings.get_int(settingsKey); - if (option > 0) { - items.push(options[option - 1]); - } - }; - - // clang-format off - addComponent('profile-animation-type', [_('Opening Windows'), - _('Closing Windows')]); - addComponent('profile-window-type', [_('Normal Windows'), - _('Dialog Windows')]); - addComponent('profile-color-scheme', [_('Default Color Scheme'), - _('Dark Color Scheme')]); - addComponent('profile-power-mode', [_('On Battery'), - _('Plugged In')]); - addComponent('profile-power-profile', [_('Power-Saver Mode'), - _('Balanced Mode'), - _('Performance Mode'), - _('Power Saver or Balanced'), - _('Balanced or Performance')]); - // clang-format on - - // If the profile is a high-priority profile, also add this.. - if (settings.get_boolean('profile-high-priority')) { - items.push(_('High Priority')); - } - - let name = ''; - - if (items.length == 0) { - name = _('Standard Profile'); - } else { - name = items.join(' · '); - } - - return name; - } - - // This wrapper connects a given callback to a profile settings key. Use this instead of - // the direct connect() call on the profile settings to make sure that the callback is - // disconnected when the active profile changes. - _connectProfileSetting(key, callback) { - this._profileConnections.push(this.getProfileSettings().connect(key, callback)); - } - - // This is called whenever the currently edited effect profile changes. It unbinds all - // callbacks bound with the above method before. - _disconnectProfileSettings() { - this._profileConnections.forEach(c => this.getProfileSettings().disconnect(c)); - this._profileConnections = []; - } - - // Searches for a reset button for the given settings key and make it reset the settings - // key when clicked. - _bindResetButton(settingsKey) { - const resetButton = this._builder.get_object('reset-' + settingsKey); - if (resetButton && !resetButton._isConnected) { - resetButton.connect('clicked', () => { - this.getProfileSettings().reset(settingsKey); - }); - resetButton._isConnected = true; - } - } - - // Connects any widget's property to a settings key. The widget must have the same ID as - // the settings key. It also binds the corresponding reset button. - _bind(settingsKey, property) { - const object = this._builder.get_object(settingsKey); - - if (object) { - // Unbind any previous binding (potentially to a previously edited profile). - Gio.Settings.unbind(object, property); - this.getProfileSettings().bind(settingsKey, object, property, - Gio.SettingsBindFlags.DEFAULT); - } - - this._bindResetButton(settingsKey); - } - - // Reads the contents of a JSON file contained in the global resources archive. The data - // is parsed and returned as a JavaScript object / array. - _getJSONResource(path) { - return JSON.parse(utils.getStringResource(path)); - } - - // This traverses the widget tree below the given parent recursively and returns the - // first widget of the given type. - _findWidgetByType(parent, type) { - for (const child of [...parent]) { - if (child instanceof type) return child; - - const match = this._findWidgetByType(child, type); - if (match) return match; - } - - return null; - } - - // Opens a modal window using the given effect. - _previewEffect(effect) { - - // Set the to-be-previewed effect. - this._settings.set_string('preview-effect', effect.getNick()); - - // Make sure that the window.show() firther below "sees" this change. - Gio.Settings.sync(); - - // Create the preview-window. - const window = new Gtk.Window({ - // Translators: %s will be replaced by the effect's name. - title: _('Preview for %s').replace('%s', effect.getLabel()), - default_width: 800, - default_height: 450, - modal: true, - transient_for: this._widget.get_root() - }); - - // Add a header bar to the window. - const header = Gtk.HeaderBar.new(); - window.set_titlebar(header); - - const box = new Gtk.Box({ - orientation: Gtk.Orientation.VERTICAL, - valign: Gtk.Align.CENTER, - spacing: 10, - margin_start: 50, - margin_end: 50 - }); - - const label = Gtk.Label.new(_('Close this Window to Preview the Effect!')); - label.wrap = true; - label.justify = Gtk.Justification.CENTER; - label.get_style_context().add_class('large-title'); - - const image = new Gtk.Image({ - icon_name: 'burn-my-windows-symbolic', - pixel_size: 128, - }); - - box.append(image); - box.append(label); - - window.set_child(box); - window.show(); - } - - // Helper function to show a message dialog. The dialog is modal and has a title, a - // message and a list of buttons. Each button is an object with a label, a default flag - // and a destructive flag. Each button object can also have an "action" callback that is - // called when the button is clicked. - // This method works on GTK3, GTK4, and libadwaita. - _createMessageDialog(title, message, window, buttons) { - let dialog = new Adw.MessageDialog({ - heading: title, - body: message, - body_use_markup: true, - modal: true, - }); - - buttons.forEach((button, i) => { - const response = i.toString(); - dialog.add_response(response, button.label); - - if (button.default) { - dialog.set_default_response(response); - dialog.set_close_response(response); - } - - if (button.destructive) { - dialog.set_response_appearance(response, Adw.ResponseAppearance.DESTRUCTIVE); - } - }); - - dialog.set_hide_on_close(true); - dialog.set_transient_for(window); - - // If the dialog is closed or a button is clicked, we hide the dialog and call the - // button's action callback if it exists. - dialog.connect('response', (dialog, response) => { - const i = parseInt(response); - - const button = buttons[i]; - if (button && button.action) { - button.action(); - } - - dialog.hide(); - }); - - return dialog; - } -} |