788 lines
30 KiB
JavaScript
788 lines
30 KiB
JavaScript
|
|
/* 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 GLib = imports.gi.GLib;
|
|
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 ByteArray = imports.byteArray;
|
|
const Signals = imports.signals;
|
|
const Gettext = imports.gettext.domain('ding');
|
|
|
|
const _ = Gettext.gettext;
|
|
|
|
var desktopIconItem = class desktopIconItem {
|
|
constructor(desktopManager, fileExtra) {
|
|
this._signalIds = [];
|
|
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._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() {
|
|
/* Regular file data */
|
|
if (this._queryFileInfoCancellable) {
|
|
this._queryFileInfoCancellable.cancel();
|
|
}
|
|
|
|
/* Thumbnailing */
|
|
if (this._loadThumbnailDataCancellable) {
|
|
this._loadThumbnailDataCancellable.cancel();
|
|
}
|
|
/* Disconnect signals */
|
|
for (let [object, signalId] of this._signalIds) {
|
|
object.disconnect(signalId);
|
|
}
|
|
this._signalIds = [];
|
|
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;
|
|
}
|
|
|
|
_onDestroy() {
|
|
if (!this._destroyed) {
|
|
this._destroy();
|
|
this._destroyed = true;
|
|
}
|
|
}
|
|
|
|
_connectSignal(object, signal, callback) {
|
|
this._signalIds.push([object, object.connect(signal, callback)]);
|
|
}
|
|
|
|
/** *********************
|
|
* Creators *
|
|
***********************/
|
|
|
|
_createIconActor() {
|
|
this.container = new Gtk.Box({orientation: Gtk.Orientation.VERTICAL, halign: Gtk.Align.CENTER});
|
|
this._connectSignal(this.container, 'destroy', () => this._onDestroy());
|
|
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._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._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._isSelected && this._styleContext.has_class('desktop-icons-selected')) {
|
|
this._styleContext.remove_class('desktop-icons-selected');
|
|
this._labelStyleContext.remove_class('desktop-icons-selected');
|
|
}
|
|
}
|
|
|
|
_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() {
|
|
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 isSpecial() {
|
|
return this._isSpecial;
|
|
}
|
|
|
|
get dropCoordinates() {
|
|
return this._dropCoordinates;
|
|
}
|
|
|
|
set dropCoordinates(pos) {
|
|
this._dropCoordinates = pos;
|
|
}
|
|
};
|
|
Signals.addSignalMethods(desktopIconItem.prototype);
|