diff --git a/analog-clock@cobinja.de/files/analog-clock@cobinja.de/6.4/default_settings.json b/analog-clock@cobinja.de/files/analog-clock@cobinja.de/6.4/default_settings.json new file mode 100644 index 000000000..26b08ece7 --- /dev/null +++ b/analog-clock@cobinja.de/files/analog-clock@cobinja.de/6.4/default_settings.json @@ -0,0 +1,12 @@ +{ + "theme": "Default", + "size": 150, + "show-seconds": true, + "hide-decorations": false, + "timezone-use": false, + "timezone": { + "city": "Berlin", + "region": "Europe" + }, + "timezone-display": true +} diff --git a/analog-clock@cobinja.de/files/analog-clock@cobinja.de/6.4/desklet.js b/analog-clock@cobinja.de/files/analog-clock@cobinja.de/6.4/desklet.js new file mode 100644 index 000000000..eea8b8c87 --- /dev/null +++ b/analog-clock@cobinja.de/files/analog-clock@cobinja.de/6.4/desklet.js @@ -0,0 +1,494 @@ +const Cinnamon = imports.gi.Cinnamon; +const St = imports.gi.St; + +const Desklet = imports.ui.desklet; + +//~ const Lang = imports.lang; +//~ const Mainloop = imports.mainloop; +const Signals = imports.signals; +const Util = imports.misc.util; +const UPowerGlib = imports.gi.UPowerGlib; +const GLib = imports.gi.GLib; +const Gio = imports.gi.Gio; +const GdkPixbuf = imports.gi.GdkPixbuf; +const Clutter = imports.gi.Clutter; +const Cogl = imports.gi.Cogl; +const SignalManager = imports.misc.signalManager; +const { + _sourceIds, + timeout_add_seconds, + timeout_add, + setTimeout, + clearTimeout, + setInterval, + clearInterval, + source_exists, + source_remove, + remove_all_sources +} = require("./mainloopTools") + +const UUID = "analog-clock@cobinja.de"; + +const DESKLET_DIR = imports.ui.deskletManager.deskletMeta[UUID].path; + +const DEG_PER_SECOND = 360 / 60; +const DEG_PER_HOUR = 360 / 12; +const MARGIN = 5; + +function getImageAtScale(imageFileName, scale) { + let width, height, fileInfo; + [fileInfo, width, height] = GdkPixbuf.Pixbuf.get_file_info(imageFileName); + + let scaledWidth = scale * width; + let scaledHeight = scale * height; + + let pixBuf = GdkPixbuf.Pixbuf.new_from_file_at_size(imageFileName, scaledWidth, scaledHeight); + let image = new Clutter.Image(); + image.set_data( + pixBuf.get_pixels(), + pixBuf.get_has_alpha() ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGBA_888, + scaledWidth, scaledHeight, + pixBuf.get_rowstride() + ); + + let actor = new Clutter.Actor({width: scaledWidth, height: scaledHeight}); + actor.set_content(image); + + return {actor: actor, origWidth: width, origHeight: height}; +} + +function CobiAnalogClockSettings(instanceId) { + this._init(instanceId); +} + +CobiAnalogClockSettings.prototype = { + _init: function(instanceId) { + this._instanceId = instanceId; + this._signalManager = new SignalManager.SignalManager(null); + this.values = {}; + + let settingsDirName = GLib.get_user_config_dir(); + if (!settingsDirName) { + settingsDirName = GLib.get_home_dir() + "/.config"; + } + settingsDirName += "/cobinja/" + UUID; + this._settingsDir = Gio.file_new_for_path(settingsDirName); + if (!this._settingsDir.query_exists(null)) { + this._settingsDir.make_directory_with_parents(null); + } + + this._settingsFile = this._settingsDir.get_child(this._instanceId + ".json"); + if (!this._settingsFile.query_exists(null)) { + this._getDefaultSettingsFile().copy(this._settingsFile, 0, null, null); + } + + this._onSettingsChanged(); + + this._upgradeSettings(); + + this._monitor = this._settingsFile.monitor(Gio.FileMonitorFlags.NONE, null); + this._signalManager.connect(this._monitor, "changed", this._onSettingsChanged, this); + }, + + _getDefaultSettingsFile: function() { + return Gio.file_new_for_path(DESKLET_DIR + "/default_settings.json"); + }, + + _onSettingsChanged: function() { + let settings; + try { + settings = JSON.parse(Cinnamon.get_file_contents_utf8_sync(this._settingsFile.get_path())); + } + catch (e) { + global.logError("Could not parse CobiAnalogClock's settings.json", e) + return true; + } + + for (let key in settings) { + if (settings.hasOwnProperty(key)) { + let comparison; + if (key == "timezone") { + let currentTZ = this.values[key]; + if (currentTZ) { + let settingRegion = settings[key]["region"]; + let settingCity = settings[key]["city"]; + let valueRegion = currentTZ["region"]; + let valueCity = currentTZ["city"]; + comparison = settingRegion !== valueRegion || settingCity !== valueCity; + } + else { + comparison = true; + } + } + else { + comparison = this.values[key] !== settings[key]; + } + if (comparison) { + this.values[key] = settings[key]; + this.emit(key + "-changed", this.values[key]); + this.emit("changed", key, this.values[key]); + } + } + } + return true; + }, + + _upgradeSettings: function() { + let defaultSettings; + try { + defaultSettings = JSON.parse(Cinnamon.get_file_contents_utf8_sync(this._getDefaultSettingsFile().get_path())); + } + catch (e) { + global.logError("Could not parse CobiAnalogClock's default_settings.json", e); + return true; + } + for (let key in defaultSettings) { + if (defaultSettings.hasOwnProperty(key) && !(key in this.values)) { + this.values[key] = defaultSettings[key]; + } + } + for (let key in this.values) { + if (this.values.hasOwnProperty(key) && !(key in defaultSettings)) { + delete this.values[key]; + } + } + this._writeSettings(); + return false; + }, + + setValue: function(key, value) { + if (!compareArray(value, this.values[key])) { + this.values[key] = value; + this.emit(key + "-changed", this.values[key]); + this._writeSettings(); + } + }, + + _writeSettings: function() { + let filedata = JSON.stringify(this.values, null, " "); + GLib.file_set_contents(this._settingsFile.get_path(), filedata); + }, + + destroy: function(deleteConfig) { + this._signalManager.disconnectAllSignals(); + this._monitor.cancel(); + if (deleteConfig !== undefined && deleteConfig == true) { + let file = this._settingsFile; + file.delete(null); + file = file.get_parent(); // dir analog-clock@cobinja.de/ + file.delete(null); + file = file.get_parent(); // dir ~/config/cobinja/ + file.delete(null); + } + this.values = null; + } +} + +Signals.addSignalMethods(CobiAnalogClockSettings.prototype); + +function CobiAnalogClock(metadata, instanceId){ + this._init(metadata, instanceId); +} + +CobiAnalogClock.prototype = { + __proto__: Desklet.Desklet.prototype, + + _init: function(metadata, instanceId){ + Desklet.Desklet.prototype._init.call(this, metadata, instanceId); + this._signalManager = new SignalManager.SignalManager(null); + this._settings = new CobiAnalogClockSettings(instanceId); + + this._displayTime = new GLib.DateTime(); + + this._menu.addAction(_("Settings"), () => {Util.spawnCommandLine(DESKLET_DIR + "/settings.py " + instanceId);}); + }, + + on_desklet_added_to_desktop: function(userEnabled) { + this.metadata["prevent-decorations"] = this._settings.values["hide-decorations"]; + this._updateDecoration(); + + this._clockSize = this._settings.values["size"] * global.ui_scale; + + this._clockActor = new St.Widget(); + + this._tzLabel = new St.Label(); + + this.setHeader(_("Clock")); + this.setContent(this._clockActor); + + this._onSizeChanged(); + + let currentMillis = new Date().getMilliseconds(); + let timeoutMillis = (1000 - currentMillis) % 1000; + + this._signalManager.connect(global, "scale-changed", this._onSizeChanged, this); + this._signalManager.connect(this._settings, "size-changed", this._onSizeChanged, this); + this._signalManager.connect(this._settings, "theme-changed", this._onThemeChanged, this); + this._signalManager.connect(this._settings, "hide-decorations-changed", this._onHideDecorationsChanged, this); + this._signalManager.connect(global.settings, "changed::desklet-decorations", this._onHideDecorationsChanged, this); + this._signalManager.connect(this._settings, "show-seconds-changed", this._onShowSecondsChanged, this); + + this._signalManager.connect(this._settings, "timezone-use-changed", this._onTimezoneChanged, this); + this._signalManager.connect(this._settings, "timezone-changed", this._onTimezoneChanged, this); + this._signalManager.connect(this._settings, "timezone-display-changed", this._onTimezoneDisplayChanged, this); + + this._upClient = new UPowerGlib.Client(); + try { + this._upClient.connect('notify-resume', () => { this._onPowerResume() }); + } + catch (e) { + this._upClient.connect('notify::resume', () => { this._onPowerResume() }); + } + this._onTimezoneChanged(); + }, + + _onPowerResume: function() { + this._initialUpdate = true; + this._updateClock(); + }, + + _loadTheme: function() { + let themeName = this._settings.values["theme"]; + let themesDir = Gio.file_new_for_path(DESKLET_DIR + "/../themes"); + let themeDir = themesDir.get_child(themeName); + let metaDataFile = themeDir.get_child("metadata.json"); + let metaData = JSON.parse(Cinnamon.get_file_contents_utf8_sync(metaDataFile.get_path())); + + let clock = {"size": metaData["size"], "tz-label": metaData["tz-label"]}; + let scale = this._clockSize / clock["size"]; + + let bodyFileName = metaData["body"]; + let body = getImageAtScale(themeDir.get_child(bodyFileName).get_path(), scale); + clock.body = body; + + let clockfaceFileName = metaData["clockface"]; + let clockface = getImageAtScale(themeDir.get_child(clockfaceFileName).get_path(), scale); + clock.clockface = clockface; + + let frameFileName = metaData["frame"]; + let frame = getImageAtScale(themeDir.get_child(frameFileName).get_path(), scale); + clock.frame = frame; + + let hourFileName = metaData["hour"]["fileName"]; + let hour = getImageAtScale(themeDir.get_child(hourFileName).get_path(), scale); + hour.pivotX = metaData["hour"]["pivot-x"]; + hour.pivotY = metaData["hour"]["pivot-y"]; + clock.hour = hour; + + let minuteFileName = metaData["minute"]["fileName"]; + let minute = getImageAtScale(themeDir.get_child(minuteFileName).get_path(), scale); + minute.pivotX = metaData["minute"]["pivot-x"]; + minute.pivotY = metaData["minute"]["pivot-y"]; + clock.minute = minute; + + let secondFileName = metaData["second"]["fileName"]; + let second = getImageAtScale(themeDir.get_child(secondFileName).get_path(), scale); + second.pivotX = metaData["second"]["pivot-x"]; + second.pivotY = metaData["second"]["pivot-y"]; + clock.second = second; + + return clock; + }, + + _loadClock: function() { + let newClock = this._loadTheme(); + this._clock = newClock; + this._clockActor.remove_all_children(); + + this._clockActor.set_style(this._clock["tz-label"]); + this._clockActor.add_actor(this._clock.body.actor); + this._clock.body.actor.set_position(MARGIN * global.ui_scale, MARGIN * global.ui_scale); + + this._clockActor.add_actor(this._clock.clockface.actor); + this._clock.clockface.actor.set_position(MARGIN * global.ui_scale, MARGIN * global.ui_scale); + + // add timezone label + this._clockActor.add_actor(this._tzLabel); + //this._tzLabel.set_style(this._clock["tz-label"]); + this._updateTzLabel(); + + // add hands + let hour = this._clock.hour; + this._clockActor.add_actor(hour.actor); + let pivotPoint = {}; + pivotPoint.x = hour.pivotX / hour.origWidth; + pivotPoint.y = hour.pivotY / hour.origHeight; + hour.actor.set_pivot_point(pivotPoint.x, pivotPoint.y); + hour.actor.set_position(((this._clockSize / 2) - pivotPoint.x * hour.actor.size.width) + MARGIN * global.ui_scale, + (this._clockSize / 2) - pivotPoint.y * hour.actor.size.height + MARGIN * global.ui_scale); + + let minute = this._clock.minute; + this._clockActor.add_actor(minute.actor); + pivotPoint.x = minute.pivotX / minute.origWidth; + pivotPoint.y = minute.pivotY / minute.origHeight; + minute.actor.set_pivot_point(pivotPoint.x, pivotPoint.y); + minute.actor.set_position((this._clockSize / 2) - pivotPoint.x * minute.actor.size.width + MARGIN * global.ui_scale, + (this._clockSize / 2) - pivotPoint.y * minute.actor.size.height + MARGIN * global.ui_scale); + + let second = this._clock.second; + this._clockActor.add_actor(second.actor); + pivotPoint.x = second.pivotX / second.origWidth; + pivotPoint.y = second.pivotY / second.origHeight; + second.actor.set_pivot_point(pivotPoint.x, pivotPoint.y); + second.actor.set_position((this._clockSize / 2) - pivotPoint.x * second.actor.size.width + MARGIN * global.ui_scale, + (this._clockSize / 2) - pivotPoint.y * second.actor.size.height + MARGIN * global.ui_scale); + this._showSecondsChangedImpl(); + + this._clockActor.add_actor(this._clock.frame.actor); + this._clock.frame.actor.set_position(MARGIN * global.ui_scale, MARGIN * global.ui_scale); + + this._initialUpdate = true; + }, + + _onThemeChanged: function() { + if (this._timeoutId) + source_remove(this._timeoutId); + try { + this._loadClock(); + } + catch (e) { + global.logError("Could not load analog clock theme", e); + } + this._updateClock(); + }, + + _onSizeChanged: function() { + let size = this._settings.values["size"] * global.ui_scale; + this._clockActor.set_width(size + 2 * MARGIN * global.ui_scale); + this._clockActor.set_height(size + 2 * MARGIN * global.ui_scale); + this._clockSize = size; + this._loadClock(); + this._updateClock(); + }, + + _showSecondsChangedImpl: function() { + let showSeconds = this._settings.values["show-seconds"]; + showSeconds ? this._clock.second.actor.show() : this._clock.second.actor.hide(); + }, + + _onShowSecondsChanged: function() { + this._showSecondsChangedImpl(); + this._updateClock(); + }, + + _onHideDecorationsChanged: function() { + this.metadata["prevent-decorations"] = this._settings.values["hide-decorations"]; + this._updateDecoration(); + this._updateTzLabel(); + }, + + _onTimezoneChanged: function() { + let tz = this._settings.values["timezone"]; + let zoneName = tz["region"]; + if (tz["city"] != "") { + zoneName += "/" + tz["city"]; + } + let zoneDirName = "/usr/share/zoneinfo/"; + let zoneDir = Gio.file_new_for_path(zoneDirName); + let tzId = zoneDirName + tz["region"]; + if (tz["city"]) { + tzId += "/" + tz["city"]; + } + tzId = tzId.replace(" ", "_"); + let tzFile = Gio.file_new_for_path(tzId); + this._tzId = tzFile.query_exists(null) ? ":" + tzId : null; + this._updateHeader(); + this._updateTzLabel(); + this._initialUpdate = true; + this._updateClock(); + }, + + _onTimezoneDisplayChanged: function() { + this._updateHeader(); + this._updateTzLabel(); + }, + + _getTzLabelText: function() { + let result = _("Clock"); + if (this._settings.values["timezone-use"] && this._settings.values["timezone-display"]) { + let tz = this._settings.values["timezone"]; + if (tz["city"] && tz["city"] != "") { + result = tz["city"]; + } + else { + result = tz["region"]; + } + } + return result; + }, + + _updateTzLabel: function() { + let showLabel = (this._settings.values["hide-decorations"] || global.settings.get_int("desklet-decorations") <= 1) && + this._settings.values["timezone-use"] && + this._settings.values["timezone-display"]; + if (showLabel) { + this._tzLabel.set_text(this._getTzLabelText()); + let themeFontSize = this._clockActor.get_theme_node().get_length("font-size"); + let fontSize = themeFontSize * this._clockSize / (this._clock["size"] * global.ui_scale); + this._tzLabel.set_style("font-size: " + fontSize + "px;"); + let lSize = this._tzLabel.size; + let aSize = this._clockActor.size; + let x = Math.round((aSize.width - lSize.width) / 2.0); + let y = Math.round((aSize.height - lSize.height) * 2 / 3.0); + this._tzLabel.set_position(x, y); + this._tzLabel.show(); + } + else { + this._tzLabel.hide(); + } + }, + + _updateHeader: function() { + this.setHeader(this._getTzLabelText()); + }, + + _updateClock: function() { + if (this._inRemoval != undefined) { + return false; + } + this._displayTime = new GLib.DateTime(); + if (this._settings.values["timezone-use"] && this._tzId != null) { + let tz = GLib.TimeZone.new(this._tzId); + this._displayTime = this._displayTime.to_timezone(tz); + } + + let newTimeoutSeconds = 1; + if (!this._settings.values["show-seconds"]) { + let seconds = this._displayTime.get_second(); + newTimeoutSeconds = 60 - seconds; + } + + let hours = this._displayTime.get_hour() % 12; + let minutes = this._displayTime.get_minute() % 60; + let seconds = this._displayTime.get_second() % 60; + + if (seconds == 0 || this._initialUpdate) { + if (minutes % 2 == 0 || this._initialUpdate) { + this._clock.hour.actor.set_rotation_angle(Clutter.RotateAxis.Z_AXIS, DEG_PER_HOUR * hours + (minutes * 0.5)); + } + this._clock.minute.actor.set_rotation_angle(Clutter.RotateAxis.Z_AXIS, DEG_PER_SECOND * minutes); + this._initialUpdate = false; + } + if (this._settings.values["show-seconds"]) { + this._clock.second.actor.set_rotation_angle(Clutter.RotateAxis.Z_AXIS, DEG_PER_SECOND * seconds); + } + + this._timeoutId = timeout_add_seconds(newTimeoutSeconds, () => { this._updateClock() }); + return false; + }, + + on_desklet_removed: function(deleteConfig) { + this._inRemoval = true; + if (this._timeoutId) { + source_remove(this._timeoutId); + } + this._signalManager.disconnectAllSignals(); + remove_all_sources(); + this._settings.destroy(deleteConfig); + } +} + +function main(metadata, instanceId){ + let desklet = new CobiAnalogClock(metadata, instanceId); + return desklet; +} diff --git a/analog-clock@cobinja.de/files/analog-clock@cobinja.de/6.4/icon.png b/analog-clock@cobinja.de/files/analog-clock@cobinja.de/6.4/icon.png new file mode 120000 index 000000000..0dba589b0 --- /dev/null +++ b/analog-clock@cobinja.de/files/analog-clock@cobinja.de/6.4/icon.png @@ -0,0 +1 @@ +../icon.png \ No newline at end of file diff --git a/analog-clock@cobinja.de/files/analog-clock@cobinja.de/6.4/mainloopTools.js b/analog-clock@cobinja.de/files/analog-clock@cobinja.de/6.4/mainloopTools.js new file mode 100644 index 000000000..321a9fd0c --- /dev/null +++ b/analog-clock@cobinja.de/files/analog-clock@cobinja.de/6.4/mainloopTools.js @@ -0,0 +1,179 @@ +const GLib = imports.gi.GLib; + +/** + * _sourceIds + * Array containing IDs of all looping loops. + */ +var _sourceIds = []; + +/** + * timeout_add_seconds + * + * @callback (function) is executed every @sec (number) seconds + * with @params (dictionnary as {'key1': value1, 'key2': value2, ...}). + * + * @params is often null. + * + */ +function timeout_add_seconds(sec, callback, params=null) { + let id = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, sec, callback); + if (id && (_sourceIds.indexOf(id) === -1)) _sourceIds.push(id); + return id; +} + +/** + * timeout_add_seconds + * + * @callback (function) is executed every @ms (number) milliseconds + * with @params (dictionnary as {'key1': value1, 'key2': value2, ...}). + * + * @params is often null. + * + */ +function timeout_add(ms, callback, params=null) { + let id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, ms, callback); + if (id && (_sourceIds.indexOf(id) === -1)) _sourceIds.push(id); + return id; +} + +/** + * setTimeout: + * @callback (function): Function to call at the end of the timeout. + * @ms (number): Milliseconds until the timeout expires. + * + * Convenience wrapper for a Mainloop.timeout_add loop that + * returns false. + * + * Returns (number): The ID of the loop. + */ +function setTimeout(callback, ms) { + let args = []; + if (arguments.length > 2) { + args = args.slice.call(arguments, 2); + } + + let id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, + ms, + () => { + callback.call(null, ...args); + return false; // Stop repeating + } + ); + + if (id && (_sourceIds.indexOf(id) === -1)) _sourceIds.push(id); + + return id; +} + +/** + * clearTimeout: + * @id (number): The ID of the loop to remove. + * + * Convenience wrapper for Mainloop.source_remove. + */ +function clearTimeout(id) { + if (id) { + source_remove(id); + } +} + + +/** + * setInterval: + * @callback (function): Function to call on every interval. + * @ms (number): Milliseconds between invocations. + * + * Convenience wrapper for a Mainloop.timeout_add loop that + * returns true. + * + * Returns (number): The ID of the loop. + */ +function setInterval(callback, ms) { + let args = []; + if (arguments.length > 2) { + args = args.slice.call(arguments, 2); + } + + let id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, ms, () => { + callback.call(null, ...args); + return true; // Repeat + }); + + if (id && (_sourceIds.indexOf(id) === -1)) _sourceIds.push(id); + + return id; +} + +/** + * clearInterval: + * @id (number): The ID of the loop to remove. + * + * Convenience wrapper for Mainloop.source_remove. + */ +function clearInterval(id) { + if (id) { + source_remove(id); + } +}; + +/** + * source_exists + * + * @id (number, or null) + * + * Checks if @id is well the ID of a loop. + * + */ +function source_exists(id) { + let _id = id; + if (!_id) return false; + return (GLib.MainContext.default().find_source_by_id(_id) != null); +} + +/** + * source_remove + * + * @id (number): The ID of the loop to stop. + * @remove_from_sourceIds (boolean): *true* (by default) when we want to + * remove @id from _sourceIds. May be *false* for internal functionning. + * + * Convenience wrapper for a Mainloop.source_remove(id) that returns a + * boolean. + */ +function source_remove(id, remove_from_sourceIds=true) { + if (source_exists(id)) { + GLib.source_remove(id); + if (remove_from_sourceIds) { + const pos = _sourceIds.indexOf(id); + if (pos > -1) _sourceIds.splice(pos, 1); + } + return true; + } + return false; +} + +/** + * remove_all_sources + * + * Execute it when removing the spice. + * Tries to delete all remaining sources, in order to remove all loops. + */ +function remove_all_sources() { + while (_sourceIds.length > 0) { + let id = _sourceIds.pop(); + source_remove(id, false); + } +} + +module.exports = { + _sourceIds, + timeout_add_seconds, + timeout_add, + setTimeout, + clearTimeout, + setInterval, + clearInterval, + source_exists, + source_remove, + remove_all_sources +} diff --git a/analog-clock@cobinja.de/files/analog-clock@cobinja.de/6.4/settings.py b/analog-clock@cobinja.de/files/analog-clock@cobinja.de/6.4/settings.py new file mode 100755 index 000000000..4e3219d47 --- /dev/null +++ b/analog-clock@cobinja.de/files/analog-clock@cobinja.de/6.4/settings.py @@ -0,0 +1,334 @@ +#! /usr/bin/env python3 +# +# settings.py +# Copyright (C) 2013 Lars Mueller +# +# CobiAnalogClock 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. +# +# CobiAnalogClock 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 . + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, GLib, Gio, GObject +import os, sys +import locale +import json +import collections + +DESKLET_DIR = os.path.dirname(os.path.abspath(__file__)) +UI_FILE = DESKLET_DIR + "/settings.ui" + +UUID = "analog-clock@cobinja.de" + +localeDir = os.path.expanduser("~") + "/.local/share/locale" +locale.bindtextdomain(UUID, localeDir) + +def getThemeNames(path): + themeNames = []; + for (path, dirs, files) in os.walk(path): + if "metadata.json" in files: + themeNames.append(os.path.basename(path)) + themeNames.sort() + return themeNames + +def getTimezones(): + lsZones = Gtk.ListStore(GObject.TYPE_INT, GObject.TYPE_STRING) + regionNames = []; + lsCities = Gtk.ListStore(GObject.TYPE_INT, GObject.TYPE_STRING, GObject.TYPE_STRING) + + _tzinfo_dir = os.getenv("TZDIR") or "/usr/share/zoneinfo" + if _tzinfo_dir.endswith(os.sep): + _tzinfo_dir = _tzinfo_dir[:-1] + + timeZones = [l.split()[2] + for l in open(os.path.join(_tzinfo_dir, "zone.tab")) + if l != "" and l[0] != "#"]\ + + ['GMT', + 'US/Alaska', + 'US/Arizona', + 'US/Central', + 'US/Eastern', + 'US/Hawaii', + 'US/Mountain', + 'US/Pacific', + 'UTC'] + timeZones.sort() + + i = 0 + for tz in timeZones: + i += 1 + (region, sep, city) = tz.partition("/") + if not region in regionNames: + region = region.replace('_', ' ') + regionNames.append(region) + lsZones.append([len(regionNames), region]) + if city: + city = city.replace ('_', ' ') + lsCities.append([i, city, region]) + return (lsZones, lsCities) + +def filterDetailFunc(model, iterator, regionCombo): + rcIter = regionCombo.get_active_iter() + if rcIter == None: return False + + rcModel = regionCombo.get_model() + activeRegion = rcModel[rcIter][1] + + cityRegion = model[iterator][2] + return activeRegion == cityRegion + +class CobiSettings: + def __init__(self, instanceId): + self.instanceId = instanceId + settingsDirName = GLib.get_user_config_dir() + if not settingsDirName: + settingsDirName = GLib.get_home_dir() + "/.config" + settingsDirName += "/cobinja/" + UUID + settingsDir = Gio.file_new_for_path(settingsDirName) + + if not settingsDir.query_exists(None): + settingsDir.make_directory_with_parents(None) + + self.__settingsFile = settingsDir.get_child(instanceId + ".json") + if not self.__settingsFile.query_exists(None): + self.__getDefaultSettingsFile().copy(self.__settingsFile, 0, None, None, None) + + self.values = collections.OrderedDict() + + self.__loadSettings() + + self.__monitor = self.__settingsFile.monitor(Gio.FileMonitorFlags.NONE, None) + self.__monitorChangedId = self.__monitor.connect("changed", self.__onSettingsChanged) + + def __getDefaultSettingsFile(self): + return Gio.file_new_for_path(DESKLET_DIR + "/default_settings.json") + + def writeSettings(self): + if self.changed(): + f = open(self.__settingsFile.get_path(), 'w') + f.write(json.dumps(self.values, sort_keys=False, indent=2)) + f.close() + self.__origSettings = collections.OrderedDict(self.values) + + def setEntry(self, key, value, writeToFile): + if key in self.values.keys() and self.values[key] != value: + self.values[key] = value + if writeToFile: + self.writeSettings() + + def __onSettingsChanged(self, monitor, thisFile, otherFile, eventType): + self.__loadSettings() + + def __loadSettings(self): + f = open(self.__settingsFile.get_path(), 'r') + settings = json.loads(f.read(), object_pairs_hook=collections.OrderedDict) + f.close() + for key in settings: + value = settings[key] + oldValue = self.values[key] if key in self.values.keys() else None + if value != oldValue: + self.values[key] = value + self.__origSettings = collections.OrderedDict(self.values) + + def changed(self): + return self.values != self.__origSettings + + def __del__(self): + self.__monitor.disconnect(self.__monitorChangedId) + self.__monitor.cancel() + +class CobiAnalogClockSettings: + def __init__(self): + instanceId = sys.argv[1]; + self.__settings = CobiSettings(instanceId) + + self.builder = Gtk.Builder() + self.builder.set_translation_domain(UUID) + self.builder.add_from_file(UI_FILE) + self.builder.connect_signals(self) + + self.lsTheme = Gtk.ListStore(GObject.TYPE_INT, GObject.TYPE_STRING) + cbTheme = self.builder.get_object("cbTheme") + # Load theme names + themeNames = getThemeNames(DESKLET_DIR + "/../themes") + activeIndex = 0 + for i in range(0, len(themeNames)): + themeName = themeNames[i] + self.lsTheme.append([i, themeName]) + if themeName == self.__settings.values["theme"]: + activeIndex = i + cbTheme.set_model(self.lsTheme) + crRegions = Gtk.CellRendererText() + cbTheme.pack_start(crRegions, True) + cbTheme.add_attribute(crRegions, "text", 1) + cbTheme.set_active(activeIndex) + cbTheme.connect("changed", self.onThemeChanged) + + cbShowSeconds = self.builder.get_object("cbShowSeconds") + cbShowSeconds.set_active(self.__settings.values["show-seconds"]) + cbShowSeconds.connect("toggled", self.onShowSecondsChanged) + + cbHideDecorations = self.builder.get_object("cbHideDecorations") + cbHideDecorations.set_active(self.__settings.values["hide-decorations"]) + cbHideDecorations.connect("toggled", self.onHideDecorationsChanged) + + sbSize = self.builder.get_object("sbSize") + sbSize.set_range(20, 1000) + sbSize.set_increments(1, 1) + sbSize.set_value(self.__settings.values["size"]) + sbSize.connect("value-changed", self.onSizeChanged) + + useTimezones = self.__settings.values["timezone-use"] + + cbUseTimezone = self.builder.get_object("cbUseTimezone") + cbUseTimezone.set_active(useTimezones) + cbUseTimezone.connect("toggled", self.onUseTimezoneChanged) + + self.cbTzRegion = self.builder.get_object("cbTzRegion") + self.cbTzCity = self.builder.get_object("cbTzCity") + + (self.lsTimezoneRegions, self.lsTimezoneCities) = getTimezones() + self.lsfCities = self.lsTimezoneCities.filter_new() + self.lsfCities.set_visible_func(filterDetailFunc, self.cbTzRegion) + self.cbTzCity.set_model(self.lsfCities) + self.cbTzRegion.set_model(self.lsTimezoneRegions) + + crRegions = Gtk.CellRendererText() + self.cbTzRegion.pack_start(crRegions, True) + self.cbTzRegion.add_attribute(crRegions, "text", 1) + crCity = Gtk.CellRendererText() + self.cbTzCity.pack_start(crCity, True) + self.cbTzCity.add_attribute(crCity, "text", 1) + + self.cbTzRegion.connect("changed", self.onTzRegionChanged) + self.cbTzCity.connect("changed", self.onTzCityChanged) + + activeTimezone = self.__settings.values["timezone"] + + iterator = self.lsTimezoneRegions.get_iter_first() + while iterator: + if self.lsTimezoneRegions[iterator][1] == activeTimezone["region"]: + self.cbTzRegion.set_active_iter(iterator) + break + iterator = self.lsTimezoneRegions.iter_next(iterator) + + iterator = self.lsfCities.get_iter_first() + while iterator: + if self.lsfCities[iterator][1] == activeTimezone["city"]: + self.cbTzCity.set_active_iter(iterator) + break + iterator = self.lsfCities.iter_next(iterator) + + self.cbTzDisplayLabel = self.builder.get_object("cbTzDisplayLabel") + self.cbTzDisplayLabel.set_active(self.__settings.values["timezone-display"]) + self.cbTzDisplayLabel.connect("toggled", self.onTzDisplayLabelChanged) + + self.cbTzRegion.set_sensitive(useTimezones) + self.cbTzCity.set_sensitive(useTimezones) + self.cbTzDisplayLabel.set_sensitive(useTimezones) + self.builder.get_object("lblTzDisplayLabel").set_sensitive(useTimezones) + + self.updateApplyButtonSensitivity() + + window = self.builder.get_object("SettingsWindow") + window.show_all() + + def destroy(self, window): + Gtk.main_quit() + + def okPressed(self, button): + self.applySettings(button) + Gtk.main_quit() + + def applySettings(self, button): + self.__settings.writeSettings() + self.updateApplyButtonSensitivity() + + def cancel(self, button): + Gtk.main_quit() + + def onThemeChanged(self, button): + tree_iter = button.get_active_iter() + if tree_iter != None: + themeName = self.lsTheme[tree_iter][1] + if themeName: + self.__settings.setEntry("theme", themeName, False) + self.updateApplyButtonSensitivity() + + def onSizeChanged(self, button): + self.__settings.setEntry("size", int(button.get_value()), False) + self.updateApplyButtonSensitivity() + + def onShowSecondsChanged(self, button): + self.__settings.setEntry("show-seconds", button.get_active(), False) + self.updateApplyButtonSensitivity() + + def onHideDecorationsChanged(self, button): + self.__settings.setEntry("hide-decorations", button.get_active(), False) + self.updateApplyButtonSensitivity() + + def onUseTimezoneChanged(self, button): + active = button.get_active() + self.__settings.setEntry("timezone-use", active, False) + self.cbTzRegion.set_sensitive(active) + self.cbTzCity.set_sensitive(active) + self.cbTzDisplayLabel.set_sensitive(active) + self.builder.get_object("lblTzDisplayLabel").set_sensitive(active) + self.updateApplyButtonSensitivity() + + def onTzRegionChanged(self, button): + tree_iter = button.get_active_iter() + if tree_iter != None: + region = self.lsTimezoneRegions[tree_iter][1] + if region: + self.lsfCities.refilter() + self.cbTzCity.set_active_iter(self.lsfCities.get_iter_first()) + self.cbTzCity.set_sensitive(len(self.lsfCities) > 0) + self.updateTzSetting() + self.updateApplyButtonSensitivity() + + def onTzCityChanged(self, button): + self.updateTzSetting() + self.updateApplyButtonSensitivity() + + def onTzDisplayLabelChanged(self, button): + active = button.get_active() + self.__settings.setEntry("timezone-display", active, False) + self.updateApplyButtonSensitivity() + + def updateTzSetting(self): + newTz = {} + regionIter = self.cbTzRegion.get_active_iter() + if regionIter != None: + region = self.lsTimezoneRegions[regionIter][1] + if region: + newTz["region"] = region + newTz["city"] = "" + cityIter = self.cbTzCity.get_active_iter() + if cityIter: + newTz["city"] = self.lsfCities[cityIter][1] + self.__settings.setEntry("timezone", newTz, False) + + def updateApplyButtonSensitivity(self): + btn = self.builder.get_object("buttonApply") + changed = self.__settings.changed() + btn.set_sensitive(changed) + +def main(): + CobiAnalogClockSettings() + Gtk.main() + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: settings.py ") + exit(0); + main() diff --git a/analog-clock@cobinja.de/files/analog-clock@cobinja.de/6.4/settings.ui b/analog-clock@cobinja.de/files/analog-clock@cobinja.de/6.4/settings.ui new file mode 100644 index 000000000..e5761760b --- /dev/null +++ b/analog-clock@cobinja.de/files/analog-clock@cobinja.de/6.4/settings.ui @@ -0,0 +1,409 @@ + + + + + False + 5 + Analog Clock Settings + False + center + True + icon.png + normal + + + + False + vertical + 2 + + + False + end + + + gtk-apply + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + + False + True + 0 + + + + + gtk-cancel + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + + False + True + 1 + + + + + gtk-ok + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + + False + True + 2 + + + + + False + True + end + 0 + + + + + True + False + 10 + 3 + 3 + 1 + vertical + 3 + + + True + False + 10 + + + True + False + 0 + Theme + + + False + True + 0 + + + + + True + False + 0 + 1 + + + False + True + end + 1 + + + + + False + True + 0 + + + + + True + False + 10 + + + True + False + 0 + Size + + + False + True + 0 + + + + + True + True + + 4 + True + False + 1 + True + if-valid + + + False + True + end + 1 + + + + + False + True + 1 + + + + + True + False + 10 + + + True + False + 0 + Show Seconds + + + False + True + 0 + + + + + True + True + False + 0 + True + + + False + True + end + 1 + + + + + False + True + 2 + + + + + True + False + + + True + False + Hide decorations + + + False + True + 0 + + + + + True + True + False + 0 + True + + + False + True + end + 1 + + + + + False + True + 3 + + + + + True + False + 10 + + + True + False + Use specific timezone + + + False + True + 0 + + + + + True + True + False + 0 + True + + + False + True + end + 1 + + + + + False + True + 4 + + + + + True + False + + + True + False + Display Timezone + + + False + True + 0 + + + + + True + True + False + 0 + True + + + False + True + end + 1 + + + + + False + True + 5 + + + + + True + False + 10 + + + True + False + + + True + True + end + 1 + + + + + True + False + + + True + True + end + 2 + + + + + True + True + 6 + + + + + False + True + 1 + + + + + + buttonApply + buttonCancel + buttonOk + + + + + + + + + + + + + + + + + + vertical + + + + + + + + + + + + + + + + diff --git a/analog-clock@cobinja.de/files/analog-clock@cobinja.de/metadata.json b/analog-clock@cobinja.de/files/analog-clock@cobinja.de/metadata.json index 819e33240..dc418f756 100644 --- a/analog-clock@cobinja.de/files/analog-clock@cobinja.de/metadata.json +++ b/analog-clock@cobinja.de/files/analog-clock@cobinja.de/metadata.json @@ -1,7 +1,8 @@ { - "uuid": "analog-clock@cobinja.de", - "name": "CobiAnalogClock", - "description": "An analog clock desklet", - "max-instances": "100", - "multiversion": true + "uuid": "analog-clock@cobinja.de", + "name": "CobiAnalogClock", + "description": "An analog clock desklet", + "max-instances": "100", + "multiversion": true, + "author": "cobinja" } diff --git a/clock@schorschii/files/clock@schorschii/desklet.js b/clock@schorschii/files/clock@schorschii/desklet.js index 520691747..87c681a87 100644 --- a/clock@schorschii/files/clock@schorschii/desklet.js +++ b/clock@schorschii/files/clock@schorschii/desklet.js @@ -3,8 +3,8 @@ const St = imports.gi.St; const GLib = imports.gi.GLib; const Util = imports.misc.util; const Cinnamon = imports.gi.Cinnamon; -const Mainloop = imports.mainloop; -const Lang = imports.lang; +//~ const Mainloop = imports.mainloop; +//~ const Lang = imports.lang; const Settings = imports.ui.settings; const Main = imports.ui.main; const Clutter = imports.gi.Clutter; @@ -119,7 +119,7 @@ MyDesklet.prototype = { timeoutval = Math.ceil(3000 - (1000*seconds + mseconds/1000) % 3000); else if(this.smooth_seconds_hand == false) timeoutval = Math.ceil(1000 - mseconds/1000); - this.timeout = Mainloop.timeout_add(timeoutval, Lang.bind(this, this.refresh)); + this.timeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, timeoutval, () => { this.refresh() }); }, refreshSize: function() { @@ -202,11 +202,11 @@ MyDesklet.prototype = { this.refreshSize(); // settings changed; instant refresh - Mainloop.source_remove(this.timeout); + GLib.source_remove(this.timeout); this.refresh(); }, on_desklet_removed: function() { - Mainloop.source_remove(this.timeout); + GLib.source_remove(this.timeout); } } diff --git a/clock@schorschii/files/clock@schorschii/metadata.json b/clock@schorschii/files/clock@schorschii/metadata.json index ca12f514d..dfb4ee50f 100644 --- a/clock@schorschii/files/clock@schorschii/metadata.json +++ b/clock@schorschii/files/clock@schorschii/metadata.json @@ -3,5 +3,6 @@ "max-instances": "10", "description": "A scaleable analog clock desklet with smooth hands and free choice of images.", "name": "Analog Chronometer", - "version": "1.6" + "version": "1.7", + "author": "schorschii" }