
/* DING: Desktop Icons New Generation for GNOME Shell
 *
 * Copyright (C) 2021 Sundeep Mediratta (smedius@gmail.com)
 * Copyright (C) 2019 Sergio Costas (rastersoft@gmail.com)
 * Based on code original (C) Carlos Soriano
 * SwitcherooControl code based on code original from Marsch84
 *
 * 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, version 3 of the License.
 *
 * 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/>.
 */
/* exported dropDestination */
'use strict';
const Gtk = imports.gi.Gtk;
const Gdk = imports.gi.Gdk;
const Gio = imports.gi.Gio;
const Atk = imports.gi.Atk;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Pango = imports.gi.Pango;
const GdkPixbuf = imports.gi.GdkPixbuf;
const Cairo = imports.gi.cairo;
const DesktopIconsUtil = imports.desktopIconsUtil;

const Prefs = imports.preferences;
const Enums = imports.enums;
const SignalManager = imports.signalManager;

const ByteArray = imports.byteArray;
const Signals = imports.signals;
const Gettext = imports.gettext.domain('ding');

const _ = Gettext.gettext;

var desktopIconItem = class desktopIconItem extends SignalManager.SignalManager {
    constructor(desktopManager, fileExtra) {
        super();
        this._desktopManager = desktopManager;
        this._fileExtra = fileExtra;
        this._loadThumbnailDataCancellable = null;
        this._queryFileInfoCancellable = null;
        this._grid = null;
        this._lastClickTime = 0;
        this._lastClickButton = 0;
        this._clickCount = 0;
        this._isSelected = false;
        this._isKeyboardSelected = false;
        this._isSpecial = false;
        this._primaryButtonPressed = false;
        this._savedCoordinates = null;
        this._dropCoordinates = null;
        this._destroyed = false;
    }

    /** *********************
     * Destroyers *
     ***********************/

    removeFromGrid(callOnDestroy) {
        if (this._grid) {
            this._grid.removeItem(this);
            this._grid = null;
        }
        if (callOnDestroy) {
            this._onDestroy();
        }
    }

    _destroy() {
        this._destroyed = true;
        /* Regular file data */
        if (this._queryFileInfoCancellable) {
            this._queryFileInfoCancellable.cancel();
            this._queryFileInfoCancellable = null;
        }

        /* Thumbnailing */
        if (this._loadThumbnailDataCancellable) {
            this._loadThumbnailDataCancellable.cancel();
            this._loadThumbnailDataCancellable = null;
        }
        /* Disconnect signals */
        this.disconnectAllSignals();
        this.container.destroy();
        this.container = null;
        this._eventBox = null;
        this._shieldEventBox = null;
        this._labelEventBox = null;
        this._shieldLabelEventBox = null;
        this._icon = null;
        this._iconContainer = null;
        this._label = null;
        this._labelContainer = null;
        this.iconRectangle = null;
        if (this._grid) {
            this._grid.removeItem(this);
            this._grid = null;
        }
        this._desktopManager = null;
        this._fileExtra = null;
        this._savedCoordinates = null;
        this._dropCoordinates = null;
    }

    _onDestroy() {
        if (!this._destroyed) {
            this._destroy();
        }
    }

    /** *********************
     * Creators *
     ***********************/

    _createIconActor() {
        this.container = new Gtk.Box({orientation: Gtk.Orientation.VERTICAL, halign: Gtk.Align.CENTER});
        this._eventBox = new Gtk.EventBox({visible: true, halign: Gtk.Align.CENTER});
        this._shieldEventBox = new Gtk.EventBox({visible: true, halign: Gtk.Align.CENTER});
        this._labelEventBox = new Gtk.EventBox({visible: true, halign: Gtk.Align.CENTER});
        this._shieldLabelEventBox = new Gtk.EventBox({visible: true, halign: Gtk.Align.CENTER});

        this._icon = new Gtk.Image();
        this._iconContainer = new Gtk.Box({orientation: Gtk.Orientation.VERTICAL});
        this._iconContainer.pack_start(this._icon, true, true, 0);
        this._iconContainer.set_baseline_position(Gtk.BaselinePosition.CENTER);
        this._eventBox.add(this._iconContainer);
        this._shieldEventBox.add(this._eventBox);

        this._label = new Gtk.Label();
        this._containerAccessibility = this.container;
        this._containerAccessibility.set_can_focus(true);
        this._labelContainer = new Gtk.Box({orientation: Gtk.Orientation.VERTICAL, halign: Gtk.Align.CENTER});
        let labelStyleContext = this._label.get_style_context();
        if (this._desktopManager.darkText) {
            labelStyleContext.add_class('file-label-dark');
        } else {
            labelStyleContext.add_class('file-label');
        }
        labelStyleContext = undefined; // prevent memory leaks
        this._label.set_ellipsize(Pango.EllipsizeMode.END);
        this._label.set_line_wrap(true);
        this._label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR);
        this._label.set_yalign(0.0);
        this._label.set_justify(Gtk.Justification.CENTER);
        this._label.set_lines(2);
        this._labelContainer.pack_start(this._label, false, true, 0);
        this._labelEventBox.add(this._labelContainer);
        this._shieldLabelEventBox.add(this._labelEventBox);

        this.container.pack_start(this._shieldEventBox, false, false, 0);
        this.container.pack_start(this._shieldLabelEventBox, false, false, 0);

        this._fullStyleContext = this.container.get_style_context();
        this._styleContext = this._iconContainer.get_style_context();
        this._labelStyleContext = this._labelContainer.get_style_context();
        this._styleContext.add_class('file-item');
        this._labelStyleContext.add_class('file-item');

        this.iconRectangle = new Gdk.Rectangle();
        this.labelRectangle = new Gdk.Rectangle();

        /* We need to allow the "button-press" event to pass through the callbacks, to allow the DnD to work
         * But we must avoid them to reach the main window.
         * The solution is to allow them to pass in a EventBox, used both for detecting the events and the DnD, and block them
         * in a second EventBox, located outside.
         */
        this.connectSignal(this._shieldEventBox, 'button-press-event', (actor, event) => {
            return true;
        });
        this.connectSignal(this._shieldLabelEventBox, 'button-press-event', (actor, event) => {
            return true;
        });
        this.connectSignal(this._eventBox, 'button-press-event', (actor, event) => this._onPressButton(actor, event));
        this.connectSignal(this._eventBox, 'enter-notify-event', (actor, event) => this._onEnter(this._eventBox));
        this.connectSignal(this._eventBox, 'leave-notify-event', (actor, event) => this._onLeave(this._eventBox));
        this.connectSignal(this._eventBox, 'button-release-event', (actor, event) => this._onReleaseButton(actor, event));
        this.connectSignal(this._eventBox, 'drag-motion', (widget, context, x, y, time) => {
            this.highLightDropTarget(x, y);
            this._updateDragStatus(context, time);
        });
        this.connectSignal(this._eventBox, 'drag-leave', () => {
            this.unHighLightDropTarget();
        });
        this.connectSignal(this._eventBox, 'size-allocate', () => this._calculateIconRectangle());
        this.connectSignal(this._labelEventBox, 'button-press-event', (actor, event) => this._onPressButton(actor, event));
        this.connectSignal(this._labelEventBox, 'enter-notify-event', (actor, event) => this._onEnter(this._labelEventBox));
        this.connectSignal(this._labelEventBox, 'leave-notify-event', (actor, event) => this._onLeave(this._labelEventBox));
        this.connectSignal(this._labelEventBox, 'button-release-event', (actor, event) => this._onReleaseButton(actor, event));
        this.connectSignal(this._labelEventBox, 'drag-motion', (widget, context, x, y, time) => {
            this.highLightDropTarget(x, y);
            this._updateDragStatus(context, time);
        });
        this.connectSignal(this._labelEventBox, 'drag-leave', () => {
            this.unHighLightDropTarget();
        });
        this.connectSignal(this._labelEventBox, 'size-allocate', () => {
            this._doLabelSizeAllocated();
        });
        this.connectSignal(this.container, 'drag-motion', (widget, context, x, y, time) => {
            this.highLightDropTarget(x, y);
            this._updateDragStatus(context, time);
        });
        this.connectSignal(this.container, 'drag-leave', () => {
            this.unHighLightDropTarget();
        });

        if (this._desktopManager.showDropPlace) {
            this._setDropDestination(this.container);
        } else {
            this._setDropDestination(this._eventBox);
            this._setDropDestination(this._labelEventBox);
        }
        this._setDragSource(this._eventBox);
        this._setDragSource(this._labelEventBox);
        this.container.show_all();
    }

    _doLabelSizeAllocated() {
        this._calculateLabelRectangle();
    }

    _calculateIconRectangle() {
        this.iconwidth = this._iconContainer.get_allocated_width();
        this.iconheight = this._iconContainer.get_allocated_height();
        let [x, y] = this._grid.coordinatesLocalToGlobal(0, 0, this._iconContainer);
        this.iconRectangle.x = x;
        this.iconRectangle.y = y;
        this.iconRectangle.width = this.iconwidth;
        this.iconRectangle.height = this.iconheight;
    }

    _calculateLabelRectangle() {
        this.labelwidth = this._labelContainer.get_allocated_width();
        this.labelheight = this._labelContainer.get_allocated_height();
        let [x, y] = this._grid.coordinatesLocalToGlobal(0, 0, this._labelContainer);
        this.labelRectangle.x = x;
        this.labelRectangle.y = y;
        this.labelRectangle.width = this.labelwidth;
        this.labelRectangle.height = this.labelheight;
    }

    setCoordinates(x, y, width, height, margin, grid) {
        this._x1 = x;
        this._y1 = y;
        this.width = width;
        this.height = height;
        this._grid = grid;
        this.container.set_size_request(width, height);
        this._label.margin_start = margin;
        this._label.margin_end = margin;
        this._label.margin_bottom = margin;
        this._iconContainer.margin_top = margin;
        this._calculateIconRectangle();
        this._calculateLabelRectangle();
    }

    getCoordinates() {
        this._x2 = this._x1 + this.container.get_allocated_width() - 1;
        this._y2 = this._y1 + this.container.get_allocated_height() - 1;
        return [this._x1, this._y1, this._x2, this._y2, this._grid];
    }

    _setLabelName(text) {
        this._currentFileName = text;
        this._eventBox.set_tooltip_text(text);
        let lastCutPos = -1;
        let newText = '';
        for (let pos = 0; pos < text.length; pos++) {
            let character = text[pos];
            newText += character;
            if (pos < (text.length - 1)) {
                var nextChar = text[pos + 1];
            } else {
                var nextChar = '';
            }
            if (character == ' ') {
                lastCutPos = pos;
            }
            if (['.', ',', '-', '_', '@', ':'].includes(character)) {
                /* if the next character is already an space or this is the last
                 * character, the string will be naturally cut here, so we do
                 * nothing.
                 */
                if ((nextChar == ' ') || (nextChar == '')) {
                    continue;
                }
                /* if there is a cut element in the last four previous characters,
                 * do not add a new cut element.
                 */
                if ((lastCutPos > -1) && ((pos - lastCutPos) < 4)) {
                    continue;
                }
                newText += '\u200B';
            }
        }
        this._label.label = newText;
    }

    /** *********************
     * Button Clicks *
     ***********************/

    _updateClickState(event) {
        let settings = Gtk.Settings.get_default();

        if ((event.get_button()[1] == this._lastClickButton) &&
            ((event.get_time() - this._lastClickTime) < settings.gtk_double_click_time)) {
            this._clickCount++;
        } else {
            this._clickCount = 1;
        }

        this._lastClickTime = event.get_time();
        this._lastClickButton = event.get_button()[1];
    }

    getClickCount() {
        return this._clickCount;
    }

    _onPressButton(actor, event) {
        this._updateClickState(event);
        let button = event.get_button()[1];
        let [a, x, y] = event.get_coords();
        let state = event.get_state()[1];
        this._buttonPressInitialX = x;
        this._buttonPressInitialY = y;
        let shiftPressed = !!(state & Gdk.ModifierType.SHIFT_MASK);
        let controlPressed = !!(state & Gdk.ModifierType.CONTROL_MASK);
        if (button == 3) {
            this._doButtonThreePressed(event, shiftPressed, controlPressed);
        } else if (button == 1) {
            this._doButtonOnePressed(event, shiftPressed, controlPressed);
        }
        return false;
    }

    _onReleaseButton(actor, event) {
        let button = event.get_button()[1];
        if (button == 1) {
            this._doButtonOneReleased(event);
        }
        return false;
    }

    _doButtonThreePressed(event) {
        if (!this._isSelected) {
            this._desktopManager.selected(this, Enums.Selection.RIGHT_BUTTON);
        }
        this._desktopManager.fileItemMenu.showMenu(this, event);
    }

    _doButtonOnePressed(event, shiftPressed, controlPressed) {
        if (this.getClickCount() == 1) {
            this._primaryButtonPressed = true;
            if (shiftPressed || controlPressed) {
                this._desktopManager.selected(this, Enums.Selection.WITH_SHIFT);
            } else {
                this._desktopManager.selected(this, Enums.Selection.ALONE);
            }
        }
    }

    _doButtonOneReleased(event) {
    }

    /** *********************
     * Drag and Drop *
     ***********************/

    _onEnter(element) {
        if (!this._styleContext.has_class('file-item-hover')) {
            this._styleContext.add_class('file-item-hover');
            this._labelStyleContext.add_class('file-item-hover');
        }
        if (Prefs.CLICK_POLICY_SINGLE) {
            let window = element.get_window();
            if (window) {
                window.set_cursor(Gdk.Cursor.new_from_name(Gdk.Display.get_default(), 'hand'));
            }
        }
        return false;
    }

    _onLeave(element) {
        this._primaryButtonPressed = false;
        if (this._styleContext.has_class('file-item-hover')) {
            this._styleContext.remove_class('file-item-hover');
            this._labelStyleContext.remove_class('file-item-hover');
        }
        if (Prefs.CLICK_POLICY_SINGLE) {
            let window = element.get_window();
            if (window) {
                window.set_cursor(Gdk.Cursor.new_from_name(Gdk.Display.get_default(), 'default'));
            }
        }
        return false;
    }

    _hasToRouteDragToGrid() {
        if (this._grid) {
            return true;
        }
        return false;
    }

    _updateDragStatus(context, time) {
        if (DesktopIconsUtil.getModifiersInDnD(context, Gdk.ModifierType.CONTROL_MASK)) {
            Gdk.drag_status(context, Gdk.DragAction.COPY, time);
        } else {
            Gdk.drag_status(context, Gdk.DragAction.MOVE, time);
        }
    }

    highLightDropTarget() {
        if (this._hasToRouteDragToGrid()) {
            this._grid.receiveMotion(this._x1, this._y1, true);
            return;
        }
        if (!this._styleContext.has_class('desktop-icons-selected')) {
            this._styleContext.add_class('desktop-icons-selected');
            this._labelStyleContext.add_class('desktop-icons-selected');
        }
        this._grid.highLightGridAt(this._x1, this._y1);
    }

    unHighLightDropTarget() {
        if (this._hasToRouteDragToGrid()) {
            this._grid.receiveLeave();
            return;
        }
        if (!this._isSelected && this._styleContext.has_class('desktop-icons-selected')) {
            this._styleContext.remove_class('desktop-icons-selected');
            this._labelStyleContext.remove_class('desktop-icons-selected');
        }
        this._grid.unHighLightGrids();
    }

    setSelected() {
        this._isSelected = true;
        this._setSelectedStatus();
    }

    unsetSelected() {
        this._isSelected = false;
        this._setSelectedStatus();
    }

    toggleSelected() {
        this._isSelected = !this._isSelected;
        this._setSelectedStatus();
    }

    _setSelectedStatus() {
        if (this._isSelected && !this._styleContext.has_class('desktop-icons-selected')) {
            this._styleContext.add_class('desktop-icons-selected');
            this._labelStyleContext.add_class('desktop-icons-selected');
            if (!this._isKeyboardSelected) {
                this._containerAccessibility.grab_focus();
            }
        }
        if (!this._isSelected && this._styleContext.has_class('desktop-icons-selected')) {
            this._styleContext.remove_class('desktop-icons-selected');
            this._labelStyleContext.remove_class('desktop-icons-selected');
        }
        if (this._isKeyboardSelected && !this._fullStyleContext.has_class('desktop-icons-selected')) {
            this._fullStyleContext.add_class('desktop-icons-selected');
            this._containerAccessibility.grab_focus();
        }
        if (!this._isKeyboardSelected && this._fullStyleContext.has_class('desktop-icons-selected')) {
            this._fullStyleContext.remove_class('desktop-icons-selected');
        }
        this.setAccessibleName(this._getVisibleName());
    }

    setAccessibleName(name) {}

    _setDragSource(widget) {
        widget.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, null, Gdk.DragAction.MOVE | Gdk.DragAction.COPY);
        let targets = new Gtk.TargetList(null);
        targets.add(Gdk.atom_intern('x-special/ding-icon-list', false),
            Gtk.TargetFlags.SAME_APP, Enums.DndTargetInfo.DING_ICON_LIST);
        if ((this._fileExtra != Enums.FileType.USER_DIRECTORY_TRASH) &&
            (this._fileExtra != Enums.FileType.USER_DIRECTORY_HOME) &&
            (this._fileExtra != Enums.FileType.EXTERNAL_DRIVE)) {
            targets.add(Gdk.atom_intern('x-special/gnome-icon-list', false), 0,
                Enums.DndTargetInfo.GNOME_ICON_LIST);
            targets.add(Gdk.atom_intern('text/uri-list', false), 0,
                Enums.DndTargetInfo.URI_LIST);
        }
        widget.drag_source_set_target_list(targets);
        targets = undefined; // prevent memory leaks
        this.connectSignal(widget, 'drag-begin', (w, context) => {
            const scale = this._icon.get_scale_factor();
            let surf = new Cairo.ImageSurface(Cairo.SurfaceType.IMAGE, this.container.get_allocated_width() * scale, this.container.get_allocated_height() * scale);
            // setDeviceScale was introduced to GJS in version 1.69.2
            if (scale != 1.0 && surf.setDeviceScale !== undefined) {
                surf.setDeviceScale(scale, scale);
            }
            let cr = new Cairo.Context(surf);
            this.container.draw(cr);
            let itemnumber = this._desktopManager.getNumberOfSelectedItems();
            if (itemnumber > 1) {
                Gdk.cairo_set_source_rgba(cr, new Gdk.RGBA({
                    red: this._desktopManager.selectColor.red,
                    green: this._desktopManager.selectColor.green,
                    blue: this._desktopManager.selectColor.blue,
                    alpha: 0.6,
                })
                );
                itemnumber -= 1;
                switch (itemnumber.toString().length) {
                case 1:
                    cr.rectangle(1, 1, 30, 20);
                    break;
                case 2:
                    cr.rectangle(1, 1, 40, 20);
                    break;
                default:
                    cr.rectangle(1, 1, 50, 20);
                    break;
                }
                cr.fill();
                cr.setFontSize(18);
                Gdk.cairo_set_source_rgba(cr, new Gdk.RGBA({red: 1.0, green: 1.0, blue: 1.0, alpha: 1}));
                cr.moveTo(1, 17);
                cr.showText(`+${itemnumber}`);
            }
            Gtk.drag_set_icon_surface(context, surf);
            let [x, y] = this._calculateOffset(widget);
            context.set_hotspot(x, y);
            this._desktopManager.onDragBegin(this);
            cr.$dispose();
        });
        this.connectSignal(widget, 'drag-data-get', (w, context, data, info, time) => {
            let dragData = this._desktopManager.fillDragDataGet(info);
            if (dragData != null) {
                let list = ByteArray.fromString(dragData[1]);
                data.set(dragData[0], 8, list);
            }
        });
        this.connectSignal(widget, 'drag-end', (w, context) => {
            this._desktopManager.onDragEnd();
        });
    }

    _calculateOffset(widget) {
        if (widget == this._eventBox) {
            return [((this.width - this.iconwidth) / 2) + this._buttonPressInitialX, this._buttonPressInitialY];
        } else {
            return [((this.width - this.labelwidth) / 2) + this._buttonPressInitialX, (this.iconheight + 2) + this._buttonPressInitialY];
        }
    }

    _setDropDestination(dropDestination) {

    }

    /** *********************
     * Icon Rendering *
     ***********************/

    updateIcon() {
        return this._updateIcon();
    }

    async _updateIcon() {
        if (this._destroyed) {
            return;
        }

        this._icon.set_padding(0, 0);
        try {
            let customIcon = this._fileInfo.get_attribute_as_string('metadata::custom-icon');
            if (customIcon && (customIcon != '')) {
                let customIconFile = Gio.File.new_for_uri(customIcon);
                if (customIconFile.query_exists(null)) {
                    let loadedImage = await this._loadImageAsIcon(customIconFile);
                    if (loadedImage | this._destroyed) {
                        return;
                    }
                }
            }
        } catch (error) {
            print(`Error while updating icon: ${error.message}.\n${error.stack}`);
        }

        if (this._fileExtra == Enums.FileType.USER_DIRECTORY_TRASH) {
            let pixbuf = this._createEmblemedIcon(this._fileInfo.get_icon(), null);
            const scale = this._icon.get_scale_factor();
            let surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale, null);
            this._icon.set_from_surface(surface);
            return;
        }
        let iconSet = false;
        if (Prefs.nautilusSettings.get_string('show-image-thumbnails') != 'never') {
            let thumbnail = this._desktopManager.thumbnailLoader.getThumbnail(this, this._updateIcon.bind(this));
            if (thumbnail != null) {
                let thumbnailFile = Gio.File.new_for_path(thumbnail);
                iconSet = await this._loadImageAsIcon(thumbnailFile);
                if (this._destroyed) {
                    return;
                }
            }
        }

        if (!iconSet) {
            let pixbuf;
            if (this._isBrokenSymlink) {
                pixbuf = this._createEmblemedIcon(null, 'text-x-generic');
            } else if (this._desktopFile && this._desktopFile.has_key('Icon')) {
                pixbuf = this._createEmblemedIcon(null, this._desktopFile.get_string('Icon'));
            } else {
                pixbuf = this._createEmblemedIcon(this._getDefaultIcon(), null);
            }
            const scale = this._icon.get_scale_factor();
            let surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale, null);
            this._icon.set_from_surface(surface);
        }
    }

    _getDefaultIcon() {
        if (this._fileExtra == Enums.FileType.EXTERNAL_DRIVE) {
            return this._custom.get_icon();
        }
        return this._fileInfo.get_icon();
    }

    _loadImageAsIcon(imageFile) {
        if (this._loadThumbnailDataCancellable) {
            this._loadThumbnailDataCancellable.cancel();
        }
        this._loadThumbnailDataCancellable = new Gio.Cancellable();

        return new Promise((resolve, reject) => {
            imageFile.load_bytes_async(this._loadThumbnailDataCancellable, (source, result) => {
                this._loadThumbnailDataCancellable = null;
                try {
                    let [thumbnailData, etagOut] = 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 width = Prefs.get_desired_width() - 8;
                        let height = Prefs.get_icon_size() - 8;
                        let aspectRatio = thumbnailPixbuf.width / thumbnailPixbuf.height;
                        if ((width / height) > aspectRatio) {
                            width = height * aspectRatio;
                        } else {
                            height = width / aspectRatio;
                        }
                        const scale = this._icon.get_scale_factor();
                        width *= scale;
                        height *= scale;
                        let pixbuf = thumbnailPixbuf.scale_simple(Math.floor(width), Math.floor(height), GdkPixbuf.InterpType.BILINEAR);
                        pixbuf = this._addEmblemsToPixbufIfNeeded(pixbuf);
                        let surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale, null);
                        this._icon.set_from_surface(surface);
                        this._icon.set_padding(4, 4);
                        resolve(true);
                    }
                    resolve(false);
                } catch (e) {
                    resolve(false);
                }
            });
        });
    }

    _copyAndResizeIfNeeded(pixbuf) {
        /**
         * If the pixbuf is the original from the theme, copies it into a new one, to be able
         * to paint the emblems without altering the cached pixbuf in the theme object.
         * Also, ensures that the copied pixbuf is, at least, as big as the desired icon size,
         * to ensure that the emblems fit.
         */

        if (this._copiedPixbuf) {
            return pixbuf;
        }

        this._copiedPixbuf = true;
        let minsize = Prefs.get_icon_size();
        if ((pixbuf.width < minsize) || (pixbuf.height < minsize)) {
            let width = pixbuf.width < minsize ? minsize : pixbuf.width;
            let height = pixbuf.height < minsize ? minsize : pixbuf.height;
            let newpixbuf = GdkPixbuf.Pixbuf.new(pixbuf.colorspace, true, pixbuf.bits_per_sample, width, height);
            newpixbuf.fill(0);
            let x = Math.floor((width - pixbuf.width) / 2);
            let y = Math.floor((height - pixbuf.height) / 2);
            pixbuf.composite(newpixbuf, x, y, pixbuf.width, pixbuf.height, x, y, 1, 1,  GdkPixbuf.InterpType.NEAREST, 255);
            return newpixbuf;
        } else {
            return pixbuf.copy();
        }
    }

    _addEmblemsToPixbufIfNeeded(pixbuf) {
        const scale = this._icon.get_scale_factor();
        this._copiedPixbuf = false;
        let emblem = null;
        let finalSize = Math.floor(Prefs.get_icon_size() / 3) * scale;

        if (this._isDesktopFile && (!this._isValidDesktopFile || !this.trustedDesktopFile)) {
            pixbuf = this._copyAndResizeIfNeeded(pixbuf);
            pixbuf.saturate_and_pixelate(pixbuf, 0.5, true);
            emblem = Gio.ThemedIcon.new('emblem-unreadable');
            pixbuf = this._copyAndResizeIfNeeded(pixbuf);
            let theme = Gtk.IconTheme.get_default();
            let emblemIcon = theme.lookup_by_gicon_for_scale(emblem, finalSize / scale, scale, Gtk.IconLookupFlags.FORCE_SIZE).load_icon();
            emblemIcon.composite(pixbuf, pixbuf.width - finalSize, pixbuf.height - finalSize, finalSize, finalSize, pixbuf.width - finalSize, pixbuf.height - finalSize, 1, 1, GdkPixbuf.InterpType.BILINEAR, 255);
        }

        if (this._isSymlink && (this._desktopManager.showLinkEmblem || this._isBrokenSymlink)) {
            if (this._isBrokenSymlink) {
                emblem = Gio.ThemedIcon.new('emblem-unreadable');
            } else {
                emblem = Gio.ThemedIcon.new('emblem-symbolic-link');
            }
            pixbuf = this._copyAndResizeIfNeeded(pixbuf);
            let theme = Gtk.IconTheme.get_default();
            let emblemIcon = theme.lookup_by_gicon_for_scale(emblem, finalSize / scale, scale, Gtk.IconLookupFlags.FORCE_SIZE).load_icon();
            emblemIcon.composite(pixbuf, pixbuf.width - finalSize, pixbuf.height - finalSize, finalSize, finalSize, pixbuf.width - finalSize, pixbuf.height - finalSize, 1, 1, GdkPixbuf.InterpType.BILINEAR, 255);
        }

        if (this.isStackTop && !this.stackUnique) {
            pixbuf = this._copyAndResizeIfNeeded(pixbuf);
            let theme = Gtk.IconTheme.get_default();
            emblem = Gio.ThemedIcon.new('emblem-downloads');
            let emblemIcon = theme.lookup_by_gicon_for_scale(emblem, finalSize / scale, scale, Gtk.IconLookupFlags.FORCE_SIZE).load_icon();
            emblemIcon.composite(pixbuf, 0, 0, finalSize, finalSize, 0, 0, 1, 1, GdkPixbuf.InterpType.BILINEAR, 255);
        }
        return pixbuf;
    }

    _createEmblemedIcon(icon, iconName) {
        if (icon == null) {
            if (GLib.path_is_absolute(iconName)) {
                try {
                    let iconFile = Gio.File.new_for_commandline_arg(iconName);
                    icon = new Gio.FileIcon({file: iconFile});
                } catch (e) {
                    icon = Gio.ThemedIcon.new_with_default_fallbacks(iconName);
                }
            } else {
                icon = Gio.ThemedIcon.new_with_default_fallbacks(iconName);
            }
        }
        let theme = Gtk.IconTheme.get_default();

        const scale = this._icon.get_scale_factor();
        let itemIcon = null;
        try {
            itemIcon = theme.lookup_by_gicon_for_scale(icon, Prefs.get_icon_size(), scale, Gtk.IconLookupFlags.FORCE_SIZE).load_icon();
        } catch (e) {
            itemIcon = theme.load_icon_for_scale('text-x-generic', Prefs.get_icon_size(), scale, Gtk.IconLookupFlags.FORCE_SIZE);
        }

        itemIcon = this._addEmblemsToPixbufIfNeeded(itemIcon);

        return itemIcon;
    }

    /** *********************
     * Getters and setters *
     ***********************/

    get state() {
        return this._state;
    }

    set state(state) {
        if (state == this._state) {
            return;
        }

        this._state = state;
    }

    get isDrive() {
        return this._fileExtra == Enums.FileType.EXTERNAL_DRIVE;
    }

    get isSelected() {
        return this._isSelected;
    }

    get isKeyboardSelected() {
        return this._isKeyboardSelected;
    }

    set isKeyboardSelected(status) {
        this._isKeyboardSelected = status;
        this._setSelectedStatus();
    }

    get isSpecial() {
        return this._isSpecial;
    }

    get dropCoordinates() {
        return this._dropCoordinates;
    }

    set dropCoordinates(pos) {
        this._dropCoordinates = pos;
    }
};
Signals.addSignalMethods(desktopIconItem.prototype);
