2025-05-09 09:48:24 +02:00

467 lines
18 KiB
JavaScript

import Adw from 'gi://Adw';
import Gdk from 'gi://Gdk';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
import {AboutPage} from './settings/AboutPage.js';
import {DonatePage} from './settings/DonatePage.js';
import {DialogWindow} from './settings/DialogWindow.js';
import {WidgetSettingsPage} from './settings/WidgetSettingsPage.js';
import * as Utils from './utils.js';
import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
const WidgetType = {
DIGITAL: 0,
ANALOG: 1,
CUSTOM: 2,
WEATHER: 3,
};
export default class AzClockPrefs extends ExtensionPreferences {
fillPreferencesWindow(window) {
const iconTheme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default());
if (!iconTheme.get_search_path().includes(`${this.path}/media`))
iconTheme.add_search_path(`${this.path}/media`);
const settings = this.getSettings();
let pageChangedId = settings.connect('changed::prefs-visible-page', () => {
if (settings.get_string('prefs-visible-page') !== '')
this._setVisiblePage(window, settings);
});
window.connect('close-request', () => {
if (pageChangedId) {
settings.disconnect(pageChangedId);
pageChangedId = null;
}
});
window.set_default_size(750, 800);
const homePage = new HomePage(this, settings);
window.add(homePage);
this.createRows = () => homePage.createRows();
const donatePage = new DonatePage(this.metadata);
window.add(donatePage);
const aboutPage = new AboutPage(settings, this.metadata, this.path);
window.add(aboutPage);
this._setVisiblePage(window, settings);
}
_setVisiblePage(window, settings) {
const prefsVisiblePage = settings.get_string('prefs-visible-page');
window.pop_subpage();
if (prefsVisiblePage === '') {
window.set_visible_page_name('HomePage');
} else if (prefsVisiblePage === 'DonatePage') {
window.set_visible_page_name('DonatePage');
} else if (prefsVisiblePage === 'WhatsNewPage') {
window.set_visible_page_name('AboutPage');
const page = window.get_visible_page();
page.showWhatsNewPage();
}
settings.set_string('prefs-visible-page', '');
}
}
var HomePage = GObject.registerClass(
class azClockHomePage extends Adw.PreferencesPage {
_init(extension, settings) {
super._init({
title: _('Settings'),
icon_name: 'preferences-system-symbolic',
name: 'HomePage',
});
this._extension = extension;
this._settings = settings;
this._widgetRows = [];
const addClockButton = new Gtk.Button({
halign: Gtk.Align.START,
icon_name: 'list-add-symbolic',
valign: Gtk.Align.CENTER,
css_classes: ['suggested-action'],
label: _('Add Widget...'),
});
addClockButton.connect('clicked', () => {
const dialog = new AddWidgetsDialog(this._extension, this._settings, this);
dialog.show();
dialog.connect('response', (_w, response) => {
if (response === Gtk.ResponseType.APPLY) {
this.createRows();
dialog.destroy();
}
});
});
this.clocksGroup = new Adw.PreferencesGroup();
this.add(this.clocksGroup);
this.clocksGroup.set_header_suffix(addClockButton);
this.createRows();
}
get settings() {
return this._settings;
}
createRows() {
for (let row of this._widgetRows) {
this.clocksGroup.remove(row);
row = null;
}
this._widgetRows = [];
const widgets = this._settings.get_value('widgets').recursiveUnpack();
widgets.forEach(widget => {
for (const [widgetId] of Object.entries(widget)) {
const widgetRow = this._createWidgetRow(widgetId);
this.clocksGroup.add(widgetRow);
}
});
}
_createWidgetRow(widgetId) {
const widgetSchema = `${this._settings.schema_id}.widget-data`;
const widgetPath = `${this._settings.path}widget-data/${widgetId}/`;
const widgetSettings = Utils.getSettings(this._extension, widgetSchema, widgetPath);
const name = widgetSettings.get_string('name');
// Data for the widget is always the first element in array.
const widgetRow = new WidgetRow(this._settings, widgetSettings, widgetId, {
title: `<b>${_(name)}</b>`,
});
widgetRow.use_markup = true;
widgetRow.connect('drag-drop-done', (_widget, oldIndex, newIndex) => {
const widgets = this._settings.get_value('widgets').deepUnpack();
const movedData = widgets.splice(oldIndex, 1)[0];
widgets.splice(newIndex, 0, movedData);
this._settings.set_value('widgets', new GLib.Variant('aa{sv}', widgets));
this.createRows();
});
this._widgetRows.push(widgetRow);
return widgetRow;
}
});
var WidgetRow = GObject.registerClass({
Signals: {
'drag-drop-done': {param_types: [GObject.TYPE_UINT, GObject.TYPE_UINT]},
'drag-drop-prepare': {},
},
}, class AzClockWidgetRow extends Adw.ActionRow {
_init(settings, widgetSettings, schemaId, params) {
super._init({
activatable: true,
...params,
});
this._params = params;
this._settings = settings;
this._widgetSettings = widgetSettings;
this._schemaId = schemaId;
this.dragIcon = new Gtk.Image({
gicon: Gio.icon_new_for_string('list-drag-handle-symbolic'),
pixel_size: 12,
});
this.add_prefix(this.dragIcon);
const dragSource = new Gtk.DragSource({actions: Gdk.DragAction.MOVE});
this.add_controller(dragSource);
const dropTarget = new Gtk.DropTargetAsync({actions: Gdk.DragAction.MOVE});
this.add_controller(dropTarget);
dragSource.connect('drag-begin', (self, gdkDrag) => {
this._dragParent = self.get_widget().get_parent();
this._dragParent.dragRow = this;
const alloc = this.get_allocation();
const dragWidget = self.get_widget().createDragRow(alloc);
this._dragParent.dragWidget = dragWidget;
const icon = Gtk.DragIcon.get_for_drag(gdkDrag);
icon.set_child(dragWidget);
gdkDrag.set_hotspot(this._dragParent.dragX, this._dragParent.dragY);
});
dragSource.connect('prepare', (self, x, y) => {
this.emit('drag-drop-prepare');
this.set_state_flags(Gtk.StateFlags.NORMAL, true);
const parent = self.get_widget().get_parent();
// store drag start cursor location
parent.dragX = x;
parent.dragY = y;
return new Gdk.ContentProvider();
});
dragSource.connect('drag-end', (_self, _gdkDrag, _deleteData) => {
this._dragParent.dragWidget = null;
this._dragParent.drag_unhighlight_row();
_deleteData = true;
});
dropTarget.connect('drag-enter', self => {
const parent = self.get_widget().get_parent();
const widget = self.get_widget();
parent.drag_highlight_row(widget);
});
dropTarget.connect('drag-leave', self => {
const parent = self.get_widget().get_parent();
parent.drag_unhighlight_row();
});
dropTarget.connect('drop', (_self, gdkDrop) => {
const parent = this.get_parent();
const dragRow = parent.dragRow; // The row being dragged.
const dragRowStartIndex = dragRow.get_index();
const dragRowNewIndex = this.get_index();
gdkDrop.read_value_async(AzClockWidgetRow, 1, null, () => gdkDrop.finish(Gdk.DragAction.MOVE));
// The drag row hasn't moved
if (dragRowStartIndex === dragRowNewIndex)
return true;
this.emit('drag-drop-done', dragRowStartIndex, dragRowNewIndex);
return true;
});
this.connect('activated', () => {
const widgetSettingsWindow = new WidgetSettingsPage(this._settings, this._widgetSettings, {
title: this._widgetSettings.get_string('name'),
schema_id: this._schemaId,
transient_for: this.get_root(),
modal: true,
resizable: false,
default_width: 900,
default_height: 700,
widget_index: this.get_index(),
});
widgetSettingsWindow.connect('notify::title', () => (this.title = `<b>${widgetSettingsWindow.title}</b>`));
widgetSettingsWindow.present();
});
const configureLabel = new Gtk.Label({
label: _('Configure'),
halign: Gtk.Align.END,
valign: Gtk.Align.CENTER,
hexpand: false,
vexpand: false,
});
this.add_suffix(configureLabel);
const goNextImage = new Gtk.Image({
gicon: Gio.icon_new_for_string('go-next-symbolic'),
halign: Gtk.Align.END,
valign: Gtk.Align.CENTER,
hexpand: false,
vexpand: false,
});
this.add_suffix(goNextImage);
}
createDragRow(alloc) {
const dragWidget = new Gtk.ListBox();
dragWidget.set_size_request(alloc.width, -1);
const dragRow = new WidgetRow(this._settings, this._widgetSettings, this._schemaId, this._params);
dragWidget.append(dragRow);
dragWidget.drag_highlight_row(dragRow);
return dragWidget;
}
});
var AddWidgetsDialog = GObject.registerClass(
class AzClockAddWidgetsDialog extends DialogWindow {
_init(extension, settings, parent) {
super._init(_('Add Widget'), parent);
this._extension = extension;
this._settings = settings;
this.search_enabled = false;
this.set_default_size(550, -1);
this.pageGroup.title = _('Preset Widgets');
this.pageGroup.add(this.addPresetWidget(_('Digital Clock Widget'), WidgetType.DIGITAL));
this.pageGroup.add(this.addPresetWidget(_('Analog Clock Widget'), WidgetType.ANALOG));
this.pageGroup.add(this.addPresetWidget(_('Weather Widget'), WidgetType.WEATHER));
this.pageGroup.add(this.addPresetWidget(_('Custom Widget'), WidgetType.CUSTOM,
_('Add your own custom elements')));
this.cloneGroup = new Adw.PreferencesGroup({
title: _('Clone existing Widget'),
});
this.cloneGroup.use_markup = true;
this.page.add(this.cloneGroup);
const widgets = this._settings.get_value('widgets').recursiveUnpack();
widgets.forEach(widget => {
for (const [widgetId] of Object.entries(widget)) {
const widgetSchema = `${this._settings.schema_id}.widget-data`;
const widgetPath = `${this._settings.path}widget-data/${widgetId}/`;
const widgetSettings = Utils.getSettings(this._extension, widgetSchema, widgetPath);
const name = widgetSettings.get_string('name');
this.cloneGroup.add(this.addPresetWidget(name, widgetSettings));
}
});
}
addPresetWidget(title, widgetType, subtitle) {
const addButton = new Gtk.Button({
icon_name: 'list-add-symbolic',
valign: Gtk.Align.CENTER,
});
addButton.connect('clicked', () => {
const widgetIds = [];
const widgets = this._settings.get_value('widgets').deepUnpack();
widgets.forEach(widget => {
for (const [widgetId] of Object.entries(widget))
widgetIds.push(widgetId);
});
const newWidget = {};
const widgetFolderId = GLib.uuid_string_random();
const widgetSchema = `${this._settings.schema_id}.widget-data`;
const widgetPath = `${this._settings.path}widget-data/${widgetFolderId}/`;
const widgetSettings = Utils.getSettings(this._extension, widgetSchema, widgetPath);
const elements = [];
const elementSchema = `${this._settings.schema_id}.element-data`;
const elementPath = `${widgetSettings.path}element-data/`;
let randomId;
if (widgetType === WidgetType.DIGITAL) {
try {
widgetSettings.set_string('name', _('Digital Clock Widget'));
let element = {};
randomId = GLib.uuid_string_random();
let elementSettings = Utils.getSettings(this._extension, elementSchema, `${elementPath}${randomId}/`);
element[randomId] = new GLib.Variant('a{sv}', {
enabled: GLib.Variant.new_boolean(true),
});
elements.push(element);
elementSettings.set_string('name', _('Time Label'));
element = {};
randomId = GLib.uuid_string_random();
elementSettings = Utils.getSettings(this._extension, elementSchema, `${elementPath}${randomId}/`);
element[randomId] = new GLib.Variant('a{sv}', {
enabled: GLib.Variant.new_boolean(true),
});
elements.push(element);
elementSettings.set_string('name', _('Date Label'));
elementSettings.set_string('date-format', '%A %b %d');
elementSettings.set_int('font-size', 32);
widgetSettings.set_value('elements', new GLib.Variant('aa{sv}', elements));
} catch (e) {
console.log(`Error creating New Digital Clock Widget: ${e}`);
}
} else if (widgetType === WidgetType.ANALOG) {
widgetSettings.set_string('name', _('Analog Clock Widget'));
const element = {};
randomId = GLib.uuid_string_random();
const elementSettings = Utils.getSettings(this._extension, elementSchema, `${elementPath}${randomId}/`);
element[randomId] = new GLib.Variant('a{sv}', {
enabled: GLib.Variant.new_boolean(true),
});
elements.push(element);
elementSettings.set_string('name', _('Analog Clock'));
elementSettings.set_enum('element-type', Utils.ElementType.ANALOG_CLOCK);
elementSettings.set_value('shadow', new GLib.Variant('(bsiiii)', [true, 'rgba(55, 55, 55, 0.3)', 3, 3, 0, 0]));
elementSettings.set_int('border-radius', 999);
elementSettings.set_int('border-width', 2);
elementSettings.set_string('background-color', 'white');
elementSettings.set_boolean('show-border', true);
elementSettings.set_string('border-color', 'black');
elementSettings.set_string('foreground-color', 'black');
widgetSettings.set_value('elements', new GLib.Variant('aa{sv}', elements));
} else if (widgetType === WidgetType.WEATHER) {
widgetSettings.set_string('name', _('Weather Widget'));
widgetSettings.set_boolean('show-background', true);
widgetSettings.set_string('background-color', 'rgba(0, 0, 0, .6)');
const element = {};
randomId = GLib.uuid_string_random();
const elementSettings = Utils.getSettings(this._extension, elementSchema, `${elementPath}${randomId}/`);
element[randomId] = new GLib.Variant('a{sv}', {
enabled: GLib.Variant.new_boolean(true),
});
elements.push(element);
elementSettings.set_string('name', _('Weather Forecast'));
elementSettings.set_enum('element-type', Utils.ElementType.WEATHER_ELEMENT);
elementSettings.set_int('polling-interval', 300);
widgetSettings.set_value('elements', new GLib.Variant('aa{sv}', elements));
} else if (widgetType === WidgetType.CUSTOM) {
widgetSettings.set_string('name', _('Custom Widget'));
} else {
const setValue = (copySetting, newSetting, key) => {
const defaultValue = copySetting.get_default_value(key);
const value = copySetting.get_value(key);
if (!defaultValue.equal(value))
newSetting.set_value(key, value);
};
const copyWidgetSettings = widgetType;
const copyElements = copyWidgetSettings.get_value('elements').deepUnpack();
const copyElementPath = `${copyWidgetSettings.path}element-data/`;
const keys = copyWidgetSettings.settings_schema.list_keys();
for (const key of keys)
setValue(copyWidgetSettings, widgetSettings, key);
copyElements.forEach(element => {
for (const [elementId] of Object.entries(element)) {
const copyElementSettings = Utils.getSettings(this._extension, elementSchema, `${copyElementPath}${elementId}/`);
const elementSettings = Utils.getSettings(this._extension, elementSchema, `${elementPath}${elementId}/`);
const elementKeys = copyElementSettings.settings_schema.list_keys();
for (const key of elementKeys)
setValue(copyElementSettings, elementSettings, key);
}
});
}
newWidget[widgetFolderId] = new GLib.Variant('a{sv}', {
enabled: GLib.Variant.new_boolean(true),
});
widgets.push(newWidget);
this._settings.set_value('widgets', new GLib.Variant('aa{sv}', widgets));
this.emit('response', Gtk.ResponseType.APPLY);
});
const row = new Adw.ActionRow({
subtitle: subtitle ? _(subtitle) : '',
title: _(title),
activatable_widget: addButton,
});
row.add_suffix(addButton);
return row;
}
});