<commit_before>/**
 * Copyright (c) 2014-2015 <NAME> <<EMAIL>>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const Main = imports.ui.main;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const Meta = imports.gi.Meta;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Util = imports.misc.util;

const DeskChangerDaemon = Me.imports.daemon.DeskChangerDaemon;
const DeskChangerSettings = Me.imports.settings.DeskChangerSettings;
const DeskChangerVersion = Me.metadata.version;
const debug = Me.imports.utils.debug;

const DeskChangerButton = new Lang.Class({
    Name: 'DeskChangerButton',
    Extends: St.Button,

    _init: function (icon, callback) {
        this.icon = new St.Icon({icon_name: icon + '-symbolic', icon_size: 20});
        this.parent({
            child: this.icon,
            style_class: 'system-menu-action'// : 'notification-icon-button control-button'
        });
        this._handler = this.connect('clicked', callback);
    },

    destroy: function () {
        debug('removing button clicked handler ' + this._handler);
        this.disconnect(this._handler);
        this.icon.destroy();
        this.parent();
    },

    set_icon: function (icon) {
        this.icon.icon_name = icon + '-symbolic';
    }
});

const DeskChangerControls = new Lang.Class({
    Name: 'DeskChangerControls',
    Extends: PopupMenu.PopupBaseMenuItem,

    _init: function (dbus, settings) {
        this._dbus = dbus;
        this._settings = settings;
        this._bindings = new Array();
        this.parent({can_focus: false, reactive: false});

        this._addKeyBinding('next-wallpaper', Lang.bind(this, this.next));
        this._addKeyBinding('prev-wallpaper', Lang.bind(this._dbus, this.prev));

        this._next = new DeskChangerButton('media-skip-forward', Lang.bind(this, this.next));
        this._prev = new DeskChangerButton('media-skip-backward', Lang.bind(this, this.prev));
        this._random = new DeskChangerStateButton([
            {
                icon: 'media-playlist-shuffle',
                name: 'random'
            },
            {
                icon: 'media-playlist-repeat',
                name: 'ordered'
            }
        ], Lang.bind(this, this._toggle_random));
        this._random.set_state((this._settings.random) ? 'random' : 'ordered');
        this._timer = new DeskChangerStateButton([
            {
                icon: 'media-playback-stop',
                name: 'enable'
            },
            {
                icon: 'media-playback-start',
                name: 'disable'
            }
        ], Lang.bind(this, this._toggle_timer));
        this._timer.set_state((this._settings.timer_enabled) ? 'enable' : 'disable');

        if (this.addActor) {
            this._box = new St.BoxLayout({style: 'spacing: 20px;'});
            this.addActor(this._box, {align: St.Align.MIDDLE, span: -1});
            this._box.add_actor(this._prev, {expand: true});
            this._box.add_actor(this._random, {expand: true});
            this._box.add_actor(this._timer, {expand: true});
            this._box.add_actor(this._next, {expand: true});
        } else {
            this.actor.add_actor(this._prev, {expand: true, x_fill: false});
            this.actor.add_actor(this._random, {expand: true, x_fill: false});
            this.actor.add_actor(this._timer, {expand: true, x_fill: false});
            this.actor.add_actor(this._next, {expand: true, x_fill: false});
        }
    },

    destroy: function () {
        let size = this._bindings.length;

        for (let i = 0; i < size; i++) {
            this._removeKeyBinding(this._bindings[i]);
        }

        this._next.destroy();
        this._prev.destroy();
        this._random.destroy();
        this._timer.destroy();
        this.parent();
    },

    next: function () {
        debug('next');
        this._dbus.NextSync();
    },

    prev: function() {
        debug('prev');
        this._dbus.PrevRemote(function (result) {
            if (result[0].length == 0) {
                Main.notifyError('Desk Changer', 'Unable to go back any further, no history available');
            }
        });
    },

    _addKeyBinding: function (key, handler) {
        let success = false;
        if (Shell.ActionMode) { // 3.16 and above
            success = Main.wm.addKeybinding(
                key,
                this._settings.schema,
                Meta.KeyBindingFlags.NONE,
                Shell.ActionMode.NORMAL,
                handler
            );
        } else { // 3.8 and above
            success = Main.wm.addKeybinding(
                key,
                this._settings.schema,
                Meta.KeyBindingFlags.NONE,
                Shell.KeyBindingMode.NORMAL,
                handler
            );
        }

        this._bindings.push(key);
        if (success) {
            debug('added keybinding ' + key);
        } else {
            debug('failed to add keybinding ' + key);
            debug(success);
        }
    },

    _removeKeyBinding: function (key) {
        if (this._bindings.indexOf(key)) {
            this._bindings.splice(this._bindings.indexOf(key), 1);
        }

        debug('removing keybinding ' + key);
        Main.wm.removeKeybinding(key);
    },

    _toggle_random: function (state) {
        debug('setting order to ' + state);
        this._settings.random = (state == 'random');
    },

    _toggle_timer: function (state) {
        debug(state + 'ing timer');
        this._settings.timer_enabled = (state == 'enable');
    }
});

const DeskChangerDaemonControls = new Lang.Class({
    Name: 'DeskChangerDaemonControls',
    Extends: PopupMenu.PopupSwitchMenuItem,

    _init: function (daemon) {
        this._daemon = daemon;
        this.parent('DeskChanger Daemon');
        this.setToggleState(this._daemon.is_running);
        this._handler = this.connect('toggled', Lang.bind(this, function () {
            this._daemon.toggle();
        }));
        this._daemon_handler = this._daemon.connect('toggled', Lang.bind(this, function (obj, state) {
            this.setToggleState(state);
        }));
    },

    destroy: function () {
        // not sure why, but removing this handler causes the extension to crash on unload... meh
        //debug('removing daemon switch handler '+this._handler);
        //this.disconnect(this._handler);
        debug('removing daemon toggled handler ' + this._daemon_handler);
        this.disconnect(this._daemon_handler);
        this._daemon.destroy();
        this.parent();
    }
});

const DeskChangerIcon = new Lang.Class({
    Name: 'DeskChangerIcon',
    Extends: St.Bin,

    _init: function (_dbus, settings) {
        this._dbus = _dbus;
        this._settings = settings;
        this.parent({style_class: 'panel-status-menu-box'});
        // fallback when the daemon is not running
        this._icon = null;
        // the preview can be shown as the icon instead
        this._preview = null;
        this._settings.connect('changed::icon-preview', Lang.bind(this, this.update_child));
        this.update_child();
    },

    destroy: function () {
        if (this._icon) {
            this._icon.destroy();
        }

        if (this._preview) {
            this._preview.destroy();
        }

        this.parent();
    },

    update_child: function () {
        if (this._settings.icon_preview && this._createPreview()) {
            debug('updating icon to preview');
            this.set_child(this._preview);

            if (this._icon) {
                this._icon.destroy();
                this._icon = null;
            }
        } else if (!(this._icon)) {
            this._icon = new St.Icon({
                icon_name: 'emblem-photos-symbolic',
                style_class: 'system-status-icon'
            });
            this.set_child(this._icon);

            if (this._preview) {
                this._preview.destroy();
                this._preview = null;
            }
        }
    },

    _createPreview: function () {
        this._preview = new DeskChangerPreview(34, this._dbus, Lang.bind(this, this.update_child));

        if (!(this._preview.file)) {
            this._preview.destroy();
            this._preview = null;
            return false;
        }

        return true;
    }
});

/**
 * This is the actual indicator that should be added to the main panel.
 *
 * @type {Lang.Class}
 */
const DeskChangerIndicator = new Lang.Class({
    Name: 'DeskChangerIndicator',
    Extends: PanelMenu.Button,

    _init: function () {
        this.settings = new DeskChangerSettings();
        this.settings.connect('changed::current-profile', Lang.bind(this, function () {
            if (this.settings.notifications)
                Main.notify('Desk Changer', 'Profile changed to ' + this.settings.current_profile);
        }));
        this.settings.connect('changed::notifications', Lang.bind(this, function () {
            Main.notify('Desk Changer', 'Notifications are now ' + ((this.settings.notifications) ? 'enabled' : 'disabled'));
        }));
        this.parent(0.0, 'DeskChanger');
        this.daemon = new DeskChangerDaemon();
        this._dbus = this.daemon.bus;
        this._dbus_handler = this._dbus.connectSignal('Changed', Lang.bind(this, function (emitter, signalName, parameters) {
            if (this.settings.notifications)
                Main.notify('Desk Changer', 'Wallpaper Changed: ' + parameters[0]);
        }));
        this.actor.add_child(new DeskChangerIcon(this._dbus, this.settings));
        this.menu.addMenuItem(new DeskChangerProfile(this.settings));
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this.menu.addMenuItem(new DeskChangerSwitch('Change with Profile', 'auto_rotate', this.settings));
        this.menu.addMenuItem(new DeskChangerSwitch('Notifications', 'notifications', this.settings));
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this.menu.addMenuItem(new DeskChangerPreviewMenuItem(this._dbus));
        this.menu.addMenuItem(new DeskChangerOpenCurrent());
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this.menu.addMenuItem(new DeskChangerControls(this._dbus, this.settings));
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this.menu.addMenuItem(new DeskChangerDaemonControls(this.daemon));
        // Simple settings for the extension
        var settings = new PopupMenu.PopupMenuItem('DeskChanger Settings');
        settings.connect('activate', function () {
            Util.spawn(['gnome-shell-extension-prefs', Me.metadata.uuid]);
        });
        this.menu.addMenuItem(settings);

        if (!this.daemon.is_running && this.settings.auto_start) {
            // start the auto start goodness.
            this.daemon.toggle();
        }
    },

    destroy: function () {
        debug('removing dbus changed handler ' + this._dbus_handler);
        this._dbus.disconnectSignal(this._dbus_handler);
        this.settings.destroy();
        this.parent();
    }
});

const DeskChangerOpenCurrent = new Lang.Class({
    Name: 'DeskChangerOpenCurrent',
    Extends: PopupMenu.PopupMenuItem,

    _init: function () {
        this._background = new Gio.Settings({'schema': 'org.gnome.desktop.background'});
        this.parent('Open Current Wallpaper');
        this._activate_id = this.connect('activate', Lang.bind(this, this._activate));
    },

    destroy: function () {
        debug('removing current activate handler ' + this._activate_id);
        this.disconnect(this._activate_id);
        this.parent();
    },

    _activate: function () {
        debug('opening current wallpaper ' + this._background.get_string('picture-uri'));
        Util.spawn(['xdg-open', this._background.get_string('picture-uri')]);
    }
});

const DeskChangerPreview = new Lang.Class({
    Name: 'DeskChangerPreview',
    Extends: St.Bin,

    _init: function (width, _dbus, callback) {
        this.parent({});
        this._file = null;
        this._callback = callback;
        this._dbus = _dbus;
        this._texture = null;
        this._width = width;
        this._next_file_id = this._dbus.connectSignal('Preview', Lang.bind(this, function (emitter, signalName, parameters) {
            var file = parameters[0];
            this.set_wallpaper(file);
        }));
        debug('added dbus Preview handler ' + this._next_file_id);
        if (this._dbus.queue) {
            this.set_wallpaper(this._dbus.queue[0], false);
        }
    },

    destroy: function () {
        debug('removing dbus Preview handler ' + this._next_file_id);
        this._dbus.disconnectSignal(this._next_file_id);
        if (this._texture) {
            this._texture.destroy();
        }
    },

    set_wallpaper: function (file, c) {
        if (this._texture) {
            this._texture.destroy();
        }

        this._texture = new Clutter.Texture({
            filter_quality: Clutter.TextureQuality.HIGH,
            keep_aspect_ratio: true,
            width: this._width
        });
        this._file = file;
        file = file.replace('file://', '');
        debug('setting preview to ' + file);
        if (this._texture.set_from_file(file) === false) {
            debug('ERROR: Failed to set preview of ' + file);
            this._texture.destroy();
            this._texture = null;
        } else {
            if (c == true && this._callback && typeof this._callback == 'function') {
                this._callback(file);
            }
            this.set_child(this._texture);
        }
    },

    get file() {<commit_msg>Adding urldecode ability since we're technically dealing with URLs and not files<commit_after>/**
 * Copyright (c) 2014-2015 <NAME> <<EMAIL>>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Lang = imports.lang;
const Main = imports.ui.main;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const Meta = imports.gi.Meta;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Util = imports.misc.util;

const DeskChangerDaemon = Me.imports.daemon.DeskChangerDaemon;
const DeskChangerSettings = Me.imports.settings.DeskChangerSettings;
const DeskChangerVersion = Me.metadata.version;
const debug = Me.imports.utils.debug;

const DeskChangerButton = new Lang.Class({
    Name: 'DeskChangerButton',
    Extends: St.Button,

    _init: function (icon, callback) {
        this.icon = new St.Icon({icon_name: icon + '-symbolic', icon_size: 20});
        this.parent({
            child: this.icon,
            style_class: 'system-menu-action'// : 'notification-icon-button control-button'
        });
        this._handler = this.connect('clicked', callback);
    },

    destroy: function () {
        debug('removing button clicked handler ' + this._handler);
        this.disconnect(this._handler);
        this.icon.destroy();
        this.parent();
    },

    set_icon: function (icon) {
        this.icon.icon_name = icon + '-symbolic';
    }
});

const DeskChangerControls = new Lang.Class({
    Name: 'DeskChangerControls',
    Extends: PopupMenu.PopupBaseMenuItem,

    _init: function (dbus, settings) {
        this._dbus = dbus;
        this._settings = settings;
        this._bindings = new Array();
        this.parent({can_focus: false, reactive: false});

        this._addKeyBinding('next-wallpaper', Lang.bind(this, this.next));
        this._addKeyBinding('prev-wallpaper', Lang.bind(this._dbus, this.prev));

        this._next = new DeskChangerButton('media-skip-forward', Lang.bind(this, this.next));
        this._prev = new DeskChangerButton('media-skip-backward', Lang.bind(this, this.prev));
        this._random = new DeskChangerStateButton([
            {
                icon: 'media-playlist-shuffle',
                name: 'random'
            },
            {
                icon: 'media-playlist-repeat',
                name: 'ordered'
            }
        ], Lang.bind(this, this._toggle_random));
        this._random.set_state((this._settings.random) ? 'random' : 'ordered');
        this._timer = new DeskChangerStateButton([
            {
                icon: 'media-playback-stop',
                name: 'enable'
            },
            {
                icon: 'media-playback-start',
                name: 'disable'
            }
        ], Lang.bind(this, this._toggle_timer));
        this._timer.set_state((this._settings.timer_enabled) ? 'enable' : 'disable');

        if (this.addActor) {
            this._box = new St.BoxLayout({style: 'spacing: 20px;'});
            this.addActor(this._box, {align: St.Align.MIDDLE, span: -1});
            this._box.add_actor(this._prev, {expand: true});
            this._box.add_actor(this._random, {expand: true});
            this._box.add_actor(this._timer, {expand: true});
            this._box.add_actor(this._next, {expand: true});
        } else {
            this.actor.add_actor(this._prev, {expand: true, x_fill: false});
            this.actor.add_actor(this._random, {expand: true, x_fill: false});
            this.actor.add_actor(this._timer, {expand: true, x_fill: false});
            this.actor.add_actor(this._next, {expand: true, x_fill: false});
        }
    },

    destroy: function () {
        let size = this._bindings.length;

        for (let i = 0; i < size; i++) {
            this._removeKeyBinding(this._bindings[i]);
        }

        this._next.destroy();
        this._prev.destroy();
        this._random.destroy();
        this._timer.destroy();
        this.parent();
    },

    next: function () {
        debug('next');
        this._dbus.NextSync();
    },

    prev: function() {
        debug('prev');
        this._dbus.PrevRemote(function (result) {
            if (result[0].length == 0) {
                Main.notifyError('Desk Changer', 'Unable to go back any further, no history available');
            }
        });
    },

    _addKeyBinding: function (key, handler) {
        let success = false;
        if (Shell.ActionMode) { // 3.16 and above
            success = Main.wm.addKeybinding(
                key,
                this._settings.schema,
                Meta.KeyBindingFlags.NONE,
                Shell.ActionMode.NORMAL,
                handler
            );
        } else { // 3.8 and above
            success = Main.wm.addKeybinding(
                key,
                this._settings.schema,
                Meta.KeyBindingFlags.NONE,
                Shell.KeyBindingMode.NORMAL,
                handler
            );
        }

        this._bindings.push(key);
        if (success) {
            debug('added keybinding ' + key);
        } else {
            debug('failed to add keybinding ' + key);
            debug(success);
        }
    },

    _removeKeyBinding: function (key) {
        if (this._bindings.indexOf(key)) {
            this._bindings.splice(this._bindings.indexOf(key), 1);
        }

        debug('removing keybinding ' + key);
        Main.wm.removeKeybinding(key);
    },

    _toggle_random: function (state) {
        debug('setting order to ' + state);
        this._settings.random = (state == 'random');
    },

    _toggle_timer: function (state) {
        debug(state + 'ing timer');
        this._settings.timer_enabled = (state == 'enable');
    }
});

const DeskChangerDaemonControls = new Lang.Class({
    Name: 'DeskChangerDaemonControls',
    Extends: PopupMenu.PopupSwitchMenuItem,

    _init: function (daemon) {
        this._daemon = daemon;
        this.parent('DeskChanger Daemon');
        this.setToggleState(this._daemon.is_running);
        this._handler = this.connect('toggled', Lang.bind(this, function () {
            this._daemon.toggle();
        }));
        this._daemon_handler = this._daemon.connect('toggled', Lang.bind(this, function (obj, state) {
            this.setToggleState(state);
        }));
    },

    destroy: function () {
        // not sure why, but removing this handler causes the extension to crash on unload... meh
        //debug('removing daemon switch handler '+this._handler);
        //this.disconnect(this._handler);
        debug('removing daemon toggled handler ' + this._daemon_handler);
        this.disconnect(this._daemon_handler);
        this._daemon.destroy();
        this.parent();
    }
});

const DeskChangerIcon = new Lang.Class({
    Name: 'DeskChangerIcon',
    Extends: St.Bin,

    _init: function (_dbus, settings) {
        this._dbus = _dbus;
        this._settings = settings;
        this.parent({style_class: 'panel-status-menu-box'});
        // fallback when the daemon is not running
        this._icon = null;
        // the preview can be shown as the icon instead
        this._preview = null;
        this._settings.connect('changed::icon-preview', Lang.bind(this, this.update_child));
        this.update_child();
    },

    destroy: function () {
        if (this._icon) {
            this._icon.destroy();
        }

        if (this._preview) {
            this._preview.destroy();
        }

        this.parent();
    },

    update_child: function () {
        if (this._settings.icon_preview && this._createPreview()) {
            debug('updating icon to preview');
            this.set_child(this._preview);

            if (this._icon) {
                this._icon.destroy();
                this._icon = null;
            }
        } else if (!(this._icon)) {
            this._icon = new St.Icon({
                icon_name: 'emblem-photos-symbolic',
                style_class: 'system-status-icon'
            });
            this.set_child(this._icon);

            if (this._preview) {
                this._preview.destroy();
                this._preview = null;
            }
        }
    },

    _createPreview: function () {
        this._preview = new DeskChangerPreview(34, this._dbus, Lang.bind(this, this.update_child));

        if (!(this._preview.file)) {
            this._preview.destroy();
            this._preview = null;
            return false;
        }

        return true;
    }
});

/**
 * This is the actual indicator that should be added to the main panel.
 *
 * @type {Lang.Class}
 */
const DeskChangerIndicator = new Lang.Class({
    Name: 'DeskChangerIndicator',
    Extends: PanelMenu.Button,

    _init: function () {
        this.settings = new DeskChangerSettings();
        this.settings.connect('changed::current-profile', Lang.bind(this, function () {
            if (this.settings.notifications)
                Main.notify('Desk Changer', 'Profile changed to ' + this.settings.current_profile);
        }));
        this.settings.connect('changed::notifications', Lang.bind(this, function () {
            Main.notify('Desk Changer', 'Notifications are now ' + ((this.settings.notifications) ? 'enabled' : 'disabled'));
        }));
        this.parent(0.0, 'DeskChanger');
        this.daemon = new DeskChangerDaemon();
        this._dbus = this.daemon.bus;
        this._dbus_handler = this._dbus.connectSignal('Changed', Lang.bind(this, function (emitter, signalName, parameters) {
            if (this.settings.notifications)
                Main.notify('Desk Changer', 'Wallpaper Changed: ' + parameters[0]);
        }));
        this.actor.add_child(new DeskChangerIcon(this._dbus, this.settings));
        this.menu.addMenuItem(new DeskChangerProfile(this.settings));
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this.menu.addMenuItem(new DeskChangerSwitch('Change with Profile', 'auto_rotate', this.settings));
        this.menu.addMenuItem(new DeskChangerSwitch('Notifications', 'notifications', this.settings));
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this.menu.addMenuItem(new DeskChangerPreviewMenuItem(this._dbus));
        this.menu.addMenuItem(new DeskChangerOpenCurrent());
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this.menu.addMenuItem(new DeskChangerControls(this._dbus, this.settings));
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this.menu.addMenuItem(new DeskChangerDaemonControls(this.daemon));
        // Simple settings for the extension
        var settings = new PopupMenu.PopupMenuItem('DeskChanger Settings');
        settings.connect('activate', function () {
            Util.spawn(['gnome-shell-extension-prefs', Me.metadata.uuid]);
        });
        this.menu.addMenuItem(settings);

        if (!this.daemon.is_running && this.settings.auto_start) {
            // start the auto start goodness.
            this.daemon.toggle();
        }
    },

    destroy: function () {
        debug('removing dbus changed handler ' + this._dbus_handler);
        this._dbus.disconnectSignal(this._dbus_handler);
        this.settings.destroy();
        this.parent();
    }
});

const DeskChangerOpenCurrent = new Lang.Class({
    Name: 'DeskChangerOpenCurrent',
    Extends: PopupMenu.PopupMenuItem,

    _init: function () {
        this._background = new Gio.Settings({'schema': 'org.gnome.desktop.background'});
        this.parent('Open Current Wallpaper');
        this._activate_id = this.connect('activate', Lang.bind(this, this._activate));
    },

    destroy: function () {
        debug('removing current activate handler ' + this._activate_id);
        this.disconnect(this._activate_id);
        this.parent();
    },

    _activate: function () {
        debug('opening current wallpaper ' + this._background.get_string('picture-uri'));
        Util.spawn(['xdg-open', this._background.get_string('picture-uri')]);
    }
});

const DeskChangerPreview = new Lang.Class({
    Name: 'DeskChangerPreview',
    Extends: St.Bin,

    _init: function (width, _dbus, callback) {
        this.parent({});
        this._file = null;
        this._callback = callback;
        this._dbus = _dbus;
        this._texture = null;
        this._width = width;
        this._next_file_id = this._dbus.connectSignal('Preview', Lang.bind(this, function (emitter, signalName, parameters) {
            var file = parameters[0];
            this.set_wallpaper(file);
        }));
        debug('added dbus Preview handler ' + this._next_file_id);
        if (this._dbus.queue) {
            this.set_wallpaper(this._dbus.queue[0], false);
        }
    },

    destroy: function () {
        debug('removing dbus Preview handler ' + this._next_file_id);
        this._dbus.disconnectSignal(this._next_file_id);
        if (this._texture) {
            this._texture.destroy();
        }
    },

    set_wallpaper: function (file, c) {
        if (this._texture) {
            this._texture.destroy();
        }

        this._texture = new Clutter.Texture({
            filter_quality: Clutter.TextureQuality.HIGH,
            keep_aspect_ratio: true,
            width: this._width
        });
        this._file = file = GLib.uri_unescape_string(file, null);
        file = file.replace('file://', '');
        debug('setting preview to ' + file);
        if (this._texture.set_from_file(file) === false) {
            debug('ERROR: Failed to set preview of ' + file);
            this._texture.destroy();
            this._texture = null;
        } else {
            if (c == true && this._callback && typeof this._callback == 'function') {
                this._callback(file);
            }
            this.set_child(this._texture);
        }
    },

    get file() {
{'fraction_non_alphanumeric': 0.12623696175447982, 'fraction_numerical': 0.0019390211286440225, 'mean_word_length': 2.2936577846289365, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2674802', 'n_tokens_mistral': 8603, 'n_tokens_neox': 8422, 'n_words': 1866}