[gnome-shell/gbsneto/icon-grid-dnd: 41/43] appIcon: Create and delete folders with DnD



commit 854922866bc07a6ed21f134dce0cea2c63bba466
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Tue Jul 16 17:51:25 2019 -0300

    appIcon: Create and delete folders with DnD
    
    Create a new folder when dropping an icon over another
    icon. Try and find a good folder name by looking into
    the categories of the applications.
    
    Delete the folder when removing the last icon.
    
    https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/603

 js/ui/appDisplay.js | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 119 insertions(+), 3 deletions(-)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index cf71d7b10..79437e24a 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -85,6 +85,44 @@ function clamp(value, min, max) {
     return Math.max(min, Math.min(max, value));
 }
 
+function _findBestFolderName(apps) {
+    let appInfos = apps.map(app => app.get_app_info());
+
+    let categoryCounter = {};
+    let commonCategories = [];
+
+    appInfos.reduce((categories, appInfo) => {
+        for (let category of appInfo.get_categories().split(';')) {
+            if (!(category in categoryCounter))
+                categoryCounter[category] = 0;
+
+            categoryCounter[category] += 1;
+
+            // If a category is present in all apps, its counter will
+            // reach appInfos.length
+            if (category.length > 0 &&
+                categoryCounter[category] == appInfos.length) {
+                categories.push(category);
+            }
+        }
+        return categories;
+    }, commonCategories);
+
+    for (let category of commonCategories) {
+        let keyfile = new GLib.KeyFile();
+        let path = 'desktop-directories/%s.directory'.format(category);
+
+        try {
+            keyfile.load_from_data_dirs(path, GLib.KeyFileFlags.NONE);
+            return keyfile.get_locale_string('Desktop Entry', 'Name', null);
+        } catch (e) {
+            continue;
+        }
+    }
+
+    return null;
+}
+
 class BaseAppView {
     constructor(params, gridParams) {
         if (this.constructor === BaseAppView)
@@ -911,6 +949,46 @@ var AllView = class AllView extends BaseAppView {
         this._nEventBlockerInhibits--;
         this._eventBlocker.visible = this._nEventBlockerInhibits == 0;
     }
+
+    createFolder(apps, position=-1) {
+        let newFolderId = GLib.uuid_string_random();
+
+        let folders = this._folderSettings.get_strv('folder-children');
+        folders.push(newFolderId);
+        this._folderSettings.set_strv('folder-children', folders);
+
+        // Position the new folder before creating it
+        if (position >= 0) {
+            let iconsData = this._gridSettings.get_value('icons-data').deep_unpack();
+            iconsData[newFolderId] = new GLib.Variant('a{sv}', {
+                'position': GLib.Variant.new_uint32(position),
+            });
+            this._gridSettings.set_value('icons-data',
+                new GLib.Variant('a{sv}', iconsData));
+        }
+
+        // Create the new folder. We are cannot use but Gio.Settings.new_with_path()
+        // for that.
+        let newFolderPath = this._folderSettings.path.concat('folders/', newFolderId, '/');
+        let newFolderSettings = Gio.Settings.new_with_path('org.gnome.desktop.app-folders.folder',
+                                                           newFolderPath);
+        if (!newFolderSettings) {
+            log('Error creating new folder');
+            return false;
+        }
+
+        let appItems = apps.map(id => this._items[id].app);
+        let folderName = _findBestFolderName(appItems);
+        if (!folderName)
+            folderName = _("Unnamed Folder");
+
+        newFolderSettings.delay();
+        newFolderSettings.set_string('name', folderName);
+        newFolderSettings.set_strv('apps', apps);
+        newFolderSettings.apply();
+
+        return true;
+    }
 };
 Signals.addSignalMethods(AllView.prototype);
 
@@ -1264,11 +1342,12 @@ var AppSearchProvider = class AppSearchProvider {
 };
 
 var FolderView = class FolderView extends BaseAppView {
-    constructor(folder, parentView) {
+    constructor(folder, id, parentView) {
         super(null, null);
         // If it not expand, the parent doesn't take into account its preferred_width when allocating
         // the second time it allocates, so we apply the "Standard hack for ClutterBinLayout"
         this._grid.x_expand = true;
+        this._id = id;
         this._folder = folder;
         this._parentView = parentView;
 
@@ -1450,7 +1529,30 @@ var FolderView = class FolderView extends BaseAppView {
 
         folderApps.splice(index, 1);
 
-        this._folder.set_strv('apps', folderApps);
+        // Remove the folder if this is the last app icon; otherwise,
+        // just remove the icon
+        if (folderApps.length == 0) {
+            let settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders' });
+            let folders = settings.get_strv('folder-children');
+            folders.splice(folders.indexOf(this._id), 1);
+            settings.set_strv('folder-children', folders);
+
+            // Resetting all keys deletes the relocatable schema
+            let keys = this._folder.settings_schema.list_keys();
+            for (let key of keys)
+                this._folder.reset(key);
+
+            // Remove the folder from the custom position list too
+            settings = new Gio.Settings({ schema_id: 'org.gnome.shell' });
+            let iconsData = settings.get_value('icons-data').deep_unpack();
+            if (iconsData[this._id]) {
+                delete iconsData[this._id];
+                settings.set_value('icons-data',
+                    new GLib.Variant('a{sv}', iconsData));
+            }
+        } else {
+            this._folder.set_strv('apps', folderApps);
+        }
 
         return true;
     }
@@ -1699,7 +1801,7 @@ var FolderIcon = class FolderIcon extends BaseViewIcon {
         this.actor.set_child(this.icon);
         this.actor.label_actor = this.icon.label;
 
-        this._folderView = new FolderView(this._folder, parentView);
+        this._folderView = new FolderView(this._folder, id, parentView);
 
         this.actor.connect('clicked', this.open.bind(this));
         this.actor.connect('destroy', this.onDestroy.bind(this));
@@ -2306,6 +2408,20 @@ var AppIcon = class AppIcon extends BaseViewIcon {
                (source instanceof AppIcon) &&
                (this.view instanceof AllView);
     }
+
+    acceptDrop(source, actor, x, y, time) {
+        if (!super.acceptDrop(source, actor, x, y, time))
+            return false;
+
+        let apps = [this.id, source.id];
+        let visibleItems = this.view.getAllItems().filter(item => item.actor.visible);
+        let position = visibleItems.indexOf(this);
+
+        if (visibleItems.indexOf(source) < position)
+            position -= 1;
+
+        return this.view.createFolder(apps, position);
+    }
 };
 Signals.addSignalMethods(AppIcon.prototype);
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]