updated to latest tio settings & removed skel folder
This commit is contained in:
@ -1,542 +0,0 @@
|
||||
// appfolderDialog.js
|
||||
// GPLv3
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const St = imports.gi.St;
|
||||
const Main = imports.ui.main;
|
||||
const ModalDialog = imports.ui.modalDialog;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const ShellEntry = imports.ui.shellEntry;
|
||||
const Signals = imports.signals;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
const Extension = Me.imports.extension;
|
||||
|
||||
const Gettext = imports.gettext.domain('appfolders-manager');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
let FOLDER_SCHEMA;
|
||||
let FOLDER_LIST;
|
||||
|
||||
//--------------------------------------------------------------
|
||||
|
||||
// This is a modal dialog for creating a new folder, or renaming or modifying
|
||||
// categories of existing folders.
|
||||
var AppfolderDialog = class AppfolderDialog {
|
||||
|
||||
// build a new dialog. If folder is null, the dialog will be for creating a new
|
||||
// folder, else app is null, and the dialog will be for editing an existing folder
|
||||
constructor (folder, app, id) {
|
||||
this._folder = folder;
|
||||
this._app = app;
|
||||
this._id = id;
|
||||
this.super_dialog = new ModalDialog.ModalDialog({ destroyOnClose: true });
|
||||
|
||||
FOLDER_SCHEMA = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders' });
|
||||
FOLDER_LIST = FOLDER_SCHEMA.get_strv('folder-children');
|
||||
|
||||
let nameSection = this._buildNameSection();
|
||||
let categoriesSection = this._buildCategoriesSection();
|
||||
|
||||
this.super_dialog.contentLayout.style = 'spacing: 20px';
|
||||
this.super_dialog.contentLayout.add(nameSection, {
|
||||
x_fill: false,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.START
|
||||
});
|
||||
if ( Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('categories') ) {
|
||||
this.super_dialog.contentLayout.add(categoriesSection, {
|
||||
x_fill: false,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.START
|
||||
});
|
||||
}
|
||||
|
||||
if (this._folder == null) {
|
||||
this.super_dialog.setButtons([
|
||||
{ action: this.destroy.bind(this),
|
||||
label: _("Cancel"),
|
||||
key: Clutter.Escape },
|
||||
|
||||
{ action: this._apply.bind(this),
|
||||
label: _("Create"),
|
||||
key: Clutter.Return }
|
||||
]);
|
||||
} else {
|
||||
this.super_dialog.setButtons([
|
||||
{ action: this.destroy.bind(this),
|
||||
label: _("Cancel"),
|
||||
key: Clutter.Escape },
|
||||
|
||||
{ action: this._deleteFolder.bind(this),
|
||||
label: _("Delete"),
|
||||
key: Clutter.Delete },
|
||||
|
||||
{ action: this._apply.bind(this),
|
||||
label: _("Apply"),
|
||||
key: Clutter.Return }
|
||||
]);
|
||||
}
|
||||
|
||||
this._nameEntryText.connect('key-press-event', (o, e) => {
|
||||
let symbol = e.get_key_symbol();
|
||||
|
||||
if (symbol == Clutter.Return || symbol == Clutter.KP_Enter) {
|
||||
this.super_dialog.popModal();
|
||||
this._apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// build the section of the UI handling the folder's name and returns it.
|
||||
_buildNameSection () {
|
||||
let nameSection = new St.BoxLayout({
|
||||
style: 'spacing: 5px;',
|
||||
vertical: true,
|
||||
x_expand: true,
|
||||
natural_width_set: true,
|
||||
natural_width: 350,
|
||||
});
|
||||
|
||||
let nameLabel = new St.Label({
|
||||
text: _("Folder's name:"),
|
||||
style: 'font-weight: bold;',
|
||||
});
|
||||
nameSection.add(nameLabel, { y_align: St.Align.START });
|
||||
|
||||
this._nameEntry = new St.Entry({
|
||||
x_expand: true,
|
||||
});
|
||||
this._nameEntryText = null; ///???
|
||||
this._nameEntryText = this._nameEntry.clutter_text;
|
||||
|
||||
nameSection.add(this._nameEntry, { y_align: St.Align.START });
|
||||
ShellEntry.addContextMenu(this._nameEntry);
|
||||
this.super_dialog.setInitialKeyFocus(this._nameEntryText);
|
||||
|
||||
if (this._folder != null) {
|
||||
this._nameEntryText.set_text(this._folder.get_string('name'));
|
||||
}
|
||||
|
||||
return nameSection;
|
||||
}
|
||||
|
||||
// build the section of the UI handling the folder's categories and returns it.
|
||||
_buildCategoriesSection () {
|
||||
let categoriesSection = new St.BoxLayout({
|
||||
style: 'spacing: 5px;',
|
||||
vertical: true,
|
||||
x_expand: true,
|
||||
natural_width_set: true,
|
||||
natural_width: 350,
|
||||
});
|
||||
|
||||
let categoriesLabel = new St.Label({
|
||||
text: _("Categories:"),
|
||||
style: 'font-weight: bold;',
|
||||
});
|
||||
categoriesSection.add(categoriesLabel, {
|
||||
x_fill: false,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.START,
|
||||
});
|
||||
|
||||
let categoriesBox = new St.BoxLayout({
|
||||
style: 'spacing: 5px;',
|
||||
vertical: false,
|
||||
x_expand: true,
|
||||
});
|
||||
|
||||
// at the left, how to add categories
|
||||
let addCategoryBox = new St.BoxLayout({
|
||||
style: 'spacing: 5px;',
|
||||
vertical: true,
|
||||
x_expand: true,
|
||||
});
|
||||
|
||||
this._categoryEntry = new St.Entry({
|
||||
can_focus: true,
|
||||
x_expand: true,
|
||||
hint_text: _("Other category?"),
|
||||
secondary_icon: new St.Icon({
|
||||
icon_name: 'list-add-symbolic',
|
||||
icon_size: 16,
|
||||
style_class: 'system-status-icon',
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
}),
|
||||
});
|
||||
ShellEntry.addContextMenu(this._categoryEntry, null);
|
||||
this._categoryEntry.connect('secondary-icon-clicked', this._addCategory.bind(this));
|
||||
|
||||
this._categoryEntryText = null; ///???
|
||||
this._categoryEntryText = this._categoryEntry.clutter_text;
|
||||
this._catSelectButton = new SelectCategoryButton(this);
|
||||
|
||||
addCategoryBox.add(this._catSelectButton.actor, { y_align: St.Align.CENTER });
|
||||
addCategoryBox.add(this._categoryEntry, { y_align: St.Align.START });
|
||||
categoriesBox.add(addCategoryBox, {
|
||||
x_fill: true,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.START,
|
||||
});
|
||||
|
||||
// at the right, a list of categories
|
||||
this.listContainer = new St.BoxLayout({
|
||||
vertical: true,
|
||||
x_expand: true,
|
||||
});
|
||||
this.noCatLabel = new St.Label({ text: _("No category") });
|
||||
this.listContainer.add_actor(this.noCatLabel);
|
||||
categoriesBox.add(this.listContainer, {
|
||||
x_fill: true,
|
||||
x_align: St.Align.END,
|
||||
y_align: St.Align.START,
|
||||
});
|
||||
|
||||
categoriesSection.add(categoriesBox, {
|
||||
x_fill: true,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.START,
|
||||
});
|
||||
|
||||
// Load categories is necessary even if no this._folder,
|
||||
// because it initializes the value of this._categories
|
||||
this._loadCategories();
|
||||
|
||||
return categoriesSection;
|
||||
}
|
||||
|
||||
open () {
|
||||
this.super_dialog.open();
|
||||
}
|
||||
|
||||
// returns if a folder id already exists
|
||||
_alreadyExists (folderId) {
|
||||
for(var i = 0; i < FOLDER_LIST.length; i++) {
|
||||
if (FOLDER_LIST[i] == folderId) {
|
||||
// this._showError( _("This appfolder already exists.") );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy () {
|
||||
if ( Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('debug') ) {
|
||||
log('[AppfolderDialog v2] destroying dialog');
|
||||
}
|
||||
this._catSelectButton.destroy(); // TODO ?
|
||||
this.super_dialog.destroy(); //XXX crée des erreurs reloues ???
|
||||
}
|
||||
|
||||
// Generates a valid folder id, which as no space, no dot, no slash, and which
|
||||
// doesn't already exist.
|
||||
_folderId (newName) {
|
||||
let tmp0 = newName.split(" ");
|
||||
let folderId = "";
|
||||
for(var i = 0; i < tmp0.length; i++) {
|
||||
folderId += tmp0[i];
|
||||
}
|
||||
tmp0 = folderId.split(".");
|
||||
folderId = "";
|
||||
for(var i = 0; i < tmp0.length; i++) {
|
||||
folderId += tmp0[i];
|
||||
}
|
||||
tmp0 = folderId.split("/");
|
||||
folderId = "";
|
||||
for(var i = 0; i < tmp0.length; i++) {
|
||||
folderId += tmp0[i];
|
||||
}
|
||||
if(this._alreadyExists(folderId)) {
|
||||
folderId = this._folderId(folderId+'_');
|
||||
}
|
||||
return folderId;
|
||||
}
|
||||
|
||||
// creates a folder from the data filled by the user (with no properties)
|
||||
_create () {
|
||||
let folderId = this._folderId(this._nameEntryText.get_text());
|
||||
|
||||
FOLDER_LIST.push(folderId);
|
||||
FOLDER_SCHEMA.set_strv('folder-children', FOLDER_LIST);
|
||||
|
||||
this._folder = new Gio.Settings({
|
||||
schema_id: 'org.gnome.desktop.app-folders.folder',
|
||||
path: '/org/gnome/desktop/app-folders/folders/' + folderId + '/'
|
||||
});
|
||||
// this._folder.set_string('name', this._nameEntryText.get_text()); //superflu
|
||||
// est-il nécessaire d'initialiser la clé apps à [] ??
|
||||
this._addToFolder();
|
||||
}
|
||||
|
||||
// sets the name to the folder
|
||||
_applyName () {
|
||||
let newName = this._nameEntryText.get_text();
|
||||
this._folder.set_string('name', newName); // génère un bug ?
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
|
||||
// loads categories, as set in gsettings, to the UI
|
||||
_loadCategories () {
|
||||
if (this._folder == null) {
|
||||
this._categories = [];
|
||||
} else {
|
||||
this._categories = this._folder.get_strv('categories');
|
||||
if ((this._categories == null) || (this._categories.length == 0)) {
|
||||
this._categories = [];
|
||||
} else {
|
||||
this.noCatLabel.visible = false;
|
||||
}
|
||||
}
|
||||
this._categoriesButtons = [];
|
||||
for (var i = 0; i < this._categories.length; i++) {
|
||||
this._addCategoryBox(i);
|
||||
}
|
||||
}
|
||||
|
||||
_addCategoryBox (i) {
|
||||
let aCategory = new AppCategoryBox(this, i);
|
||||
this.listContainer.add_actor(aCategory.super_box);
|
||||
}
|
||||
|
||||
// adds a category to the UI (will be added to gsettings when pressing "apply" only)
|
||||
_addCategory (entry, new_cat_name) {
|
||||
if (new_cat_name == null) {
|
||||
new_cat_name = this._categoryEntryText.get_text();
|
||||
}
|
||||
if (this._categories.indexOf(new_cat_name) != -1) {
|
||||
return;
|
||||
}
|
||||
if (new_cat_name == '') {
|
||||
return;
|
||||
}
|
||||
this._categories.push(new_cat_name);
|
||||
this._categoryEntryText.set_text('');
|
||||
this.noCatLabel.visible = false;
|
||||
this._addCategoryBox(this._categories.length-1);
|
||||
}
|
||||
|
||||
// adds all categories to gsettings
|
||||
_applyCategories () {
|
||||
this._folder.set_strv('categories', this._categories);
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
|
||||
// Apply everything by calling methods above, and reload the view
|
||||
_apply () {
|
||||
if (this._app != null) {
|
||||
this._create();
|
||||
// this._addToFolder();
|
||||
}
|
||||
this._applyCategories();
|
||||
this._applyName();
|
||||
this.destroy();
|
||||
//-----------------------
|
||||
Main.overview.viewSelector.appDisplay._views[1].view._redisplay();
|
||||
if ( Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('debug') ) {
|
||||
log('[AppfolderDialog v2] reload the view');
|
||||
}
|
||||
}
|
||||
|
||||
// initializes the folder with its first app. This is not optional since empty
|
||||
// folders are not displayed. TODO use the equivalent method from extension.js
|
||||
_addToFolder () {
|
||||
let content = this._folder.get_strv('apps');
|
||||
content.push(this._app);
|
||||
this._folder.set_strv('apps', content);
|
||||
}
|
||||
|
||||
// Delete the folder, using the extension.js method
|
||||
_deleteFolder () {
|
||||
if (this._folder != null) {
|
||||
Extension.deleteFolder(this._id);
|
||||
}
|
||||
this.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------
|
||||
|
||||
// Very complex way to have a menubutton for displaying a menu with standard
|
||||
// categories. Button part.
|
||||
class SelectCategoryButton {
|
||||
constructor (dialog) {
|
||||
this._dialog = dialog;
|
||||
|
||||
let catSelectBox = new St.BoxLayout({
|
||||
vertical: false,
|
||||
x_expand: true,
|
||||
});
|
||||
let catSelectLabel = new St.Label({
|
||||
text: _("Select a category…"),
|
||||
x_align: Clutter.ActorAlign.START,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
x_expand: true,
|
||||
});
|
||||
let catSelectIcon = new St.Icon({
|
||||
icon_name: 'pan-down-symbolic',
|
||||
icon_size: 16,
|
||||
style_class: 'system-status-icon',
|
||||
x_expand: false,
|
||||
x_align: Clutter.ActorAlign.END,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
});
|
||||
catSelectBox.add(catSelectLabel, { y_align: St.Align.MIDDLE });
|
||||
catSelectBox.add(catSelectIcon, { y_align: St.Align.END });
|
||||
this.actor = new St.Button ({
|
||||
x_align: Clutter.ActorAlign.CENTER,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
child: catSelectBox,
|
||||
style_class: 'button',
|
||||
style: 'padding: 5px 5px;',
|
||||
x_expand: true,
|
||||
y_expand: false,
|
||||
x_fill: true,
|
||||
y_fill: true,
|
||||
});
|
||||
this.actor.connect('button-press-event', this._onButtonPress.bind(this));
|
||||
|
||||
this._menu = null;
|
||||
this._menuManager = new PopupMenu.PopupMenuManager(this);
|
||||
}
|
||||
|
||||
popupMenu () {
|
||||
this.actor.fake_release();
|
||||
if (!this._menu) {
|
||||
this._menu = new SelectCategoryMenu(this, this._dialog);
|
||||
this._menu.super_menu.connect('open-state-changed', (menu, isPoppedUp) => {
|
||||
if (!isPoppedUp) {
|
||||
this.actor.sync_hover();
|
||||
this.emit('menu-state-changed', false);
|
||||
}
|
||||
});
|
||||
this._menuManager.addMenu(this._menu.super_menu);
|
||||
}
|
||||
this.emit('menu-state-changed', true);
|
||||
this.actor.set_hover(true);
|
||||
this._menu.popup();
|
||||
this._menuManager.ignoreRelease();
|
||||
return false;
|
||||
}
|
||||
|
||||
_onButtonPress (actor, event) {
|
||||
this.popupMenu();
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
|
||||
destroy () {
|
||||
if (this._menu) {
|
||||
this._menu.destroy();
|
||||
}
|
||||
this.actor.destroy();
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(SelectCategoryButton.prototype);
|
||||
|
||||
//------------------------------------------------
|
||||
|
||||
// Very complex way to have a menubutton for displaying a menu with standard
|
||||
// categories. Menu part.
|
||||
class SelectCategoryMenu {
|
||||
constructor (source, dialog) {
|
||||
this.super_menu = new PopupMenu.PopupMenu(source.actor, 0.5, St.Side.RIGHT);
|
||||
this._source = source;
|
||||
this._dialog = dialog;
|
||||
this.super_menu.actor.add_style_class_name('app-well-menu');
|
||||
this._source.actor.connect('destroy', this.super_menu.destroy.bind(this));
|
||||
|
||||
// We want to keep the item hovered while the menu is up //XXX used ??
|
||||
this.super_menu.blockSourceEvents = true;
|
||||
|
||||
Main.uiGroup.add_actor(this.super_menu.actor);
|
||||
|
||||
// This is a really terrible hack to overwrite _redisplay without
|
||||
// actually inheriting from PopupMenu.PopupMenu
|
||||
this.super_menu._redisplay = this._redisplay;
|
||||
this.super_menu._dialog = this._dialog;
|
||||
}
|
||||
|
||||
_redisplay () {
|
||||
this.removeAll();
|
||||
let mainCategories = ['AudioVideo', 'Audio', 'Video', 'Development',
|
||||
'Education', 'Game', 'Graphics', 'Network', 'Office', 'Science',
|
||||
'Settings', 'System', 'Utility'];
|
||||
for (var i=0; i<mainCategories.length; i++) {
|
||||
let labelItem = mainCategories[i] ;
|
||||
let item = new PopupMenu.PopupMenuItem( labelItem );
|
||||
item.connect('activate', () => {
|
||||
this._dialog._addCategory(null, labelItem);
|
||||
});
|
||||
this.addMenuItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
popup (activatingButton) {
|
||||
this.super_menu._redisplay();
|
||||
this.super_menu.open();
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this.super_menu.close(); //FIXME error in the logs but i don't care
|
||||
this.super_menu.destroy();
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(SelectCategoryMenu.prototype);
|
||||
|
||||
//----------------------------------------
|
||||
|
||||
// This custom widget is a deletable row, displaying a category name.
|
||||
class AppCategoryBox {
|
||||
constructor (dialog, i) {
|
||||
this.super_box = new St.BoxLayout({
|
||||
vertical: false,
|
||||
style_class: 'appCategoryBox',
|
||||
});
|
||||
this._dialog = dialog;
|
||||
this.catName = this._dialog._categories[i];
|
||||
this.super_box.add_actor(new St.Label({
|
||||
text: this.catName,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
x_align: Clutter.ActorAlign.CENTER,
|
||||
}));
|
||||
this.super_box.add_actor( new St.BoxLayout({ x_expand: true }) );
|
||||
this.deleteButton = new St.Button({
|
||||
x_expand: false,
|
||||
y_expand: true,
|
||||
style_class: 'appCategoryDeleteBtn',
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
x_align: Clutter.ActorAlign.CENTER,
|
||||
child: new St.Icon({
|
||||
icon_name: 'edit-delete-symbolic',
|
||||
icon_size: 16,
|
||||
style_class: 'system-status-icon',
|
||||
x_expand: false,
|
||||
y_expand: true,
|
||||
style: 'margin: 3px;',
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
x_align: Clutter.ActorAlign.CENTER,
|
||||
}),
|
||||
});
|
||||
this.super_box.add_actor(this.deleteButton);
|
||||
this.deleteButton.connect('clicked', this.removeFromList.bind(this));
|
||||
}
|
||||
|
||||
removeFromList () {
|
||||
this._dialog._categories.splice(this._dialog._categories.indexOf(this.catName), 1);
|
||||
if (this._dialog._categories.length == 0) {
|
||||
this._dialog.noCatLabel.visible = true;
|
||||
}
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this.deleteButton.destroy();
|
||||
this.super_box.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,92 +0,0 @@
|
||||
/* -*- mode: js; js-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
/*
|
||||
Copyright (c) 2011-2012, Giovanni Campagna <scampa.giovanni@gmail.com>
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the GNOME nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
const Gettext = imports.gettext;
|
||||
const Gio = imports.gi.Gio;
|
||||
|
||||
const Config = imports.misc.config;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
|
||||
/**
|
||||
* initTranslations:
|
||||
* @domain: (optional): the gettext domain to use
|
||||
*
|
||||
* Initialize Gettext to load translations from extensionsdir/locale.
|
||||
* If @domain is not provided, it will be taken from metadata['gettext-domain']
|
||||
*/
|
||||
function initTranslations(domain) {
|
||||
let extension = ExtensionUtils.getCurrentExtension();
|
||||
domain = domain || extension.metadata['gettext-domain'];
|
||||
|
||||
// check if this extension was built with "make zip-file", and thus
|
||||
// has the locale files in a subfolder
|
||||
// otherwise assume that extension has been installed in the
|
||||
// same prefix as gnome-shell
|
||||
let localeDir = extension.dir.get_child('locale');
|
||||
if (localeDir.query_exists(null))
|
||||
Gettext.bindtextdomain(domain, localeDir.get_path());
|
||||
else
|
||||
Gettext.bindtextdomain(domain, Config.LOCALEDIR);
|
||||
}
|
||||
|
||||
/**
|
||||
* getSettings:
|
||||
* @schema: (optional): the GSettings schema id
|
||||
*
|
||||
* Builds and return a GSettings schema for @schema, using schema files
|
||||
* in extensionsdir/schemas. If @schema is not provided, it is taken from
|
||||
* metadata['settings-schema'].
|
||||
*/
|
||||
function getSettings(schema) {
|
||||
let extension = ExtensionUtils.getCurrentExtension();
|
||||
schema = schema || extension.metadata['settings-schema'];
|
||||
|
||||
const GioSSS = Gio.SettingsSchemaSource;
|
||||
|
||||
// check if this extension was built with "make zip-file", and thus
|
||||
// has the schema files in a subfolder
|
||||
// otherwise assume that extension has been installed in the
|
||||
// same prefix as gnome-shell (and therefore schemas are available
|
||||
// in the standard folders)
|
||||
let schemaDir = extension.dir.get_child('schemas');
|
||||
let schemaSource;
|
||||
if (schemaDir.query_exists(null)) {
|
||||
schemaSource = GioSSS.new_from_directory(schemaDir.get_path(),
|
||||
GioSSS.get_default(),
|
||||
false);
|
||||
} else {
|
||||
schemaSource = GioSSS.get_default();
|
||||
}
|
||||
|
||||
let schemaObj = schemaSource.lookup(schema, true);
|
||||
if (!schemaObj)
|
||||
throw new Error('Schema ' + schema + ' could not be found for extension '
|
||||
+ extension.metadata.uuid + '. Please check your installation.');
|
||||
|
||||
return new Gio.Settings({ settings_schema: schemaObj });
|
||||
}
|
||||
|
@ -1,550 +0,0 @@
|
||||
// dragAndDrop.js
|
||||
// GPLv3
|
||||
|
||||
const DND = imports.ui.dnd;
|
||||
const AppDisplay = imports.ui.appDisplay;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const St = imports.gi.St;
|
||||
const Main = imports.ui.main;
|
||||
const Mainloop = imports.mainloop;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
const Extension = Me.imports.extension;
|
||||
|
||||
const CHANGE_PAGE_TIMEOUT = 400;
|
||||
|
||||
const Gettext = imports.gettext.domain('appfolders-manager');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
//-------------------------------------------------
|
||||
|
||||
var OVERLAY_MANAGER;
|
||||
|
||||
/* This method is called by extension.js' enable function. It does code injections
|
||||
* to AppDisplay.AppIcon, connecting it to DND-related signals.
|
||||
*/
|
||||
function initDND () {
|
||||
OVERLAY_MANAGER = new OverlayManager();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
|
||||
/* Amazing! A singleton! It allows easy (and safer?) access to general methods,
|
||||
* managing other objects: it creates/updates/deletes all overlays (for folders,
|
||||
* pages, creation, removing).
|
||||
*/
|
||||
class OverlayManager {
|
||||
constructor () {
|
||||
this.addActions = [];
|
||||
this.removeAction = new FolderActionArea('remove');
|
||||
this.createAction = new FolderActionArea('create');
|
||||
this.upAction = new NavigationArea('up');
|
||||
this.downAction = new NavigationArea('down');
|
||||
|
||||
this.next_drag_should_recompute = true;
|
||||
this.current_width = 0;
|
||||
}
|
||||
|
||||
on_drag_begin () {
|
||||
this.ensurePopdowned();
|
||||
this.ensureFolderOverlayActors();
|
||||
this.updateFoldersVisibility();
|
||||
this.updateState(true);
|
||||
}
|
||||
|
||||
on_drag_end () {
|
||||
// force to compute new positions if a drop occurs
|
||||
this.next_drag_should_recompute = true;
|
||||
this.updateState(false);
|
||||
}
|
||||
|
||||
on_drag_cancelled () {
|
||||
this.updateState(false);
|
||||
}
|
||||
|
||||
updateArrowVisibility () {
|
||||
let grid = Main.overview.viewSelector.appDisplay._views[1].view._grid;
|
||||
if (grid.currentPage == 0) {
|
||||
this.upAction.setActive(false);
|
||||
} else {
|
||||
this.upAction.setActive(true);
|
||||
}
|
||||
if (grid.currentPage == grid._nPages -1) {
|
||||
this.downAction.setActive(false);
|
||||
} else {
|
||||
this.downAction.setActive(true);
|
||||
}
|
||||
this.upAction.show();
|
||||
this.downAction.show();
|
||||
}
|
||||
|
||||
updateState (isDragging) {
|
||||
if (isDragging) {
|
||||
this.removeAction.show();
|
||||
if (this.openedFolder == null) {
|
||||
this.removeAction.setActive(false);
|
||||
} else {
|
||||
this.removeAction.setActive(true);
|
||||
}
|
||||
this.createAction.show();
|
||||
this.updateArrowVisibility();
|
||||
} else {
|
||||
this.hideAll();
|
||||
}
|
||||
}
|
||||
|
||||
hideAll () {
|
||||
this.removeAction.hide();
|
||||
this.createAction.hide();
|
||||
this.upAction.hide();
|
||||
this.downAction.hide();
|
||||
this.hideAllFolders();
|
||||
}
|
||||
|
||||
hideAllFolders () {
|
||||
for (var i = 0; i < this.addActions.length; i++) {
|
||||
this.addActions[i].hide();
|
||||
}
|
||||
}
|
||||
|
||||
updateActorsPositions () {
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
this.topOfTheGrid = Main.overview.viewSelector.actor.get_parent().get_parent().get_allocation_box().y1;
|
||||
let temp = Main.overview.viewSelector.appDisplay._views[1].view.actor.get_parent();
|
||||
let bottomOfTheGrid = this.topOfTheGrid + temp.get_allocation_box().y2;
|
||||
|
||||
let _availHeight = bottomOfTheGrid - this.topOfTheGrid;
|
||||
let _availWidth = Main.overview.viewSelector.appDisplay._views[1].view._grid.actor.width;
|
||||
let sideMargin = (monitor.width - _availWidth) / 2;
|
||||
|
||||
let xMiddle = ( monitor.x + monitor.width ) / 2;
|
||||
let yMiddle = ( monitor.y + monitor.height ) / 2;
|
||||
|
||||
// Positions of areas
|
||||
this.removeAction.setPosition( xMiddle , bottomOfTheGrid );
|
||||
this.createAction.setPosition( xMiddle, Main.overview._panelGhost.height );
|
||||
this.upAction.setPosition( 0, Main.overview._panelGhost.height );
|
||||
this.downAction.setPosition( 0, bottomOfTheGrid );
|
||||
|
||||
// Sizes of areas
|
||||
this.removeAction.setSize(xMiddle, monitor.height - bottomOfTheGrid);
|
||||
this.createAction.setSize(xMiddle, this.topOfTheGrid - Main.overview._panelGhost.height);
|
||||
this.upAction.setSize(xMiddle, this.topOfTheGrid - Main.overview._panelGhost.height);
|
||||
this.downAction.setSize(xMiddle, monitor.height - bottomOfTheGrid);
|
||||
|
||||
this.updateArrowVisibility();
|
||||
}
|
||||
|
||||
ensureFolderOverlayActors () {
|
||||
// A folder was opened, and just closed.
|
||||
if (this.openedFolder != null) {
|
||||
this.updateActorsPositions();
|
||||
this.computeFolderOverlayActors();
|
||||
this.next_drag_should_recompute = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// The grid "moved" or the whole shit needs forced updating
|
||||
let allAppsGrid = Main.overview.viewSelector.appDisplay._views[1].view._grid;
|
||||
let new_width = allAppsGrid.actor.allocation.get_width();
|
||||
if (new_width != this.current_width || this.next_drag_should_recompute) {
|
||||
this.next_drag_should_recompute = false;
|
||||
this.updateActorsPositions();
|
||||
this.computeFolderOverlayActors();
|
||||
}
|
||||
}
|
||||
|
||||
computeFolderOverlayActors () {
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
let xMiddle = ( monitor.x + monitor.width ) / 2;
|
||||
let yMiddle = ( monitor.y + monitor.height ) / 2;
|
||||
let allAppsGrid = Main.overview.viewSelector.appDisplay._views[1].view._grid;
|
||||
|
||||
let nItems = 0;
|
||||
let indexes = [];
|
||||
let folders = [];
|
||||
let x, y;
|
||||
|
||||
Main.overview.viewSelector.appDisplay._views[1].view._allItems.forEach(function(icon) {
|
||||
if (icon.actor.visible) {
|
||||
if (icon instanceof AppDisplay.FolderIcon) {
|
||||
indexes.push(nItems);
|
||||
folders.push(icon);
|
||||
}
|
||||
nItems++;
|
||||
}
|
||||
});
|
||||
|
||||
this.current_width = allAppsGrid.actor.allocation.get_width();
|
||||
let x_correction = (monitor.width - this.current_width)/2;
|
||||
let availHeightPerPage = (allAppsGrid.actor.height)/(allAppsGrid._nPages);
|
||||
|
||||
for (var i = 0; i < this.addActions.length; i++) {
|
||||
this.addActions[i].actor.destroy();
|
||||
}
|
||||
|
||||
for (var i = 0; i < indexes.length; i++) {
|
||||
let inPageIndex = indexes[i] % allAppsGrid._childrenPerPage;
|
||||
let page = Math.floor(indexes[i] / allAppsGrid._childrenPerPage);
|
||||
x = folders[i].actor.get_allocation_box().x1;
|
||||
y = folders[i].actor.get_allocation_box().y1;
|
||||
|
||||
// Invalid coords (example: when dragging out of the folder) should
|
||||
// not produce a visible overlay, a negative page number is an easy
|
||||
// way to be sure it stays hidden.
|
||||
if (x == 0) {
|
||||
page = -1;
|
||||
}
|
||||
x = Math.floor(x + x_correction);
|
||||
y = y + this.topOfTheGrid;
|
||||
y = y - (page * availHeightPerPage);
|
||||
|
||||
this.addActions[i] = new FolderArea(folders[i].id, x, y, page);
|
||||
}
|
||||
}
|
||||
|
||||
updateFoldersVisibility () {
|
||||
let appView = Main.overview.viewSelector.appDisplay._views[1].view;
|
||||
for (var i = 0; i < this.addActions.length; i++) {
|
||||
if ((this.addActions[i].page == appView._grid.currentPage) && (!appView._currentPopup)) {
|
||||
this.addActions[i].show();
|
||||
} else {
|
||||
this.addActions[i].hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ensurePopdowned () {
|
||||
let appView = Main.overview.viewSelector.appDisplay._views[1].view;
|
||||
if (appView._currentPopup) {
|
||||
this.openedFolder = appView._currentPopup._source.id;
|
||||
appView._currentPopup.popdown();
|
||||
} else {
|
||||
this.openedFolder = null;
|
||||
}
|
||||
}
|
||||
|
||||
goToPage (nb) {
|
||||
Main.overview.viewSelector.appDisplay._views[1].view.goToPage( nb );
|
||||
this.updateArrowVisibility();
|
||||
this.hideAllFolders();
|
||||
this.updateFoldersVisibility(); //load folders of the new page
|
||||
}
|
||||
|
||||
destroy () {
|
||||
for (let i = 0; i > this.addActions.length; i++) {
|
||||
this.addActions[i].destroy();
|
||||
}
|
||||
this.removeAction.destroy();
|
||||
this.createAction.destroy();
|
||||
this.upAction.destroy();
|
||||
this.downAction.destroy();
|
||||
//log('OverlayManager destroyed');
|
||||
}
|
||||
};
|
||||
|
||||
//-------------------------------------------------------
|
||||
|
||||
// Abstract overlay with very generic methods
|
||||
class DroppableArea {
|
||||
|
||||
constructor (id) {
|
||||
this.id = id;
|
||||
this.styleClass = 'folderArea';
|
||||
|
||||
this.actor = new St.BoxLayout ({
|
||||
width: 10,
|
||||
height: 10,
|
||||
visible: false,
|
||||
});
|
||||
this.actor._delegate = this;
|
||||
|
||||
this.lock = true;
|
||||
this.use_frame = Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('debug');
|
||||
}
|
||||
|
||||
setPosition (x, y) {
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
this.actor.set_position(monitor.x + x, monitor.y + y);
|
||||
}
|
||||
|
||||
setSize (w, h) {
|
||||
this.actor.width = w;
|
||||
this.actor.height = h;
|
||||
}
|
||||
|
||||
hide () {
|
||||
this.actor.visible = false;
|
||||
this.lock = true;
|
||||
}
|
||||
|
||||
show () {
|
||||
this.actor.visible = true;
|
||||
}
|
||||
|
||||
setActive (active) {
|
||||
this._active = active;
|
||||
if (this._active) {
|
||||
this.actor.style_class = this.styleClass;
|
||||
} else {
|
||||
this.actor.style_class = 'insensitiveArea';
|
||||
}
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this.actor.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/* Overlay representing an "action". Actions can be creating a folder, or
|
||||
* removing an app from a folder. These areas accept drop, and display a label.
|
||||
*/
|
||||
class FolderActionArea extends DroppableArea {
|
||||
constructor (id) {
|
||||
super(id);
|
||||
|
||||
let x, y, label;
|
||||
|
||||
switch (this.id) {
|
||||
case 'create':
|
||||
label = _("Create a new folder");
|
||||
this.styleClass = 'shadowedAreaTop';
|
||||
break;
|
||||
case 'remove':
|
||||
label = '';
|
||||
this.styleClass = 'shadowedAreaBottom';
|
||||
break;
|
||||
default:
|
||||
label = 'invalid id';
|
||||
break;
|
||||
}
|
||||
if (this.use_frame) {
|
||||
this.styleClass = 'framedArea';
|
||||
}
|
||||
this.actor.style_class = this.styleClass;
|
||||
|
||||
this.label = new St.Label({
|
||||
text: label,
|
||||
style_class: 'dropAreaLabel',
|
||||
x_expand: true,
|
||||
y_expand: true,
|
||||
x_align: Clutter.ActorAlign.CENTER,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
});
|
||||
this.actor.add(this.label);
|
||||
|
||||
this.setPosition(10, 10);
|
||||
Main.layoutManager.overviewGroup.add_actor(this.actor);
|
||||
}
|
||||
|
||||
getRemoveLabel () {
|
||||
let label;
|
||||
if (OVERLAY_MANAGER.openedFolder == null) {
|
||||
label = '…';
|
||||
} else {
|
||||
let folder_schema = Extension.folderSchema (OVERLAY_MANAGER.openedFolder);
|
||||
label = folder_schema.get_string('name');
|
||||
}
|
||||
return (_("Remove from %s")).replace('%s', label);
|
||||
}
|
||||
|
||||
setActive (active) {
|
||||
super.setActive(active);
|
||||
if (this.id == 'remove') {
|
||||
this.label.text = this.getRemoveLabel();
|
||||
}
|
||||
}
|
||||
|
||||
handleDragOver (source, actor, x, y, time) {
|
||||
if (source instanceof AppDisplay.AppIcon && this._active) {
|
||||
return DND.DragMotionResult.MOVE_DROP;
|
||||
}
|
||||
Main.overview.endItemDrag(this);
|
||||
return DND.DragMotionResult.NO_DROP;
|
||||
}
|
||||
|
||||
acceptDrop (source, actor, x, y, time) {
|
||||
if ((source instanceof AppDisplay.AppIcon) && (this.id == 'create')) {
|
||||
Extension.createNewFolder(source);
|
||||
Main.overview.endItemDrag(this);
|
||||
return true;
|
||||
}
|
||||
if ((source instanceof AppDisplay.AppIcon) && (this.id == 'remove')) {
|
||||
this.removeApp(source);
|
||||
Main.overview.endItemDrag(this);
|
||||
return true;
|
||||
}
|
||||
Main.overview.endItemDrag(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
removeApp (source) {
|
||||
let id = source.app.get_id();
|
||||
Extension.removeFromFolder(id, OVERLAY_MANAGER.openedFolder);
|
||||
OVERLAY_MANAGER.updateState(false);
|
||||
Main.overview.viewSelector.appDisplay._views[1].view._redisplay();
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this.label.destroy();
|
||||
super.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
/* Overlay reacting to hover, but isn't droppable. The goal is to go to an other
|
||||
* page of the grid while dragging an app.
|
||||
*/
|
||||
class NavigationArea extends DroppableArea {
|
||||
constructor (id) {
|
||||
super(id);
|
||||
|
||||
let x, y, i;
|
||||
switch (this.id) {
|
||||
case 'up':
|
||||
i = 'pan-up-symbolic';
|
||||
this.styleClass = 'shadowedAreaTop';
|
||||
break;
|
||||
case 'down':
|
||||
i = 'pan-down-symbolic';
|
||||
this.styleClass = 'shadowedAreaBottom';
|
||||
break;
|
||||
default:
|
||||
i = 'dialog-error-symbolic';
|
||||
break;
|
||||
}
|
||||
if (this.use_frame) {
|
||||
this.styleClass = 'framedArea';
|
||||
}
|
||||
this.actor.style_class = this.styleClass;
|
||||
|
||||
this.actor.add(new St.Icon({
|
||||
icon_name: i,
|
||||
icon_size: 24,
|
||||
style_class: 'system-status-icon',
|
||||
x_expand: true,
|
||||
y_expand: true,
|
||||
x_align: Clutter.ActorAlign.CENTER,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
}));
|
||||
|
||||
this.setPosition(x, y);
|
||||
Main.layoutManager.overviewGroup.add_actor(this.actor);
|
||||
}
|
||||
|
||||
handleDragOver (source, actor, x, y, time) {
|
||||
if (this.id == 'up' && this._active) {
|
||||
this.pageUp();
|
||||
return DND.DragMotionResult.CONTINUE;
|
||||
}
|
||||
|
||||
if (this.id == 'down' && this._active) {
|
||||
this.pageDown();
|
||||
return DND.DragMotionResult.CONTINUE;
|
||||
}
|
||||
|
||||
Main.overview.endItemDrag(this);
|
||||
return DND.DragMotionResult.NO_DROP;
|
||||
}
|
||||
|
||||
pageUp () {
|
||||
if(this.lock && !this.timeoutSet) {
|
||||
this._timeoutId = Mainloop.timeout_add(CHANGE_PAGE_TIMEOUT, this.unlock.bind(this));
|
||||
this.timeoutSet = true;
|
||||
}
|
||||
if(!this.lock) {
|
||||
let currentPage = Main.overview.viewSelector.appDisplay._views[1].view._grid.currentPage;
|
||||
this.lock = true;
|
||||
OVERLAY_MANAGER.goToPage(currentPage - 1);
|
||||
}
|
||||
}
|
||||
|
||||
pageDown () {
|
||||
if(this.lock && !this.timeoutSet) {
|
||||
this._timeoutId = Mainloop.timeout_add(CHANGE_PAGE_TIMEOUT, this.unlock.bind(this));
|
||||
this.timeoutSet = true;
|
||||
}
|
||||
if(!this.lock) {
|
||||
let currentPage = Main.overview.viewSelector.appDisplay._views[1].view._grid.currentPage;
|
||||
this.lock = true;
|
||||
OVERLAY_MANAGER.goToPage(currentPage + 1);
|
||||
}
|
||||
}
|
||||
|
||||
acceptDrop (source, actor, x, y, time) {
|
||||
Main.overview.endItemDrag(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
unlock () {
|
||||
this.lock = false;
|
||||
this.timeoutSet = false;
|
||||
Mainloop.source_remove(this._timeoutId);
|
||||
}
|
||||
};
|
||||
|
||||
/* This overlay is the area upon a folder. Position and visibility of the actor
|
||||
* is handled by exterior functions.
|
||||
* "this.id" is the folder's id, a string, as written in the gsettings key.
|
||||
* Dropping an app on this folder will add it to the folder
|
||||
*/
|
||||
class FolderArea extends DroppableArea {
|
||||
constructor (id, asked_x, asked_y, page) {
|
||||
super(id);
|
||||
this.page = page;
|
||||
|
||||
let grid = Main.overview.viewSelector.appDisplay._views[1].view._grid;
|
||||
this.actor.width = grid._getHItemSize();
|
||||
this.actor.height = grid._getVItemSize();
|
||||
|
||||
if (this.use_frame) {
|
||||
this.styleClass = 'framedArea';
|
||||
this.actor.add(new St.Label({
|
||||
text: this.id,
|
||||
x_expand: true,
|
||||
y_expand: true,
|
||||
x_align: Clutter.ActorAlign.CENTER,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
}));
|
||||
} else {
|
||||
this.styleClass = 'folderArea';
|
||||
this.actor.add(new St.Icon({
|
||||
icon_name: 'list-add-symbolic',
|
||||
icon_size: 24,
|
||||
style_class: 'system-status-icon',
|
||||
x_expand: true,
|
||||
y_expand: true,
|
||||
x_align: Clutter.ActorAlign.CENTER,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
}));
|
||||
}
|
||||
if (this.use_frame) {
|
||||
this.styleClass = 'framedArea';
|
||||
}
|
||||
this.actor.style_class = this.styleClass;
|
||||
|
||||
this.setPosition(asked_x, asked_y);
|
||||
Main.layoutManager.overviewGroup.add_actor(this.actor);
|
||||
}
|
||||
|
||||
handleDragOver (source, actor, x, y, time) {
|
||||
if (source instanceof AppDisplay.AppIcon) {
|
||||
return DND.DragMotionResult.MOVE_DROP;
|
||||
}
|
||||
Main.overview.endItemDrag(this);
|
||||
return DND.DragMotionResult.NO_DROP;
|
||||
}
|
||||
|
||||
acceptDrop (source, actor, x, y, time) { //FIXME recharger la vue ou au minimum les miniatures des dossiers
|
||||
if ((source instanceof AppDisplay.AppIcon) &&
|
||||
!Extension.isInFolder(source.id, this.id)) {
|
||||
Extension.addToFolder(source, this.id);
|
||||
Main.overview.endItemDrag(this);
|
||||
return true;
|
||||
}
|
||||
Main.overview.endItemDrag(this);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
@ -1,445 +0,0 @@
|
||||
// extension.js
|
||||
// GPLv3
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const St = imports.gi.St;
|
||||
const Main = imports.ui.main;
|
||||
const AppDisplay = imports.ui.appDisplay;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Mainloop = imports.mainloop;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
|
||||
const AppfolderDialog = Me.imports.appfolderDialog;
|
||||
const DragAndDrop = Me.imports.dragAndDrop;
|
||||
|
||||
const Gettext = imports.gettext.domain('appfolders-manager');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
let FOLDER_SCHEMA;
|
||||
let FOLDER_LIST;
|
||||
|
||||
let INIT_TIME;
|
||||
|
||||
function init () {
|
||||
Convenience.initTranslations();
|
||||
INIT_TIME = getTimeStamp();
|
||||
}
|
||||
|
||||
function getTimeStamp () {
|
||||
let today = new Date();
|
||||
let str = today.getDate() + '' + today.getHours() + '' + today.getMinutes()
|
||||
+ '' + today.getSeconds();
|
||||
return parseInt(str);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/* do not edit this section */
|
||||
|
||||
function injectToFunction(parent, name, func) {
|
||||
let origin = parent[name];
|
||||
parent[name] = function() {
|
||||
let ret;
|
||||
ret = origin.apply(this, arguments);
|
||||
if (ret === undefined)
|
||||
ret = func.apply(this, arguments);
|
||||
return ret;
|
||||
}
|
||||
return origin;
|
||||
}
|
||||
|
||||
function removeInjection(object, injection, name) {
|
||||
if (injection[name] === undefined)
|
||||
delete object[name];
|
||||
else
|
||||
object[name] = injection[name];
|
||||
}
|
||||
|
||||
var injections=[];
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/* this function injects items (1 or 2 submenus) in AppIconMenu's _redisplay method. */
|
||||
function injectionInAppsMenus() {
|
||||
injections['_redisplay'] = injectToFunction(AppDisplay.AppIconMenu.prototype, '_redisplay', function() {
|
||||
if (Main.overview.viewSelector.getActivePage() == 2
|
||||
|| Main.overview.viewSelector.getActivePage() == 3) {
|
||||
//ok
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
this._appendSeparator(); //TODO injecter ailleurs dans le menu?
|
||||
|
||||
let mainAppView = Main.overview.viewSelector.appDisplay._views[1].view;
|
||||
FOLDER_LIST = FOLDER_SCHEMA.get_strv('folder-children');
|
||||
|
||||
//------------------------------------------------------------------
|
||||
|
||||
let addto = new PopupMenu.PopupSubMenuMenuItem(_("Add to"));
|
||||
|
||||
let newAppFolder = new PopupMenu.PopupMenuItem('+ ' + _("New AppFolder"));
|
||||
newAppFolder.connect('activate', () => {
|
||||
this._source._menuManager._grabHelper.ungrab({ actor: this.actor });
|
||||
// XXX broken scrolling ??
|
||||
// We can't popdown the folder immediately because the
|
||||
// AppDisplay.AppFolderPopup.popdown() method tries to ungrab
|
||||
// the global focus from the folder's popup actor, which isn't
|
||||
// having the focus since the menu is still open. Menus' animation
|
||||
// last ~0.25s so we will wait 0.30s before doing anything.
|
||||
let a = Mainloop.timeout_add(300, () => {
|
||||
if (mainAppView._currentPopup) {
|
||||
mainAppView._currentPopup.popdown();
|
||||
}
|
||||
createNewFolder(this._source);
|
||||
mainAppView._redisplay();
|
||||
Mainloop.source_remove(a);
|
||||
});
|
||||
});
|
||||
addto.menu.addMenuItem(newAppFolder);
|
||||
|
||||
for (var i = 0 ; i < FOLDER_LIST.length ; i++) {
|
||||
let _folder = FOLDER_LIST[i];
|
||||
let shouldShow = !isInFolder( this._source.app.get_id(), _folder );
|
||||
let iFolderSchema = folderSchema(_folder);
|
||||
let item = new PopupMenu.PopupMenuItem( AppDisplay._getFolderName(iFolderSchema) );
|
||||
if ( Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('debug') ) {
|
||||
shouldShow = true; //TODO ??? et l'exclusion ?
|
||||
}
|
||||
if(shouldShow) {
|
||||
item.connect('activate', () => {
|
||||
this._source._menuManager._grabHelper.ungrab({ actor: this.actor });
|
||||
// XXX broken scrolling ??
|
||||
// We can't popdown the folder immediatly because the
|
||||
// AppDisplay.AppFolderPopup.popdown() method tries to
|
||||
// ungrab the global focus from the folder's popup actor,
|
||||
// which isn't having the focus since the menu is still
|
||||
// open. Menus' animation last ~0.25s so we will wait 0.30s
|
||||
// before doing anything.
|
||||
let a = Mainloop.timeout_add(300, () => {
|
||||
if (mainAppView._currentPopup) {
|
||||
mainAppView._currentPopup.popdown();
|
||||
}
|
||||
addToFolder(this._source, _folder);
|
||||
mainAppView._redisplay();
|
||||
Mainloop.source_remove(a);
|
||||
});
|
||||
});
|
||||
addto.menu.addMenuItem(item);
|
||||
}
|
||||
}
|
||||
this.addMenuItem(addto);
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
let removeFrom = new PopupMenu.PopupSubMenuMenuItem(_("Remove from"));
|
||||
let shouldShow2 = false;
|
||||
for (var i = 0 ; i < FOLDER_LIST.length ; i++) {
|
||||
let _folder = FOLDER_LIST[i];
|
||||
let appId = this._source.app.get_id();
|
||||
let shouldShow = isInFolder(appId, _folder);
|
||||
let iFolderSchema = folderSchema(_folder);
|
||||
let item = new PopupMenu.PopupMenuItem( AppDisplay._getFolderName(iFolderSchema) );
|
||||
|
||||
if ( Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('debug') ) {
|
||||
shouldShow = true; //FIXME ??? et l'exclusion ?
|
||||
}
|
||||
|
||||
if(shouldShow) {
|
||||
item.connect('activate', () => {
|
||||
this._source._menuManager._grabHelper.ungrab({ actor: this.actor });
|
||||
// XXX broken scrolling ??
|
||||
// We can't popdown the folder immediatly because the
|
||||
// AppDisplay.AppFolderPopup.popdown() method tries to
|
||||
// ungrab the global focus from the folder's popup actor,
|
||||
// which isn't having the focus since the menu is still
|
||||
// open. Menus' animation last ~0.25s so we will wait 0.30s
|
||||
// before doing anything.
|
||||
let a = Mainloop.timeout_add(300, () => {
|
||||
if (mainAppView._currentPopup) {
|
||||
mainAppView._currentPopup.popdown();
|
||||
}
|
||||
removeFromFolder(appId, _folder);
|
||||
mainAppView._redisplay();
|
||||
Mainloop.source_remove(a);
|
||||
});
|
||||
});
|
||||
removeFrom.menu.addMenuItem(item);
|
||||
shouldShow2 = true;
|
||||
}
|
||||
}
|
||||
if (shouldShow2) {
|
||||
this.addMenuItem(removeFrom);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//------------------------------------------------
|
||||
|
||||
function injectionInIcons() {
|
||||
// Right-click on a FolderIcon launches a new AppfolderDialog
|
||||
AppDisplay.FolderIcon = class extends AppDisplay.FolderIcon {
|
||||
constructor (id, path, parentView) {
|
||||
super(id, path, parentView);
|
||||
if (!this.isCustom) {
|
||||
this.actor.connect('button-press-event', this._onButtonPress.bind(this));
|
||||
}
|
||||
this.isCustom = true;
|
||||
}
|
||||
|
||||
_onButtonPress (actor, event) {
|
||||
let button = event.get_button();
|
||||
if (button == 3) {
|
||||
let tmp = new Gio.Settings({
|
||||
schema_id: 'org.gnome.desktop.app-folders.folder',
|
||||
path: '/org/gnome/desktop/app-folders/folders/' + this.id + '/'
|
||||
});
|
||||
let dialog = new AppfolderDialog.AppfolderDialog(tmp, null, this.id);
|
||||
dialog.open();
|
||||
}
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
}
|
||||
};
|
||||
|
||||
// Dragging an AppIcon triggers the DND mode
|
||||
AppDisplay.AppIcon = class extends AppDisplay.AppIcon {
|
||||
constructor (app, params) {
|
||||
super(app, params);
|
||||
if (!this.isCustom) {
|
||||
this._draggable.connect('drag-begin', this.onDragBeginExt.bind(this));
|
||||
this._draggable.connect('drag-cancelled', this.onDragCancelledExt.bind(this));
|
||||
this._draggable.connect('drag-end', this.onDragEndExt.bind(this));
|
||||
}
|
||||
this.isCustom = true;
|
||||
}
|
||||
|
||||
onDragBeginExt () {
|
||||
if (Main.overview.viewSelector.getActivePage() != 2) {
|
||||
return;
|
||||
}
|
||||
this._removeMenuTimeout(); // why ?
|
||||
Main.overview.beginItemDrag(this);
|
||||
DragAndDrop.OVERLAY_MANAGER.on_drag_begin();
|
||||
}
|
||||
|
||||
onDragEndExt () {
|
||||
Main.overview.endItemDrag(this);
|
||||
DragAndDrop.OVERLAY_MANAGER.on_drag_end();
|
||||
}
|
||||
|
||||
onDragCancelledExt () {
|
||||
Main.overview.cancelledItemDrag(this);
|
||||
DragAndDrop.OVERLAY_MANAGER.on_drag_cancelled();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//---------------------------------- Generic -----------------------------------
|
||||
//--------------------------------- functions ----------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
/* These functions perform the requested actions but do not care about popdowning
|
||||
* open menu/open folder, nor about hiding/showing/activating dropping areas, nor
|
||||
* about redisplaying the view.
|
||||
*/
|
||||
function removeFromFolder (app_id, folder_id) {
|
||||
let folder_schema = folderSchema(folder_id);
|
||||
if ( isInFolder(app_id, folder_id) ) {
|
||||
let pastContent = folder_schema.get_strv('apps');
|
||||
let presentContent = [];
|
||||
for(var i=0; i<pastContent.length; i++){
|
||||
if(pastContent[i] != app_id) {
|
||||
presentContent.push(pastContent[i]);
|
||||
}
|
||||
}
|
||||
folder_schema.set_strv('apps', presentContent);
|
||||
} else {
|
||||
let content = folder_schema.get_strv('excluded-apps');
|
||||
content.push(app_id);
|
||||
folder_schema.set_strv('excluded-apps', content);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
function deleteFolder (folder_id) {
|
||||
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
|
||||
let tmp = [];
|
||||
FOLDER_LIST = FOLDER_SCHEMA.get_strv('folder-children');
|
||||
for(var j=0;j < FOLDER_LIST.length;j++){
|
||||
if(FOLDER_LIST[j] != folder_id) {
|
||||
tmp.push(FOLDER_LIST[j]);
|
||||
}
|
||||
}
|
||||
|
||||
FOLDER_LIST = tmp;
|
||||
FOLDER_SCHEMA.set_strv('folder-children', FOLDER_LIST);
|
||||
|
||||
// ?? XXX (ne fonctionne pas mieux hors du meta.later_add)
|
||||
if ( Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('total-deletion') ) {
|
||||
let folder_schema = folderSchema (folder_id);
|
||||
folder_schema.reset('apps'); // génère un bug volumineux ?
|
||||
folder_schema.reset('categories'); // génère un bug volumineux ?
|
||||
folder_schema.reset('excluded-apps'); // génère un bug volumineux ?
|
||||
folder_schema.reset('name'); // génère un bug volumineux ?
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
function mergeFolders (folder_staying_id, folder_dying_id) { //unused XXX
|
||||
|
||||
let folder_dying_schema = folderSchema (folder_dying_id);
|
||||
let folder_staying_schema = folderSchema (folder_staying_id);
|
||||
let newerContent = folder_dying_schema.get_strv('categories');
|
||||
let presentContent = folder_staying_schema.get_strv('categories');
|
||||
for(var i=0;i<newerContent.length;i++){
|
||||
if(presentContent.indexOf(newerContent[i]) == -1) {
|
||||
presentContent.push(newerContent[i]);
|
||||
}
|
||||
}
|
||||
folder_staying_schema.set_strv('categories', presentContent);
|
||||
|
||||
newerContent = folder_dying_schema.get_strv('excluded-apps');
|
||||
presentContent = folder_staying_schema.get_strv('excluded-apps');
|
||||
for(var i=0;i<newerContent.length;i++){
|
||||
if(presentContent.indexOf(newerContent[i]) == -1) {
|
||||
presentContent.push(newerContent[i]);
|
||||
}
|
||||
}
|
||||
folder_staying_schema.set_strv('excluded-apps', presentContent);
|
||||
|
||||
newerContent = folder_dying_schema.get_strv('apps');
|
||||
presentContent = folder_staying_schema.get_strv('apps');
|
||||
for(var i=0;i<newerContent.length;i++){
|
||||
if(presentContent.indexOf(newerContent[i]) == -1) {
|
||||
// if(!isInFolder(newerContent[i], folder_staying_id)) {
|
||||
presentContent.push(newerContent[i]);
|
||||
//TODO utiliser addToFolder malgré ses paramètres chiants
|
||||
}
|
||||
}
|
||||
folder_staying_schema.set_strv('apps', presentContent);
|
||||
deleteFolder(folder_dying_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
function createNewFolder (app_source) {
|
||||
let id = app_source.app.get_id();
|
||||
|
||||
let dialog = new AppfolderDialog.AppfolderDialog(null , id);
|
||||
dialog.open();
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
function addToFolder (app_source, folder_id) {
|
||||
let id = app_source.app.get_id();
|
||||
let folder_schema = folderSchema (folder_id);
|
||||
|
||||
//un-exclude the application if it was excluded TODO else don't do it at all
|
||||
let pastExcluded = folder_schema.get_strv('excluded-apps');
|
||||
let presentExcluded = [];
|
||||
for(let i=0; i<pastExcluded.length; i++){
|
||||
if(pastExcluded[i] != id) {
|
||||
presentExcluded.push(pastExcluded[i]);
|
||||
}
|
||||
}
|
||||
if (presentExcluded.length > 0) {
|
||||
folder_schema.set_strv('excluded-apps', presentExcluded);
|
||||
}
|
||||
|
||||
//actually add the app
|
||||
let content = folder_schema.get_strv('apps');
|
||||
content.push(id);
|
||||
folder_schema.set_strv('apps', content); //XXX verbose errors
|
||||
|
||||
//update icons in the ugliest possible way
|
||||
let icons = Main.overview.viewSelector.appDisplay._views[1].view.folderIcons;
|
||||
for (let i=0; i<icons.length; i++) {
|
||||
let size = icons[i].icon._iconBin.width;
|
||||
icons[i].icon.icon = icons[i]._createIcon(size);
|
||||
icons[i].icon._iconBin.child = icons[i].icon.icon;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
function isInFolder (app_id, folder_id) {
|
||||
let folder_schema = folderSchema(folder_id);
|
||||
let isIn = false;
|
||||
let content_ = folder_schema.get_strv('apps');
|
||||
for(var j=0; j<content_.length; j++) {
|
||||
if(content_[j] == app_id) {
|
||||
isIn = true;
|
||||
}
|
||||
}
|
||||
return isIn;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
function folderSchema (folder_id) {
|
||||
let a = new Gio.Settings({
|
||||
schema_id: 'org.gnome.desktop.app-folders.folder',
|
||||
path: '/org/gnome/desktop/app-folders/folders/' + folder_id + '/'
|
||||
});
|
||||
return a;
|
||||
} // TODO et AppDisplay._getFolderName ??
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
function enable() {
|
||||
FOLDER_SCHEMA = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders' });
|
||||
FOLDER_LIST = FOLDER_SCHEMA.get_strv('folder-children');
|
||||
|
||||
injectionInIcons();
|
||||
if( Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('extend-menus') ) {
|
||||
injectionInAppsMenus();
|
||||
}
|
||||
DragAndDrop.initDND();
|
||||
|
||||
// Reload the view if the user load the extension at least a minute after
|
||||
// opening the session. XXX works like shit
|
||||
let delta = getTimeStamp() - INIT_TIME;
|
||||
if (delta < 0 || delta > 105) {
|
||||
Main.overview.viewSelector.appDisplay._views[1].view._redisplay();
|
||||
}
|
||||
}
|
||||
|
||||
function disable() {
|
||||
AppDisplay.FolderIcon.prototype._onButtonPress = null;
|
||||
AppDisplay.FolderIcon.prototype.popupMenu = null;
|
||||
|
||||
removeInjection(AppDisplay.AppIconMenu.prototype, injections, '_redisplay');
|
||||
|
||||
// Overwrite my shit for FolderIcon
|
||||
AppDisplay.FolderIcon = class extends AppDisplay.FolderIcon {
|
||||
_onButtonPress (actor, event) {
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
}
|
||||
};
|
||||
|
||||
// Overwrite my shit for AppIcon
|
||||
AppDisplay.AppIcon = class extends AppDisplay.AppIcon {
|
||||
onDragBeginExt () {}
|
||||
onDragEndExt () {}
|
||||
onDragCancelledExt () {}
|
||||
};
|
||||
|
||||
DragAndDrop.OVERLAY_MANAGER.destroy();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
@ -1,114 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:83
|
||||
msgid "Add to"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:85
|
||||
msgid "New AppFolder"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:139
|
||||
msgid "Remove from"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
|
||||
msgid "Create a new folder"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
|
||||
#, javascript-format
|
||||
msgid "Remove from %s"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
|
||||
msgid "Create"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
|
||||
msgid "Folder's name:"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
|
||||
msgid "Categories:"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
|
||||
msgid "Other category?"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
|
||||
msgid "No category"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
|
||||
msgid "Select a category…"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:31
|
||||
msgid "Modifications will be effective after reloading the extension."
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:38
|
||||
msgid "Main settings"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:39
|
||||
msgid "Categories"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:46
|
||||
msgid "Delete all related settings when an appfolder is deleted"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:48
|
||||
msgid "Use the right-click menus in addition to the drag-and-drop"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:56
|
||||
msgid "Use categories"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:59
|
||||
msgid "More informations about \"additional categories\""
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:74
|
||||
msgid "Report bugs or ideas"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:85
|
||||
msgid ""
|
||||
"This extension can be deactivated once your applications are organized as "
|
||||
"wished."
|
||||
msgstr ""
|
Binary file not shown.
@ -1,123 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
|
||||
"PO-Revision-Date: 2018-12-30 14:03+0300\n"
|
||||
"Last-Translator: Максім Крапіўка <metalomaniax@gmail.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: be\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.2\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||
"%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:83
|
||||
msgid "Add to"
|
||||
msgstr "Дадаць у"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:85
|
||||
msgid "New AppFolder"
|
||||
msgstr "Новая папка праграм"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:139
|
||||
msgid "Remove from"
|
||||
msgstr "Выдаліць з"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
|
||||
msgid "Create a new folder"
|
||||
msgstr "Стварыць новую папку"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
|
||||
#, javascript-format
|
||||
msgid "Remove from %s"
|
||||
msgstr "Выдаліць з %s"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
|
||||
msgid "Cancel"
|
||||
msgstr "Скасаваць"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
|
||||
msgid "Create"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
|
||||
msgid "Delete"
|
||||
msgstr "Выдаліць"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
|
||||
msgid "Apply"
|
||||
msgstr "Дастасаваць"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
|
||||
msgid "Folder's name:"
|
||||
msgstr "Назва папкі"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
|
||||
msgid "Categories:"
|
||||
msgstr "Катэгорыі:"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
|
||||
msgid "Other category?"
|
||||
msgstr "Іншая катэгорыя?"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
|
||||
msgid "No category"
|
||||
msgstr "Без катэгорыі"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
|
||||
msgid "Select a category…"
|
||||
msgstr "Выбраць катэгорыю..."
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:31
|
||||
msgid "Modifications will be effective after reloading the extension."
|
||||
msgstr "Змены ўступяць у сілу пасля перазагрузкі пашырэння."
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:38
|
||||
msgid "Main settings"
|
||||
msgstr "Галоўныя налады"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:39
|
||||
msgid "Categories"
|
||||
msgstr "Катэгорыі"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:46
|
||||
msgid "Delete all related settings when an appfolder is deleted"
|
||||
msgstr "Выдаляць усе звязаныя налады пры выдаленні папкі праграм"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:48
|
||||
msgid "Use the right-click menus in addition to the drag-and-drop"
|
||||
msgstr ""
|
||||
"Выкарыстоўваць націсканне правай кнопкай мышы ў дадатак да перацягвання "
|
||||
"значкоў"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:56
|
||||
msgid "Use categories"
|
||||
msgstr "Выкарыстоўваць катэгорыі"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:59
|
||||
msgid "More informations about \"additional categories\""
|
||||
msgstr "Больш інфармацыі пра «дадатковыя» катэгорыі"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:74
|
||||
msgid "Report bugs or ideas"
|
||||
msgstr "Паведаміць пра памылкі або ідэі"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:85
|
||||
msgid ""
|
||||
"This extension can be deactivated once your applications are organized as "
|
||||
"wished."
|
||||
msgstr ""
|
||||
"Гэта пашырэнне можна адключыць, пасля таго як вы канчаткова наладзіце "
|
||||
"арганізацыю сваіх праграм."
|
||||
|
||||
#~ msgid "Standard specification"
|
||||
#~ msgstr "Стандартная спецыфікацыя"
|
Binary file not shown.
@ -1,124 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
|
||||
"PO-Revision-Date: 2017-10-28 01:50+0200\n"
|
||||
"Last-Translator: Jonatan Hatakeyama Zeidler <jonatan_zeidler@gmx.de>\n"
|
||||
"Language-Team: https://github.com/hobbypunk90/appfolders-manager-gnome-"
|
||||
"extension\n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.0.4\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:83
|
||||
msgid "Add to"
|
||||
msgstr "Hinzufügen zu"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:85
|
||||
msgid "New AppFolder"
|
||||
msgstr "Neuer Ordner"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:139
|
||||
msgid "Remove from"
|
||||
msgstr "Löschen aus"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
|
||||
msgid "Create a new folder"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Remove from %s"
|
||||
msgstr "Löschen aus"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
|
||||
msgid "Cancel"
|
||||
msgstr "Abbrechen"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
|
||||
msgid "Create"
|
||||
msgstr "Erstelle"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
|
||||
msgid "Delete"
|
||||
msgstr "Löschen"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
|
||||
msgid "Folder's name:"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
|
||||
msgid "Categories:"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
|
||||
#, fuzzy
|
||||
msgid "Other category?"
|
||||
msgstr "Lösche eine Kategorie"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
|
||||
#, fuzzy
|
||||
msgid "No category"
|
||||
msgstr "Kategorie hinzufügen"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
|
||||
#, fuzzy
|
||||
msgid "Select a category…"
|
||||
msgstr "Lösche eine Kategorie"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:31
|
||||
msgid "Modifications will be effective after reloading the extension."
|
||||
msgstr "Änderungen werden nach Neustart der Erweiterung übernommen."
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:38
|
||||
msgid "Main settings"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:39
|
||||
msgid "Categories"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:46
|
||||
msgid "Delete all related settings when an appfolder is deleted"
|
||||
msgstr "Lösche alle Einstellungen, wenn ein Ordner gelöscht wird"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:48
|
||||
msgid "Use the right-click menus in addition to the drag-and-drop"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:56
|
||||
#, fuzzy
|
||||
msgid "Use categories"
|
||||
msgstr "Hinzugefügte Kategorien"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:59
|
||||
msgid "More informations about \"additional categories\""
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:74
|
||||
msgid "Report bugs or ideas"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:85
|
||||
msgid ""
|
||||
"This extension can be deactivated once your applications are organized as "
|
||||
"wished."
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "This appfolder already exists."
|
||||
#~ msgstr "Dieser Ordner existiert bereits."
|
Binary file not shown.
@ -1,118 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
|
||||
"PO-Revision-Date: 2017-05-29 23:26+0300\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: el\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.0.1\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:83
|
||||
msgid "Add to"
|
||||
msgstr "Προσθήκη σε"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:85
|
||||
msgid "New AppFolder"
|
||||
msgstr "Νέος φάκελος"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:139
|
||||
msgid "Remove from"
|
||||
msgstr "Αφαίρεση από"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
|
||||
msgid "Create a new folder"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Remove from %s"
|
||||
msgstr "Αφαίρεση από"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
|
||||
msgid "Cancel"
|
||||
msgstr "Αναίρεση"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
|
||||
msgid "Create"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
|
||||
msgid "Delete"
|
||||
msgstr "Διαγραφή"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
|
||||
msgid "Folder's name:"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
|
||||
msgid "Categories:"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
|
||||
msgid "Other category?"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
|
||||
msgid "No category"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
|
||||
msgid "Select a category…"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:31
|
||||
msgid "Modifications will be effective after reloading the extension."
|
||||
msgstr ""
|
||||
"Οι τροποποιήσεις απαιτούν την επανεκκίνηση της επέκτασης για να "
|
||||
"λειτουργήσουν."
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:38
|
||||
msgid "Main settings"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:39
|
||||
msgid "Categories"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:46
|
||||
msgid "Delete all related settings when an appfolder is deleted"
|
||||
msgstr "Διαγραφή όλων των σχετικών ρυθμίσεων κατά την διαγραφή ενός φακέλου"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:48
|
||||
msgid "Use the right-click menus in addition to the drag-and-drop"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:56
|
||||
msgid "Use categories"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:59
|
||||
msgid "More informations about \"additional categories\""
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:74
|
||||
msgid "Report bugs or ideas"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:85
|
||||
msgid ""
|
||||
"This extension can be deactivated once your applications are organized as "
|
||||
"wished."
|
||||
msgstr ""
|
Binary file not shown.
@ -1,116 +0,0 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
|
||||
"PO-Revision-Date: 2017-02-05 16:47+0100\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 1.8.11\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:83
|
||||
msgid "Add to"
|
||||
msgstr "Ajouter à"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:85
|
||||
msgid "New AppFolder"
|
||||
msgstr "Nouvel AppFolder"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:139
|
||||
msgid "Remove from"
|
||||
msgstr "Retirer de"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
|
||||
msgid "Create a new folder"
|
||||
msgstr "Créer un nouveau dossier"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
|
||||
#, javascript-format
|
||||
msgid "Remove from %s"
|
||||
msgstr "Retirer de %s"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
|
||||
msgid "Create"
|
||||
msgstr "Créer"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
|
||||
msgid "Apply"
|
||||
msgstr "Appliquer"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
|
||||
msgid "Folder's name:"
|
||||
msgstr "Nom du dossier :"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
|
||||
msgid "Categories:"
|
||||
msgstr "Catégories :"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
|
||||
msgid "Other category?"
|
||||
msgstr "Autre catégorie ?"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
|
||||
msgid "No category"
|
||||
msgstr "Aucune catégorie"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
|
||||
msgid "Select a category…"
|
||||
msgstr "Choisir une catégorie…"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:31
|
||||
msgid "Modifications will be effective after reloading the extension."
|
||||
msgstr "Les modifications prendront effet après avoir rechargé l'extension"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:38
|
||||
msgid "Main settings"
|
||||
msgstr "Réglages principaux"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:39
|
||||
msgid "Categories"
|
||||
msgstr "Catégories"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:46
|
||||
msgid "Delete all related settings when an appfolder is deleted"
|
||||
msgstr ""
|
||||
"Supprimer tous les réglages relatifs à un appfolder lors de sa suppression"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:48
|
||||
msgid "Use the right-click menus in addition to the drag-and-drop"
|
||||
msgstr "Utiliser le menu du clic-droit en complément du glisser-déposer"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:56
|
||||
msgid "Use categories"
|
||||
msgstr "Utiliser des catégories"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:59
|
||||
msgid "More informations about \"additional categories\""
|
||||
msgstr "Plus d'informations à propos des \"catégories supplémentaires\""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:74
|
||||
msgid "Report bugs or ideas"
|
||||
msgstr "Reporter un bug ou une idée"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:85
|
||||
msgid ""
|
||||
"This extension can be deactivated once your applications are organized as "
|
||||
"wished."
|
||||
msgstr ""
|
||||
"Cette extension peut être désactivée une fois que vos applications sont "
|
||||
"organisées comme souhaité."
|
||||
|
||||
#~ msgid "Standard specification"
|
||||
#~ msgstr "Spécification standard"
|
Binary file not shown.
@ -1,116 +0,0 @@
|
||||
# Hungarian translation for appfolders-manager.
|
||||
# Copyright (C) 2017 Free Software Foundation, Inc.
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Balázs Úr <urbalazs@gmail.com>, 2017.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: appfolders-manager master\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
|
||||
"PO-Revision-Date: 2017-05-31 20:41+0100\n"
|
||||
"Last-Translator: Balázs Úr <urbalazs@gmail.com>\n"
|
||||
"Language-Team: Hungarian <openscope@googlegroups.com>\n"
|
||||
"Language: hu\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Lokalize 2.0\n"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:83
|
||||
msgid "Add to"
|
||||
msgstr "Hozzáadás ehhez"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:85
|
||||
msgid "New AppFolder"
|
||||
msgstr "Új alkalmazásmappa"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:139
|
||||
msgid "Remove from"
|
||||
msgstr "Eltávolítás innen"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
|
||||
msgid "Create a new folder"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Remove from %s"
|
||||
msgstr "Eltávolítás innen"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
|
||||
msgid "Cancel"
|
||||
msgstr "Mégse"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
|
||||
msgid "Create"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
|
||||
msgid "Delete"
|
||||
msgstr "Törlés"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
|
||||
msgid "Folder's name:"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
|
||||
msgid "Categories:"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
|
||||
msgid "Other category?"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
|
||||
msgid "No category"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
|
||||
msgid "Select a category…"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:31
|
||||
msgid "Modifications will be effective after reloading the extension."
|
||||
msgstr "A módosítások a kiterjesztés újratöltése után lépnek hatályba."
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:38
|
||||
msgid "Main settings"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:39
|
||||
msgid "Categories"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:46
|
||||
msgid "Delete all related settings when an appfolder is deleted"
|
||||
msgstr ""
|
||||
"Az összes kapcsolódó beállítás törlése, ha egy alkalmazásmappa törölve lesz"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:48
|
||||
msgid "Use the right-click menus in addition to the drag-and-drop"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:56
|
||||
msgid "Use categories"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:59
|
||||
msgid "More informations about \"additional categories\""
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:74
|
||||
msgid "Report bugs or ideas"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:85
|
||||
msgid ""
|
||||
"This extension can be deactivated once your applications are organized as "
|
||||
"wished."
|
||||
msgstr ""
|
Binary file not shown.
@ -1,127 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
|
||||
"PO-Revision-Date: 2018-03-08 17:21+0100\n"
|
||||
"Last-Translator: Jimmy Scionti <jimmy.scionti@gmail.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: it_IT\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.0.4\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:83
|
||||
msgid "Add to"
|
||||
msgstr "Aggiungi a"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:85
|
||||
msgid "New AppFolder"
|
||||
msgstr "Nuova AppFolder"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:139
|
||||
msgid "Remove from"
|
||||
msgstr "Rimuovi da"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
|
||||
msgid "Create a new folder"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Remove from %s"
|
||||
msgstr "Rimuovi da"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
|
||||
msgid "Cancel"
|
||||
msgstr "Annulla"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
|
||||
msgid "Create"
|
||||
msgstr "Crea"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
|
||||
msgid "Delete"
|
||||
msgstr "Elimina"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
|
||||
msgid "Folder's name:"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
|
||||
msgid "Categories:"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
|
||||
#, fuzzy
|
||||
msgid "Other category?"
|
||||
msgstr "Rimuovi una categoria"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
|
||||
#, fuzzy
|
||||
msgid "No category"
|
||||
msgstr "Aggiungi una categoria"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
|
||||
#, fuzzy
|
||||
msgid "Select a category…"
|
||||
msgstr "Rimuovi una categoria"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:31
|
||||
msgid "Modifications will be effective after reloading the extension."
|
||||
msgstr "Le modifiche avranno effetto dopo aver ricaricato l'estensione."
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:38
|
||||
msgid "Main settings"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:39
|
||||
msgid "Categories"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:46
|
||||
msgid "Delete all related settings when an appfolder is deleted"
|
||||
msgstr "Elimina tutte le impostazioni dell'AppFolder quando viene rimossa"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:48
|
||||
msgid "Use the right-click menus in addition to the drag-and-drop"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:56
|
||||
#, fuzzy
|
||||
msgid "Use categories"
|
||||
msgstr "Categorie aggiuntive"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:59
|
||||
msgid "More informations about \"additional categories\""
|
||||
msgstr "Maggiori informazioni sulle \"categorie aggiuntive\""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:74
|
||||
msgid "Report bugs or ideas"
|
||||
msgstr "Invia segnalazioni di bug o idee"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:85
|
||||
msgid ""
|
||||
"This extension can be deactivated once your applications are organized as "
|
||||
"wished."
|
||||
msgstr ""
|
||||
"Questa estensione può essere disattivata dopo aver organizzato le "
|
||||
"applicazione come desiderato."
|
||||
|
||||
#~ msgid "Standard specification"
|
||||
#~ msgstr "Specifiche standards"
|
||||
|
||||
#~ msgid "This appfolder already exists."
|
||||
#~ msgstr "Questa AppFolder esiste già."
|
Binary file not shown.
@ -1,126 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Appfolders Management (GNOME extension)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
|
||||
"PO-Revision-Date: 2018-12-19 21:13+0100\n"
|
||||
"Last-Translator: Piotr Komur <pkomur@gmail.com>\n"
|
||||
"Language-Team: Piotr Komur\n"
|
||||
"Language: pl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.2\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
|
||||
"|| n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:83
|
||||
msgid "Add to"
|
||||
msgstr "Dodaj do folderu"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:85
|
||||
msgid "New AppFolder"
|
||||
msgstr "Nowy folder programów"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:139
|
||||
msgid "Remove from"
|
||||
msgstr "Usuń z folderu"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
|
||||
msgid "Create a new folder"
|
||||
msgstr "Utwórz nowy folder"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Remove from %s"
|
||||
msgstr "Usuń z folderu"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
|
||||
msgid "Cancel"
|
||||
msgstr "Anuluj"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
|
||||
msgid "Create"
|
||||
msgstr "Utwórz"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
|
||||
msgid "Delete"
|
||||
msgstr "Usuń"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
|
||||
msgid "Apply"
|
||||
msgstr "Zastosuj"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
|
||||
msgid "Folder's name:"
|
||||
msgstr "Nazwa folderu:"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
|
||||
msgid "Categories:"
|
||||
msgstr "Kategorie programów:"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
|
||||
msgid "Other category?"
|
||||
msgstr "Nowa kategoria"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
|
||||
msgid "No category"
|
||||
msgstr "Brak wybranych kategorii"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
|
||||
#, fuzzy
|
||||
msgid "Select a category…"
|
||||
msgstr "Wybierz kategorię"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:31
|
||||
msgid "Modifications will be effective after reloading the extension."
|
||||
msgstr "Zmiany będą aktywne po ponownym zrestartowaniu środowiska Gnome."
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:38
|
||||
msgid "Main settings"
|
||||
msgstr "Ustawienia"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:39
|
||||
msgid "Categories"
|
||||
msgstr "Kategorie"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:46
|
||||
msgid "Delete all related settings when an appfolder is deleted"
|
||||
msgstr "Usuń powiązane zmiany wraz z usunięciem folderu programów."
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:48
|
||||
msgid "Use the right-click menus in addition to the drag-and-drop"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:56
|
||||
msgid "Use categories"
|
||||
msgstr "Użyj standardowych kategorii"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:59
|
||||
msgid "More informations about \"additional categories\""
|
||||
msgstr "Więcej informacji o \"standardowych kategoriach\""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:74
|
||||
msgid "Report bugs or ideas"
|
||||
msgstr "Zgłoś błędy lub nowe funkcje"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:85
|
||||
msgid ""
|
||||
"This extension can be deactivated once your applications are organized as "
|
||||
"wished."
|
||||
msgstr ""
|
||||
"Można dezaktywować to rozszerzenie po zakończeniu porządkowania programów w "
|
||||
"folderach."
|
||||
|
||||
#~ msgid "Standard specification"
|
||||
#~ msgstr "Specyfikacja standardu"
|
||||
|
||||
#~ msgid "This appfolder already exists."
|
||||
#~ msgstr "Folder o tej nazwie już istnieje."
|
Binary file not shown.
@ -1,124 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
|
||||
"PO-Revision-Date: 2019-02-10 11:19-0200\n"
|
||||
"Last-Translator: Fábio Nogueira <fnogueira@gnome.org>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: pt_BR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.2.1\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:83
|
||||
msgid "Add to"
|
||||
msgstr "Adicionar para"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:85
|
||||
msgid "New AppFolder"
|
||||
msgstr "Nova AppFolder"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:139
|
||||
msgid "Remove from"
|
||||
msgstr "Remover de"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
|
||||
msgid "Create a new folder"
|
||||
msgstr "Criar uma nova pasta"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
|
||||
#, javascript-format
|
||||
msgid "Remove from %s"
|
||||
msgstr "Remover de %s"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
|
||||
msgid "Create"
|
||||
msgstr "Criar"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
|
||||
msgid "Delete"
|
||||
msgstr "Excluir"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
|
||||
msgid "Apply"
|
||||
msgstr "Aplicar"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
|
||||
msgid "Folder's name:"
|
||||
msgstr "Nome da pasta:"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
|
||||
msgid "Categories:"
|
||||
msgstr "Categorias:"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
|
||||
msgid "Other category?"
|
||||
msgstr "Outra categoria?"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
|
||||
msgid "No category"
|
||||
msgstr "Nenhuma categoria"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
|
||||
msgid "Select a category…"
|
||||
msgstr "Selecione uma categoria…"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:31
|
||||
msgid "Modifications will be effective after reloading the extension."
|
||||
msgstr "As modificações entrarão em vigor depois de recarregar a extensão."
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:38
|
||||
msgid "Main settings"
|
||||
msgstr "Configurações principais"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:39
|
||||
msgid "Categories"
|
||||
msgstr "Categorias"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:46
|
||||
msgid "Delete all related settings when an appfolder is deleted"
|
||||
msgstr ""
|
||||
"Excluir todas as configurações relacionadas quando um appfolder for excluído"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:48
|
||||
msgid "Use the right-click menus in addition to the drag-and-drop"
|
||||
msgstr "Use os menus do botão direito, além do arrastar e soltar"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:56
|
||||
msgid "Use categories"
|
||||
msgstr "Usar categorias"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:59
|
||||
msgid "More informations about \"additional categories\""
|
||||
msgstr "Mais informações sobre \"categorias adicionais\""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:74
|
||||
msgid "Report bugs or ideas"
|
||||
msgstr "Comunicar erros ou ideias"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:85
|
||||
msgid ""
|
||||
"This extension can be deactivated once your applications are organized as "
|
||||
"wished."
|
||||
msgstr ""
|
||||
"Esta extensão pode ser desativada assim que seus aplicativos forem "
|
||||
"organizados conforme desejado."
|
||||
|
||||
#~ msgid "Standard specification"
|
||||
#~ msgstr "Especificação padrão"
|
||||
|
||||
#~ msgid "This appfolder already exists."
|
||||
#~ msgstr "Esta appfolder já existe."
|
Binary file not shown.
@ -1,125 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Max Krapiŭka <metalomaniax@gmail.com>\n"
|
||||
"Language-Team: RUSSIAN <metalomaniax@gmail.com>\n"
|
||||
"Language: be\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:83
|
||||
msgid "Add to"
|
||||
msgstr "Добавить в"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:85
|
||||
msgid "New AppFolder"
|
||||
msgstr "Новая папка"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:139
|
||||
msgid "Remove from"
|
||||
msgstr "Удалить из"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
|
||||
msgid "Create a new folder"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Remove from %s"
|
||||
msgstr "Удалить из"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
|
||||
msgid "Cancel"
|
||||
msgstr "Отмена"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
|
||||
msgid "Create"
|
||||
msgstr "Стварыць"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
|
||||
msgid "Delete"
|
||||
msgstr "Удалить"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
|
||||
msgid "Folder's name:"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
|
||||
msgid "Categories:"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
|
||||
#, fuzzy
|
||||
msgid "Other category?"
|
||||
msgstr "Удалить категорию"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
|
||||
#, fuzzy
|
||||
msgid "No category"
|
||||
msgstr "Добавить категорию"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
|
||||
#, fuzzy
|
||||
msgid "Select a category…"
|
||||
msgstr "Удалить категорию"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:31
|
||||
msgid "Modifications will be effective after reloading the extension."
|
||||
msgstr "Изменения вступят в силу после перезагрузки расширения."
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:38
|
||||
msgid "Main settings"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:39
|
||||
msgid "Categories"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:46
|
||||
msgid "Delete all related settings when an appfolder is deleted"
|
||||
msgstr "Удалять все связанные настройки при удалении папки приложений."
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:48
|
||||
msgid "Use the right-click menus in addition to the drag-and-drop"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:56
|
||||
#, fuzzy
|
||||
msgid "Use categories"
|
||||
msgstr "Дополнительные категории"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:59
|
||||
msgid "More informations about \"additional categories\""
|
||||
msgstr "Больше информации про \"дополнительные категории\""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:74
|
||||
msgid "Report bugs or ideas"
|
||||
msgstr "Сообщить об ошибках, предложить идеи"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:85
|
||||
msgid ""
|
||||
"This extension can be deactivated once your applications are organized as "
|
||||
"wished."
|
||||
msgstr ""
|
||||
"Это расширение может быть деактивировано, после того как вы окончательно "
|
||||
"настроите организацию своих приложений."
|
||||
|
||||
#~ msgid "Standard specification"
|
||||
#~ msgstr "Стандартная спецификация"
|
||||
|
||||
#~ msgid "This appfolder already exists."
|
||||
#~ msgstr "Эта папка приложений уже существует"
|
Binary file not shown.
@ -1,124 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
|
||||
"PO-Revision-Date: 2017-09-15 16:40+0200\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: sr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.0.3\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:83
|
||||
msgid "Add to"
|
||||
msgstr "Додај у"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:85
|
||||
msgid "New AppFolder"
|
||||
msgstr "Нова фаскила програма"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:139
|
||||
msgid "Remove from"
|
||||
msgstr "Уклони ставку"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
|
||||
msgid "Create a new folder"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Remove from %s"
|
||||
msgstr "Уклони ставку"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
|
||||
msgid "Cancel"
|
||||
msgstr "Откажи"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
|
||||
msgid "Create"
|
||||
msgstr "Направи"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
|
||||
msgid "Delete"
|
||||
msgstr "Обриши"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
|
||||
msgid "Folder's name:"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
|
||||
msgid "Categories:"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
|
||||
#, fuzzy
|
||||
msgid "Other category?"
|
||||
msgstr "Уклони категорију"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
|
||||
#, fuzzy
|
||||
msgid "No category"
|
||||
msgstr "Додај категорију"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
|
||||
#, fuzzy
|
||||
msgid "Select a category…"
|
||||
msgstr "Уклони категорију"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:31
|
||||
msgid "Modifications will be effective after reloading the extension."
|
||||
msgstr "Измене ће ступити на снагу по поновном учитавању проширења."
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:38
|
||||
msgid "Main settings"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:39
|
||||
msgid "Categories"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:46
|
||||
msgid "Delete all related settings when an appfolder is deleted"
|
||||
msgstr "Обриши све припадајуће поставке при брисању проширења"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:48
|
||||
msgid "Use the right-click menus in addition to the drag-and-drop"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:56
|
||||
#, fuzzy
|
||||
msgid "Use categories"
|
||||
msgstr "Додатне категорије"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:59
|
||||
msgid "More informations about \"additional categories\""
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:74
|
||||
msgid "Report bugs or ideas"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:85
|
||||
msgid ""
|
||||
"This extension can be deactivated once your applications are organized as "
|
||||
"wished."
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "This appfolder already exists."
|
||||
#~ msgstr "Ова фасцикла програма већ постоји."
|
Binary file not shown.
@ -1,124 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
|
||||
"PO-Revision-Date: 2017-09-15 16:40+0200\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: sr@latin\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.0.3\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:83
|
||||
msgid "Add to"
|
||||
msgstr "Dodaj u"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:85
|
||||
msgid "New AppFolder"
|
||||
msgstr "Nova faskila programa"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:139
|
||||
msgid "Remove from"
|
||||
msgstr "Ukloni stavku"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
|
||||
msgid "Create a new folder"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Remove from %s"
|
||||
msgstr "Ukloni stavku"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
|
||||
msgid "Cancel"
|
||||
msgstr "Otkaži"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
|
||||
msgid "Create"
|
||||
msgstr "Napravi"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
|
||||
msgid "Delete"
|
||||
msgstr "Obriši"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
|
||||
msgid "Folder's name:"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
|
||||
msgid "Categories:"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
|
||||
#, fuzzy
|
||||
msgid "Other category?"
|
||||
msgstr "Ukloni kategoriju"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
|
||||
#, fuzzy
|
||||
msgid "No category"
|
||||
msgstr "Dodaj kategoriju"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
|
||||
#, fuzzy
|
||||
msgid "Select a category…"
|
||||
msgstr "Ukloni kategoriju"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:31
|
||||
msgid "Modifications will be effective after reloading the extension."
|
||||
msgstr "Izmene će stupiti na snagu po ponovnom učitavanju proširenja."
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:38
|
||||
msgid "Main settings"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:39
|
||||
msgid "Categories"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:46
|
||||
msgid "Delete all related settings when an appfolder is deleted"
|
||||
msgstr "Obriši sve pripadajuće postavke pri brisanju proširenja"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:48
|
||||
msgid "Use the right-click menus in addition to the drag-and-drop"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:56
|
||||
#, fuzzy
|
||||
msgid "Use categories"
|
||||
msgstr "Dodatne kategorije"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:59
|
||||
msgid "More informations about \"additional categories\""
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:74
|
||||
msgid "Report bugs or ideas"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:85
|
||||
msgid ""
|
||||
"This extension can be deactivated once your applications are organized as "
|
||||
"wished."
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "This appfolder already exists."
|
||||
#~ msgstr "Ova fascikla programa već postoji."
|
Binary file not shown.
@ -1,118 +0,0 @@
|
||||
# Uygulama klasörleme Türkçe çeviri.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# Serdar Sağlam <teknomobil@yandex.com>, 2019.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: v13\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
|
||||
"PO-Revision-Date: 2019-01-19 12:10+0300\n"
|
||||
"Last-Translator: Serdar Sağlam <teknomobil@yandex.com>\n"
|
||||
"Language-Team: Türkçe <teknomobil@yandex.com>\n"
|
||||
"Language: tr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:83
|
||||
msgid "Add to"
|
||||
msgstr "Klasör Grubuna Taşı"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:85
|
||||
msgid "New AppFolder"
|
||||
msgstr "Yeni Klasör Grubu"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:139
|
||||
msgid "Remove from"
|
||||
msgstr "Buradan Kaldır"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
|
||||
msgid "Create a new folder"
|
||||
msgstr "Yeni klasör oluştur"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
|
||||
#, javascript-format
|
||||
msgid "Remove from %s"
|
||||
msgstr "Buradan Kaldır %s"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
|
||||
msgid "Cancel"
|
||||
msgstr "İptal"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
|
||||
msgid "Create"
|
||||
msgstr "Oluştur"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
|
||||
msgid "Delete"
|
||||
msgstr "Sil"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
|
||||
msgid "Apply"
|
||||
msgstr "Onayla"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
|
||||
msgid "Folder's name:"
|
||||
msgstr "Klasör İsmi:"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
|
||||
msgid "Categories:"
|
||||
msgstr "Kategoriler:"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
|
||||
msgid "Other category?"
|
||||
msgstr "Diğer Kategori?"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
|
||||
msgid "No category"
|
||||
msgstr "Kategori Yok"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
|
||||
msgid "Select a category…"
|
||||
msgstr "Kategori Seç…"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:31
|
||||
msgid "Modifications will be effective after reloading the extension."
|
||||
msgstr "Değişiklikler,eklenti yeniden başladıktan sonra etkili olacaktır."
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:38
|
||||
msgid "Main settings"
|
||||
msgstr "Ayarlar"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:39
|
||||
msgid "Categories"
|
||||
msgstr "Kategoriler"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:46
|
||||
msgid "Delete all related settings when an appfolder is deleted"
|
||||
msgstr "Bir klasör grubu silindiğinde tüm ilgili ayarları silin"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:48
|
||||
msgid "Use the right-click menus in addition to the drag-and-drop"
|
||||
msgstr "Sürükle ve bırak işlevine ek olarak sağ tıklama menülerini kullanın"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:56
|
||||
msgid "Use categories"
|
||||
msgstr "Kategorileri Kullan"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:59
|
||||
msgid "More informations about \"additional categories\""
|
||||
msgstr "Hakkında daha fazla bilgi \"ek kategoriler\""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:74
|
||||
msgid "Report bugs or ideas"
|
||||
msgstr "Yeni bir fikir veya hata bildirin"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:85
|
||||
msgid ""
|
||||
"This extension can be deactivated once your applications are organized as "
|
||||
"wished."
|
||||
msgstr ""
|
||||
"Menüleri istediğiniz şekilde düzenlendiğinde bu uzantı devre dışı "
|
||||
"bırakılabilir."
|
||||
|
||||
#~ msgid "Standard specification"
|
||||
#~ msgstr "Standart şartname"
|
Binary file not shown.
@ -1,123 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-04-27 21:15+0200\n"
|
||||
"PO-Revision-Date: 2018-12-19 05:43+0200\n"
|
||||
"Last-Translator: Igor Gordiichuk <igor_ck@outlook.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: uk_UA\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.2\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:83
|
||||
msgid "Add to"
|
||||
msgstr "Додати до"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:85
|
||||
msgid "New AppFolder"
|
||||
msgstr "Нова тека програм"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/extension.js:139
|
||||
msgid "Remove from"
|
||||
msgstr "Вилучити з"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:312
|
||||
msgid "Create a new folder"
|
||||
msgstr "Створити нову теку"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/dragAndDrop.js:350
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Remove from %s"
|
||||
msgstr "Вилучити з "
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:62
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:72
|
||||
msgid "Cancel"
|
||||
msgstr "Скасувати"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:66
|
||||
msgid "Create"
|
||||
msgstr ""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:76
|
||||
msgid "Delete"
|
||||
msgstr "Вилучити"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:80
|
||||
msgid "Apply"
|
||||
msgstr "Застосовувати"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:106
|
||||
msgid "Folder's name:"
|
||||
msgstr "Назва теки:"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:139
|
||||
msgid "Categories:"
|
||||
msgstr "Категорії:"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:164
|
||||
msgid "Other category?"
|
||||
msgstr "Інша категорія?"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:192
|
||||
msgid "No category"
|
||||
msgstr "Без категорії"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/appfolderDialog.js:375
|
||||
#, fuzzy
|
||||
msgid "Select a category…"
|
||||
msgstr "Обрати категорію"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:31
|
||||
msgid "Modifications will be effective after reloading the extension."
|
||||
msgstr "Зміни буде застосовано після перезавантаження розширення."
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:38
|
||||
msgid "Main settings"
|
||||
msgstr "Основні налаштування"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:39
|
||||
msgid "Categories"
|
||||
msgstr "Категорії"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:46
|
||||
msgid "Delete all related settings when an appfolder is deleted"
|
||||
msgstr "Вилучити всі пов'язані налаштування, коли видаляється тека"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:48
|
||||
msgid "Use the right-click menus in addition to the drag-and-drop"
|
||||
msgstr "Використовувати контекстне меню додатково до перетягування мишкою"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:56
|
||||
msgid "Use categories"
|
||||
msgstr "Використати категорії"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:59
|
||||
msgid "More informations about \"additional categories\""
|
||||
msgstr "Більше інформації про \"додаткові категорії\""
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:74
|
||||
msgid "Report bugs or ideas"
|
||||
msgstr "Повідомити про ваду або запропонувати ідею"
|
||||
|
||||
#: appfolders-manager@maestroschan.fr/prefs.js:85
|
||||
msgid ""
|
||||
"This extension can be deactivated once your applications are organized as "
|
||||
"wished."
|
||||
msgstr "Це розширення можна деактивувати, якщо програми було впорядковано."
|
||||
|
||||
#~ msgid "Standard specification"
|
||||
#~ msgstr "Стандартні категорії"
|
||||
|
||||
#~ msgid "This appfolder already exists."
|
||||
#~ msgstr "Ця тека програм вже існує."
|
@ -1,15 +0,0 @@
|
||||
{
|
||||
"_generated": "Generated by SweetTooth, do not edit",
|
||||
"description": "An easy way to arrange your applications in folders, directly from the applications grid. Create folders and add/remove apps using drag-and-drop, rename/delete them with a right-click.",
|
||||
"gettext-domain": "appfolders-manager",
|
||||
"name": "Appfolders Management extension",
|
||||
"shell-version": [
|
||||
"3.26",
|
||||
"3.28",
|
||||
"3.30",
|
||||
"3.32"
|
||||
],
|
||||
"url": "https://github.com/maoschanz/appfolders-manager-gnome-extension",
|
||||
"uuid": "appfolders-manager@maestroschan.fr",
|
||||
"version": 16
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
|
||||
const Gettext = imports.gettext.domain('appfolders-manager');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Convenience = Me.imports.convenience;
|
||||
|
||||
//-----------------------------------------------
|
||||
|
||||
const appfoldersManagerSettingsWidget = new GObject.Class({
|
||||
Name: 'appfoldersManager.Prefs.Widget',
|
||||
GTypeName: 'appfoldersManagerPrefsWidget',
|
||||
Extends: Gtk.Box,
|
||||
|
||||
_init: function (params) {
|
||||
this.parent(params);
|
||||
this.margin = 30;
|
||||
this.spacing = 18;
|
||||
this.set_orientation(Gtk.Orientation.VERTICAL);
|
||||
|
||||
this._settings = Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager');
|
||||
this._settings.set_boolean('debug', this._settings.get_boolean('debug'));
|
||||
|
||||
//----------------------------
|
||||
|
||||
let labelMain = new Gtk.Label({
|
||||
label: _("Modifications will be effective after reloading the extension."),
|
||||
use_markup: true,
|
||||
wrap: true,
|
||||
halign: Gtk.Align.START
|
||||
});
|
||||
this.add(labelMain);
|
||||
|
||||
let generalSection = this.add_section(_("Main settings"));
|
||||
let categoriesSection = this.add_section(_("Categories"));
|
||||
|
||||
//----------------------------
|
||||
|
||||
// let autoDeleteBox = this.build_switch('auto-deletion',
|
||||
// _("Delete automatically empty folders"));
|
||||
let deleteAllBox = this.build_switch('total-deletion',
|
||||
_("Delete all related settings when an appfolder is deleted"));
|
||||
let menusBox = this.build_switch('extend-menus',
|
||||
_("Use the right-click menus in addition to the drag-and-drop"));
|
||||
|
||||
// this.add_row(autoDeleteBox, generalSection);
|
||||
this.add_row(deleteAllBox, generalSection);
|
||||
this.add_row(menusBox, generalSection);
|
||||
|
||||
//-------------------------
|
||||
|
||||
let categoriesBox = this.build_switch('categories', _("Use categories"));
|
||||
|
||||
let categoriesLinkButton = new Gtk.LinkButton({
|
||||
label: _("More informations about \"additional categories\""),
|
||||
uri: "https://standards.freedesktop.org/menu-spec/latest/apas02.html"
|
||||
});
|
||||
|
||||
this.add_row(categoriesBox, categoriesSection);
|
||||
this.add_row(categoriesLinkButton, categoriesSection);
|
||||
|
||||
//-------------------------
|
||||
|
||||
let aboutBox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, spacing: 10 });
|
||||
let about_label = new Gtk.Label({
|
||||
label: '(v' + Me.metadata.version.toString() + ')',
|
||||
halign: Gtk.Align.START
|
||||
});
|
||||
let url_button = new Gtk.LinkButton({
|
||||
label: _("Report bugs or ideas"),
|
||||
uri: Me.metadata.url.toString()
|
||||
});
|
||||
aboutBox.pack_start(url_button, false, false, 0);
|
||||
aboutBox.pack_end(about_label, false, false, 0);
|
||||
|
||||
this.pack_end(aboutBox, false, false, 0);
|
||||
|
||||
//-------------------------
|
||||
|
||||
let desacLabel = new Gtk.Label({
|
||||
label: _("This extension can be deactivated once your applications are organized as wished."),
|
||||
wrap: true,
|
||||
halign: Gtk.Align.CENTER
|
||||
});
|
||||
this.pack_end(desacLabel, false, false, 0);
|
||||
},
|
||||
|
||||
add_section: function (titre) {
|
||||
let section = new Gtk.Box({
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
margin: 6,
|
||||
spacing: 6,
|
||||
});
|
||||
|
||||
let frame = new Gtk.Frame({
|
||||
label: titre,
|
||||
label_xalign: 0.1,
|
||||
});
|
||||
frame.add(section);
|
||||
this.add(frame);
|
||||
return section;
|
||||
},
|
||||
|
||||
add_row: function (filledbox, section) {
|
||||
section.add(filledbox);
|
||||
},
|
||||
|
||||
build_switch: function (key, label) {
|
||||
let rowLabel = new Gtk.Label({
|
||||
label: label,
|
||||
halign: Gtk.Align.START,
|
||||
wrap: true,
|
||||
visible: true,
|
||||
});
|
||||
|
||||
let rowSwitch = new Gtk.Switch({ valign: Gtk.Align.CENTER });
|
||||
rowSwitch.set_state(this._settings.get_boolean(key));
|
||||
rowSwitch.connect('notify::active', (widget) => {
|
||||
this._settings.set_boolean(key, widget.active);
|
||||
});
|
||||
|
||||
let rowBox = new Gtk.Box({
|
||||
orientation: Gtk.Orientation.HORIZONTAL,
|
||||
spacing: 15,
|
||||
margin: 6,
|
||||
visible: true,
|
||||
});
|
||||
rowBox.pack_start(rowLabel, false, false, 0);
|
||||
rowBox.pack_end(rowSwitch, false, false, 0);
|
||||
|
||||
return rowBox;
|
||||
},
|
||||
});
|
||||
|
||||
//-----------------------------------------------
|
||||
|
||||
function init() {
|
||||
Convenience.initTranslations();
|
||||
}
|
||||
|
||||
//I guess this is like the "enable" in extension.js : something called each
|
||||
//time he user try to access the settings' window
|
||||
function buildPrefsWidget () {
|
||||
let widget = new appfoldersManagerSettingsWidget();
|
||||
widget.show_all();
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
Binary file not shown.
@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<schemalist gettext-domain="appfolders-manager">
|
||||
<schema id="org.gnome.shell.extensions.appfolders-manager" path="/org/gnome/shell/extensions/appfolders-manager/">
|
||||
<key type="b" name="total-deletion">
|
||||
<default>true</default>
|
||||
<summary>Complete deletion of an appfolder</summary>
|
||||
<description>if a deleted appfolder should be 100% deleted (false = restauration is possible)</description>
|
||||
</key>
|
||||
<key type="b" name="categories">
|
||||
<default>true</default>
|
||||
<summary>Use categories</summary>
|
||||
<description>If the interface for managing categories should be shown.</description>
|
||||
</key>
|
||||
<key type="b" name="debug">
|
||||
<default>false</default>
|
||||
<summary>Debug key</summary>
|
||||
<description>this is not supposed to be activated by the user</description>
|
||||
</key>
|
||||
<key type="b" name="extend-menus">
|
||||
<default>true</default>
|
||||
<summary>Show items in right-click menus</summary>
|
||||
<description>The legacy interface, with submenus in the right-click menu on application icons, can be shown in addition to the default drag-and-drop behavior.</description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
@ -1,56 +0,0 @@
|
||||
.dropAreaLabel {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.framedArea {
|
||||
background-color: rgba(255,255,255,0.0);
|
||||
border-color: rgba(255,255,255,1.0);
|
||||
border-width: 1px;
|
||||
color: rgba(255, 255, 255, 1.0);
|
||||
}
|
||||
|
||||
.shadowedAreaTop {
|
||||
background-gradient-start: rgba(0, 0, 0, 0.7);
|
||||
background-gradient-end: rgba(0, 0, 0, 0.1);
|
||||
background-gradient-direction: vertical;
|
||||
color: rgba(255, 255, 255, 1.0);
|
||||
}
|
||||
|
||||
.shadowedAreaBottom {
|
||||
background-gradient-start: rgba(0, 0, 0, 0.1);
|
||||
background-gradient-end: rgba(0, 0, 0, 0.7);
|
||||
background-gradient-direction: vertical;
|
||||
color: rgba(255, 255, 255, 1.0);
|
||||
}
|
||||
|
||||
.folderArea {
|
||||
background-gradient-start: rgba(0, 0, 0, 0.4);
|
||||
background-gradient-end: rgba(0, 0, 0, 0.0);
|
||||
background-gradient-direction: vertical;
|
||||
border-color: rgba(255,255,255,1.0);
|
||||
border-radius: 4px;
|
||||
border-width: 2px;
|
||||
color: rgba(255, 255, 255, 1.0);
|
||||
}
|
||||
|
||||
.insensitiveArea {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
color: rgba(100, 100, 100, 0.6);
|
||||
}
|
||||
|
||||
.appCategoryBox {
|
||||
background-color: rgba(100, 100, 100, 0.3);
|
||||
border-radius: 3px;
|
||||
margin: 3px;
|
||||
padding: 2px;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
.appCategoryDeleteBtn {
|
||||
background-color: rgba(100, 100, 100, 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,25 +0,0 @@
|
||||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2014, 2018, Andreas Fuchs
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -1,37 +0,0 @@
|
||||
Gnome-Shell Extension Audio-Output-Switcher
|
||||
===========================================
|
||||
|
||||
This extension adds a little entry to the status-menu that shows the currently
|
||||
selected pulse-audio-output device. Clicking on that will open a submenu with
|
||||
all available output devices and let's you choose which one to use.
|
||||
|
||||
Since version 5, it also provides a shortcut to quickly switch output sources:
|
||||
`<Super>`+`q`
|
||||
|
||||
Most importantly this extension is as simple as possible. Therefore it does not
|
||||
include an input switcher or similar.
|
||||
See Audio-Input-Switcher (https://github.com/anduchs/audio-input-switcher)
|
||||
extension for microphone selection.
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
Either via
|
||||
|
||||
https://extensions.gnome.org
|
||||
|
||||
or via
|
||||
|
||||
git clone https://github.com/adaxi/audio-output-switcher.git ~/.local/share/gnome-shell/extensions/audio-output-switcher@anduchs
|
||||
|
||||
Then resart the gnome-shell via `Alt+F2` then `r` and enable the extension using gnome-tweak-tool
|
||||
|
||||
To update later, just issue
|
||||
|
||||
(cd ~/.local/share/gnome-shell/extensions/audio-output-switcher@anduchs && git pull)
|
||||
|
||||
Credits
|
||||
-------
|
||||
|
||||
Originally authored by @anduchs.
|
||||
|
@ -1,128 +0,0 @@
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Main = imports.ui.main;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Utils = Me.imports.utils;
|
||||
|
||||
const AudioOutputSubMenu = class AudioOutputSubMenu extends PopupMenu.PopupSubMenuMenuItem {
|
||||
constructor() {
|
||||
super("Audio Output: Connecting...", true);
|
||||
this._control = Main.panel.statusArea.aggregateMenu._volume._control;
|
||||
|
||||
this._controlSignal = this._control.connect('default-sink-changed', () => {
|
||||
this._updateDefaultSink();
|
||||
});
|
||||
this._updateDefaultSink();
|
||||
this.menu.connect('open-state-changed', (menu, isOpen) => {
|
||||
if (isOpen)
|
||||
this._updateSinkList();
|
||||
});
|
||||
//Unless there is at least one item here, no 'open' will be emitted...
|
||||
let item = new PopupMenu.PopupMenuItem('Connecting...');
|
||||
this.menu.addMenuItem(item);
|
||||
}
|
||||
|
||||
_updateDefaultSink() {
|
||||
let defsink = this._control.get_default_sink();
|
||||
//Unfortunately, Gvc neglects some pulse-devices, such as all "Monitor of ..."
|
||||
if (!defsink)
|
||||
this.label.set_text("Other");
|
||||
else
|
||||
this.label.set_text(defsink.get_description());
|
||||
}
|
||||
|
||||
_updateSinkList() {
|
||||
this.menu.removeAll();
|
||||
|
||||
let defsink = this._control.get_default_sink();
|
||||
let sinklist = this._control.get_sinks();
|
||||
let control = this._control;
|
||||
|
||||
for (let i=0; i<sinklist.length; i++) {
|
||||
let sink = sinklist[i];
|
||||
if (sink === defsink)
|
||||
continue;
|
||||
let item = new PopupMenu.PopupMenuItem(sink.get_description());
|
||||
item.connect('activate', () => {
|
||||
control.set_default_sink(sink);
|
||||
});
|
||||
this.menu.addMenuItem(item);
|
||||
}
|
||||
if (sinklist.length == 0 ||
|
||||
(sinklist.length == 1 && sinklist[0] === defsink)) {
|
||||
item = new PopupMenu.PopupMenuItem("No more Devices");
|
||||
this.menu.addMenuItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._control.disconnect(this._controlSignal);
|
||||
this.parent();
|
||||
}
|
||||
}
|
||||
|
||||
let sinkIndex = 0;
|
||||
let settings = null;
|
||||
let audioOutputSubMenu = null;
|
||||
|
||||
function init () {
|
||||
sinkIndex = 0;
|
||||
settings = Utils.getSettings();
|
||||
}
|
||||
|
||||
function enable () {
|
||||
if (audioOutputSubMenu) {
|
||||
return;
|
||||
}
|
||||
audioOutputSubMenu = new AudioOutputSubMenu();
|
||||
|
||||
//Try to add the output-switcher right below the output slider...
|
||||
let volMen = Main.panel.statusArea.aggregateMenu._volume._volumeMenu;
|
||||
let items = volMen._getMenuItems();
|
||||
let i = 0;
|
||||
while (i < items.length)
|
||||
if (items[i] === volMen._output.item)
|
||||
break;
|
||||
else
|
||||
i++;
|
||||
volMen.addMenuItem(audioOutputSubMenu, i+1);
|
||||
|
||||
//Add keyboard shortcut for fast switching
|
||||
|
||||
let keyBindingMode = null;
|
||||
if (Shell.ActionMode) {
|
||||
//KeyBindingMode was renamed to ActionMode in Gnome 3.15.3
|
||||
keyBindingMode = Shell.ActionMode.ALL;
|
||||
} else {
|
||||
keyBindingMode = Shell.KeyBindingMode.ALL;
|
||||
}
|
||||
|
||||
Main.wm.addKeybinding("switch-next-audio-output",
|
||||
settings,
|
||||
Meta.KeyBindingFlags.NONE,
|
||||
keyBindingMode,
|
||||
function(display, screen, window, binding) {
|
||||
|
||||
let control = Main.panel.statusArea.aggregateMenu._volume._control;
|
||||
let sinklist = control.get_sinks();
|
||||
|
||||
if (sinklist.length === 0) {
|
||||
return;
|
||||
}
|
||||
sinkIndex++
|
||||
if (sinkIndex >= sinklist.length) {
|
||||
sinkIndex = 0
|
||||
}
|
||||
let sink = sinklist[sinkIndex];
|
||||
control.set_default_sink(sink);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function disable () {
|
||||
audioOutputSubMenu.destroy();
|
||||
audioOutputSubMenu = null;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"_generated": "Generated by SweetTooth, do not edit",
|
||||
"description": "Adds a switch for choosing audio output to the system menu.",
|
||||
"name": "Audio Output Switcher",
|
||||
"settings-schema": "org.gnome.shell.extensions.audio-output-switcher",
|
||||
"shell-version": [
|
||||
"3.32"
|
||||
],
|
||||
"url": "http://github.com/adaxi/audio-output-switcher",
|
||||
"uuid": "audio-output-switcher@anduchs",
|
||||
"version": 9
|
||||
}
|
Binary file not shown.
@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<schemalist>
|
||||
<schema id="org.gnome.shell.extensions.audio-output-switcher" path="/org/gnome/shell/extensions/audio-output-switcher/">
|
||||
<key type="as" name="switch-next-audio-output">
|
||||
<default><![CDATA[['<Super>q']]]></default>
|
||||
<summary>Switch to the next audio output device</summary>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
@ -1,92 +0,0 @@
|
||||
/* -*- mode: js; js-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
/*
|
||||
Copyright (c) 2011-2012, Giovanni Campagna <scampa.giovanni@gmail.com>
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the GNOME nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
const Gettext = imports.gettext;
|
||||
const Gio = imports.gi.Gio;
|
||||
|
||||
const Config = imports.misc.config;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
|
||||
/**
|
||||
* initTranslations:
|
||||
* @domain: (optional): the gettext domain to use
|
||||
*
|
||||
* Initialize Gettext to load translations from extensionsdir/locale.
|
||||
* If @domain is not provided, it will be taken from metadata['gettext-domain']
|
||||
*/
|
||||
function initTranslations(domain) {
|
||||
let extension = ExtensionUtils.getCurrentExtension();
|
||||
|
||||
domain = domain || extension.metadata['gettext-domain'];
|
||||
|
||||
// check if this extension was built with "make zip-file", and thus
|
||||
// has the locale files in a subfolder
|
||||
// otherwise assume that extension has been installed in the
|
||||
// same prefix as gnome-shell
|
||||
let localeDir = extension.dir.get_child('locale');
|
||||
if (localeDir.query_exists(null))
|
||||
Gettext.bindtextdomain(domain, localeDir.get_path());
|
||||
else
|
||||
Gettext.bindtextdomain(domain, Config.LOCALEDIR);
|
||||
}
|
||||
|
||||
/**
|
||||
* getSettings:
|
||||
* @schema: (optional): the GSettings schema id
|
||||
*
|
||||
* Builds and return a GSettings schema for @schema, using schema files
|
||||
* in extensionsdir/schemas. If @schema is not provided, it is taken from
|
||||
* metadata['settings-schema'].
|
||||
*/
|
||||
function getSettings(schema) {
|
||||
let extension = ExtensionUtils.getCurrentExtension();
|
||||
|
||||
schema = schema || extension.metadata['settings-schema'];
|
||||
|
||||
const GioSSS = Gio.SettingsSchemaSource;
|
||||
|
||||
// check if this extension was built with "make zip-file", and thus
|
||||
// has the schema files in a subfolder
|
||||
// otherwise assume that extension has been installed in the
|
||||
// same prefix as gnome-shell (and therefore schemas are available
|
||||
// in the standard folders)
|
||||
let schemaDir = extension.dir.get_child('schemas');
|
||||
let schemaSource;
|
||||
if (schemaDir.query_exists(null))
|
||||
schemaSource = GioSSS.new_from_directory(schemaDir.get_path(),
|
||||
GioSSS.get_default(),
|
||||
false);
|
||||
else
|
||||
schemaSource = GioSSS.get_default();
|
||||
|
||||
let schemaObj = schemaSource.lookup(schema, true);
|
||||
if (!schemaObj)
|
||||
throw new Error('Schema ' + schema + ' could not be found for extension '
|
||||
+ extension.metadata.uuid + '. Please check your installation.');
|
||||
|
||||
return new Gio.Settings({ settings_schema: schemaObj });
|
||||
}
|
@ -1,164 +0,0 @@
|
||||
/* Desktop Icons GNOME Shell extension
|
||||
*
|
||||
* Copyright (C) 2019 Andrea Azzaronea <andrea.azzarone@canonical.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const { Clutter, GObject, GLib, Gio, St } = imports.gi;
|
||||
|
||||
const Signals = imports.signals;
|
||||
|
||||
const Dialog = imports.ui.dialog;
|
||||
const Gettext = imports.gettext.domain('desktop-icons');
|
||||
const ModalDialog = imports.ui.modalDialog;
|
||||
const ShellEntry = imports.ui.shellEntry;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const DesktopIconsUtil = Me.imports.desktopIconsUtil;
|
||||
const Extension = Me.imports.extension;
|
||||
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const DIALOG_GROW_TIME = 0.1;
|
||||
|
||||
var CreateFolderDialog = class extends ModalDialog.ModalDialog {
|
||||
|
||||
constructor() {
|
||||
super({ styleClass: 'create-folder-dialog' });
|
||||
|
||||
this._buildLayout();
|
||||
}
|
||||
|
||||
_buildLayout() {
|
||||
let label = new St.Label({ style_class: 'create-folder-dialog-label',
|
||||
text: _('New folder name') });
|
||||
this.contentLayout.add(label, { x_align: St.Align.START });
|
||||
|
||||
this._entry = new St.Entry({ style_class: 'create-folder-dialog-entry',
|
||||
can_focus: true });
|
||||
this._entry.clutter_text.connect('activate', this._onEntryActivate.bind(this));
|
||||
this._entry.clutter_text.connect('text-changed', this._onTextChanged.bind(this));
|
||||
ShellEntry.addContextMenu(this._entry);
|
||||
this.contentLayout.add(this._entry);
|
||||
this.setInitialKeyFocus(this._entry);
|
||||
|
||||
this._errorBox = new St.BoxLayout({ style_class: 'create-folder-dialog-error-box',
|
||||
visible: false });
|
||||
this.contentLayout.add(this._errorBox, { expand: true });
|
||||
|
||||
this._errorMessage = new St.Label({ style_class: 'create-folder-dialog-error-label' });
|
||||
this._errorMessage.clutter_text.line_wrap = true;
|
||||
this._errorBox.add(this._errorMessage, { expand: true,
|
||||
x_align: St.Align.START,
|
||||
x_fill: false,
|
||||
y_align: St.Align.MIDDLE,
|
||||
y_fill: false });
|
||||
|
||||
this._createButton = this.addButton({ action: this._onCreateButton.bind(this),
|
||||
label: _('Create') });
|
||||
this.addButton({ action: this.close.bind(this),
|
||||
label: _('Cancel'),
|
||||
key: Clutter.Escape });
|
||||
this._onTextChanged();
|
||||
}
|
||||
|
||||
_showError(message) {
|
||||
this._errorMessage.set_text(message);
|
||||
|
||||
if (!this._errorBox.visible) {
|
||||
let [errorBoxMinHeight, errorBoxNaturalHeight] = this._errorBox.get_preferred_height(-1);
|
||||
let parentActor = this._errorBox.get_parent();
|
||||
|
||||
Tweener.addTween(parentActor,
|
||||
{ height: parentActor.height + errorBoxNaturalHeight,
|
||||
time: DIALOG_GROW_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: () => {
|
||||
parentActor.set_height(-1);
|
||||
this._errorBox.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_hideError() {
|
||||
if (this._errorBox.visible) {
|
||||
let [errorBoxMinHeight, errorBoxNaturalHeight] = this._errorBox.get_preferred_height(-1);
|
||||
let parentActor = this._errorBox.get_parent();
|
||||
|
||||
Tweener.addTween(parentActor,
|
||||
{ height: parentActor.height - errorBoxNaturalHeight,
|
||||
time: DIALOG_GROW_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: () => {
|
||||
parentActor.set_height(-1);
|
||||
this._errorBox.hide();
|
||||
this._errorMessage.set_text('');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_onCreateButton() {
|
||||
this._onEntryActivate();
|
||||
}
|
||||
|
||||
_onEntryActivate() {
|
||||
if (!this._createButton.reactive)
|
||||
return;
|
||||
|
||||
this.emit('response', this._entry.get_text());
|
||||
this.close();
|
||||
}
|
||||
|
||||
_onTextChanged() {
|
||||
let text = this._entry.get_text();
|
||||
let is_valid = true;
|
||||
|
||||
let found_name = false;
|
||||
for(let name of Extension.desktopManager.getDesktopFileNames()) {
|
||||
if (name === text) {
|
||||
found_name = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (text.trim().length == 0) {
|
||||
is_valid = false;
|
||||
this._hideError();
|
||||
} else if (text.includes('/')) {
|
||||
is_valid = false;
|
||||
this._showError(_('Folder names cannot contain “/”.'));
|
||||
} else if (text === '.') {
|
||||
is_valid = false;
|
||||
this._showError(_('A folder cannot be called “.”.'));
|
||||
} else if (text === '..') {
|
||||
is_valid = false;
|
||||
this._showError(_('A folder cannot be called “..”.'));
|
||||
} else if (text.startsWith('.')) {
|
||||
this._showError(_('Folders with “.” at the beginning of their name are hidden.'));
|
||||
} else if (found_name) {
|
||||
this._showError(_('There is already a file or folder with that name.'));
|
||||
is_valid = false;
|
||||
} else {
|
||||
this._hideError();
|
||||
}
|
||||
|
||||
this._createButton.reactive = is_valid;
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(CreateFolderDialog.prototype);
|
@ -1,35 +0,0 @@
|
||||
#!/usr/bin/gjs
|
||||
|
||||
/* Desktop Icons GNOME Shell extension
|
||||
*
|
||||
* Copyright (C) 2018 Sergio Costas <rastersoft@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const GnomeDesktop = imports.gi.GnomeDesktop;
|
||||
const Gio = imports.gi.Gio;
|
||||
|
||||
let thumbnailFactory = GnomeDesktop.DesktopThumbnailFactory.new(GnomeDesktop.DesktopThumbnailSize.LARGE);
|
||||
|
||||
let file = Gio.File.new_for_path(ARGV[0]);
|
||||
let fileUri = file.get_uri();
|
||||
|
||||
let fileInfo = file.query_info('standard::content-type,time::modified', Gio.FileQueryInfoFlags.NONE, null);
|
||||
let modifiedTime = fileInfo.get_attribute_uint64('time::modified');
|
||||
let thumbnailPixbuf = thumbnailFactory.generate_thumbnail(fileUri, fileInfo.get_content_type());
|
||||
if (thumbnailPixbuf == null)
|
||||
thumbnailFactory.create_failed_thumbnail(fileUri, modifiedTime);
|
||||
else
|
||||
thumbnailFactory.save_thumbnail(thumbnailPixbuf, fileUri, modifiedTime);
|
@ -1,103 +0,0 @@
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
var NautilusFileOperationsProxy;
|
||||
var FreeDesktopFileManagerProxy;
|
||||
|
||||
const NautilusFileOperationsInterface = `<node>
|
||||
<interface name='org.gnome.Nautilus.FileOperations'>
|
||||
<method name='CopyURIs'>
|
||||
<arg name='URIs' type='as' direction='in'/>
|
||||
<arg name='Destination' type='s' direction='in'/>
|
||||
</method>
|
||||
<method name='MoveURIs'>
|
||||
<arg name='URIs' type='as' direction='in'/>
|
||||
<arg name='Destination' type='s' direction='in'/>
|
||||
</method>
|
||||
<method name='EmptyTrash'>
|
||||
</method>
|
||||
<method name='TrashFiles'>
|
||||
<arg name='URIs' type='as' direction='in'/>
|
||||
</method>
|
||||
<method name='CreateFolder'>
|
||||
<arg name='URI' type='s' direction='in'/>
|
||||
</method>
|
||||
<method name='RenameFile'>
|
||||
<arg name='URI' type='s' direction='in'/>
|
||||
<arg name='NewName' type='s' direction='in'/>
|
||||
</method>
|
||||
<method name='Undo'>
|
||||
</method>
|
||||
<method name='Redo'>
|
||||
</method>
|
||||
<property name='UndoStatus' type='i' access='read'/>
|
||||
</interface>
|
||||
</node>`;
|
||||
|
||||
const NautilusFileOperationsProxyInterface = Gio.DBusProxy.makeProxyWrapper(NautilusFileOperationsInterface);
|
||||
|
||||
const FreeDesktopFileManagerInterface = `<node>
|
||||
<interface name='org.freedesktop.FileManager1'>
|
||||
<method name='ShowItems'>
|
||||
<arg name='URIs' type='as' direction='in'/>
|
||||
<arg name='StartupId' type='s' direction='in'/>
|
||||
</method>
|
||||
<method name='ShowItemProperties'>
|
||||
<arg name='URIs' type='as' direction='in'/>
|
||||
<arg name='StartupId' type='s' direction='in'/>
|
||||
</method>
|
||||
</interface>
|
||||
</node>`;
|
||||
|
||||
const FreeDesktopFileManagerProxyInterface = Gio.DBusProxy.makeProxyWrapper(FreeDesktopFileManagerInterface);
|
||||
|
||||
function init() {
|
||||
NautilusFileOperationsProxy = new NautilusFileOperationsProxyInterface(
|
||||
Gio.DBus.session,
|
||||
'org.gnome.Nautilus',
|
||||
'/org/gnome/Nautilus',
|
||||
(proxy, error) => {
|
||||
if (error) {
|
||||
log('Error connecting to Nautilus');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
FreeDesktopFileManagerProxy = new FreeDesktopFileManagerProxyInterface(
|
||||
Gio.DBus.session,
|
||||
'org.freedesktop.FileManager1',
|
||||
'/org/freedesktop/FileManager1',
|
||||
(proxy, error) => {
|
||||
if (error) {
|
||||
log('Error connecting to Nautilus');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function openFileWithOtherApplication(filePath) {
|
||||
let fdList = new Gio.UnixFDList();
|
||||
let channel = GLib.IOChannel.new_file(filePath, "r");
|
||||
fdList.append(channel.unix_get_fd());
|
||||
channel.set_close_on_unref(true);
|
||||
let builder = GLib.VariantBuilder.new(GLib.VariantType.new("a{sv}"));
|
||||
let options = builder.end();
|
||||
let parameters = GLib.Variant.new_tuple([GLib.Variant.new_string("0"),
|
||||
GLib.Variant.new_handle(0),
|
||||
options]);
|
||||
Gio.bus_get(Gio.BusType.SESSION, null,
|
||||
(source, result) => {
|
||||
let dbus_connection = Gio.bus_get_finish(result);
|
||||
dbus_connection.call_with_unix_fd_list("org.freedesktop.portal.Desktop",
|
||||
"/org/freedesktop/portal/desktop",
|
||||
"org.freedesktop.portal.OpenURI",
|
||||
"OpenFile",
|
||||
parameters,
|
||||
GLib.VariantType.new("o"),
|
||||
Gio.DBusCallFlags.NONE,
|
||||
-1,
|
||||
fdList,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
);
|
||||
}
|
@ -1,692 +0,0 @@
|
||||
/* Desktop Icons GNOME Shell extension
|
||||
*
|
||||
* Copyright (C) 2017 Carlos Soriano <csoriano@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const St = imports.gi.St;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const Signals = imports.signals;
|
||||
|
||||
const Layout = imports.ui.layout;
|
||||
const Main = imports.ui.main;
|
||||
const BoxPointer = imports.ui.boxpointer;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const GrabHelper = imports.ui.grabHelper;
|
||||
const Config = imports.misc.config;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const CreateFolderDialog = Me.imports.createFolderDialog;
|
||||
const Extension = Me.imports.extension;
|
||||
const FileItem = Me.imports.fileItem;
|
||||
const Prefs = Me.imports.prefs;
|
||||
const DBusUtils = Me.imports.dbusUtils;
|
||||
const DesktopIconsUtil = Me.imports.desktopIconsUtil;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
const Clipboard = St.Clipboard.get_default();
|
||||
const CLIPBOARD_TYPE = St.ClipboardType.CLIPBOARD;
|
||||
const Gettext = imports.gettext.domain('desktop-icons');
|
||||
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
|
||||
/* From NautilusFileUndoManagerState */
|
||||
var UndoStatus = {
|
||||
NONE: 0,
|
||||
UNDO: 1,
|
||||
REDO: 2,
|
||||
};
|
||||
|
||||
var StoredCoordinates = {
|
||||
PRESERVE: 0,
|
||||
OVERWRITE:1,
|
||||
ASSIGN:2,
|
||||
};
|
||||
|
||||
class Placeholder extends St.Bin {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
var DesktopGrid = class {
|
||||
|
||||
constructor(bgManager) {
|
||||
this._bgManager = bgManager;
|
||||
|
||||
this._fileItemHandlers = new Map();
|
||||
this._fileItems = [];
|
||||
|
||||
this.layout = new Clutter.GridLayout({
|
||||
orientation: Clutter.Orientation.VERTICAL,
|
||||
column_homogeneous: true,
|
||||
row_homogeneous: true
|
||||
});
|
||||
|
||||
this._actorLayout = new Clutter.BinLayout({
|
||||
x_align: Clutter.BinAlignment.FIXED,
|
||||
y_align: Clutter.BinAlignment.FIXED
|
||||
});
|
||||
|
||||
this.actor = new St.Widget({
|
||||
layout_manager: this._actorLayout
|
||||
});
|
||||
this.actor._delegate = this;
|
||||
|
||||
this._grid = new St.Widget({
|
||||
name: 'DesktopGrid',
|
||||
layout_manager: this.layout,
|
||||
reactive: true,
|
||||
x_expand: true,
|
||||
y_expand: true,
|
||||
can_focus: true,
|
||||
opacity: 255
|
||||
});
|
||||
this.actor.add_child(this._grid);
|
||||
|
||||
this._renamePopup = new RenamePopup(this);
|
||||
this.actor.add_child(this._renamePopup.actor);
|
||||
|
||||
this._bgManager._container.add_child(this.actor);
|
||||
|
||||
this.actor.connect('destroy', () => this._onDestroy());
|
||||
|
||||
let monitorIndex = bgManager._monitorIndex;
|
||||
this._monitorConstraint = new Layout.MonitorConstraint({
|
||||
index: monitorIndex,
|
||||
work_area: true
|
||||
});
|
||||
this.actor.add_constraint(this._monitorConstraint);
|
||||
|
||||
this._addDesktopBackgroundMenu();
|
||||
|
||||
this._bgDestroyedId = bgManager.backgroundActor.connect('destroy',
|
||||
() => this._backgroundDestroyed());
|
||||
|
||||
this._grid.connect('button-press-event', (actor, event) => this._onPressButton(actor, event));
|
||||
|
||||
this._grid.connect('key-press-event', this._onKeyPress.bind(this));
|
||||
|
||||
this._grid.connect('allocation-changed', () => Extension.desktopManager.scheduleReLayoutChildren());
|
||||
}
|
||||
|
||||
_onKeyPress(actor, event) {
|
||||
if (global.stage.get_key_focus() != actor)
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
|
||||
let symbol = event.get_key_symbol();
|
||||
let isCtrl = (event.get_state() & Clutter.ModifierType.CONTROL_MASK) != 0;
|
||||
let isShift = (event.get_state() & Clutter.ModifierType.SHIFT_MASK) != 0;
|
||||
if (isCtrl && isShift && [Clutter.Z, Clutter.z].indexOf(symbol) > -1) {
|
||||
this._doRedo();
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
else if (isCtrl && [Clutter.Z, Clutter.z].indexOf(symbol) > -1) {
|
||||
this._doUndo();
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
else if (isCtrl && [Clutter.C, Clutter.c].indexOf(symbol) > -1) {
|
||||
Extension.desktopManager.doCopy();
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
else if (isCtrl && [Clutter.X, Clutter.x].indexOf(symbol) > -1) {
|
||||
Extension.desktopManager.doCut();
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
else if (isCtrl && [Clutter.V, Clutter.v].indexOf(symbol) > -1) {
|
||||
this._doPaste();
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
else if (symbol == Clutter.Return) {
|
||||
Extension.desktopManager.doOpen();
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
else if (symbol == Clutter.Delete) {
|
||||
Extension.desktopManager.doTrash();
|
||||
return Clutter.EVENT_STOP;
|
||||
} else if (symbol == Clutter.F2) {
|
||||
// Support renaming other grids file items.
|
||||
Extension.desktopManager.doRename();
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
_backgroundDestroyed() {
|
||||
this._bgDestroyedId = 0;
|
||||
if (this._bgManager == null)
|
||||
return;
|
||||
|
||||
if (this._bgManager._backgroundSource) {
|
||||
this._bgDestroyedId = this._bgManager.backgroundActor.connect('destroy',
|
||||
() => this._backgroundDestroyed());
|
||||
} else {
|
||||
this.actor.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
_onDestroy() {
|
||||
if (this._bgDestroyedId && this._bgManager.backgroundActor != null)
|
||||
this._bgManager.backgroundActor.disconnect(this._bgDestroyedId);
|
||||
this._bgDestroyedId = 0;
|
||||
this._bgManager = null;
|
||||
}
|
||||
|
||||
_onNewFolderClicked() {
|
||||
|
||||
let dialog = new CreateFolderDialog.CreateFolderDialog();
|
||||
|
||||
dialog.connect('response', (dialog, name) => {
|
||||
let dir = DesktopIconsUtil.getDesktopDir().get_child(name);
|
||||
DBusUtils.NautilusFileOperationsProxy.CreateFolderRemote(dir.get_uri(),
|
||||
(result, error) => {
|
||||
if (error)
|
||||
throw new Error('Error creating new folder: ' + error.message);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
dialog.open();
|
||||
}
|
||||
|
||||
_parseClipboardText(text) {
|
||||
if (text === null)
|
||||
return [false, false, null];
|
||||
|
||||
let lines = text.split('\n');
|
||||
let [mime, action, ...files] = lines;
|
||||
|
||||
if (mime != 'x-special/nautilus-clipboard')
|
||||
return [false, false, null];
|
||||
|
||||
if (!(['copy', 'cut'].includes(action)))
|
||||
return [false, false, null];
|
||||
let isCut = action == 'cut';
|
||||
|
||||
/* Last line is empty due to the split */
|
||||
if (files.length <= 1)
|
||||
return [false, false, null];
|
||||
/* Remove last line */
|
||||
files.pop();
|
||||
|
||||
return [true, isCut, files];
|
||||
}
|
||||
|
||||
_doPaste() {
|
||||
Clipboard.get_text(CLIPBOARD_TYPE,
|
||||
(clipboard, text) => {
|
||||
let [valid, is_cut, files] = this._parseClipboardText(text);
|
||||
if (!valid)
|
||||
return;
|
||||
|
||||
let desktopDir = `${DesktopIconsUtil.getDesktopDir().get_uri()}`;
|
||||
if (is_cut) {
|
||||
DBusUtils.NautilusFileOperationsProxy.MoveURIsRemote(files, desktopDir,
|
||||
(result, error) => {
|
||||
if (error)
|
||||
throw new Error('Error moving files: ' + error.message);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
DBusUtils.NautilusFileOperationsProxy.CopyURIsRemote(files, desktopDir,
|
||||
(result, error) => {
|
||||
if (error)
|
||||
throw new Error('Error copying files: ' + error.message);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_onPasteClicked() {
|
||||
this._doPaste();
|
||||
}
|
||||
|
||||
_doUndo() {
|
||||
DBusUtils.NautilusFileOperationsProxy.UndoRemote(
|
||||
(result, error) => {
|
||||
if (error)
|
||||
throw new Error('Error performing undo: ' + error.message);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_onUndoClicked() {
|
||||
this._doUndo();
|
||||
}
|
||||
|
||||
_doRedo() {
|
||||
DBusUtils.NautilusFileOperationsProxy.RedoRemote(
|
||||
(result, error) => {
|
||||
if (error)
|
||||
throw new Error('Error performing redo: ' + error.message);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_onRedoClicked() {
|
||||
this._doRedo();
|
||||
}
|
||||
|
||||
_onOpenDesktopInFilesClicked() {
|
||||
Gio.AppInfo.launch_default_for_uri_async(DesktopIconsUtil.getDesktopDir().get_uri(),
|
||||
null, null,
|
||||
(source, result) => {
|
||||
try {
|
||||
Gio.AppInfo.launch_default_for_uri_finish(result);
|
||||
} catch (e) {
|
||||
log('Error opening Desktop in Files: ' + e.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_onOpenTerminalClicked() {
|
||||
let desktopPath = DesktopIconsUtil.getDesktopDir().get_path();
|
||||
DesktopIconsUtil.launchTerminal(desktopPath);
|
||||
}
|
||||
|
||||
_syncUndoRedo() {
|
||||
this._undoMenuItem.actor.visible = DBusUtils.NautilusFileOperationsProxy.UndoStatus == UndoStatus.UNDO;
|
||||
this._redoMenuItem.actor.visible = DBusUtils.NautilusFileOperationsProxy.UndoStatus == UndoStatus.REDO;
|
||||
}
|
||||
|
||||
_undoStatusChanged(proxy, properties, test) {
|
||||
if ('UndoStatus' in properties.deep_unpack())
|
||||
this._syncUndoRedo();
|
||||
}
|
||||
|
||||
_createDesktopBackgroundMenu() {
|
||||
let menu = new PopupMenu.PopupMenu(Main.layoutManager.dummyCursor,
|
||||
0, St.Side.TOP);
|
||||
menu.addAction(_("New Folder"), () => this._onNewFolderClicked());
|
||||
menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this._pasteMenuItem = menu.addAction(_("Paste"), () => this._onPasteClicked());
|
||||
this._undoMenuItem = menu.addAction(_("Undo"), () => this._onUndoClicked());
|
||||
this._redoMenuItem = menu.addAction(_("Redo"), () => this._onRedoClicked());
|
||||
menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
menu.addAction(_("Show Desktop in Files"), () => this._onOpenDesktopInFilesClicked());
|
||||
menu.addAction(_("Open in Terminal"), () => this._onOpenTerminalClicked());
|
||||
menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
menu.addSettingsAction(_("Change Background…"), 'gnome-background-panel.desktop');
|
||||
menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
menu.addSettingsAction(_("Display Settings"), 'gnome-display-panel.desktop');
|
||||
menu.addSettingsAction(_("Settings"), 'gnome-control-center.desktop');
|
||||
|
||||
menu.actor.add_style_class_name('background-menu');
|
||||
|
||||
Main.layoutManager.uiGroup.add_child(menu.actor);
|
||||
menu.actor.hide();
|
||||
|
||||
menu._propertiesChangedId = DBusUtils.NautilusFileOperationsProxy.connect('g-properties-changed',
|
||||
this._undoStatusChanged.bind(this));
|
||||
this._syncUndoRedo();
|
||||
|
||||
menu.connect('destroy',
|
||||
() => DBusUtils.NautilusFileOperationsProxy.disconnect(menu._propertiesChangedId));
|
||||
menu.connect('open-state-changed',
|
||||
(popupm, isOpen) => {
|
||||
if (isOpen) {
|
||||
Clipboard.get_text(CLIPBOARD_TYPE,
|
||||
(clipBoard, text) => {
|
||||
let [valid, is_cut, files] = this._parseClipboardText(text);
|
||||
this._pasteMenuItem.setSensitive(valid);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
this._pasteMenuItem.setSensitive(false);
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
_openMenu(x, y) {
|
||||
Main.layoutManager.setDummyCursorGeometry(x, y, 0, 0);
|
||||
this.actor._desktopBackgroundMenu.open(BoxPointer.PopupAnimation.NONE);
|
||||
/* Since the handler is in the press event it needs to ignore the release event
|
||||
* to not immediately close the menu on release
|
||||
*/
|
||||
this.actor._desktopBackgroundManager.ignoreRelease();
|
||||
}
|
||||
|
||||
_addFileItemTo(fileItem, column, row, coordinatesAction) {
|
||||
let placeholder = this.layout.get_child_at(column, row);
|
||||
placeholder.child = fileItem.actor;
|
||||
this._fileItems.push(fileItem);
|
||||
let selectedId = fileItem.connect('selected', this._onFileItemSelected.bind(this));
|
||||
let renameId = fileItem.connect('rename-clicked', this.doRename.bind(this));
|
||||
this._fileItemHandlers.set(fileItem, [selectedId, renameId]);
|
||||
|
||||
/* If this file is new in the Desktop and hasn't yet
|
||||
* fixed coordinates, store the new possition to ensure
|
||||
* that the next time it will be shown in the same possition.
|
||||
* Also store the new possition if it has been moved by the user,
|
||||
* and not triggered by a screen change.
|
||||
*/
|
||||
if ((fileItem.savedCoordinates == null) || (coordinatesAction == StoredCoordinates.OVERWRITE)) {
|
||||
let [fileX, fileY] = placeholder.get_transformed_position();
|
||||
fileItem.savedCoordinates = [Math.round(fileX), Math.round(fileY)];
|
||||
}
|
||||
}
|
||||
|
||||
addFileItemCloseTo(fileItem, x, y, coordinatesAction) {
|
||||
let [column, row] = this._getEmptyPlaceClosestTo(x, y, coordinatesAction);
|
||||
this._addFileItemTo(fileItem, column, row, coordinatesAction);
|
||||
}
|
||||
|
||||
_getEmptyPlaceClosestTo(x, y, coordinatesAction) {
|
||||
let maxColumns = this._getMaxColumns();
|
||||
let maxRows = this._getMaxRows();
|
||||
|
||||
let [actorX, actorY] = this._grid.get_transformed_position();
|
||||
let actorWidth = this._grid.allocation.x2 - this._grid.allocation.x1;
|
||||
let actorHeight = this._grid.allocation.y2 - this._grid.allocation.y1;
|
||||
let placeX = Math.round((x - actorX) * maxColumns / actorWidth);
|
||||
let placeY = Math.round((y - actorY) * maxRows / actorHeight);
|
||||
|
||||
placeX = DesktopIconsUtil.clamp(placeX, 0, maxColumns - 1);
|
||||
placeY = DesktopIconsUtil.clamp(placeY, 0, maxRows - 1);
|
||||
if (this.layout.get_child_at(placeX, placeY).child == null)
|
||||
return [placeX, placeY];
|
||||
let found = false;
|
||||
let resColumn = null;
|
||||
let resRow = null;
|
||||
let minDistance = Infinity;
|
||||
for (let column = 0; column < maxColumns; column++) {
|
||||
for (let row = 0; row < maxRows; row++) {
|
||||
let placeholder = this.layout.get_child_at(column, row);
|
||||
if (placeholder.child != null)
|
||||
continue;
|
||||
|
||||
let [proposedX, proposedY] = placeholder.get_transformed_position();
|
||||
if (coordinatesAction == StoredCoordinates.ASSIGN)
|
||||
return [column, row];
|
||||
let distance = DesktopIconsUtil.distanceBetweenPoints(proposedX, proposedY, x, y);
|
||||
if (distance < minDistance) {
|
||||
found = true;
|
||||
minDistance = distance;
|
||||
resColumn = column;
|
||||
resRow = row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
throw new Error(`Not enough place at monitor ${this._bgManager._monitorIndex}`);
|
||||
|
||||
return [resColumn, resRow];
|
||||
}
|
||||
|
||||
removeFileItem(fileItem) {
|
||||
let index = this._fileItems.indexOf(fileItem);
|
||||
if (index > -1)
|
||||
this._fileItems.splice(index, 1);
|
||||
else
|
||||
throw new Error('Error removing children from container');
|
||||
|
||||
let [column, row] = this._getPosOfFileItem(fileItem);
|
||||
let placeholder = this.layout.get_child_at(column, row);
|
||||
placeholder.child = null;
|
||||
let [selectedId, renameId] = this._fileItemHandlers.get(fileItem);
|
||||
fileItem.disconnect(selectedId);
|
||||
fileItem.disconnect(renameId);
|
||||
this._fileItemHandlers.delete(fileItem);
|
||||
}
|
||||
|
||||
_fillPlaceholders() {
|
||||
for (let column = 0; column < this._getMaxColumns(); column++) {
|
||||
for (let row = 0; row < this._getMaxRows(); row++) {
|
||||
this.layout.attach(new Placeholder(), column, row, 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
let tmpFileItemsCopy = this._fileItems.slice();
|
||||
for (let fileItem of tmpFileItemsCopy)
|
||||
this.removeFileItem(fileItem);
|
||||
this._grid.remove_all_children();
|
||||
|
||||
this._fillPlaceholders();
|
||||
}
|
||||
|
||||
_onStageMotion(actor, event) {
|
||||
if (this._drawingRubberBand) {
|
||||
let [x, y] = event.get_coords();
|
||||
this._updateRubberBand(x, y);
|
||||
this._selectFromRubberband(x, y);
|
||||
}
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
_onPressButton(actor, event) {
|
||||
let button = event.get_button();
|
||||
let [x, y] = event.get_coords();
|
||||
|
||||
this._grid.grab_key_focus();
|
||||
|
||||
if (button == 1) {
|
||||
let shiftPressed = !!(event.get_state() & Clutter.ModifierType.SHIFT_MASK);
|
||||
let controlPressed = !!(event.get_state() & Clutter.ModifierType.CONTROL_MASK);
|
||||
if (!shiftPressed && !controlPressed)
|
||||
Extension.desktopManager.clearSelection();
|
||||
let [gridX, gridY] = this._grid.get_transformed_position();
|
||||
Extension.desktopManager.startRubberBand(x, y, gridX, gridY);
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
|
||||
if (button == 3) {
|
||||
this._openMenu(x, y);
|
||||
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
_addDesktopBackgroundMenu() {
|
||||
this.actor._desktopBackgroundMenu = this._createDesktopBackgroundMenu();
|
||||
this.actor._desktopBackgroundManager = new PopupMenu.PopupMenuManager({ actor: this.actor });
|
||||
this.actor._desktopBackgroundManager.addMenu(this.actor._desktopBackgroundMenu);
|
||||
|
||||
this.actor.connect('destroy', () => {
|
||||
this.actor._desktopBackgroundMenu.destroy();
|
||||
this.actor._desktopBackgroundMenu = null;
|
||||
this.actor._desktopBackgroundManager = null;
|
||||
});
|
||||
}
|
||||
|
||||
_getMaxColumns() {
|
||||
let gridWidth = this._grid.allocation.x2 - this._grid.allocation.x1;
|
||||
return Math.floor(gridWidth / Prefs.get_desired_width(St.ThemeContext.get_for_stage(global.stage).scale_factor));
|
||||
}
|
||||
|
||||
_getMaxRows() {
|
||||
let gridHeight = this._grid.allocation.y2 - this._grid.allocation.y1;
|
||||
return Math.floor(gridHeight / Prefs.get_desired_height(St.ThemeContext.get_for_stage(global.stage).scale_factor));
|
||||
}
|
||||
|
||||
acceptDrop(source, actor, x, y, time) {
|
||||
/* Coordinates are relative to the grid, we want to transform them to
|
||||
* absolute coordinates to work across monitors */
|
||||
let [gridX, gridY] = this.actor.get_transformed_position();
|
||||
let [absoluteX, absoluteY] = [x + gridX, y + gridY];
|
||||
return Extension.desktopManager.acceptDrop(absoluteX, absoluteY);
|
||||
}
|
||||
|
||||
_getPosOfFileItem(itemToFind) {
|
||||
if (itemToFind == null)
|
||||
throw new Error('Error at _getPosOfFileItem: child cannot be null');
|
||||
|
||||
let found = false;
|
||||
let maxColumns = this._getMaxColumns();
|
||||
let maxRows = this._getMaxRows();
|
||||
let column = 0;
|
||||
let row = 0;
|
||||
for (column = 0; column < maxColumns; column++) {
|
||||
for (row = 0; row < maxRows; row++) {
|
||||
let item = this.layout.get_child_at(column, row);
|
||||
if (item.child && item.child._delegate.file.equal(itemToFind.file)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
throw new Error('Position of file item was not found');
|
||||
|
||||
return [column, row];
|
||||
}
|
||||
|
||||
_onFileItemSelected(fileItem, keepCurrentSelection, addToSelection) {
|
||||
this._grid.grab_key_focus();
|
||||
}
|
||||
|
||||
doRename(fileItem) {
|
||||
this._renamePopup.onFileItemRenameClicked(fileItem);
|
||||
}
|
||||
};
|
||||
|
||||
var RenamePopup = class {
|
||||
|
||||
constructor(grid) {
|
||||
this._source = null;
|
||||
this._isOpen = false;
|
||||
|
||||
this._renameEntry = new St.Entry({ hint_text: _("Enter file name…"),
|
||||
can_focus: true,
|
||||
x_expand: true });
|
||||
this._renameEntry.clutter_text.connect('activate', this._onRenameAccepted.bind(this));
|
||||
this._renameOkButton= new St.Button({ label: _("OK"),
|
||||
style_class: 'app-view-control button',
|
||||
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
x_expand: true });
|
||||
this._renameCancelButton = new St.Button({ label: _("Cancel"),
|
||||
style_class: 'app-view-control button',
|
||||
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
x_expand: true });
|
||||
this._renameCancelButton.connect('clicked', () => { this._onRenameCanceled(); });
|
||||
this._renameOkButton.connect('clicked', () => { this._onRenameAccepted(); });
|
||||
let renameButtonsBoxLayout = new Clutter.BoxLayout({ homogeneous: true });
|
||||
let renameButtonsBox = new St.Widget({ layout_manager: renameButtonsBoxLayout,
|
||||
x_expand: true });
|
||||
renameButtonsBox.add_child(this._renameCancelButton);
|
||||
renameButtonsBox.add_child(this._renameOkButton);
|
||||
|
||||
let renameContentLayout = new Clutter.BoxLayout({ spacing: 6,
|
||||
orientation: Clutter.Orientation.VERTICAL });
|
||||
let renameContent = new St.Widget({ style_class: 'rename-popup',
|
||||
layout_manager: renameContentLayout,
|
||||
x_expand: true });
|
||||
renameContent.add_child(this._renameEntry);
|
||||
renameContent.add_child(renameButtonsBox);
|
||||
|
||||
this._boxPointer = new BoxPointer.BoxPointer(St.Side.TOP, { can_focus: false, x_expand: false });
|
||||
this.actor = this._boxPointer.actor;
|
||||
this.actor.style_class = 'popup-menu-boxpointer';
|
||||
this.actor.add_style_class_name('popup-menu');
|
||||
this.actor.visible = false;
|
||||
this._boxPointer.bin.set_child(renameContent);
|
||||
|
||||
this._grabHelper = new GrabHelper.GrabHelper(grid.actor, { actionMode: Shell.ActionMode.POPUP });
|
||||
this._grabHelper.addActor(this.actor);
|
||||
}
|
||||
|
||||
_popup() {
|
||||
if (this._isOpen)
|
||||
return;
|
||||
|
||||
this._isOpen = this._grabHelper.grab({ actor: this.actor,
|
||||
onUngrab: this._popdown.bind(this) });
|
||||
|
||||
if (!this._isOpen) {
|
||||
this._grabHelper.ungrab({ actor: this.actor });
|
||||
return;
|
||||
}
|
||||
|
||||
this._boxPointer.setPosition(this._source.actor, 0.5);
|
||||
if (ExtensionUtils.versionCheck(['3.28', '3.30'], Config.PACKAGE_VERSION))
|
||||
this._boxPointer.show(BoxPointer.PopupAnimation.FADE |
|
||||
BoxPointer.PopupAnimation.SLIDE);
|
||||
else
|
||||
this._boxPointer.open(BoxPointer.PopupAnimation.FADE |
|
||||
BoxPointer.PopupAnimation.SLIDE);
|
||||
|
||||
this.emit('open-state-changed', true);
|
||||
}
|
||||
|
||||
_popdown() {
|
||||
if (!this._isOpen)
|
||||
return;
|
||||
|
||||
this._grabHelper.ungrab({ actor: this.actor });
|
||||
|
||||
if (ExtensionUtils.versionCheck(['3.28', '3.30'], Config.PACKAGE_VERSION))
|
||||
this._boxPointer.hide(BoxPointer.PopupAnimation.FADE |
|
||||
BoxPointer.PopupAnimation.SLIDE);
|
||||
else
|
||||
this._boxPointer.close(BoxPointer.PopupAnimation.FADE |
|
||||
BoxPointer.PopupAnimation.SLIDE);
|
||||
|
||||
this._isOpen = false;
|
||||
this.emit('open-state-changed', false);
|
||||
}
|
||||
|
||||
onFileItemRenameClicked(fileItem) {
|
||||
this._source = fileItem;
|
||||
|
||||
this._renameEntry.text = fileItem.displayName;
|
||||
|
||||
this._popup();
|
||||
this._renameEntry.grab_key_focus();
|
||||
this._renameEntry.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
let extensionOffset = DesktopIconsUtil.getFileExtensionOffset(fileItem.displayName, fileItem.isDirectory);
|
||||
this._renameEntry.clutter_text.set_selection(0, extensionOffset);
|
||||
}
|
||||
|
||||
_onRenameAccepted() {
|
||||
this._popdown();
|
||||
DBusUtils.NautilusFileOperationsProxy.RenameFileRemote(this._source.file.get_uri(),
|
||||
this._renameEntry.get_text(),
|
||||
(result, error) => {
|
||||
if (error)
|
||||
throw new Error('Error renaming file: ' + error.message);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_onRenameCanceled() {
|
||||
this._popdown();
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(RenamePopup.prototype);
|
@ -1,123 +0,0 @@
|
||||
/* Desktop Icons GNOME Shell extension
|
||||
*
|
||||
* Copyright (C) 2017 Carlos Soriano <csoriano@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Prefs = Me.imports.prefs;
|
||||
|
||||
const TERMINAL_SCHEMA = 'org.gnome.desktop.default-applications.terminal';
|
||||
const EXEC_KEY = 'exec';
|
||||
|
||||
var DEFAULT_ATTRIBUTES = 'metadata::*,standard::*,access::*,time::modified,unix::mode';
|
||||
|
||||
function getDesktopDir() {
|
||||
let desktopPath = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP);
|
||||
return Gio.File.new_for_commandline_arg(desktopPath);
|
||||
}
|
||||
|
||||
function clamp(value, min, max) {
|
||||
return Math.max(Math.min(value, max), min);
|
||||
};
|
||||
|
||||
function launchTerminal(workdir) {
|
||||
let terminalSettings = new Gio.Settings({ schema_id: TERMINAL_SCHEMA });
|
||||
let exec = terminalSettings.get_string(EXEC_KEY);
|
||||
let argv = [exec, `--working-directory=${workdir}`];
|
||||
|
||||
/* The following code has been extracted from GNOME Shell's
|
||||
* source code in Misc.Util.trySpawn function and modified to
|
||||
* set the working directory.
|
||||
*
|
||||
* https://gitlab.gnome.org/GNOME/gnome-shell/blob/gnome-3-30/js/misc/util.js
|
||||
*/
|
||||
|
||||
var success, pid;
|
||||
try {
|
||||
[success, pid] = GLib.spawn_async(workdir, argv, null,
|
||||
GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
|
||||
null);
|
||||
} catch (err) {
|
||||
/* Rewrite the error in case of ENOENT */
|
||||
if (err.matches(GLib.SpawnError, GLib.SpawnError.NOENT)) {
|
||||
throw new GLib.SpawnError({ code: GLib.SpawnError.NOENT,
|
||||
message: _("Command not found") });
|
||||
} else if (err instanceof GLib.Error) {
|
||||
// The exception from gjs contains an error string like:
|
||||
// Error invoking GLib.spawn_command_line_async: Failed to
|
||||
// execute child process "foo" (No such file or directory)
|
||||
// We are only interested in the part in the parentheses. (And
|
||||
// we can't pattern match the text, since it gets localized.)
|
||||
let message = err.message.replace(/.*\((.+)\)/, '$1');
|
||||
throw new (err.constructor)({ code: err.code,
|
||||
message: message });
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
// Dummy child watch; we don't want to double-fork internally
|
||||
// because then we lose the parent-child relationship, which
|
||||
// can break polkit. See https://bugzilla.redhat.com//show_bug.cgi?id=819275
|
||||
GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, () => {});
|
||||
}
|
||||
|
||||
function distanceBetweenPoints(x, y, x2, y2) {
|
||||
return (Math.pow(x - x2, 2) + Math.pow(y - y2, 2));
|
||||
}
|
||||
|
||||
function getExtraFolders() {
|
||||
let extraFolders = new Array();
|
||||
if (Prefs.settings.get_boolean('show-home')) {
|
||||
extraFolders.push([Gio.File.new_for_commandline_arg(GLib.get_home_dir()), Prefs.FileType.USER_DIRECTORY_HOME]);
|
||||
}
|
||||
if (Prefs.settings.get_boolean('show-trash')) {
|
||||
extraFolders.push([Gio.File.new_for_uri('trash:///'), Prefs.FileType.USER_DIRECTORY_TRASH]);
|
||||
}
|
||||
return extraFolders;
|
||||
}
|
||||
|
||||
function getFileExtensionOffset(filename, isDirectory) {
|
||||
let offset = filename.length;
|
||||
|
||||
if (!isDirectory) {
|
||||
let doubleExtensions = ['.gz', '.bz2', '.sit', '.Z', '.bz', '.xz'];
|
||||
for (let extension of doubleExtensions) {
|
||||
if (filename.endsWith(extension)) {
|
||||
offset -= extension.length;
|
||||
filename = filename.substring(0, offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let lastDot = filename.lastIndexOf('.');
|
||||
if (lastDot > 0)
|
||||
offset = lastDot;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
function getGtkClassBackgroundColor(classname, state) {
|
||||
let widget = new Gtk.WidgetPath();
|
||||
widget.append_type(Gtk.Widget);
|
||||
|
||||
let context = new Gtk.StyleContext();
|
||||
context.set_path(widget);
|
||||
context.add_class(classname);
|
||||
return context.get_background_color(state);
|
||||
}
|
@ -1,752 +0,0 @@
|
||||
/* Desktop Icons GNOME Shell extension
|
||||
*
|
||||
* Copyright (C) 2017 Carlos Soriano <csoriano@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const St = imports.gi.St;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
|
||||
const Animation = imports.ui.animation;
|
||||
const Background = imports.ui.background;
|
||||
const DND = imports.ui.dnd;
|
||||
const Main = imports.ui.main;
|
||||
const GrabHelper = imports.ui.grabHelper;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Extension = Me.imports.extension;
|
||||
const DesktopGrid = Me.imports.desktopGrid;
|
||||
const FileItem = Me.imports.fileItem;
|
||||
const Prefs = Me.imports.prefs;
|
||||
const DBusUtils = Me.imports.dbusUtils;
|
||||
const DesktopIconsUtil = Me.imports.desktopIconsUtil;
|
||||
|
||||
const Clipboard = St.Clipboard.get_default();
|
||||
const CLIPBOARD_TYPE = St.ClipboardType.CLIPBOARD;
|
||||
|
||||
var S_IWOTH = 0x00002;
|
||||
|
||||
function getDpy() {
|
||||
return global.screen || global.display;
|
||||
}
|
||||
|
||||
function findMonitorIndexForPos(x, y) {
|
||||
return getDpy().get_monitor_index_for_rect(new Meta.Rectangle({x, y}));
|
||||
}
|
||||
|
||||
|
||||
var DesktopManager = GObject.registerClass({
|
||||
Properties: {
|
||||
'writable-by-others': GObject.ParamSpec.boolean(
|
||||
'writable-by-others',
|
||||
'WritableByOthers',
|
||||
'Whether the desktop\'s directory can be written by others (o+w unix permission)',
|
||||
GObject.ParamFlags.READABLE,
|
||||
false
|
||||
)
|
||||
}
|
||||
}, class DesktopManager extends GObject.Object {
|
||||
_init(params) {
|
||||
super._init(params);
|
||||
|
||||
this._layoutChildrenId = 0;
|
||||
this._deleteChildrenId = 0;
|
||||
this._monitorDesktopDir = null;
|
||||
this._desktopMonitorCancellable = null;
|
||||
this._desktopGrids = {};
|
||||
this._fileItemHandlers = new Map();
|
||||
this._fileItems = new Map();
|
||||
this._dragCancelled = false;
|
||||
this._queryFileInfoCancellable = null;
|
||||
this._unixMode = null;
|
||||
this._writableByOthers = null;
|
||||
|
||||
this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', () => this._recreateDesktopIcons());
|
||||
this._rubberBand = new St.Widget({ style_class: 'rubber-band' });
|
||||
this._rubberBand.hide();
|
||||
Main.layoutManager._backgroundGroup.add_child(this._rubberBand);
|
||||
this._grabHelper = new GrabHelper.GrabHelper(global.stage);
|
||||
|
||||
this._addDesktopIcons();
|
||||
this._monitorDesktopFolder();
|
||||
|
||||
this.settingsId = Prefs.settings.connect('changed', () => this._recreateDesktopIcons());
|
||||
this.gtkSettingsId = Prefs.gtkSettings.connect('changed', (obj, key) => {
|
||||
if (key == 'show-hidden')
|
||||
this._recreateDesktopIcons();
|
||||
});
|
||||
|
||||
this._selection = new Set();
|
||||
this._currentSelection = new Set();
|
||||
this._inDrag = false;
|
||||
this._dragXStart = Number.POSITIVE_INFINITY;
|
||||
this._dragYStart = Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
startRubberBand(x, y) {
|
||||
this._rubberBandInitialX = x;
|
||||
this._rubberBandInitialY = y;
|
||||
this._initRubberBandColor();
|
||||
this._updateRubberBand(x, y);
|
||||
this._rubberBand.show();
|
||||
this._grabHelper.grab({ actor: global.stage });
|
||||
Extension.lockActivitiesButton = true;
|
||||
this._stageReleaseEventId = global.stage.connect('button-release-event', (actor, event) => {
|
||||
this.endRubberBand();
|
||||
});
|
||||
this._rubberBandId = global.stage.connect('motion-event', (actor, event) => {
|
||||
/* In some cases, when the user starts a rubberband selection and ends it
|
||||
* (by releasing the left button) over a window instead of doing it over
|
||||
* the desktop, the stage doesn't receive the "button-release" event.
|
||||
* This happens currently with, at least, Dash to Dock extension, but
|
||||
* it probably also happens with other applications or extensions.
|
||||
* To fix this, we also end the rubberband selection if we detect mouse
|
||||
* motion in the stage without the left button pressed during a
|
||||
* rubberband selection.
|
||||
* */
|
||||
let button = event.get_state();
|
||||
if (!(button & Clutter.ModifierType.BUTTON1_MASK)) {
|
||||
this.endRubberBand();
|
||||
return;
|
||||
}
|
||||
[x, y] = event.get_coords();
|
||||
this._updateRubberBand(x, y);
|
||||
let x0, y0, x1, y1;
|
||||
if (x >= this._rubberBandInitialX) {
|
||||
x0 = this._rubberBandInitialX;
|
||||
x1 = x;
|
||||
} else {
|
||||
x1 = this._rubberBandInitialX;
|
||||
x0 = x;
|
||||
}
|
||||
if (y >= this._rubberBandInitialY) {
|
||||
y0 = this._rubberBandInitialY;
|
||||
y1 = y;
|
||||
} else {
|
||||
y1 = this._rubberBandInitialY;
|
||||
y0 = y;
|
||||
}
|
||||
for (let [fileUri, fileItem] of this._fileItems) {
|
||||
fileItem.emit('selected', true, true,
|
||||
fileItem.intersectsWith(x0, y0, x1 - x0, y1 - y0));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
endRubberBand() {
|
||||
this._rubberBand.hide();
|
||||
Extension.lockActivitiesButton = false;
|
||||
this._grabHelper.ungrab();
|
||||
global.stage.disconnect(this._rubberBandId);
|
||||
global.stage.disconnect(this._stageReleaseEventId);
|
||||
this._rubberBandId = 0;
|
||||
this._stageReleaseEventId = 0;
|
||||
|
||||
this._selection = new Set([...this._selection, ...this._currentSelection]);
|
||||
this._currentSelection.clear();
|
||||
}
|
||||
|
||||
_updateRubberBand(currentX, currentY) {
|
||||
let x = this._rubberBandInitialX < currentX ? this._rubberBandInitialX
|
||||
: currentX;
|
||||
let y = this._rubberBandInitialY < currentY ? this._rubberBandInitialY
|
||||
: currentY;
|
||||
let width = Math.abs(this._rubberBandInitialX - currentX);
|
||||
let height = Math.abs(this._rubberBandInitialY - currentY);
|
||||
/* TODO: Convert to gobject.set for 3.30 */
|
||||
this._rubberBand.set_position(x, y);
|
||||
this._rubberBand.set_size(width, height);
|
||||
}
|
||||
|
||||
_recreateDesktopIcons() {
|
||||
this._destroyDesktopIcons();
|
||||
this._addDesktopIcons();
|
||||
}
|
||||
|
||||
_addDesktopIcons() {
|
||||
forEachBackgroundManager(bgManager => {
|
||||
let newGrid = new DesktopGrid.DesktopGrid(bgManager);
|
||||
newGrid.actor.connect('destroy', (actor) => {
|
||||
// if a grid loses its actor, remove it from the grid list
|
||||
for (let grid in this._desktopGrids)
|
||||
if (this._desktopGrids[grid].actor == actor) {
|
||||
delete this._desktopGrids[grid];
|
||||
break;
|
||||
}
|
||||
});
|
||||
this._desktopGrids[bgManager._monitorIndex] = newGrid;
|
||||
});
|
||||
|
||||
this._scanFiles();
|
||||
}
|
||||
|
||||
_destroyDesktopIcons() {
|
||||
Object.values(this._desktopGrids).forEach(grid => grid.actor.destroy());
|
||||
this._desktopGrids = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize rubberband color from the GTK rubberband class
|
||||
* */
|
||||
_initRubberBandColor() {
|
||||
let rgba = DesktopIconsUtil.getGtkClassBackgroundColor('rubberband', Gtk.StateFlags.NORMAL);
|
||||
let background_color =
|
||||
'rgba(' + rgba.red * 255 + ', ' + rgba.green * 255 + ', ' + rgba.blue * 255 + ', 0.4)';
|
||||
this._rubberBand.set_style('background-color: ' + background_color);
|
||||
}
|
||||
|
||||
async _scanFiles() {
|
||||
for (let [fileItem, id] of this._fileItemHandlers)
|
||||
fileItem.disconnect(id);
|
||||
this._fileItemHandlers = new Map();
|
||||
|
||||
if (!this._unixMode) {
|
||||
let desktopDir = DesktopIconsUtil.getDesktopDir();
|
||||
let fileInfo = desktopDir.query_info(Gio.FILE_ATTRIBUTE_UNIX_MODE,
|
||||
Gio.FileQueryInfoFlags.NONE,
|
||||
null);
|
||||
this._unixMode = fileInfo.get_attribute_uint32(Gio.FILE_ATTRIBUTE_UNIX_MODE);
|
||||
this._setWritableByOthers((this._unixMode & S_IWOTH) != 0);
|
||||
}
|
||||
|
||||
try {
|
||||
let tmpFileItems = new Map();
|
||||
for (let [file, info, extra] of await this._enumerateDesktop()) {
|
||||
let fileItem = new FileItem.FileItem(file, info, extra);
|
||||
tmpFileItems.set(fileItem.file.get_uri(), fileItem);
|
||||
let id = fileItem.connect('selected',
|
||||
this._onFileItemSelected.bind(this));
|
||||
|
||||
this._fileItemHandlers.set(fileItem, id);
|
||||
}
|
||||
this._fileItems = tmpFileItems;
|
||||
} catch (e) {
|
||||
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
|
||||
log(`Error loading desktop files ${e.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.scheduleReLayoutChildren();
|
||||
}
|
||||
|
||||
getDesktopFileNames () {
|
||||
let fileList = [];
|
||||
for (let [uri, item] of this._fileItems) {
|
||||
fileList.push(item.fileName);
|
||||
}
|
||||
return fileList;
|
||||
}
|
||||
|
||||
_enumerateDesktop() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this._desktopEnumerateCancellable)
|
||||
this._desktopEnumerateCancellable.cancel();
|
||||
|
||||
this._desktopEnumerateCancellable = new Gio.Cancellable();
|
||||
|
||||
let desktopDir = DesktopIconsUtil.getDesktopDir();
|
||||
desktopDir.enumerate_children_async(DesktopIconsUtil.DEFAULT_ATTRIBUTES,
|
||||
Gio.FileQueryInfoFlags.NONE,
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
this._desktopEnumerateCancellable,
|
||||
(source, result) => {
|
||||
try {
|
||||
let fileEnum = source.enumerate_children_finish(result);
|
||||
let resultGenerator = function *() {
|
||||
let info;
|
||||
for (let [newFolder, extras] of DesktopIconsUtil.getExtraFolders()) {
|
||||
yield [newFolder, newFolder.query_info(DesktopIconsUtil.DEFAULT_ATTRIBUTES, Gio.FileQueryInfoFlags.NONE, this._desktopEnumerateCancellable), extras];
|
||||
}
|
||||
while ((info = fileEnum.next_file(null)))
|
||||
yield [fileEnum.get_child(info), info, Prefs.FileType.NONE];
|
||||
}.bind(this);
|
||||
resolve(resultGenerator());
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_monitorDesktopFolder() {
|
||||
if (this._monitorDesktopDir) {
|
||||
this._monitorDesktopDir.cancel();
|
||||
this._monitorDesktopDir = null;
|
||||
}
|
||||
|
||||
let desktopDir = DesktopIconsUtil.getDesktopDir();
|
||||
this._monitorDesktopDir = desktopDir.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, null);
|
||||
this._monitorDesktopDir.set_rate_limit(1000);
|
||||
this._monitorDesktopDir.connect('changed', (obj, file, otherFile, eventType) => this._updateDesktopIfChanged(file, otherFile, eventType));
|
||||
}
|
||||
|
||||
checkIfSpecialFilesAreSelected() {
|
||||
for (let fileItem of this._selection) {
|
||||
if (fileItem.isSpecial)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getNumberOfSelectedItems() {
|
||||
return this._selection.size;
|
||||
}
|
||||
|
||||
get writableByOthers() {
|
||||
return this._writableByOthers;
|
||||
}
|
||||
|
||||
_setWritableByOthers(value) {
|
||||
if (value == this._writableByOthers)
|
||||
return;
|
||||
|
||||
this._writableByOthers = value
|
||||
this.notify('writable-by-others');
|
||||
}
|
||||
|
||||
_updateDesktopIfChanged (file, otherFile, eventType) {
|
||||
let {
|
||||
DELETED, MOVED_IN, MOVED_OUT, CREATED, RENAMED, CHANGES_DONE_HINT, ATTRIBUTE_CHANGED
|
||||
} = Gio.FileMonitorEvent;
|
||||
|
||||
let fileUri = file.get_uri();
|
||||
let fileItem = null;
|
||||
if (this._fileItems.has(fileUri))
|
||||
fileItem = this._fileItems.get(fileUri);
|
||||
switch(eventType) {
|
||||
case RENAMED:
|
||||
this._fileItems.delete(fileUri);
|
||||
this._fileItems.set(otherFile.get_uri(), fileItem);
|
||||
fileItem.onFileRenamed(otherFile);
|
||||
return;
|
||||
case CHANGES_DONE_HINT:
|
||||
case ATTRIBUTE_CHANGED:
|
||||
/* a file changed, rather than the desktop itself */
|
||||
let desktopDir = DesktopIconsUtil.getDesktopDir();
|
||||
if (file.get_uri() != desktopDir.get_uri())
|
||||
return;
|
||||
|
||||
if (this._queryFileInfoCancellable)
|
||||
this._queryFileInfoCancellable.cancel();
|
||||
|
||||
file.query_info_async(Gio.FILE_ATTRIBUTE_UNIX_MODE,
|
||||
Gio.FileQueryInfoFlags.NONE,
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
this._queryFileInfoCancellable,
|
||||
(source, result) => {
|
||||
try {
|
||||
let info = source.query_info_finish(result);
|
||||
this._queryFileInfoCancellable = null;
|
||||
|
||||
this._unixMode = info.get_attribute_uint32(Gio.FILE_ATTRIBUTE_UNIX_MODE);
|
||||
this._setWritableByOthers((this._unixMode & S_IWOTH) != 0);
|
||||
|
||||
if (this._writableByOthers)
|
||||
log(`desktop-icons: Desktop is writable by others - will not allow launching any desktop files`);
|
||||
} catch(error) {
|
||||
if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
|
||||
global.log('Error getting desktop unix mode: ' + error);
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Only get a subset of events we are interested in.
|
||||
// Note that CREATED will emit a CHANGES_DONE_HINT
|
||||
if (![DELETED, MOVED_IN, MOVED_OUT, CREATED].includes(eventType))
|
||||
return;
|
||||
|
||||
this._recreateDesktopIcons();
|
||||
}
|
||||
|
||||
_setupDnD() {
|
||||
this._draggableContainer = new St.Widget({
|
||||
visible: true,
|
||||
width: 1,
|
||||
height: 1,
|
||||
x: 0,
|
||||
y: 0,
|
||||
style_class: 'draggable'
|
||||
});
|
||||
this._draggableContainer._delegate = this;
|
||||
this._draggable = DND.makeDraggable(this._draggableContainer,
|
||||
{
|
||||
manualMode: true,
|
||||
dragActorOpacity: 100
|
||||
});
|
||||
|
||||
this._draggable.connect('drag-cancelled', () => this._onDragCancelled());
|
||||
this._draggable.connect('drag-end', () => this._onDragEnd());
|
||||
|
||||
this._draggable._dragActorDropped = event => this._dragActorDropped(event);
|
||||
}
|
||||
|
||||
dragStart() {
|
||||
if (this._inDrag) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._setupDnD();
|
||||
let event = Clutter.get_current_event();
|
||||
let [x, y] = event.get_coords();
|
||||
[this._dragXStart, this._dragYStart] = event.get_coords();
|
||||
this._inDrag = true;
|
||||
|
||||
for (let fileItem of this._selection) {
|
||||
let clone = new Clutter.Clone({
|
||||
source: fileItem.actor,
|
||||
reactive: false
|
||||
});
|
||||
clone.x = fileItem.actor.get_transformed_position()[0];
|
||||
clone.y = fileItem.actor.get_transformed_position()[1];
|
||||
this._draggableContainer.add_child(clone);
|
||||
}
|
||||
|
||||
Main.layoutManager.uiGroup.add_child(this._draggableContainer);
|
||||
this._draggable.startDrag(x, y, global.get_current_time(), event.get_event_sequence());
|
||||
}
|
||||
|
||||
_onDragCancelled() {
|
||||
let event = Clutter.get_current_event();
|
||||
let [x, y] = event.get_coords();
|
||||
this._dragCancelled = true;
|
||||
}
|
||||
|
||||
_onDragEnd() {
|
||||
this._inDrag = false;
|
||||
Main.layoutManager.uiGroup.remove_child(this._draggableContainer);
|
||||
}
|
||||
|
||||
_dragActorDropped(event) {
|
||||
let [dropX, dropY] = event.get_coords();
|
||||
let target = this._draggable._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
|
||||
dropX, dropY);
|
||||
|
||||
// We call observers only once per motion with the innermost
|
||||
// target actor. If necessary, the observer can walk the
|
||||
// parent itself.
|
||||
let dropEvent = {
|
||||
dropActor: this._draggable._dragActor,
|
||||
targetActor: target,
|
||||
clutterEvent: event
|
||||
};
|
||||
for (let dragMonitor of DND.dragMonitors) {
|
||||
let dropFunc = dragMonitor.dragDrop;
|
||||
if (dropFunc)
|
||||
switch (dropFunc(dropEvent)) {
|
||||
case DragDropResult.FAILURE:
|
||||
case DragDropResult.SUCCESS:
|
||||
return true;
|
||||
case DragDropResult.CONTINUE:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// At this point it is too late to cancel a drag by destroying
|
||||
// the actor, the fate of which is decided by acceptDrop and its
|
||||
// side-effects
|
||||
this._draggable._dragCancellable = false;
|
||||
|
||||
let destroyActor = false;
|
||||
while (target) {
|
||||
if (target._delegate && target._delegate.acceptDrop) {
|
||||
let [r, targX, targY] = target.transform_stage_point(dropX, dropY);
|
||||
if (target._delegate.acceptDrop(this._draggable.actor._delegate,
|
||||
this._draggable._dragActor,
|
||||
targX,
|
||||
targY,
|
||||
event.get_time())) {
|
||||
// If it accepted the drop without taking the actor,
|
||||
// handle it ourselves.
|
||||
if (this._draggable._dragActor.get_parent() == Main.uiGroup) {
|
||||
if (this._draggable._restoreOnSuccess) {
|
||||
this._draggable._restoreDragActor(event.get_time());
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// We need this in order to make sure drag-end is fired
|
||||
destroyActor = true;
|
||||
}
|
||||
}
|
||||
|
||||
this._draggable._dragInProgress = false;
|
||||
getDpy().set_cursor(Meta.Cursor.DEFAULT);
|
||||
this._draggable.emit('drag-end', event.get_time(), true);
|
||||
if (destroyActor) {
|
||||
this._draggable._dragActor.destroy();
|
||||
}
|
||||
this._draggable._dragComplete();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
target = target.get_parent();
|
||||
}
|
||||
|
||||
this._draggable._cancelDrag(event.get_time());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
acceptDrop(xEnd, yEnd) {
|
||||
let savedCoordinates = new Map();
|
||||
let [xDiff, yDiff] = [xEnd - this._dragXStart, yEnd - this._dragYStart];
|
||||
/* Remove all items before dropping new ones, so we can freely reposition
|
||||
* them.
|
||||
*/
|
||||
for (let item of this._selection) {
|
||||
let [itemX, itemY] = item.actor.get_transformed_position();
|
||||
let monitorIndex = findMonitorIndexForPos(itemX, itemY);
|
||||
savedCoordinates.set(item, [itemX, itemY]);
|
||||
this._desktopGrids[monitorIndex].removeFileItem(item);
|
||||
}
|
||||
|
||||
for (let item of this._selection) {
|
||||
let [itemX, itemY] = savedCoordinates.get(item);
|
||||
/* Set the new ideal position where the item drop should happen */
|
||||
let newFileX = Math.round(xDiff + itemX);
|
||||
let newFileY = Math.round(yDiff + itemY);
|
||||
let monitorIndex = findMonitorIndexForPos(newFileX, newFileY);
|
||||
this._desktopGrids[monitorIndex].addFileItemCloseTo(item, newFileX, newFileY, DesktopGrid.StoredCoordinates.OVERWRITE);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
selectionDropOnFileItem (fileItemDestination) {
|
||||
if (!fileItemDestination.isDirectory)
|
||||
return false;
|
||||
|
||||
let droppedUris = [];
|
||||
for (let fileItem of this._selection) {
|
||||
if (fileItem.isSpecial)
|
||||
return false;
|
||||
if (fileItemDestination.file.get_uri() == fileItem.file.get_uri())
|
||||
return false;
|
||||
droppedUris.push(fileItem.file.get_uri());
|
||||
}
|
||||
|
||||
if (droppedUris.length == 0)
|
||||
return true;
|
||||
|
||||
DBusUtils.NautilusFileOperationsProxy.MoveURIsRemote(droppedUris,
|
||||
fileItemDestination.file.get_uri(),
|
||||
(result, error) => {
|
||||
if (error)
|
||||
throw new Error('Error moving files: ' + error.message);
|
||||
}
|
||||
);
|
||||
for (let fileItem of this._selection) {
|
||||
fileItem.state = FileItem.State.GONE;
|
||||
}
|
||||
|
||||
this._recreateDesktopIcons();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_resetGridsAndScheduleLayout() {
|
||||
this._deleteChildrenId = 0;
|
||||
|
||||
Object.values(this._desktopGrids).forEach((grid) => grid.reset());
|
||||
|
||||
this._layoutChildrenId = GLib.idle_add(GLib.PRIORITY_LOW, () => this._layoutChildren());
|
||||
|
||||
return GLib.SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
scheduleReLayoutChildren() {
|
||||
if (this._deleteChildrenId != 0)
|
||||
return;
|
||||
|
||||
if (this._layoutChildrenId != 0) {
|
||||
GLib.source_remove(this._layoutChildrenId);
|
||||
this._layoutChildrenId = 0;
|
||||
}
|
||||
|
||||
|
||||
this._deleteChildrenId = GLib.idle_add(GLib.PRIORITY_LOW, () => this._resetGridsAndScheduleLayout());
|
||||
}
|
||||
|
||||
_addFileItemCloseTo(item) {
|
||||
let coordinates;
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
let coordinatesAction = DesktopGrid.StoredCoordinates.ASSIGN;
|
||||
if (item.savedCoordinates != null) {
|
||||
[x, y] = item.savedCoordinates;
|
||||
coordinatesAction = DesktopGrid.StoredCoordinates.PRESERVE;
|
||||
}
|
||||
let monitorIndex = findMonitorIndexForPos(x, y);
|
||||
let desktopGrid = this._desktopGrids[monitorIndex];
|
||||
try {
|
||||
desktopGrid.addFileItemCloseTo(item, x, y, coordinatesAction);
|
||||
} catch (e) {
|
||||
log(`Error adding children to desktop: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
_layoutChildren() {
|
||||
let showHidden = Prefs.gtkSettings.get_boolean('show-hidden');
|
||||
/*
|
||||
* Paint the icons in two passes:
|
||||
* * first pass paints those that have their coordinates defined in the metadata
|
||||
* * second pass paints those new files that still don't have their definitive coordinates
|
||||
*/
|
||||
for (let [fileUri, fileItem] of this._fileItems) {
|
||||
if (fileItem.savedCoordinates == null)
|
||||
continue;
|
||||
if (fileItem.state != FileItem.State.NORMAL)
|
||||
continue;
|
||||
if (!showHidden && fileItem.isHidden)
|
||||
continue;
|
||||
this._addFileItemCloseTo(fileItem);
|
||||
}
|
||||
|
||||
for (let [fileUri, fileItem] of this._fileItems) {
|
||||
if (fileItem.savedCoordinates !== null)
|
||||
continue;
|
||||
if (fileItem.state != FileItem.State.NORMAL)
|
||||
continue;
|
||||
if (!showHidden && fileItem.isHidden)
|
||||
continue;
|
||||
this._addFileItemCloseTo(fileItem);
|
||||
}
|
||||
|
||||
this._layoutChildrenId = 0;
|
||||
return GLib.SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
doRename() {
|
||||
if (this._selection.size != 1)
|
||||
return;
|
||||
|
||||
let item = [...this._selection][0];
|
||||
if (item.canRename())
|
||||
item.doRename();
|
||||
}
|
||||
|
||||
doOpen() {
|
||||
for (let fileItem of this._selection)
|
||||
fileItem.doOpen();
|
||||
}
|
||||
|
||||
doTrash() {
|
||||
DBusUtils.NautilusFileOperationsProxy.TrashFilesRemote([...this._selection].map((x) => { return x.file.get_uri(); }),
|
||||
(source, error) => {
|
||||
if (error)
|
||||
throw new Error('Error trashing files on the desktop: ' + error.message);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
doEmptyTrash() {
|
||||
DBusUtils.NautilusFileOperationsProxy.EmptyTrashRemote( (source, error) => {
|
||||
if (error)
|
||||
throw new Error('Error trashing files on the desktop: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
_onFileItemSelected(fileItem, keepCurrentSelection, rubberBandSelection, addToSelection) {
|
||||
|
||||
if (!keepCurrentSelection && !this._inDrag)
|
||||
this.clearSelection();
|
||||
|
||||
let selection = keepCurrentSelection && rubberBandSelection ? this._currentSelection : this._selection;
|
||||
if (addToSelection)
|
||||
selection.add(fileItem);
|
||||
else
|
||||
selection.delete(fileItem);
|
||||
|
||||
for (let [fileUri, fileItem] of this._fileItems)
|
||||
fileItem.isSelected = this._currentSelection.has(fileItem) || this._selection.has(fileItem);
|
||||
}
|
||||
|
||||
clearSelection() {
|
||||
for (let [fileUri, fileItem] of this._fileItems)
|
||||
fileItem.isSelected = false;
|
||||
this._selection = new Set();
|
||||
this._currentSelection = new Set();
|
||||
}
|
||||
|
||||
_getClipboardText(isCopy) {
|
||||
let action = isCopy ? 'copy' : 'cut';
|
||||
let text = `x-special/nautilus-clipboard\n${action}\n${
|
||||
[...this._selection].map(s => s.file.get_uri()).join('\n')
|
||||
}\n`;
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
doCopy() {
|
||||
Clipboard.set_text(CLIPBOARD_TYPE, this._getClipboardText(true));
|
||||
}
|
||||
|
||||
doCut() {
|
||||
Clipboard.set_text(CLIPBOARD_TYPE, this._getClipboardText(false));
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this._monitorDesktopDir)
|
||||
this._monitorDesktopDir.cancel();
|
||||
this._monitorDesktopDir = null;
|
||||
|
||||
if (this.settingsId)
|
||||
Prefs.settings.disconnect(this.settingsId);
|
||||
this.settingsId = 0;
|
||||
if (this.gtkSettingsId)
|
||||
Prefs.gtkSettings.disconnect(this.gtkSettingsId);
|
||||
this.gtkSettingsId = 0;
|
||||
|
||||
if (this._layoutChildrenId)
|
||||
GLib.source_remove(this._layoutChildrenId);
|
||||
this._layoutChildrenId = 0;
|
||||
|
||||
if (this._deleteChildrenId)
|
||||
GLib.source_remove(this._deleteChildrenId);
|
||||
this._deleteChildrenId = 0;
|
||||
|
||||
if (this._monitorsChangedId)
|
||||
Main.layoutManager.disconnect(this._monitorsChangedId);
|
||||
this._monitorsChangedId = 0;
|
||||
if (this._stageReleaseEventId)
|
||||
global.stage.disconnect(this._stageReleaseEventId);
|
||||
this._stageReleaseEventId = 0;
|
||||
|
||||
if (this._rubberBandId)
|
||||
global.stage.disconnect(this._rubberBandId);
|
||||
this._rubberBandId = 0;
|
||||
|
||||
this._rubberBand.destroy();
|
||||
|
||||
if (this._queryFileInfoCancellable)
|
||||
this._queryFileInfoCancellable.cancel();
|
||||
|
||||
Object.values(this._desktopGrids).forEach(grid => grid.actor.destroy());
|
||||
this._desktopGrids = {}
|
||||
}
|
||||
});
|
||||
|
||||
function forEachBackgroundManager(func) {
|
||||
Main.layoutManager._bgManagers.forEach(func);
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
/* Desktop Icons GNOME Shell extension
|
||||
*
|
||||
* Copyright (C) 2017 Carlos Soriano <csoriano@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Prefs = Me.imports.prefs;
|
||||
const { DesktopManager } = Me.imports.desktopManager;
|
||||
const DBusUtils = Me.imports.dbusUtils;
|
||||
|
||||
var desktopManager = null;
|
||||
var addBackgroundMenuOrig = null;
|
||||
var _startupPreparedId;
|
||||
var lockActivitiesButton = false;
|
||||
|
||||
var oldShouldToggleByCornerOrButtonFunction = null;
|
||||
|
||||
function init() {
|
||||
addBackgroundMenuOrig = Main.layoutManager._addBackgroundMenu;
|
||||
|
||||
Prefs.initTranslations();
|
||||
}
|
||||
|
||||
function newShouldToggleByCornerOrButton() {
|
||||
if (lockActivitiesButton)
|
||||
return false;
|
||||
else
|
||||
return oldShouldToggleByCornerOrButtonFunction.bind(Main.overview);
|
||||
}
|
||||
|
||||
function enable() {
|
||||
// register a new function to allow to lock the Activities button when doing a rubberband selection
|
||||
oldShouldToggleByCornerOrButtonFunction = Main.overview.shouldToggleByCornerOrButton;
|
||||
Main.overview.shouldToggleByCornerOrButton = newShouldToggleByCornerOrButton;
|
||||
// wait until the startup process has ended
|
||||
if (Main.layoutManager._startingUp)
|
||||
_startupPreparedId = Main.layoutManager.connect('startup-complete', () => innerEnable(true));
|
||||
else
|
||||
innerEnable(false);
|
||||
}
|
||||
|
||||
function innerEnable(disconnectSignal) {
|
||||
if (disconnectSignal)
|
||||
Main.layoutManager.disconnect(_startupPreparedId);
|
||||
DBusUtils.init();
|
||||
Prefs.init();
|
||||
Main.layoutManager._addBackgroundMenu = function() {};
|
||||
desktopManager = new DesktopManager();
|
||||
}
|
||||
|
||||
function disable() {
|
||||
desktopManager.destroy();
|
||||
Main.layoutManager._addBackgroundMenu = addBackgroundMenuOrig;
|
||||
Main.overview.shouldToggleByCornerOrButton = oldShouldToggleByCornerOrButtonFunction;
|
||||
}
|
@ -1,800 +0,0 @@
|
||||
/* Desktop Icons GNOME Shell extension
|
||||
*
|
||||
* Copyright (C) 2017 Carlos Soriano <csoriano@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const St = imports.gi.St;
|
||||
const Pango = imports.gi.Pango;
|
||||
const Meta = imports.gi.Meta;
|
||||
const GdkPixbuf = imports.gi.GdkPixbuf;
|
||||
const Cogl = imports.gi.Cogl;
|
||||
const GnomeDesktop = imports.gi.GnomeDesktop;
|
||||
|
||||
const Mainloop = imports.mainloop;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const Background = imports.ui.background;
|
||||
const Main = imports.ui.main;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Extension = Me.imports.extension;
|
||||
const Prefs = Me.imports.prefs;
|
||||
const DBusUtils = Me.imports.dbusUtils;
|
||||
const DesktopIconsUtil = Me.imports.desktopIconsUtil;
|
||||
|
||||
const Gettext = imports.gettext.domain('desktop-icons');
|
||||
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const DRAG_TRESHOLD = 8;
|
||||
|
||||
var S_IXUSR = 0o00100;
|
||||
var S_IWOTH = 0o00002;
|
||||
|
||||
var State = {
|
||||
NORMAL: 0,
|
||||
GONE: 1,
|
||||
};
|
||||
|
||||
var FileItem = class {
|
||||
|
||||
constructor(file, fileInfo, fileExtra) {
|
||||
this._fileExtra = fileExtra;
|
||||
this._loadThumbnailDataCancellable = null;
|
||||
this._thumbnailScriptWatch = 0;
|
||||
this._setMetadataCancellable = null;
|
||||
this._queryFileInfoCancellable = null;
|
||||
this._isSpecial = this._fileExtra != Prefs.FileType.NONE;
|
||||
|
||||
this._file = file;
|
||||
|
||||
this._savedCoordinates = null;
|
||||
let savedCoordinates = fileInfo.get_attribute_as_string('metadata::nautilus-icon-position');
|
||||
if (savedCoordinates != null)
|
||||
this._savedCoordinates = savedCoordinates.split(',').map(x => Number(x));
|
||||
|
||||
this._state = State.NORMAL;
|
||||
|
||||
this.actor = new St.Bin({ visible: true });
|
||||
this.actor.set_fill(true, true);
|
||||
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
|
||||
this.actor.set_height(Prefs.get_desired_height(scaleFactor));
|
||||
this.actor.set_width(Prefs.get_desired_width(scaleFactor));
|
||||
this.actor._delegate = this;
|
||||
this.actor.connect('destroy', () => this._onDestroy());
|
||||
|
||||
this._container = new St.BoxLayout({ reactive: true,
|
||||
track_hover: true,
|
||||
can_focus: true,
|
||||
style_class: 'file-item',
|
||||
x_expand: true,
|
||||
y_expand: true,
|
||||
x_align: Clutter.ActorAlign.FILL,
|
||||
vertical: true });
|
||||
this.actor.set_child(this._container);
|
||||
this._icon = new St.Bin();
|
||||
this._icon.set_height(Prefs.get_icon_size() * scaleFactor);
|
||||
|
||||
this._iconContainer = new St.Bin({ visible: true });
|
||||
this._iconContainer.child = this._icon;
|
||||
this._container.add_child(this._iconContainer);
|
||||
|
||||
this._label = new St.Label({
|
||||
style_class: 'name-label'
|
||||
});
|
||||
|
||||
this._container.add_child(this._label);
|
||||
let clutterText = this._label.get_clutter_text();
|
||||
/* TODO: Convert to gobject.set for 3.30 */
|
||||
clutterText.set_line_wrap(true);
|
||||
clutterText.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR);
|
||||
clutterText.set_ellipsize(Pango.EllipsizeMode.END);
|
||||
|
||||
this._container.connect('button-press-event', (actor, event) => this._onPressButton(actor, event));
|
||||
this._container.connect('motion-event', (actor, event) => this._onMotion(actor, event));
|
||||
this._container.connect('leave-event', (actor, event) => this._onLeave(actor, event));
|
||||
this._container.connect('button-release-event', (actor, event) => this._onReleaseButton(actor, event));
|
||||
|
||||
/* Set the metadata and update relevant UI */
|
||||
this._updateMetadataFromFileInfo(fileInfo);
|
||||
|
||||
if (this._isDesktopFile) {
|
||||
/* watch for the executable bit being removed or added */
|
||||
this._monitorDesktopFile = this._file.monitor_file(Gio.FileMonitorFlags.NONE, null);
|
||||
this._monitorDesktopFileId = this._monitorDesktopFile.connect('changed',
|
||||
(obj, file, otherFile, eventType) => {
|
||||
switch(eventType) {
|
||||
case Gio.FileMonitorEvent.ATTRIBUTE_CHANGED:
|
||||
this._refreshMetadataAsync(true);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this._createMenu();
|
||||
this._updateIcon();
|
||||
|
||||
this._isSelected = false;
|
||||
this._primaryButtonPressed = false;
|
||||
if (this._attributeCanExecute && !this._isDesktopFile)
|
||||
this._execLine = this.file.get_path();
|
||||
if (fileExtra == Prefs.FileType.USER_DIRECTORY_TRASH) {
|
||||
// if this icon is the trash, monitor the state of the directory to update the icon
|
||||
this._trashChanged = false;
|
||||
this._trashInitializeCancellable = null;
|
||||
this._scheduleTrashRefreshId = 0;
|
||||
this._monitorTrashDir = this._file.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, null);
|
||||
this._monitorTrashId = this._monitorTrashDir.connect('changed', (obj, file, otherFile, eventType) => {
|
||||
switch(eventType) {
|
||||
case Gio.FileMonitorEvent.DELETED:
|
||||
case Gio.FileMonitorEvent.MOVED_OUT:
|
||||
case Gio.FileMonitorEvent.CREATED:
|
||||
case Gio.FileMonitorEvent.MOVED_IN:
|
||||
if (this._queryTrashInfoCancellable || this._scheduleTrashRefreshId) {
|
||||
if (this._scheduleTrashRefreshId)
|
||||
GLib.source_remove(this._scheduleTrashRefreshId);
|
||||
this._scheduleTrashRefreshId = Mainloop.timeout_add(200, () => this._refreshTrashIcon());
|
||||
} else {
|
||||
this._refreshTrashIcon();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Extension.desktopManager.connect('notify::writable-by-others', () => {
|
||||
if (!this._isDesktopFile)
|
||||
return;
|
||||
this._refreshMetadataAsync(true);
|
||||
});
|
||||
}
|
||||
|
||||
_onDestroy() {
|
||||
/* Regular file data */
|
||||
if (this._setMetadataCancellable)
|
||||
this._setMetadataCancellable.cancel();
|
||||
if (this._queryFileInfoCancellable)
|
||||
this._queryFileInfoCancellable.cancel();
|
||||
|
||||
/* Thumbnailing */
|
||||
if (this._thumbnailScriptWatch)
|
||||
GLib.source_remove(this._thumbnailScriptWatch);
|
||||
if (this._loadThumbnailDataCancellable)
|
||||
this._loadThumbnailDataCancellable.cancel();
|
||||
|
||||
/* Desktop file */
|
||||
if (this._monitorDesktopFileId) {
|
||||
this._monitorDesktopFile.disconnect(this._monitorDesktopFileId);
|
||||
this._monitorDesktopFile.cancel();
|
||||
}
|
||||
|
||||
/* Trash */
|
||||
if (this._monitorTrashDir) {
|
||||
this._monitorTrashDir.disconnect(this._monitorTrashId);
|
||||
this._monitorTrashDir.cancel();
|
||||
}
|
||||
if (this._queryTrashInfoCancellable)
|
||||
this._queryTrashInfoCancellable.cancel();
|
||||
if (this._scheduleTrashRefreshId)
|
||||
GLib.source_remove(this._scheduleTrashRefreshId);
|
||||
}
|
||||
|
||||
_refreshMetadataAsync(rebuild) {
|
||||
if (this._queryFileInfoCancellable)
|
||||
this._queryFileInfoCancellable.cancel();
|
||||
this._queryFileInfoCancellable = new Gio.Cancellable();
|
||||
this._file.query_info_async(DesktopIconsUtil.DEFAULT_ATTRIBUTES,
|
||||
Gio.FileQueryInfoFlags.NONE,
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
this._queryFileInfoCancellable,
|
||||
(source, result) => {
|
||||
try {
|
||||
let newFileInfo = source.query_info_finish(result);
|
||||
this._queryFileInfoCancellable = null;
|
||||
this._updateMetadataFromFileInfo(newFileInfo);
|
||||
if (rebuild) {
|
||||
this._createMenu();
|
||||
this._updateIcon();
|
||||
}
|
||||
} catch(error) {
|
||||
if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
|
||||
global.log("Error getting the file info: " + error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_updateMetadataFromFileInfo(fileInfo) {
|
||||
this._fileInfo = fileInfo;
|
||||
|
||||
let oldLabelText = this._label.text;
|
||||
|
||||
this._displayName = fileInfo.get_attribute_as_string('standard::display-name');
|
||||
this._attributeCanExecute = fileInfo.get_attribute_boolean('access::can-execute');
|
||||
this._unixmode = fileInfo.get_attribute_uint32('unix::mode')
|
||||
this._writableByOthers = (this._unixmode & S_IWOTH) != 0;
|
||||
this._trusted = fileInfo.get_attribute_as_string('metadata::trusted') == 'true';
|
||||
this._attributeContentType = fileInfo.get_content_type();
|
||||
this._isDesktopFile = this._attributeContentType == 'application/x-desktop';
|
||||
|
||||
if (this._isDesktopFile && this._writableByOthers)
|
||||
log(`desktop-icons: File ${this._displayName} is writable by others - will not allow launching`);
|
||||
|
||||
if (this._isDesktopFile) {
|
||||
this._desktopFile = Gio.DesktopAppInfo.new_from_filename(this._file.get_path());
|
||||
if (!this._desktopFile) {
|
||||
log(`Couldn’t parse ${this._displayName} as a desktop file, will treat it as a regular file.`);
|
||||
this._isDesktopFile = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.displayName != oldLabelText) {
|
||||
this._label.text = this.displayName;
|
||||
}
|
||||
|
||||
this._fileType = fileInfo.get_file_type();
|
||||
this._isDirectory = this._fileType == Gio.FileType.DIRECTORY;
|
||||
this._isSpecial = this._fileExtra != Prefs.FileType.NONE;
|
||||
this._isHidden = fileInfo.get_is_hidden() | fileInfo.get_is_backup();
|
||||
this._isSymlink = fileInfo.get_is_symlink();
|
||||
this._modifiedTime = this._fileInfo.get_attribute_uint64("time::modified");
|
||||
/*
|
||||
* This is a glib trick to detect broken symlinks. If a file is a symlink, the filetype
|
||||
* points to the final file, unless it is broken; thus if the file type is SYMBOLIC_LINK,
|
||||
* it must be a broken link.
|
||||
* https://developer.gnome.org/gio/stable/GFile.html#g-file-query-info
|
||||
*/
|
||||
this._isBrokenSymlink = this._isSymlink && this._fileType == Gio.FileType.SYMBOLIC_LINK
|
||||
}
|
||||
|
||||
onFileRenamed(file) {
|
||||
this._file = file;
|
||||
this._refreshMetadataAsync(false);
|
||||
}
|
||||
|
||||
_updateIcon() {
|
||||
if (this._fileExtra == Prefs.FileType.USER_DIRECTORY_TRASH) {
|
||||
this._icon.child = this._createEmblemedStIcon(this._fileInfo.get_icon(), null);
|
||||
return;
|
||||
}
|
||||
|
||||
let thumbnailFactory = GnomeDesktop.DesktopThumbnailFactory.new(GnomeDesktop.DesktopThumbnailSize.LARGE);
|
||||
if (thumbnailFactory.can_thumbnail(this._file.get_uri(),
|
||||
this._attributeContentType,
|
||||
this._modifiedTime)) {
|
||||
let thumbnail = thumbnailFactory.lookup(this._file.get_uri(), this._modifiedTime);
|
||||
if (thumbnail == null) {
|
||||
if (!thumbnailFactory.has_valid_failed_thumbnail(this._file.get_uri(),
|
||||
this._modifiedTime)) {
|
||||
let argv = [];
|
||||
argv.push(GLib.build_filenamev([ExtensionUtils.getCurrentExtension().path,
|
||||
'createThumbnail.js']));
|
||||
argv.push(this._file.get_path());
|
||||
let [success, pid] = GLib.spawn_async(null, argv, null,
|
||||
GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD, null);
|
||||
if (this._thumbnailScriptWatch)
|
||||
GLib.source_remove(this._thumbnailScriptWatch);
|
||||
this._thumbnailScriptWatch = GLib.child_watch_add(GLib.PRIORITY_DEFAULT,
|
||||
pid,
|
||||
(pid, exitCode) => {
|
||||
if (exitCode == 0)
|
||||
this._updateIcon();
|
||||
else
|
||||
global.log('Failed to generate thumbnail for ' + this._filePath);
|
||||
GLib.spawn_close_pid(pid);
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (this._loadThumbnailDataCancellable)
|
||||
this._loadThumbnailDataCancellable.cancel();
|
||||
this._loadThumbnailDataCancellable = new Gio.Cancellable();
|
||||
let thumbnailFile = Gio.File.new_for_path(thumbnail);
|
||||
thumbnailFile.load_bytes_async(this._loadThumbnailDataCancellable,
|
||||
(source, result) => {
|
||||
try {
|
||||
let [thumbnailData, etag_out] = source.load_bytes_finish(result);
|
||||
let thumbnailStream = Gio.MemoryInputStream.new_from_bytes(thumbnailData);
|
||||
let thumbnailPixbuf = GdkPixbuf.Pixbuf.new_from_stream(thumbnailStream, null);
|
||||
|
||||
if (thumbnailPixbuf != null) {
|
||||
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
|
||||
let thumbnailImage = new Clutter.Image();
|
||||
thumbnailImage.set_data(thumbnailPixbuf.get_pixels(),
|
||||
thumbnailPixbuf.has_alpha ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888,
|
||||
thumbnailPixbuf.width,
|
||||
thumbnailPixbuf.height,
|
||||
thumbnailPixbuf.rowstride
|
||||
);
|
||||
let icon = new Clutter.Actor();
|
||||
icon.set_content(thumbnailImage);
|
||||
let width = Prefs.get_desired_width(scaleFactor);
|
||||
let height = Prefs.get_icon_size() * scaleFactor;
|
||||
let aspectRatio = thumbnailPixbuf.width / thumbnailPixbuf.height;
|
||||
if ((width / height) > aspectRatio)
|
||||
icon.set_size(height * aspectRatio, height);
|
||||
else
|
||||
icon.set_size(width, width / aspectRatio);
|
||||
this._icon.child = icon;
|
||||
}
|
||||
} catch (error) {
|
||||
if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
|
||||
global.log('Error while loading thumbnail: ' + error);
|
||||
this._icon.child = this._createEmblemedStIcon(this._fileInfo.get_icon(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._isBrokenSymlink) {
|
||||
this._icon.child = this._createEmblemedStIcon(null, 'text-x-generic');
|
||||
} else {
|
||||
if (this.trustedDesktopFile && this._desktopFile.has_key('Icon'))
|
||||
this._icon.child = this._createEmblemedStIcon(null, this._desktopFile.get_string('Icon'));
|
||||
else
|
||||
this._icon.child = this._createEmblemedStIcon(this._fileInfo.get_icon(), null);
|
||||
}
|
||||
}
|
||||
|
||||
_refreshTrashIcon() {
|
||||
if (this._queryTrashInfoCancellable)
|
||||
this._queryTrashInfoCancellable.cancel();
|
||||
this._queryTrashInfoCancellable = new Gio.Cancellable();
|
||||
|
||||
this._file.query_info_async(DesktopIconsUtil.DEFAULT_ATTRIBUTES,
|
||||
Gio.FileQueryInfoFlags.NONE,
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
this._queryTrashInfoCancellable,
|
||||
(source, result) => {
|
||||
try {
|
||||
this._fileInfo = source.query_info_finish(result);
|
||||
this._queryTrashInfoCancellable = null;
|
||||
this._updateIcon();
|
||||
} catch(error) {
|
||||
if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
|
||||
global.log('Error getting the number of files in the trash: ' + error);
|
||||
}
|
||||
});
|
||||
|
||||
this._scheduleTrashRefreshId = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
get file() {
|
||||
return this._file;
|
||||
}
|
||||
|
||||
get isHidden() {
|
||||
return this._isHidden;
|
||||
}
|
||||
|
||||
_createEmblemedStIcon(icon, iconName) {
|
||||
if (icon == null) {
|
||||
if (GLib.path_is_absolute(iconName)) {
|
||||
let iconFile = Gio.File.new_for_commandline_arg(iconName);
|
||||
icon = new Gio.FileIcon({ file: iconFile });
|
||||
} else {
|
||||
icon = Gio.ThemedIcon.new_with_default_fallbacks(iconName);
|
||||
}
|
||||
}
|
||||
let itemIcon = Gio.EmblemedIcon.new(icon, null);
|
||||
|
||||
if (this._isSymlink) {
|
||||
if (this._isBrokenSymlink)
|
||||
itemIcon.add_emblem(Gio.Emblem.new(Gio.ThemedIcon.new('emblem-unreadable')));
|
||||
else
|
||||
itemIcon.add_emblem(Gio.Emblem.new(Gio.ThemedIcon.new('emblem-symbolic-link')));
|
||||
} else if (this.trustedDesktopFile) {
|
||||
itemIcon.add_emblem(Gio.Emblem.new(Gio.ThemedIcon.new('emblem-symbolic-link')));
|
||||
}
|
||||
|
||||
return new St.Icon({ gicon: itemIcon,
|
||||
icon_size: Prefs.get_icon_size()
|
||||
});
|
||||
}
|
||||
|
||||
doRename() {
|
||||
if (!this.canRename()) {
|
||||
log (`Error: ${this.file.get_uri()} cannot be renamed`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('rename-clicked');
|
||||
}
|
||||
|
||||
doOpen() {
|
||||
if (this._isBrokenSymlink) {
|
||||
log(`Error: Can’t open ${this.file.get_uri()} because it is a broken symlink.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.trustedDesktopFile) {
|
||||
this._desktopFile.launch_uris_as_manager([], null, GLib.SpawnFlags.SEARCH_PATH, null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._attributeCanExecute && !this._isDirectory && !this._isDesktopFile) {
|
||||
if (this._execLine)
|
||||
Util.spawnCommandLine(this._execLine);
|
||||
return;
|
||||
}
|
||||
|
||||
Gio.AppInfo.launch_default_for_uri_async(this.file.get_uri(),
|
||||
null, null,
|
||||
(source, result) => {
|
||||
try {
|
||||
Gio.AppInfo.launch_default_for_uri_finish(result);
|
||||
} catch (e) {
|
||||
log('Error opening file ' + this.file.get_uri() + ': ' + e.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_onCopyClicked() {
|
||||
Extension.desktopManager.doCopy();
|
||||
}
|
||||
|
||||
_onCutClicked() {
|
||||
Extension.desktopManager.doCut();
|
||||
}
|
||||
|
||||
_onShowInFilesClicked() {
|
||||
|
||||
DBusUtils.FreeDesktopFileManagerProxy.ShowItemsRemote([this.file.get_uri()], '',
|
||||
(result, error) => {
|
||||
if (error)
|
||||
log('Error showing file on desktop: ' + error.message);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_onPropertiesClicked() {
|
||||
|
||||
DBusUtils.FreeDesktopFileManagerProxy.ShowItemPropertiesRemote([this.file.get_uri()], '',
|
||||
(result, error) => {
|
||||
if (error)
|
||||
log('Error showing properties: ' + error.message);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_onMoveToTrashClicked() {
|
||||
Extension.desktopManager.doTrash();
|
||||
}
|
||||
|
||||
_onEmptyTrashClicked() {
|
||||
Extension.desktopManager.doEmptyTrash();
|
||||
}
|
||||
|
||||
get _allowLaunchingText() {
|
||||
if (this.trustedDesktopFile)
|
||||
return _("Don’t Allow Launching");
|
||||
|
||||
return _("Allow Launching");
|
||||
}
|
||||
|
||||
get metadataTrusted() {
|
||||
return this._trusted;
|
||||
}
|
||||
|
||||
set metadataTrusted(value) {
|
||||
this._trusted = value;
|
||||
|
||||
let info = new Gio.FileInfo();
|
||||
info.set_attribute_string('metadata::trusted',
|
||||
value ? 'true' : 'false');
|
||||
this._file.set_attributes_async(info,
|
||||
Gio.FileQueryInfoFlags.NONE,
|
||||
GLib.PRIORITY_LOW,
|
||||
null,
|
||||
(source, result) => {
|
||||
try {
|
||||
source.set_attributes_finish(result);
|
||||
this._refreshMetadataAsync(true);
|
||||
} catch(e) {
|
||||
log(`Failed to set metadata::trusted: ${e.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_onAllowDisallowLaunchingClicked() {
|
||||
this.metadataTrusted = !this.trustedDesktopFile;
|
||||
|
||||
/*
|
||||
* we're marking as trusted, make the file executable too. note that we
|
||||
* do not ever remove the executable bit, since we don't know who set
|
||||
* it.
|
||||
*/
|
||||
if (this.metadataTrusted && !this._attributeCanExecute) {
|
||||
let info = new Gio.FileInfo();
|
||||
let newUnixMode = this._unixmode | S_IXUSR;
|
||||
info.set_attribute_uint32(Gio.FILE_ATTRIBUTE_UNIX_MODE, newUnixMode);
|
||||
this._file.set_attributes_async(info,
|
||||
Gio.FileQueryInfoFlags.NONE,
|
||||
GLib.PRIORITY_LOW,
|
||||
null,
|
||||
(source, result) => {
|
||||
try {
|
||||
source.set_attributes_finish (result);
|
||||
} catch(e) {
|
||||
log(`Failed to set unix mode: ${e.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
canRename() {
|
||||
return !this.trustedDesktopFile && this._fileExtra == Prefs.FileType.NONE;
|
||||
}
|
||||
|
||||
_doOpenWith() {
|
||||
DBusUtils.openFileWithOtherApplication(this.file.get_path());
|
||||
}
|
||||
|
||||
_getSelectionStyle() {
|
||||
let rgba = DesktopIconsUtil.getGtkClassBackgroundColor('view', Gtk.StateFlags.SELECTED);
|
||||
let background_color =
|
||||
'rgba(' + rgba.red * 255 + ', ' + rgba.green * 255 + ', ' + rgba.blue * 255 + ', 0.6)';
|
||||
let border_color =
|
||||
'rgba(' + rgba.red * 255 + ', ' + rgba.green * 255 + ', ' + rgba.blue * 255 + ', 0.8)';
|
||||
|
||||
return 'background-color: ' + background_color + ';' +
|
||||
'border-color: ' + border_color + ';'
|
||||
}
|
||||
|
||||
_createMenu() {
|
||||
this._menuManager = new PopupMenu.PopupMenuManager({ actor: this.actor });
|
||||
let side = St.Side.LEFT;
|
||||
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
|
||||
side = St.Side.RIGHT;
|
||||
this._menu = new PopupMenu.PopupMenu(this.actor, 0.5, side);
|
||||
this._menu.addAction(_('Open'), () => this.doOpen());
|
||||
switch (this._fileExtra) {
|
||||
case Prefs.FileType.NONE:
|
||||
if (!this._isDirectory)
|
||||
this._actionOpenWith = this._menu.addAction(_('Open With Other Application'), () => this._doOpenWith());
|
||||
else
|
||||
this._actionOpenWith = null;
|
||||
this._menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this._actionCut = this._menu.addAction(_('Cut'), () => this._onCutClicked());
|
||||
this._actionCopy = this._menu.addAction(_('Copy'), () => this._onCopyClicked());
|
||||
if (this.canRename())
|
||||
this._menu.addAction(_('Rename…'), () => this.doRename());
|
||||
this._actionTrash = this._menu.addAction(_('Move to Trash'), () => this._onMoveToTrashClicked());
|
||||
if (this._isDesktopFile && !Extension.desktopManager.writableByOthers && !this._writableByOthers) {
|
||||
this._menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this._allowLaunchingMenuItem = this._menu.addAction(this._allowLaunchingText,
|
||||
() => this._onAllowDisallowLaunchingClicked());
|
||||
|
||||
}
|
||||
break;
|
||||
case Prefs.FileType.USER_DIRECTORY_TRASH:
|
||||
this._menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this._menu.addAction(_('Empty Trash'), () => this._onEmptyTrashClicked());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this._menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this._menu.addAction(_('Properties'), () => this._onPropertiesClicked());
|
||||
this._menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this._menu.addAction(_('Show in Files'), () => this._onShowInFilesClicked());
|
||||
if (this._isDirectory && this.file.get_path() != null)
|
||||
this._actionOpenInTerminal = this._menu.addAction(_('Open in Terminal'), () => this._onOpenTerminalClicked());
|
||||
|
||||
this._menuManager.addMenu(this._menu);
|
||||
|
||||
Main.layoutManager.uiGroup.add_child(this._menu.actor);
|
||||
this._menu.actor.hide();
|
||||
}
|
||||
|
||||
_onOpenTerminalClicked () {
|
||||
DesktopIconsUtil.launchTerminal(this.file.get_path());
|
||||
}
|
||||
|
||||
_onPressButton(actor, event) {
|
||||
let button = event.get_button();
|
||||
if (button == 3) {
|
||||
if (!this.isSelected)
|
||||
this.emit('selected', false, false, true);
|
||||
this._menu.toggle();
|
||||
if (this._actionOpenWith) {
|
||||
let allowOpenWith = (Extension.desktopManager.getNumberOfSelectedItems() == 1);
|
||||
this._actionOpenWith.setSensitive(allowOpenWith);
|
||||
}
|
||||
let specialFilesSelected = Extension.desktopManager.checkIfSpecialFilesAreSelected();
|
||||
if (this._actionCut)
|
||||
this._actionCut.setSensitive(!specialFilesSelected);
|
||||
if (this._actionCopy)
|
||||
this._actionCopy.setSensitive(!specialFilesSelected);
|
||||
if (this._actionTrash)
|
||||
this._actionTrash.setSensitive(!specialFilesSelected);
|
||||
return Clutter.EVENT_STOP;
|
||||
} else if (button == 1) {
|
||||
if (event.get_click_count() == 1) {
|
||||
let [x, y] = event.get_coords();
|
||||
this._primaryButtonPressed = true;
|
||||
this._buttonPressInitialX = x;
|
||||
this._buttonPressInitialY = y;
|
||||
let shiftPressed = !!(event.get_state() & Clutter.ModifierType.SHIFT_MASK);
|
||||
let controlPressed = !!(event.get_state() & Clutter.ModifierType.CONTROL_MASK);
|
||||
if (!this.isSelected) {
|
||||
this.emit('selected', shiftPressed || controlPressed, false, true);
|
||||
}
|
||||
}
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
_onLeave(actor, event) {
|
||||
this._primaryButtonPressed = false;
|
||||
}
|
||||
|
||||
_onMotion(actor, event) {
|
||||
let [x, y] = event.get_coords();
|
||||
if (this._primaryButtonPressed) {
|
||||
let xDiff = x - this._buttonPressInitialX;
|
||||
let yDiff = y - this._buttonPressInitialY;
|
||||
let distance = Math.sqrt(Math.pow(xDiff, 2) + Math.pow(yDiff, 2));
|
||||
if (distance > DRAG_TRESHOLD) {
|
||||
// Don't need to track anymore this if we start drag, and also
|
||||
// avoids reentrance here
|
||||
this._primaryButtonPressed = false;
|
||||
let event = Clutter.get_current_event();
|
||||
let [x, y] = event.get_coords();
|
||||
Extension.desktopManager.dragStart();
|
||||
}
|
||||
}
|
||||
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
_onReleaseButton(actor, event) {
|
||||
let button = event.get_button();
|
||||
if (button == 1) {
|
||||
// primaryButtonPressed is TRUE only if the user has pressed the button
|
||||
// over an icon, and if (s)he has not started a drag&drop operation
|
||||
if (this._primaryButtonPressed) {
|
||||
this._primaryButtonPressed = false;
|
||||
let shiftPressed = !!(event.get_state() & Clutter.ModifierType.SHIFT_MASK);
|
||||
let controlPressed = !!(event.get_state() & Clutter.ModifierType.CONTROL_MASK);
|
||||
if ((event.get_click_count() == 1) && Prefs.CLICK_POLICY_SINGLE && !shiftPressed && !controlPressed)
|
||||
this.doOpen();
|
||||
this.emit('selected', shiftPressed || controlPressed, false, true);
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
if ((event.get_click_count() == 2) && (!Prefs.CLICK_POLICY_SINGLE))
|
||||
this.doOpen();
|
||||
}
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
get savedCoordinates() {
|
||||
return this._savedCoordinates;
|
||||
}
|
||||
|
||||
_onSetMetadataFileFinished(source, result) {
|
||||
try {
|
||||
let [success, info] = source.set_attributes_finish(result);
|
||||
} catch (error) {
|
||||
if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
|
||||
log('Error setting metadata to desktop files ', error);
|
||||
}
|
||||
}
|
||||
|
||||
set savedCoordinates(pos) {
|
||||
if (this._setMetadataCancellable)
|
||||
this._setMetadataCancellable.cancel();
|
||||
|
||||
this._setMetadataCancellable = new Gio.Cancellable();
|
||||
this._savedCoordinates = [pos[0], pos[1]];
|
||||
let info = new Gio.FileInfo();
|
||||
info.set_attribute_string('metadata::nautilus-icon-position',
|
||||
`${pos[0]},${pos[1]}`);
|
||||
this.file.set_attributes_async(info,
|
||||
Gio.FileQueryInfoFlags.NONE,
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
this._setMetadataCancellable,
|
||||
(source, result) => this._onSetMetadataFileFinished(source, result)
|
||||
);
|
||||
}
|
||||
|
||||
intersectsWith(argX, argY, argWidth, argHeight) {
|
||||
let rect = new Meta.Rectangle({ x: argX, y: argY, width: argWidth, height: argHeight });
|
||||
let [containerX, containerY] = this._container.get_transformed_position();
|
||||
let boundingBox = new Meta.Rectangle({ x: containerX,
|
||||
y: containerY,
|
||||
width: this._container.allocation.x2 - this._container.allocation.x1,
|
||||
height: this._container.allocation.y2 - this._container.allocation.y1 });
|
||||
let [intersects, _] = rect.intersect(boundingBox);
|
||||
|
||||
return intersects;
|
||||
}
|
||||
|
||||
set isSelected(isSelected) {
|
||||
isSelected = !!isSelected;
|
||||
if (isSelected == this._isSelected)
|
||||
return;
|
||||
|
||||
if (isSelected) {
|
||||
this._container.set_style(this._getSelectionStyle());
|
||||
} else {
|
||||
this._container.set_style('background-color: transparent');
|
||||
this._container.set_style('border-color: transparent');
|
||||
}
|
||||
|
||||
this._isSelected = isSelected;
|
||||
}
|
||||
|
||||
get isSelected() {
|
||||
return this._isSelected;
|
||||
}
|
||||
|
||||
get isSpecial() {
|
||||
return this._isSpecial;
|
||||
}
|
||||
|
||||
get state() {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
set state(state) {
|
||||
if (state == this._state)
|
||||
return;
|
||||
|
||||
this._state = state;
|
||||
}
|
||||
|
||||
get isDirectory() {
|
||||
return this._isDirectory;
|
||||
}
|
||||
|
||||
get trustedDesktopFile() {
|
||||
return this._isDesktopFile &&
|
||||
this._attributeCanExecute &&
|
||||
this.metadataTrusted &&
|
||||
!Extension.desktopManager.writableByOthers &&
|
||||
!this._writableByOthers;
|
||||
}
|
||||
|
||||
get fileName() {
|
||||
return this._fileInfo.get_name();
|
||||
}
|
||||
|
||||
get displayName() {
|
||||
if (this.trustedDesktopFile)
|
||||
return this._desktopFile.get_name();
|
||||
|
||||
return this._displayName || null;
|
||||
}
|
||||
|
||||
acceptDrop() {
|
||||
return Extension.desktopManager.selectionDropOnFileItem(this);
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(FileItem.prototype);
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,11 +0,0 @@
|
||||
{
|
||||
"_generated": "Generated by SweetTooth, do not edit",
|
||||
"description": "Add icons to the desktop",
|
||||
"name": "Desktop Icons",
|
||||
"shell-version": [
|
||||
"3.30.0"
|
||||
],
|
||||
"url": "",
|
||||
"uuid": "desktop-icons@csoriano",
|
||||
"version": 14
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
|
||||
/* Desktop Icons GNOME Shell extension
|
||||
*
|
||||
* Copyright (C) 2017 Carlos Soriano <csoriano@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GioSSS = Gio.SettingsSchemaSource;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Gettext = imports.gettext;
|
||||
|
||||
const Config = imports.misc.config;
|
||||
|
||||
var _ = Gettext.domain('desktop-icons').gettext;
|
||||
|
||||
const SCHEMA_NAUTILUS = 'org.gnome.nautilus.preferences';
|
||||
const SCHEMA_GTK = 'org.gtk.Settings.FileChooser';
|
||||
const SCHEMA = 'org.gnome.shell.extensions.desktop-icons';
|
||||
|
||||
const ICON_SIZE = { 'small': 48, 'standard': 64, 'large': 96 };
|
||||
const ICON_WIDTH = { 'small': 120, 'standard': 128, 'large': 128 };
|
||||
const ICON_HEIGHT = { 'small': 98, 'standard': 114, 'large': 146 };
|
||||
|
||||
var FileType = {
|
||||
NONE: null,
|
||||
USER_DIRECTORY_HOME: 'show-home',
|
||||
USER_DIRECTORY_TRASH: 'show-trash',
|
||||
}
|
||||
|
||||
var nautilusSettings;
|
||||
var gtkSettings;
|
||||
var settings;
|
||||
// This is already in Nautilus settings, so it should not be made tweakable here
|
||||
var CLICK_POLICY_SINGLE = false;
|
||||
|
||||
function initTranslations() {
|
||||
let extension = ExtensionUtils.getCurrentExtension();
|
||||
|
||||
let localedir = extension.dir.get_child('locale');
|
||||
if (localedir.query_exists(null))
|
||||
Gettext.bindtextdomain('desktop-icons', localedir.get_path());
|
||||
else
|
||||
Gettext.bindtextdomain('desktop-icons', Config.LOCALEDIR);
|
||||
}
|
||||
|
||||
function init() {
|
||||
let schemaSource = GioSSS.get_default();
|
||||
let schemaGtk = schemaSource.lookup(SCHEMA_GTK, true);
|
||||
gtkSettings = new Gio.Settings({ settings_schema: schemaGtk });
|
||||
let schemaObj = schemaSource.lookup(SCHEMA_NAUTILUS, true);
|
||||
if (!schemaObj) {
|
||||
nautilusSettings = null;
|
||||
} else {
|
||||
nautilusSettings = new Gio.Settings({ settings_schema: schemaObj });;
|
||||
nautilusSettings.connect('changed', _onNautilusSettingsChanged);
|
||||
_onNautilusSettingsChanged();
|
||||
}
|
||||
settings = get_schema(SCHEMA);
|
||||
}
|
||||
|
||||
function get_schema(schema) {
|
||||
let extension = ExtensionUtils.getCurrentExtension();
|
||||
|
||||
// check if this extension was built with "make zip-file", and thus
|
||||
// has the schema files in a subfolder
|
||||
// otherwise assume that extension has been installed in the
|
||||
// same prefix as gnome-shell (and therefore schemas are available
|
||||
// in the standard folders)
|
||||
let schemaDir = extension.dir.get_child('schemas');
|
||||
let schemaSource;
|
||||
if (schemaDir.query_exists(null))
|
||||
schemaSource = GioSSS.new_from_directory(schemaDir.get_path(), GioSSS.get_default(), false);
|
||||
else
|
||||
schemaSource = GioSSS.get_default();
|
||||
|
||||
let schemaObj = schemaSource.lookup(schema, true);
|
||||
if (!schemaObj)
|
||||
throw new Error('Schema ' + schema + ' could not be found for extension ' + extension.metadata.uuid + '. Please check your installation.');
|
||||
|
||||
return new Gio.Settings({ settings_schema: schemaObj });
|
||||
}
|
||||
|
||||
function buildPrefsWidget() {
|
||||
initTranslations();
|
||||
let frame = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, border_width: 10, spacing: 10 });
|
||||
|
||||
frame.add(buildSelector('icon-size', _("Size for the desktop icons"), { 'small': _("Small"), 'standard': _("Standard"), 'large': _("Large") }));
|
||||
frame.add(buildSwitcher('show-home', _("Show the personal folder in the desktop")));
|
||||
frame.add(buildSwitcher('show-trash', _("Show the trash icon in the desktop")));
|
||||
frame.show_all();
|
||||
return frame;
|
||||
}
|
||||
|
||||
function buildSwitcher(key, labelText) {
|
||||
let hbox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, spacing: 10 });
|
||||
let label = new Gtk.Label({ label: labelText, xalign: 0 });
|
||||
let switcher = new Gtk.Switch({ active: settings.get_boolean(key) });
|
||||
settings.bind(key, switcher, 'active', 3);
|
||||
hbox.pack_start(label, true, true, 0);
|
||||
hbox.add(switcher);
|
||||
return hbox;
|
||||
}
|
||||
|
||||
function buildSelector(key, labelText, elements) {
|
||||
let listStore = new Gtk.ListStore();
|
||||
listStore.set_column_types ([GObject.TYPE_STRING, GObject.TYPE_STRING]);
|
||||
let schemaKey = settings.settings_schema.get_key(key);
|
||||
let values = schemaKey.get_range().get_child_value(1).get_child_value(0).get_strv();
|
||||
for (let val of values) {
|
||||
let iter = listStore.append();
|
||||
let visibleText = val;
|
||||
if (visibleText in elements)
|
||||
visibleText = elements[visibleText];
|
||||
listStore.set (iter, [0, 1], [visibleText, val]);
|
||||
}
|
||||
let hbox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, spacing: 10 });
|
||||
let label = new Gtk.Label({ label: labelText, xalign: 0 });
|
||||
let combo = new Gtk.ComboBox({model: listStore});
|
||||
let rendererText = new Gtk.CellRendererText();
|
||||
combo.pack_start (rendererText, false);
|
||||
combo.add_attribute (rendererText, 'text', 0);
|
||||
combo.set_id_column(1);
|
||||
settings.bind(key, combo, 'active-id', 3);
|
||||
hbox.pack_start(label, true, true, 0);
|
||||
hbox.add(combo);
|
||||
return hbox;
|
||||
}
|
||||
|
||||
function _onNautilusSettingsChanged() {
|
||||
CLICK_POLICY_SINGLE = nautilusSettings.get_string('click-policy') == 'single';
|
||||
}
|
||||
|
||||
function get_icon_size() {
|
||||
// this one doesn't need scaling because Gnome Shell automagically scales the icons
|
||||
return ICON_SIZE[settings.get_string('icon-size')];
|
||||
}
|
||||
|
||||
function get_desired_width(scale_factor) {
|
||||
return ICON_WIDTH[settings.get_string('icon-size')] * scale_factor;
|
||||
}
|
||||
|
||||
function get_desired_height(scale_factor) {
|
||||
return ICON_HEIGHT[settings.get_string('icon-size')] * scale_factor;
|
||||
}
|
Binary file not shown.
@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<schemalist gettext-domain="desktop-icons">
|
||||
<enum id="org.gnome.shell.extension.desktop-icons.ZoomLevel">
|
||||
<value value="0" nick="small"/>
|
||||
<value value="1" nick="standard"/>
|
||||
<value value="2" nick="large"/>
|
||||
</enum>
|
||||
<schema path="/org/gnome/shell/extensions/desktop-icons/" id="org.gnome.shell.extensions.desktop-icons">
|
||||
<key name="icon-size" enum="org.gnome.shell.extension.desktop-icons.ZoomLevel">
|
||||
<default>'standard'</default>
|
||||
<summary>Icon size</summary>
|
||||
<description>Set the size for the desktop icons.</description>
|
||||
</key>
|
||||
<key type="b" name="show-home">
|
||||
<default>true</default>
|
||||
<summary>Show personal folder</summary>
|
||||
<description>Show the personal folder in the desktop.</description>
|
||||
</key>
|
||||
<key type="b" name="show-trash">
|
||||
<default>true</default>
|
||||
<summary>Show trash icon</summary>
|
||||
<description>Show the trash icon in the desktop.</description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
@ -1,38 +0,0 @@
|
||||
.file-item {
|
||||
padding: 4px;
|
||||
border: 1px;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
.file-item:hover {
|
||||
background-color: rgba(238, 238, 238, 0.2);
|
||||
}
|
||||
|
||||
.name-label {
|
||||
text-shadow: 1px 1px black;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.draggable {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.rename-popup {
|
||||
min-width: 300px;
|
||||
margin: 6px;
|
||||
}
|
||||
|
||||
.create-folder-dialog-entry {
|
||||
width: 20em;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.create-folder-dialog-label {
|
||||
padding-bottom: .4em;
|
||||
}
|
||||
|
||||
.create-folder-dialog-error-box {
|
||||
padding-top: 16px;
|
||||
spacing: 6px;
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" version="1.1">
|
||||
<path style="fill:#bebebe" d="M 8 1 C 7.45 1 7 1.45 7 2 C 7 2.03 6.9998 2.0696 7.0098 2.0996 C 6.513 2.1994 6.0506 2.385 5.6191 2.6191 L 13 10 L 13 9 L 13 7 C 13 4.57 11.28 2.5596 8.9902 2.0996 C 9.0002 2.0696 9 2.03 9 2 C 9 1.45 8.55 1 8 1 z M 4.0078 4.0078 C 3.3813 4.8419 3 5.8717 3 7 L 3 11.5 L 1 13.5 L 1 14 L 3 14 L 13 14 L 14 14 L 4.0078 4.0078 z M 6.2695 15 C 6.6295 15.62 7.29 16 8 16 C 8.71 16 9.3705 15.62 9.7305 15 L 6.2695 15 z"/>
|
||||
<path style="fill:#bebebe" d="M 2.5,1.5 15,14 13.5,15.5 1,3 Z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 596 B |
@ -1,84 +0,0 @@
|
||||
class Extension {
|
||||
constructor(dnd, toggle, indicator, remote, audio){
|
||||
this.dnd = dnd;
|
||||
this.toggle = toggle;
|
||||
this.indicator = indicator;
|
||||
this.remote = remote;
|
||||
this.audio = audio;
|
||||
|
||||
this.enabled = false;
|
||||
|
||||
this.toggle.setToggleState(this.dnd.isEnabled());
|
||||
this.toggle.show();
|
||||
this.toggle.onToggleStateChanged(() => {
|
||||
if (this.toggle.getToggleState()){
|
||||
this.enable();
|
||||
} else {
|
||||
this.disable();
|
||||
}
|
||||
});
|
||||
|
||||
// this.dndID = this.dnd.addStatusListener((dndEnabled) => this._setDND(dndEnabled));
|
||||
|
||||
this.remoteID = this.remote.addRemoteListener((dndEnabled) => this._setDND(dndEnabled));
|
||||
|
||||
this._setDND(this.remote.getRemote());
|
||||
}
|
||||
|
||||
_setDND(enabled){
|
||||
if (enabled){
|
||||
this.enable();
|
||||
} else {
|
||||
this.disable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable do not disturb mode
|
||||
*/
|
||||
enable(){
|
||||
if (this.enabled){
|
||||
return;
|
||||
}
|
||||
this.enabled = true;
|
||||
this.dnd.enable();
|
||||
this.toggle.setToggleState(true);
|
||||
this.indicator.show();
|
||||
this.remote.setRemote(true);
|
||||
this.audio.mute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable do not disturb mode
|
||||
*/
|
||||
disable(){
|
||||
if (!this.enabled){
|
||||
return;
|
||||
}
|
||||
this.enabled = false;
|
||||
this.dnd.disable();
|
||||
this.toggle.setToggleState(false);
|
||||
this.indicator.hide();
|
||||
this.remote.setRemote(false);
|
||||
this.audio.unmute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Boolean} true if it is enabled, otherwise false
|
||||
*/
|
||||
isEnabled(){
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the extension and its components
|
||||
*/
|
||||
destroy(){
|
||||
this.enabled = false;
|
||||
// this.dnd.removeStatusListener(this.dndID);
|
||||
this.remote.removeRemoteListener(this.remoteID);
|
||||
this.toggle.destroy();
|
||||
this.indicator.destroy();
|
||||
this.dnd.disable();
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
|
||||
const BUSY = 2;
|
||||
const AVAILABLE = 0;
|
||||
|
||||
/**
|
||||
* A class which can enable do not disturb mode
|
||||
*/
|
||||
class DoNotDisturb {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Presence} presence the interface to the Gnome Presence API
|
||||
*/
|
||||
constructor(presence){
|
||||
this.presence = presence;
|
||||
this.listeners = [];
|
||||
this.presenceListernerID = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable do not disturb mode
|
||||
*/
|
||||
enable(){
|
||||
this.presence.status = BUSY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable do not disturb mode
|
||||
*/
|
||||
disable(){
|
||||
this.presence.status = AVAILABLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Boolean} true if do not disturb mode is on, otherwise false
|
||||
*/
|
||||
isEnabled(){
|
||||
return this.presence.status == BUSY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a status listener for when the do not disturb mode is toggled
|
||||
* @param {[Boolean => ()]} listener the listener to add
|
||||
* @return {Integer} the ID of the listener
|
||||
*/
|
||||
addStatusListener(listener){
|
||||
|
||||
if (listener == null){
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (this.listeners.length == 0){
|
||||
this.presenceListernerID = this.presence.addStatusListener((status) => {
|
||||
this.listeners.forEach((fn) => {
|
||||
fn(status == BUSY);
|
||||
});
|
||||
});
|
||||
}
|
||||
listener(this.isEnabled());
|
||||
return this.listeners.push(listener) - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the status listener with the ID
|
||||
* @param {Integer} id the ID of the status listener
|
||||
*/
|
||||
removeStatusListener(id){
|
||||
if (id < 0 || id >= this.listeners.length){
|
||||
return;
|
||||
}
|
||||
this.listeners.splice(id, 1);
|
||||
if (this.listeners.length == 0) {
|
||||
this.presence.removeStatusListener(this.presenceListernerID);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const Settings = Me.imports.settings;
|
||||
const System = Me.imports.system;
|
||||
const Widget = Me.imports.widgets;
|
||||
const DND = Me.imports.doNotDisturb;
|
||||
const Extension = Me.imports.dndExtension.Extension;
|
||||
|
||||
/**
|
||||
* Called when the extension is loaded.
|
||||
*/
|
||||
function init() {}
|
||||
|
||||
/**
|
||||
* Enable the do not disturb extension. Adds all UI elements and monitors the settings object.
|
||||
*/
|
||||
function enable() {
|
||||
var dnd = new DND.DoNotDisturb(new System.GnomePresence());
|
||||
var toggle = new Widget.DoNotDisturbToggle();
|
||||
var indicator = new Widget.DoNotDisturbIcon(new Settings.SettingsManager(), new System.NotificationManager());
|
||||
var remote = new Settings.RemoteAPI();
|
||||
var audio = new System.AudioManager(new Settings.SettingsManager());
|
||||
this.extension = new Extension(dnd, toggle, indicator, remote, audio);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the extension. Tears down all UI components.
|
||||
*/
|
||||
function disable() {
|
||||
this.extension.destroy();
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
/* From https://github.com/pop-os/gnome-shell-extension-pop-suspend-button */
|
||||
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
/**
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gettext = imports.gettext;
|
||||
const Config = imports.misc.config;
|
||||
|
||||
function initTranslations(extension) {
|
||||
let localeDir = extension.dir.get_child('locale').get_path();
|
||||
|
||||
// Extension installed in .local
|
||||
if (GLib.file_test(localeDir, GLib.FileTest.EXISTS)) {
|
||||
Gettext.bindtextdomain('gnome-shell-extension-do-not-disturb', localeDir);
|
||||
}
|
||||
// Extension installed system-wide
|
||||
else {
|
||||
Gettext.bindtextdomain('gnome-shell-extension-do-not-disturb',
|
||||
Config.LOCALEDIR);
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,46 +0,0 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Gnome-Shell-Extension Do Not Disturb\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-04-09 18:13+0200\n"
|
||||
"PO-Revision-Date: 2018-10-14 07:30-0400\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German <mike@barkmin.eu>\n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 2.2\n"
|
||||
|
||||
#: prefs.js:31
|
||||
msgid "Enabled Icon"
|
||||
msgstr "Icon anzeigen"
|
||||
|
||||
#: prefs.js:31
|
||||
msgid "Show an indicator icon when do not disturb is enabled."
|
||||
msgstr "Zeige ein Icon, wenn nicht stören aktiviert ist."
|
||||
|
||||
#: prefs.js:38
|
||||
msgid "Count"
|
||||
msgstr "Anzahl"
|
||||
|
||||
#: prefs.js:39
|
||||
msgid "Dot"
|
||||
msgstr "Punkt"
|
||||
|
||||
#: prefs.js:40
|
||||
msgid "Nothing"
|
||||
msgstr "Nichts"
|
||||
|
||||
#: prefs.js:50
|
||||
msgid "Mute Sounds"
|
||||
msgstr "Audio stumm schalten"
|
||||
|
||||
#: prefs.js:50
|
||||
msgid "Mutes all sound when do not disturb is enabled."
|
||||
msgstr "Audio stumm schalten, wenn nicht stören aktiviert ist."
|
||||
|
||||
#: widgets.js:40
|
||||
msgid "Do not disturb"
|
||||
msgstr "Nicht stören"
|
Binary file not shown.
@ -1,18 +0,0 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Gnome-Shell-Extension Do Not Disturb\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-10-14 7:24+0200\n"
|
||||
"PO-Revision-Date: 2018-10-14 07:30-0400\n"
|
||||
"Language-Team: Spanish <kylecorry31@gmail.com>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 2.2\n"
|
||||
"Last-Translator: \n"
|
||||
"Language: es\n"
|
||||
|
||||
#: widgets.js:39
|
||||
msgid "Do not disturb"
|
||||
msgstr "No interrumpir"
|
Binary file not shown.
@ -1,18 +0,0 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Gnome-Shell-Extension Do Not Disturb\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-10-14 7:24+0200\n"
|
||||
"PO-Revision-Date: 2018-10-14 07:30-0400\n"
|
||||
"Language-Team: French <kylecorry31@gmail.com>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 2.2\n"
|
||||
"Last-Translator: \n"
|
||||
"Language: fr\n"
|
||||
|
||||
#: widgets.js:36
|
||||
msgid "Do not disturb"
|
||||
msgstr "Ne pas déranger"
|
Binary file not shown.
@ -1,18 +0,0 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Gnome-Shell-Extension Do Not Disturb\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-10-14 7:24+0200\n"
|
||||
"PO-Revision-Date: 2018-10-14 07:30-0400\n"
|
||||
"Language-Team: Portuguese <kylecorry31@gmail.com>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 2.2\n"
|
||||
"Last-Translator: \n"
|
||||
"Language: pt\n"
|
||||
|
||||
#: widgets.js:39
|
||||
msgid "Do not disturb"
|
||||
msgstr "Não perturbe"
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"_generated": "Generated by SweetTooth, do not edit",
|
||||
"description": "Activate or deactivate do not disturb mode",
|
||||
"name": "Do Not Disturb",
|
||||
"original-author": "kylecorry31@gmail.com",
|
||||
"settings-schema": "org.gnome.shell.extensions.kylecorry31-do-not-disturb",
|
||||
"shell-version": [
|
||||
"3.18",
|
||||
"3.20",
|
||||
"3.22",
|
||||
"3.24",
|
||||
"3.26",
|
||||
"3.28",
|
||||
"3.30",
|
||||
"3.32"
|
||||
],
|
||||
"url": "https://github.com/kylecorry31/gnome-shell-extension-do-not-disturb",
|
||||
"uuid": "donotdisturb@kylecorry31.github.io",
|
||||
"version": 12
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
|
||||
// Adapted from lockkeys@vaina.lt and https://github.com/pop-os/gnome-shell-extension-pop-suspend-button
|
||||
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const Gettext = imports.gettext.domain('gnome-shell-extension-do-not-disturb');
|
||||
const _ = Gettext.gettext;
|
||||
const Settings = Me.imports.settings;
|
||||
const Lib = Me.imports.lib;
|
||||
|
||||
function init() {}
|
||||
|
||||
/**
|
||||
* Builds the GTK widget which displays all of the application specific settings.
|
||||
*
|
||||
* @returns {Gtk.Box} - The frame to display.
|
||||
*/
|
||||
function buildPrefsWidget() {
|
||||
let settings = new Settings.SettingsManager();
|
||||
let frame = new Gtk.Box({
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
border_width: 10,
|
||||
margin: 20,
|
||||
spacing: 8
|
||||
});
|
||||
|
||||
var box = new Gtk.Box({
|
||||
orientation: Gtk.Orientation.HORIZONTAL
|
||||
});
|
||||
|
||||
frame.add(createSwitch(settings.shouldShowIcon(), (b) => {settings.setShowIcon(b); if (b) { box.show(); } else { box.hide(); } }, _("Enabled Icon"), _("Show an indicator icon when do not disturb is enabled.")));
|
||||
|
||||
var indicatorLbl = new Gtk.Label({
|
||||
label: "Notification Indicator",
|
||||
xalign: 0
|
||||
});
|
||||
|
||||
var showCountRadio = createRadioButton(settings.showCount, (b) => { settings.showCount = b }, _("Count"));
|
||||
var showDotRadio = createRadioButton(settings.showDot, (b) => { settings.showDot = b }, _("Dot"), showCountRadio);
|
||||
var showNothingRadio = createRadioButton(!(settings.showCount || settings.showDot), (b) => { }, _("Nothing"), showCountRadio);
|
||||
|
||||
|
||||
box.pack_start(indicatorLbl, true, true, 0);
|
||||
box.add(showCountRadio);
|
||||
box.add(showDotRadio);
|
||||
box.add(showNothingRadio);
|
||||
|
||||
frame.add(box);
|
||||
|
||||
frame.add(createSwitch(settings.shouldMuteSound(), (b) => settings.setShouldMuteSound(b), _("Mute Sounds"), _("Mutes all sound when do not disturb is enabled.")));
|
||||
|
||||
frame.show_all();
|
||||
|
||||
if (!settings.shouldShowIcon()){
|
||||
box.hide();
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
function createRadioButton(active, set, text, group){
|
||||
var widget;
|
||||
if (group){
|
||||
widget = Gtk.RadioButton.new_with_label_from_widget(group, text);
|
||||
widget.set_active(active);
|
||||
} else {
|
||||
widget = new Gtk.RadioButton({
|
||||
active: active,
|
||||
label: text
|
||||
});
|
||||
}
|
||||
widget.connect('notify::active', function(switch_widget) {
|
||||
set(switch_widget.active);
|
||||
});
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a switch setting.
|
||||
*
|
||||
* @param {boolean} active - The starting state of the switch.
|
||||
* @param {(boolean) => ()} set - The setter function which is passed the value of the switch on state change.
|
||||
* @param {string} text - The label of the widget.
|
||||
* @param {string} tooltip - The description text to display on hover.
|
||||
* @returns {Gtk.Box} - The widget containing the switch and label.
|
||||
*/
|
||||
function createSwitch(active, set, text, tooltip) {
|
||||
let box = new Gtk.Box({
|
||||
orientation: Gtk.Orientation.HORIZONTAL
|
||||
});
|
||||
let label = new Gtk.Label({
|
||||
label: text,
|
||||
xalign: 0,
|
||||
tooltip_text: tooltip
|
||||
});
|
||||
let widget = new Gtk.Switch({
|
||||
active: active
|
||||
});
|
||||
widget.connect('notify::active', function(switch_widget) {
|
||||
set(switch_widget.active);
|
||||
});
|
||||
|
||||
box.pack_start(label, true, true, 0);
|
||||
box.add(widget);
|
||||
return box;
|
||||
}
|
||||
|
||||
Lib.initTranslations(Me);
|
Binary file not shown.
@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<schemalist gettext-domain="org-gnome-shell-extensions-kylecorry31-do-not-disturb">
|
||||
<schema path="/org/gnome/shell/extensions/kylecorry31-do-not-disturb/" id="org.gnome.shell.extensions.kylecorry31-do-not-disturb">
|
||||
<key type="b" name="show-icon">
|
||||
<default>true</default>
|
||||
<summary>Show an indicator icon when do not disturb is enabled.</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
<key type="b" name="mute-sounds">
|
||||
<default>false</default>
|
||||
<summary>Mutes all sound when do not disturb is enabled.</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
<key type="b" name="show-dot">
|
||||
<default>false</default>
|
||||
<summary>Shows a dot next to the date when notifications arrive during do not disturb mode.</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
<key type="b" name="show-count">
|
||||
<default>true</default>
|
||||
<summary>Displays the number of notifications during do not disturb mode.</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
<key type="b" name="do-not-disturb">
|
||||
<default>false</default>
|
||||
<summary>Activate the do not disturb mode.</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
@ -1,219 +0,0 @@
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
|
||||
/**
|
||||
* A class which handles all interactions with the settings.
|
||||
*/
|
||||
class SettingsManager {
|
||||
/**
|
||||
* Represents a settings repository, where settings can be modified and read.
|
||||
* @constructor
|
||||
*/
|
||||
constructor() {
|
||||
this.connections = [];
|
||||
this._appSettings = _getSettings();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enable or disable the icon in the system panel when do not disturb mode is enabled.
|
||||
*
|
||||
* @param {boolean} showIcon - True if the icon should be shown, false otherwise.
|
||||
*/
|
||||
setShowIcon(showIcon) {
|
||||
this._appSettings.set_boolean('show-icon', showIcon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the icon should be shown or not.
|
||||
*
|
||||
* @returns {boolean} - True if the icon should be shown when do not disturb is enabled, false otherwise.
|
||||
*/
|
||||
shouldShowIcon() {
|
||||
return this._appSettings.get_boolean('show-icon');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a function when the status of the show icon setting has changed.
|
||||
*
|
||||
* @param {() => ()} fn - The function to call when the show icon setting is changed.
|
||||
*/
|
||||
onShowIconChanged(fn) {
|
||||
var id = this._appSettings.connect('changed::show-icon', fn);
|
||||
this.connections.push(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the sound should be muted when do not disturb is enabled.
|
||||
*
|
||||
* @returns {boolean} - True if the sound should be muted when do not disturb is enabled, false otherwise.
|
||||
*/
|
||||
shouldMuteSound() {
|
||||
return this._appSettings.get_boolean('mute-sounds');
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable the muting of sound when do not disturb mode is enabled.
|
||||
*
|
||||
* @param {boolean} muteSound - True if the sound should be muted when do not disturb is enabled, false otherwise.
|
||||
*/
|
||||
setShouldMuteSound(muteSound) {
|
||||
this._appSettings.set_boolean('mute-sounds', muteSound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a function when the status of the mute sounds setting has changed.
|
||||
*
|
||||
* @param {() => ()} fn - The function to call when the mute sounds setting is changed.
|
||||
*/
|
||||
onMuteSoundChanged(fn) {
|
||||
var id = this._appSettings.connect('changed::mute-sounds', fn);
|
||||
this.connections.push(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to show the count of notifications or not.
|
||||
* @param {Boolean} newShowCount True if the notification count should be shown.
|
||||
*/
|
||||
set showCount(newShowCount){
|
||||
this._appSettings.set_boolean('show-count', newShowCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether to show the notification count.
|
||||
* @return {Boolean} True if the notification count should be shown.
|
||||
*/
|
||||
get showCount(){
|
||||
return this._appSettings.get_boolean('show-count');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a function when the status of the show count setting has changed.
|
||||
*
|
||||
* @param {() => ()} fn - The function to call when the show count setting is changed.
|
||||
*/
|
||||
onShowCountChanged(fn){
|
||||
var id = this._appSettings.connect('changed::show-count', fn);
|
||||
this.connections.push(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to show an indicator of hidden notifications or not.
|
||||
* @param {Boolean} newShowDot True if the notification dot should be shown.
|
||||
*/
|
||||
set showDot(newShowDot){
|
||||
this._appSettings.set_boolean('show-dot', newShowDot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether to show the notification dot.
|
||||
* @return {Boolean} True if the notification dot should be shown.
|
||||
*/
|
||||
get showDot(){
|
||||
return this._appSettings.get_boolean('show-dot');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a function when the status of the show dot setting has changed.
|
||||
*
|
||||
* @param {() => ()} fn - The function to call when the show dot setting is changed.
|
||||
*/
|
||||
onShowDotChanged(fn){
|
||||
var id = this._appSettings.connect('changed::show-dot', fn);
|
||||
this.connections.push(id);
|
||||
}
|
||||
|
||||
disconnectAll() {
|
||||
this.connections.forEach((id) => {
|
||||
this._appSettings.disconnect(id);
|
||||
});
|
||||
this.connections = [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class RemoteAPI {
|
||||
constructor() {
|
||||
this._appSettings = _getSettings();
|
||||
this.listeners = [];
|
||||
|
||||
this.id = this._appSettings.connect('changed::do-not-disturb', () => {
|
||||
this.listeners.forEach((fn) => fn(this.getRemote()));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a function when the status of the do not disturb setting has changed.
|
||||
*
|
||||
* @param {() => ()} listener - The function to call when the do not disturb setting is changed.
|
||||
*/
|
||||
addRemoteListener(listener) {
|
||||
if (listener == null){
|
||||
return -1;
|
||||
}
|
||||
if (this.listeners.length == 0){
|
||||
this.id = this._appSettings.connect('changed::do-not-disturb', () => {
|
||||
this.listeners.forEach((fn) => fn(this.getRemote()));
|
||||
});
|
||||
}
|
||||
return this.listeners.push(listener) - 1;
|
||||
}
|
||||
|
||||
removeRemoteListener(id) {
|
||||
if (id < 0 || id >= this.listeners.length){
|
||||
return;
|
||||
}
|
||||
this.listeners.splice(id, 1);
|
||||
if (this.listeners.length == 0) {
|
||||
this._appSettings.disconnect(this.id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Boolean} true if the external do not disturb is on, false otherwise
|
||||
*/
|
||||
getRemote() {
|
||||
return this._appSettings.get_boolean('do-not-disturb');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Boolean} dnd true if the external do not disturb should be on, false otherwise
|
||||
*/
|
||||
setRemote(dnd) {
|
||||
this._appSettings.set_boolean('do-not-disturb', dnd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A helper function to get the application specific settings. Adapted
|
||||
* from the System76 Pop Suspend Button extension: https://github.com/pop-os/gnome-shell-extension-pop-suspend-button
|
||||
*
|
||||
* @returns {Gio.Settings} - The application specific settings object.
|
||||
*/
|
||||
function _getSettings() {
|
||||
let schemaName = 'org.gnome.shell.extensions.kylecorry31-do-not-disturb';
|
||||
let schemaDir = Me.dir.get_child('schemas').get_path();
|
||||
|
||||
// Extension installed in .local
|
||||
if (GLib.file_test(schemaDir + '/gschemas.compiled', GLib.FileTest.EXISTS)) {
|
||||
let schemaSource = Gio.SettingsSchemaSource.new_from_directory(schemaDir,
|
||||
Gio.SettingsSchemaSource.get_default(),
|
||||
false);
|
||||
let schema = schemaSource.lookup(schemaName, false);
|
||||
|
||||
return new Gio.Settings({
|
||||
settings_schema: schema
|
||||
});
|
||||
}
|
||||
// Extension installed system-wide
|
||||
else {
|
||||
if (Gio.Settings.list_schemas().indexOf(schemaName) == -1)
|
||||
throw "Schema \"%s\" not found.".format(schemaName);
|
||||
return new Gio.Settings({
|
||||
schema: schemaName
|
||||
});
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
.clear-button {
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
.do-not-disturb:active {
|
||||
background-color: inherit !important;
|
||||
}
|
||||
|
||||
.do-not-disturb-icon {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
-st-icon-style: symbolic;
|
||||
}
|
||||
|
||||
.hide-dot {
|
||||
color: transparent;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.notification-count {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
@ -1,156 +0,0 @@
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Main = imports.ui.main;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const GnomeSession = imports.misc.gnomeSession;
|
||||
const Settings = Me.imports.settings;
|
||||
|
||||
/**
|
||||
* A class for interacting with the Gnome Presence API.
|
||||
*/
|
||||
class GnomePresence {
|
||||
/**
|
||||
* Construct a new GnomePresence proxy.
|
||||
*/
|
||||
constructor() {
|
||||
this._presence = new GnomeSession.Presence();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status of GnomePresence.
|
||||
* @param {number} newStatus A GnomeSession.PresenceStatus status constant to switch to.
|
||||
*/
|
||||
set status(newStatus) {
|
||||
this._presence.SetStatusSync(newStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of GnomePresence.
|
||||
* @return {number} The current GnomeSession.PresenceStatus status.
|
||||
*/
|
||||
get status() {
|
||||
return this._presence.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener to the GnomePresence status.
|
||||
* @param {Function} fn The function to run when the status is changed (passed the current status).
|
||||
* @return {number} The ID of the listener, used by removeStatusListener.
|
||||
*/
|
||||
addStatusListener(fn) {
|
||||
return this._presence.connectSignal('StatusChanged', (proxy, _sender, [status]) => {
|
||||
if (proxy.status != status) {
|
||||
fn(status);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a status listener to the GnomePresence status.
|
||||
* @param {number} listenerID The ID of the listener to remove.
|
||||
*/
|
||||
removeStatusListener(listenerID) {
|
||||
this._presence.disconnectSignal(listenerID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class for managing the audio on Gnome.
|
||||
*/
|
||||
class AudioManager {
|
||||
|
||||
constructor(settingsManager){
|
||||
this._settings = settingsManager;
|
||||
this.shouldMute = this._settings.shouldMuteSound();
|
||||
this.muted = false;
|
||||
this._settings.onMuteSoundChanged(() => {
|
||||
var shouldMute = this._settings.shouldMuteSound();
|
||||
if (this.muted && shouldMute){
|
||||
this._internalMute();
|
||||
} else if (this.muted && !shouldMute){
|
||||
this._internalUnmute();
|
||||
}
|
||||
this.shouldMute = shouldMute;
|
||||
});
|
||||
}
|
||||
|
||||
_internalMute(){
|
||||
_runCmd(["amixer", "-q", "-D", "pulse", "sset", "Master", "mute"]);
|
||||
}
|
||||
|
||||
_internalUnmute(){
|
||||
_runCmd(["amixer", "-q", "-D", "pulse", "sset", "Master", "unmute"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mute the audio stream.
|
||||
*/
|
||||
mute() {
|
||||
this.muted = true;
|
||||
if (this.shouldMute){
|
||||
this._internalMute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmute the audio stream.
|
||||
*/
|
||||
unmute() {
|
||||
this.muted = false;
|
||||
if (this.shouldMute){
|
||||
this._internalUnmute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NotificationManager {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current number of notifications in the system tray.
|
||||
* @return {number} The number of notifications.
|
||||
*/
|
||||
get notificationCount(){
|
||||
var count = 0;
|
||||
Main.messageTray.getSources().forEach(n => count += n.count);
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if there are any notifications.
|
||||
* @return {Boolean} True if there are notifications, false otherwise
|
||||
*/
|
||||
get hasNotifications(){
|
||||
return notificationCount() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener for when the notification count changes.
|
||||
* @param {Function} fn The function to call when the notification count changes (passed the current notification count).
|
||||
* @return {number array} The IDs of the listeners.
|
||||
*/
|
||||
addNotificationCountListener(fn){
|
||||
var id1 = Main.messageTray.connect('source-added', () => fn(this.notificationCount));
|
||||
var id2 = Main.messageTray.connect('source-removed', () => fn(this.notificationCount));
|
||||
var id3 = Main.messageTray.connect('queue-changed', () => fn(this.notificationCount));
|
||||
|
||||
return [id1, id2, id3];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a notification count listener.
|
||||
* @param {number array} ids The ID of the listener to remove.
|
||||
*/
|
||||
removeNotificationCountListener(ids){
|
||||
ids.forEach((id) => {
|
||||
Main.messageTray.disconnect(id);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function _runCmd(cmd) {
|
||||
GLib.spawn_sync(null, cmd, null, GLib.SpawnFlags.SEARCH_PATH, null);
|
||||
}
|
@ -1,236 +0,0 @@
|
||||
const Lang = imports.lang;
|
||||
const Main = imports.ui.main;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const St = imports.gi.St;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Gettext = imports.gettext.domain('gnome-shell-extension-do-not-disturb');
|
||||
const _ = Gettext.gettext;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const Lib = Me.imports.lib;
|
||||
|
||||
|
||||
/**
|
||||
* A class which handles the UI of the do not disturb toggle.
|
||||
*/
|
||||
class DoNotDisturbToggle {
|
||||
/**
|
||||
* Represents a do not disturb toggle in the calendar/notification popup.
|
||||
* @constructor
|
||||
*/
|
||||
constructor() {
|
||||
this._connections = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the do not disturb toggle in the calendar/notification popup.
|
||||
*/
|
||||
show() {
|
||||
this._clearButton = Main.panel.statusArea.dateMenu._messageList._clearButton;
|
||||
|
||||
this._calendarBox = this._clearButton.get_parent();
|
||||
|
||||
this._clearBox = new St.BoxLayout({
|
||||
vertical: false,
|
||||
x_expand: true,
|
||||
y_expand: false
|
||||
});
|
||||
|
||||
|
||||
this._disturbToggle = new PopupMenu.PopupSwitchMenuItem(_("Do not disturb"));
|
||||
|
||||
this._disturbToggle.actor.add_style_class_name('do-not-disturb');
|
||||
this._disturbToggle.actor.set_x_expand(true);
|
||||
this._disturbToggle.actor.track_hover = false;
|
||||
|
||||
this._disturbToggle.actor.set_x_align(Clutter.ActorAlign.START);
|
||||
this._disturbToggle.actor.remove_child(this._disturbToggle.label);
|
||||
this._disturbToggle.actor.add_child(this._disturbToggle.label);
|
||||
this._disturbToggle.label.set_y_align(Clutter.ActorAlign.CENTER);
|
||||
this._disturbToggle.actor.remove_child(this._disturbToggle._ornamentLabel);
|
||||
|
||||
this._clearBox.add_actor(this._disturbToggle.actor);
|
||||
|
||||
this._clearButton.reparent(this._clearBox);
|
||||
this._clearButton.add_style_class_name('clear-button');
|
||||
|
||||
this._calendarBox.add_actor(this._clearBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys all UI elements of the toggle and returns the clear button to its proper location.
|
||||
*/
|
||||
destroy() {
|
||||
if (this._disturbToggle) {
|
||||
this._connections.forEach((id) => {
|
||||
this._disturbToggle.disconnect(id);
|
||||
});
|
||||
this._connections = [];
|
||||
this._disturbToggle.destroy();
|
||||
this._disturbToggle = 0;
|
||||
}
|
||||
|
||||
if (this._clearButton) {
|
||||
this._clearButton.reparent(this._calendarBox);
|
||||
this._clearButton.remove_style_class_name('clear-button');
|
||||
}
|
||||
|
||||
if (this._clearBox) {
|
||||
this._clearBox.destroy();
|
||||
this._clearBox = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the activation state of the toggle.
|
||||
*
|
||||
* @param {boolean} state - The state of the toggle: true for on, false for off.
|
||||
*/
|
||||
setToggleState(state) {
|
||||
if (this._disturbToggle) {
|
||||
this._disturbToggle.setToggleState(state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the activation state of the toggle.
|
||||
*
|
||||
* @returns {boolean} - True if the toggle is on, false otherwise.
|
||||
*/
|
||||
getToggleState() {
|
||||
if (this._disturbToggle) {
|
||||
return this._disturbToggle._switch.state;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a function when the toggle state changes.
|
||||
*
|
||||
* @param {() => ()} fn - The function to call when the toggle state changes.
|
||||
*/
|
||||
onToggleStateChanged(fn) {
|
||||
if (this._disturbToggle) {
|
||||
var id = this._disturbToggle.connect("toggled", (item, event) => fn());
|
||||
this._connections.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class which handles the UI of the do not disturb status icon.
|
||||
*/
|
||||
class DoNotDisturbIcon {
|
||||
/**
|
||||
* Represents a do not disturb icon in the system status area of the panel.
|
||||
* @constructor
|
||||
*/
|
||||
constructor(settingsManager, notificationCounter) {
|
||||
this._settings = settingsManager;
|
||||
this.notificationCounter = notificationCounter;
|
||||
this._indicatorArea = Main.panel._centerBox; //statusArea.aggregateMenu._indicators;
|
||||
|
||||
let icon = "notification-disabled-symbolic";
|
||||
let fallback = "user-busy-symbolic";
|
||||
|
||||
this._enabledIcon = new St.Icon({
|
||||
icon_name: icon,
|
||||
fallback_icon_name: fallback,
|
||||
style_class: 'popup-menu-icon do-not-disturb-icon'
|
||||
});
|
||||
|
||||
this._countLbl = new St.Label();
|
||||
this.updateCount(0);
|
||||
this._countLbl.add_style_class_name("notification-count");
|
||||
|
||||
this._iconBox = new St.BoxLayout();
|
||||
this._iconBox.add_actor(this._enabledIcon);
|
||||
this._iconBox.add_actor(this._countLbl);
|
||||
this.showDot = this._settings.showDot;
|
||||
this.showCount = this._settings.showCount;
|
||||
this.showIcon = this._settings.shouldShowIcon();
|
||||
this.shown = false;
|
||||
this.count = this.notificationCounter.notificationCount;
|
||||
this.updateCount(this.count);
|
||||
|
||||
this._settings.onShowIconChanged(() => {
|
||||
this.showIcon = this._settings.shouldShowIcon();
|
||||
if (this.shown){
|
||||
this.hide();
|
||||
this.show();
|
||||
}
|
||||
});
|
||||
this._settings.onShowCountChanged(() => {
|
||||
this.showCount = this._settings.showCount;
|
||||
this.updateCount(this.count);
|
||||
});
|
||||
this._settings.onShowDotChanged(() => {
|
||||
this.showDot = this._settings.showDot;
|
||||
this.updateCount(this.count);
|
||||
});
|
||||
this.notificationListenerID = this.notificationCounter.addNotificationCountListener((count) => {
|
||||
this.updateCount(count);
|
||||
});
|
||||
}
|
||||
|
||||
updateCount(newCount){
|
||||
this.count = newCount;
|
||||
if (newCount == 0){
|
||||
this._countLbl.add_style_class_name("hide-dot");
|
||||
} else {
|
||||
if (this.showCount){
|
||||
this._countLbl.set_text("" + newCount);
|
||||
this._countLbl.remove_style_class_name("hide-dot");
|
||||
} else if(this.showDot){
|
||||
this._countLbl.set_text("\u25CF");
|
||||
this._countLbl.remove_style_class_name("hide-dot");
|
||||
} else {
|
||||
this._countLbl.add_style_class_name("hide-dot");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the status icon.
|
||||
*/
|
||||
show() {
|
||||
if (this.showIcon){
|
||||
this._indicatorArea.add_child(this._iconBox);
|
||||
Main.panel.statusArea.dateMenu._indicator.actor.add_style_class_name("hide-dot");
|
||||
}
|
||||
this.shown = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the status icon.
|
||||
*/
|
||||
hide() {
|
||||
Main.panel.statusArea.dateMenu._indicator.actor.remove_style_class_name("hide-dot");
|
||||
if (this._iconBox.get_parent()) {
|
||||
this._indicatorArea.remove_child(this._iconBox);
|
||||
}
|
||||
this.shown = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the status icon and removes it from the system status area.
|
||||
*/
|
||||
destroy() {
|
||||
if (this._enabledIcon) {
|
||||
if (this._iconBox.get_parent()) {
|
||||
this._indicatorArea.remove_child(this._iconBox);
|
||||
}
|
||||
this._countLbl.destroy();
|
||||
this._countLbl = 0;
|
||||
this._enabledIcon.destroy();
|
||||
this._enabledIcon = 0;
|
||||
this._iconBox.destroy();
|
||||
this._iconBox = 0;
|
||||
}
|
||||
this._settings.disconnectAll();
|
||||
this.notificationCounter.removeNotificationCountListener(this.notificationListenerID);
|
||||
}
|
||||
}
|
||||
|
||||
Lib.initTranslations(Me);
|
@ -1,33 +0,0 @@
|
||||
const Main = imports.ui.main;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Config = imports.misc.config;
|
||||
|
||||
let _id;
|
||||
|
||||
function _disable_hot_corners() {
|
||||
// Disables all hot corners
|
||||
Main.layoutManager.hotCorners.forEach(function(hot_corner) {
|
||||
if (!hot_corner) {
|
||||
return;
|
||||
}
|
||||
|
||||
hot_corner._toggleOverview = function() {};
|
||||
hot_corner._pressureBarrier._trigger = function() {};
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
}
|
||||
|
||||
function enable() {
|
||||
_disable_hot_corners();
|
||||
// Hot corners may be re-created afterwards (for example, If there's a monitor change).
|
||||
// So we catch all changes.
|
||||
_id = Main.layoutManager.connect('hot-corners-changed', _disable_hot_corners);
|
||||
}
|
||||
|
||||
function disable() {
|
||||
// Disconnects the callback and re-creates the hot corners
|
||||
Main.layoutManager.disconnect(_id);
|
||||
Main.layoutManager._updateHotCorners();
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
{
|
||||
"_generated": "Generated by SweetTooth, do not edit",
|
||||
"description": "This extension disables the top left hot corners. You can still click on Activities or press the dedicated key to reach the overview. Since 3.8, should work with other extensions modifying the Activities button. On versions prior to 3.8, may not disable other hotcorners in multiscreen configurations and won't work on fallback/flashback mode.",
|
||||
"name": "No Topleft Hot Corner",
|
||||
"shell-version": [
|
||||
"3.8",
|
||||
"3.10",
|
||||
"3.12",
|
||||
"3.14",
|
||||
"3.16",
|
||||
"3.18",
|
||||
"3.20",
|
||||
"3.22",
|
||||
"3.24",
|
||||
"3.26",
|
||||
"3.28",
|
||||
"3.30",
|
||||
"3.32"
|
||||
],
|
||||
"url": "https://github.com/HROMANO/nohotcorner/",
|
||||
"uuid": "nohotcorner@azuri.free.fr",
|
||||
"version": 19
|
||||
}
|
@ -1,270 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Thiago Bellini <hackedbellini@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Some parts of this code were forked from message-notifier:
|
||||
* https://extensions.gnome.org/extension/150/message-notifier/
|
||||
* The idea of setting the menu red were inspired by pidgin-persistent-notification:
|
||||
* https://extensions.gnome.org/extension/170/pidgin-peristent-notification
|
||||
*
|
||||
*/
|
||||
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Main = imports.ui.main;
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
const GnomeSession = imports.misc.gnomeSession;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Lib = Me.imports.lib;
|
||||
|
||||
const SETTING_BLINK_RATE = 'blinkrate';
|
||||
const SETTING_COLOR = 'color';
|
||||
const SETTING_CHAT_ONLY = 'chatonly';
|
||||
const SETTING_FORCE = 'force';
|
||||
const SETTING_BLACKLIST = 'application-list';
|
||||
const SETTING_FILTER_TYPE = 'filter';
|
||||
|
||||
let settings, messageStyleHandler;
|
||||
let originalCountUpdated, originalDestroy;
|
||||
|
||||
function _MessageStyleHandler() {
|
||||
|
||||
/*
|
||||
Public API
|
||||
*/
|
||||
|
||||
this.init = function() {
|
||||
this._signals = {};
|
||||
this._statusChangedId = null;
|
||||
this._loopTimeoutId = null;
|
||||
this._oldStyle = null;
|
||||
this._hasStyleAdded = false;
|
||||
|
||||
this._presence = new GnomeSession.Presence(
|
||||
Lang.bind(this, function(proxy, error) {
|
||||
if (error) {
|
||||
logError(error, 'Error while reading gnome-session presence');
|
||||
return;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
this.enable = function() {
|
||||
this._statusChangedId = this._presence.connectSignal(
|
||||
'StatusChanged', Lang.bind(this, function(proxy, senderName, [status]) {
|
||||
this._presence.status = status;
|
||||
this._onNotificationsSwitchToggled();
|
||||
}));
|
||||
|
||||
// Connect settings change events, so we can update message style
|
||||
// as soon as the user makes the change
|
||||
this._connectSetting(SETTING_COLOR);
|
||||
this._connectSetting(SETTING_CHAT_ONLY);
|
||||
this._connectSetting(SETTING_FORCE);
|
||||
this._connectSetting(SETTING_BLINK_RATE);
|
||||
|
||||
// Check for existing message counters when extension were
|
||||
// loaded on an already running shell.
|
||||
this.updateMessageStyle();
|
||||
}
|
||||
|
||||
this.disable = function() {
|
||||
this._presence.disconnectSignal(this._statusChangedId);
|
||||
for (let key in this._signals) {
|
||||
settings.disconnect(this._signals[key]);
|
||||
delete this._signals[key];
|
||||
}
|
||||
|
||||
this._removeMessageStyle();
|
||||
}
|
||||
|
||||
this.updateMessageStyle = function() {
|
||||
this.notificationStatus =
|
||||
(this._presence.status != GnomeSession.PresenceStatus.BUSY);
|
||||
let sources = Main.messageTray.getSources();
|
||||
|
||||
if (settings.get_boolean(SETTING_FORCE) || this.notificationStatus) {
|
||||
let chatOnly = settings.get_boolean(SETTING_CHAT_ONLY);
|
||||
let filter = settings.get_int(SETTING_FILTER_TYPE);
|
||||
let currentItems = settings.get_strv(SETTING_BLACKLIST);
|
||||
currentItems = Lib.getAppNamesFromAppInfos(currentItems);
|
||||
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
let source = sources[i];
|
||||
|
||||
if (chatOnly && !source.isChat) {
|
||||
// The user choose to only be alerted by real chat notifications
|
||||
continue;
|
||||
}
|
||||
if (source.isMuted) {
|
||||
// Do not alert for muted notifications
|
||||
continue;
|
||||
}
|
||||
if((filter == 0) && (currentItems.indexOf(source.title) != -1)) {
|
||||
// Blacklist
|
||||
continue;
|
||||
}
|
||||
if((filter == 1) && (currentItems.indexOf(source.title) == -1)) {
|
||||
// Whitelist
|
||||
continue;
|
||||
}
|
||||
if (this._hasNotifications(source)) {
|
||||
this._addMessageStyle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If for above ended without adding the style, that means there's
|
||||
// no counter and we need to remove the message style.
|
||||
this._removeMessageStyle();
|
||||
}
|
||||
|
||||
/*
|
||||
Private
|
||||
*/
|
||||
|
||||
this._connectSetting = function(setting) {
|
||||
this._signals[setting] = settings.connect(
|
||||
"changed::" + setting, Lang.bind(this, this._onSettingsChanged));
|
||||
}
|
||||
|
||||
this._hasNotifications = function(source) {
|
||||
if (source.countVisible) {
|
||||
return true;
|
||||
}
|
||||
for (let n = 0; n < source.notifications.length; n++) {
|
||||
if (!source.notifications[n].resident) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this._toggleStyle = function() {
|
||||
if (!this._hasStyleAdded) {
|
||||
// Notifications may have been cleared since loop timer was added,
|
||||
// return false to stop the timeout. Just a precaution, should not happen
|
||||
return false;
|
||||
}
|
||||
|
||||
let dateMenu = Main.panel.statusArea.dateMenu;
|
||||
let actualStyle = (dateMenu.actor.style) ? dateMenu.actor.style : "";
|
||||
|
||||
let userStyle = "color: " + settings.get_string(SETTING_COLOR) + ";";
|
||||
|
||||
dateMenu.actor.style = (dateMenu.actor.style == this._oldStyle) ?
|
||||
actualStyle.concat(userStyle) : this._oldStyle;
|
||||
|
||||
|
||||
// keep looping
|
||||
return true;
|
||||
}
|
||||
|
||||
this._addMessageStyle = function() {
|
||||
if (this._hasStyleAdded) {
|
||||
this._removeMessageStyle();
|
||||
}
|
||||
|
||||
let dateMenu = Main.panel.statusArea.dateMenu;
|
||||
let loopDelay = settings.get_int(SETTING_BLINK_RATE);
|
||||
|
||||
this._oldStyle = dateMenu.actor.style;
|
||||
this._hasStyleAdded = true;
|
||||
|
||||
if (loopDelay > 0) {
|
||||
this._loopTimeoutId = Mainloop.timeout_add(
|
||||
loopDelay, Lang.bind(this, this._toggleStyle))
|
||||
} else {
|
||||
this._toggleStyle();
|
||||
}
|
||||
}
|
||||
|
||||
this._removeMessageStyle = function() {
|
||||
if (!this._hasStyleAdded) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._hasStyleAdded = false;
|
||||
if (this._loopTimeoutId != null) {
|
||||
// Stop the looping
|
||||
Mainloop.source_remove(this._loopTimeoutId);
|
||||
this._loopTimeoutId = null;
|
||||
}
|
||||
|
||||
let dateMenu = Main.panel.statusArea.dateMenu;
|
||||
dateMenu.actor.style = this._oldStyle;
|
||||
this._oldStyle = null;
|
||||
}
|
||||
|
||||
/*
|
||||
Callbacks
|
||||
*/
|
||||
|
||||
this._onSettingsChanged = function() {
|
||||
this.updateMessageStyle();
|
||||
}
|
||||
|
||||
this._onNotificationsSwitchToggled = function() {
|
||||
this.updateMessageStyle();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Monkey-patchs for MessageTray.Source
|
||||
*/
|
||||
|
||||
function _countUpdated() {
|
||||
originalCountUpdated.call(this);
|
||||
|
||||
messageStyleHandler.updateMessageStyle();
|
||||
}
|
||||
|
||||
function _destroy() {
|
||||
originalDestroy.call(this);
|
||||
|
||||
messageStyleHandler.updateMessageStyle();
|
||||
}
|
||||
|
||||
/*
|
||||
Shell-extensions handlers
|
||||
*/
|
||||
|
||||
function init() {
|
||||
Lib.initTranslations(Me);
|
||||
settings = Lib.getSettings(Me);
|
||||
|
||||
messageStyleHandler = new _MessageStyleHandler();
|
||||
messageStyleHandler.init();
|
||||
}
|
||||
|
||||
function enable() {
|
||||
originalCountUpdated = MessageTray.Source.prototype.countUpdated;
|
||||
originalDestroy = MessageTray.Source.prototype.destroy;
|
||||
|
||||
MessageTray.Source.prototype.countUpdated = _countUpdated;
|
||||
MessageTray.Source.prototype.destroy = _destroy;
|
||||
|
||||
messageStyleHandler.enable();
|
||||
}
|
||||
|
||||
function disable() {
|
||||
MessageTray.Source.prototype.countUpdated = originalCountUpdated;
|
||||
MessageTray.Source.prototype.destroy = originalDestroy;
|
||||
|
||||
messageStyleHandler.disable();
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Thiago Bellini <hackedbellini@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Most of this code was forked from gnome-shell-extensions convenience.js:
|
||||
* http://git.gnome.org/browse/gnome-shell-extensions/tree/lib/convenience.js
|
||||
*
|
||||
*/
|
||||
|
||||
const Gettext = imports.gettext;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Gio = imports.gi.Gio;
|
||||
|
||||
const Config = imports.misc.config;
|
||||
|
||||
/*
|
||||
Extension utils
|
||||
*/
|
||||
|
||||
function initTranslations(extension) {
|
||||
// This is the same as UUID from metadata.json
|
||||
let domain = 'gnome-shell-notifications-alert';
|
||||
|
||||
// check if this extension was built with "make zip-file", and thus
|
||||
// has the locale files in a subfolder
|
||||
// otherwise assume that extension has been installed in the
|
||||
// same prefix as gnome-shell
|
||||
let localeDir = extension.dir.get_child('locale');
|
||||
if (localeDir.query_exists(null)) {
|
||||
Gettext.bindtextdomain(domain, localeDir.get_path());
|
||||
} else {
|
||||
Gettext.bindtextdomain(domain, Config.LOCALEDIR);
|
||||
}
|
||||
}
|
||||
|
||||
function getSettings(extension) {
|
||||
let schema = 'org.gnome.shell.extensions.notifications-alert';
|
||||
|
||||
const GioSSS = Gio.SettingsSchemaSource;
|
||||
|
||||
// check if this extension was built with "make zip-file", and thus
|
||||
// has the schema files in a subfolder
|
||||
// otherwise assume that extension has been installed in the
|
||||
// same prefix as gnome-shell (and therefore schemas are available
|
||||
// in the standard folders)
|
||||
let schemaDir = extension.dir.get_child('schemas');
|
||||
let schemaSource;
|
||||
if (schemaDir.query_exists(null)) {
|
||||
schemaSource = GioSSS.new_from_directory(schemaDir.get_path(),
|
||||
GioSSS.get_default(),
|
||||
false);
|
||||
} else {
|
||||
schemaSource = GioSSS.get_default();
|
||||
}
|
||||
|
||||
let schemaObj = schemaSource.lookup(schema, true);
|
||||
if (!schemaObj) {
|
||||
throw new Error('Schema ' + schema + ' could not be found for extension ' +
|
||||
extension.metadata.uuid + '. Please check your installation.');
|
||||
}
|
||||
|
||||
return new Gio.Settings({settings_schema: schemaObj});
|
||||
}
|
||||
|
||||
/*
|
||||
Color utils
|
||||
*/
|
||||
|
||||
function _scaleRound(value) {
|
||||
// Based on gtk/gtkcoloreditor.c
|
||||
value = Math.floor((value / 255) + 0.5);
|
||||
value = Math.max(value, 0);
|
||||
value = Math.min(value, 255);
|
||||
return value;
|
||||
}
|
||||
|
||||
function _dec2Hex(value) {
|
||||
value = value.toString(16);
|
||||
|
||||
while (value.length < 2) {
|
||||
value = '0' + value;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function getColorByHexadecimal(hex) {
|
||||
let colorArray = Gdk.Color.parse(hex);
|
||||
let color = null;
|
||||
|
||||
if (colorArray[0]) {
|
||||
color = colorArray[1];
|
||||
} else {
|
||||
// On any error, default to red
|
||||
color = new Gdk.Color({red: 65535});
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
function getHexadecimalByColor(color) {
|
||||
let red = _scaleRound(color.red);
|
||||
let green = _scaleRound(color.green);
|
||||
let blue = _scaleRound(color.blue);
|
||||
return "#" + _dec2Hex(red) + _dec2Hex(green) + _dec2Hex(blue);
|
||||
}
|
||||
|
||||
function getAppNamesFromAppInfos(list) {
|
||||
let appNames = [ ];
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
let id = list[i];
|
||||
let appInfo = Gio.DesktopAppInfo.new(id);
|
||||
if (!appInfo)
|
||||
continue;
|
||||
appNames.push(appInfo.get_name());
|
||||
}
|
||||
return appNames;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user