main.qml 20.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/***************************************************************************
 *   Copyright (C) 2011-2013 Sebastian Kügler <sebas@kde.org>              *
 *   Copyright (C) 2011 Marco Martin <mart@kde.org>                        *
 *   Copyright (C) 2014-2015 by Eike Hein <hein@kde.org>                   *
 *                                                                         *
 *   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, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
 ***************************************************************************/
21

22
import QtQuick 2.4
23
import QtQuick.Layouts 1.1
24

25
26
27
28
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.draganddrop 2.0 as DragDrop
29
import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons
30

31
32
import org.kde.private.desktopcontainment.desktop 0.1 as Desktop

33
34
import "code/LayoutManager.js" as LayoutManager
import "code/FolderTools.js" as FolderTools
35

36
FolderViewDropArea {
37
    id: root
38
39
    objectName: isFolder ? "folder" : "desktop"

Eike Hein's avatar
Eike Hein committed
40
41
42
    width: isPopup ? undefined : preferredWidth(false) // for the initial size when placed on the desktop
    Layout.minimumWidth: preferredWidth(true)
    Layout.preferredWidth: isPopup ? preferredWidth(false) : 0 // for the popup size to change at runtime when view mode changes
43
    Plasmoid.switchWidth: isPopup ? units.iconSizeHints.panel : preferredWidth(true)
44

Eike Hein's avatar
Eike Hein committed
45
46
47
    height: isPopup ? undefined : preferredHeight(false)
    Layout.minimumHeight: preferredHeight(true)
    Layout.preferredHeight: isPopup ? preferredHeight(false) : 0
48
    Plasmoid.switchHeight: isPopup ? units.iconSizeHints.panel : preferredHeight(true)
49

50
51
52
    LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
    LayoutMirroring.childrenInherit: true

53
54
    property bool isFolder: (plasmoid.pluginName == "org.kde.plasma.folder")
    property bool isContainment: ("containmentType" in plasmoid)
55
    property bool isPopup: (plasmoid.location != PlasmaCore.Types.Floating)
56
    property bool useListViewMode: isPopup && plasmoid.configuration.viewMode === 0
57

58
    property Component appletAppearanceComponent
59
    property Item toolBox
60
    property var layoutManager: LayoutManager
61
62
63
64

    property int handleDelay: 800
    property real haloOpacity: 0.5

65
    property int iconSize: units.iconSizes.small
66
67
68
    property int iconWidth: iconSize
    property int iconHeight: iconWidth

69
70
    readonly property int hoverActivateDelay: 750 // Magic number that matches Dolphin's auto-expand folders delay.

71
72
    preventStealing: true

73
74
    // Plasmoid.title is set by a Binding {} in FolderViewLayer
    Plasmoid.toolTipSubText: ""
75
    Plasmoid.icon: (!plasmoid.configuration.useCustomIcon && folderViewLayer.ready) ? folderViewLayer.view.model.iconName : plasmoid.configuration.icon
76
    Plasmoid.compactRepresentation: (isFolder && !isContainment) ? compactRepresentation : null
77
    Plasmoid.associatedApplicationUrls: folderViewLayer.ready ? folderViewLayer.model.resolvedUrl : null
78

79
80
    onIconHeightChanged: updateGridSize()

81
    anchors {
82
83
        leftMargin: (isContainment && plasmoid.availableScreenRect) ? plasmoid.availableScreenRect.x : 0
        topMargin: (isContainment && plasmoid.availableScreenRect) ? plasmoid.availableScreenRect.y : 0
84

85
        rightMargin: (isContainment && plasmoid.availableScreenRect) && parent
86
87
            ? parent.width - (plasmoid.availableScreenRect.x + plasmoid.availableScreenRect.width) : 0

88
        bottomMargin: (isContainment && plasmoid.availableScreenRect) && parent
89
90
91
            ? parent.height - (plasmoid.availableScreenRect.y + plasmoid.availableScreenRect.height) : 0
    }

92
93
94
95
96
97
98
99
100
101
102
103
104
    Behavior on anchors.topMargin {
        NumberAnimation { duration: units.longDuration; easing.type: Easing.InOutQuad }
    }
    Behavior on anchors.leftMargin {
        NumberAnimation { duration: units.longDuration; easing.type: Easing.InOutQuad }
    }
    Behavior on anchors.rightMargin {
        NumberAnimation { duration: units.longDuration; easing.type: Easing.InOutQuad }
    }
    Behavior on anchors.bottomMargin {
        NumberAnimation { duration: units.longDuration; easing.type: Easing.InOutQuad }
    }

105
106
107
108
109
110
    function updateGridSize()
    {
        LayoutManager.cellSize.width = root.iconWidth + toolBoxSvg.elementSize("left").width + toolBoxSvg.elementSize("right").width
        LayoutManager.cellSize.height = root.iconHeight + toolBoxSvg.elementSize("top").height + toolBoxSvg.elementSize("bottom").height;
        LayoutManager.defaultAppletSize.width = LayoutManager.cellSize.width * 6;
        LayoutManager.defaultAppletSize.height = LayoutManager.cellSize.height * 6;
111
112
113
114
115
116
117
118
119
        layoutTimer.restart();
    }

    function addLauncher(desktopUrl) {
        if (!isFolder) {
            return;
        }

        folderViewLayer.view.linkHere(desktopUrl);
120
121
122
    }

    function addApplet(applet, x, y) {
123
124
        if (!appletAppearanceComponent) {
            appletAppearanceComponent = Qt.createComponent("AppletAppearance.qml");
125
        }
Marco Martin's avatar
Marco Martin committed
126

127
128
129
130
131
132
133
134
135
136
        if (appletAppearanceComponent.status !== Component.Ready) {
            console.warn("Error loading AppletAppearance.qml:", appletAppearanceComponent.errorString());
            return;
        }

        var category = "Applet-" + applet.id;

        var container = appletAppearanceComponent.createObject(resultsFlow, {
            category: category
        });
137
138
139
140

        applet.parent = container
        applet.visible = true;

141
        var config = LayoutManager.itemsConfig[category];
142

143
        // We have it in the config.
Marco Martin's avatar
Marco Martin committed
144
145
146
147
148
        if (config !== undefined && config.width !== undefined &&
            config.height !== undefined &&
            config.width > 0 && config.height > 0) {
            container.width = config.width;
            container.height = config.height;
149
        // We have a default.
Marco Martin's avatar
Marco Martin committed
150
        } else if (applet.width > 0 && applet.height > 0) {
151
152
            container.width = applet.width;
            container.height = applet.height;
153
            // The container needs to be bigger than applet of margins factor.
154
155
156
157
            if (applet.backgroundHints != PlasmaCore.Types.NoBackground) {
                container.width += container.margins.left + container.margins.right;
                container.height += container.margins.top + container.margins.bottom;
            }
158
        // Give up, assign the global default.
Marco Martin's avatar
Marco Martin committed
159
        } else {
160
161
162
            container.width = LayoutManager.defaultAppletSize.width;
            container.height = LayoutManager.defaultAppletSize.height;
        }
Marco Martin's avatar
Marco Martin committed
163

164
        container.applet = applet;
165
166

        // Coordinated passed by param?
167
        if ( x >= 0 && y >= 0) {
168
169
170
171
172
173
            if (x + container.width > root.width) {
                x = root.width - container.width - 10;
            }
            if (y + container.height > root.height) {
                x = root.height - container.height;
            }
174

175
176
            // On applet undo or via scripting, the applet position will be saved
            // in applet's scene coordinates so remap it to resultsflow's coordinates.
177
178
179
180
            var pos = root.parent.mapToItem(resultsFlow, x, y);

            container.x = pos.x;
            container.y = pos.y;
181
182
183

            // To be sure it's restored at the same position, take margins into account
            // if there is a background.
184
185
186
187
188
            if (applet.backgroundHints != PlasmaCore.Types.NoBackground) {
                container.x -= container.margins.left;
                container.y -= container.margins.top;
            }

189
        // Coordinates stored?
190
191
192
193
194
195
        } else if (config !== undefined && config.x !== undefined && config.y !== undefined &&
            config.x >= 0 && config.y >= 0) {
            container.x = config.x;
            container.y = config.y;
        }

196
        // Rotation stored and significant?
197
198
199
        if (config !== undefined && config.rotation !== undefined &&
            (config.rotation > 5 || config.rotation < -5)) {
            container.rotation = config.rotation;
200
201
        } else {
            LayoutManager.restoreRotation(container);
202
203
204
205
206
207
208
209
210
        }

        LayoutManager.itemGroups[container.category] = container;

        if (container.x >= 0 && container.y >= 0) {
            LayoutManager.positionItem(container);
        }
    }

Eike Hein's avatar
Eike Hein committed
211
    function preferredWidth(minimum) {
212
        if (isContainment || !folderViewLayer.ready) {
213
            return -1;
214
        } else if (useListViewMode) {
215
            return (minimum ? folderViewLayer.view.cellHeight * 4 : units.gridUnit * 16);
216
217
        }

Eike Hein's avatar
Eike Hein committed
218
        return (folderViewLayer.view.cellWidth * (minimum ? 1 : 3)) + (units.largeSpacing * 2);
219
220
    }

Eike Hein's avatar
Eike Hein committed
221
    function preferredHeight(minimum) {
222
        if (isContainment || !folderViewLayer.ready) {
223
            return -1;
224
        } else if (useListViewMode) {
225
            var height = (folderViewLayer.view.cellHeight * (minimum ? 1 : 15)) + units.smallSpacing;
226
        } else {
Eike Hein's avatar
Eike Hein committed
227
            var height = (folderViewLayer.view.cellHeight * (minimum ? 1 : 2)) + units.largeSpacing
228
229
230
231
232
233
        }

        if (plasmoid.configuration.labelMode != 0) {
            height += folderViewLayer.item.labelHeight;
        }

Eike Hein's avatar
Eike Hein committed
234
        return height;
235
236
    }

237
238
239
240
241
    function isDrag(fromX, fromY, toX, toY) {
        var length = Math.abs(fromX - toX) + Math.abs(fromY - toY);
        return length >= Qt.styleHints.startDragDistance;
    }

242
    onDragEnter: {
Eike Hein's avatar
Eike Hein committed
243
        if (isContainment && plasmoid.immutable && !(isFolder && FolderTools.isFileDrag(event))) {
244
245
            event.ignore();
        }
246
247
248
249
250
251

        // Firefox tabs are regular drags. Since all of our drop handling is asynchronous
        // we would accept this drop and have Firefox not spawn a new window. (Bug 337711)
        if (event.mimeData.formats.indexOf("application/x-moz-tabbrowser-tab") > -1) {
            event.ignore();
        }
252
253
    }

254
    onDragMove: {
255
256
257
258
259
        // TODO: We should reject drag moves onto file items that don't accept drops
        // (cf. QAbstractItemModel::flags() here, but DeclarativeDropArea currently
        // is currently incapable of rejecting drag events.

        // Trigger autoscroll.
260
        if (isFolder && FolderTools.isFileDrag(event)) {
261
            handleDragMove(folderViewLayer.view, mapToItem(folderViewLayer.view, event.x, event.y));
262
        } else if (isContainment) {
263
264
            placeHolder.width = LayoutManager.defaultAppletSize.width;
            placeHolder.height = LayoutManager.defaultAppletSize.height;
265
            placeHolder.minimumWidth = placeHolder.minimumHeight = 0;
266
267
268
269
270
271
            placeHolder.x = event.x - placeHolder.width / 2;
            placeHolder.y = event.y - placeHolder.width / 2;
            LayoutManager.positionItem(placeHolder);
            LayoutManager.setSpaceAvailable(placeHolder.x, placeHolder.y, placeHolder.width, placeHolder.height, true);
            placeHolderPaint.opacity = root.haloOpacity;
        }
272
273
274
    }

    onDragLeave: {
275
        // Cancel autoscroll.
276
        if (isFolder) {
277
            handleDragEnd(folderViewLayer.view);
278
279
280
281
282
        }

        if (isContainment) {
            placeHolderPaint.opacity = 0;
        }
283
284
285
    }

    onDrop: {
286
        if (isFolder && FolderTools.isFileDrag(event)) {
287
            handleDragEnd(folderViewLayer.view);
288
            folderViewLayer.view.drop(root, event, mapToItem(folderViewLayer.view, event.x, event.y));
289
        } else if (isContainment) {
290
            placeHolderPaint.opacity = 0;
291
292
            var pos = root.parent.mapFromItem(resultsFlow, event.x - placeHolder.width / 2, event.y - placeHolder.height / 2);
            plasmoid.processMimeData(event.mimeData, pos.x, pos.y);
293
            event.accept(event.proposedAction);
294
295
        }
    }
296

297
298
299
300
301
    Component {
        id: compactRepresentation
        CompactRepresentation { folderView: folderViewLayer.view }
    }

302
303
304
    Connections {
        target: plasmoid

305
306
        ignoreUnknownSignals: true

307
308
        onAppletAdded: {
            addApplet(applet, x, y);
309
            // Clean any eventual invalid chunks in the config.
310
311
312
313
            LayoutManager.save();
        }

        onAppletRemoved: {
314
            // Clean any eventual invalid chunks in the config.
315
316
317
            LayoutManager.removeApplet(applet);
            LayoutManager.save();
        }
318
319

        onImmutableChanged: {
320
            if (root.isContainment && !plasmoid.immutable) {
321
322
323
                pressToMoveHelp.show();
            }
        }
324
325
        
        onAvailableScreenRegionChanged: layoutTimer.restart();
326
327
328
329
330
331
332
333
    }

    Connections {
        target: plasmoid.configuration

        onPressToMoveChanged: {
            if (plasmoid.configuration.pressToMove && plasmoid.configuration.pressToMoveHelp && !plasmoid.immutable) {
                pressToMoveHelp.show();
334
335
336
337
            }
        }
    }

Eike Hein's avatar
--crap;    
Eike Hein committed
338
339
340
341
    Binding {
        target: toolBox
        property: "visible"
        value: plasmoid.configuration.showToolbox
342
343
    }

344
345
346
347
348
349
350
351
352
353
354
355
356
    Desktop.InfoNotification {
        id: pressToMoveHelp

        enabled: plasmoid.configuration.pressToMove && plasmoid.configuration.pressToMoveHelp

        iconName: "plasma"
        titleText: i18n("Widgets unlocked")
        text: i18n("You can press and hold widgets to move them and reveal their handles.")
        acknowledgeActionText: i18n("Got it")

        onAcknowledged: {
            plasmoid.configuration.pressToMoveHelp = false;
        }
357
358
    }

359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
    PlasmaCore.FrameSvgItem {
        id : highlightItemSvg

        visible: false

        imagePath: isPopup ? "widgets/viewitem" : ""
        prefix: "hover"
    }

    PlasmaCore.FrameSvgItem {
        id : listItemSvg

        visible: false

        imagePath: isPopup ? "widgets/viewitem" : ""
        prefix: "normal"
    }

377
378
379
380
381
382
383
384
    PlasmaCore.Svg {
        id: toolBoxSvg
        imagePath: "widgets/toolbox"
        property int rightBorder: elementSize("right").width
        property int topBorder: elementSize("top").height
        property int bottomBorder: elementSize("bottom").height
        property int leftBorder: elementSize("left").width
    }
385

386
387
388
389
390
    PlasmaCore.Svg {
        id: configIconsSvg
        imagePath: "widgets/configuration-icons"
    }

391
392
    KQuickControlsAddons.EventGenerator {
        id: eventGenerator
393
394
    }

395
396
    MouseArea { // unfocus any plasmoid when clicking empty desktop area
        anchors.fill: parent
397
398
399
        onPressed: {
            root.forceActiveFocus()
            mouse.accepted = false // Bug 351277
400
401
402
            if (toolBox && toolBox.open) {
                toolBox.open = false;
            }
403
        }
404
405
    }

406
407
408
409
410
    Loader {
        id: folderViewLayer

        anchors.fill: parent

411
        property bool ready: status == Loader.Ready
412
413
414
        property Item view: item ? item.view : null
        property QtObject model: item ? item.model : null

Eike Hein's avatar
Eike Hein committed
415
416
        focus: true

417
418
419
420
        active: isFolder
        asynchronous: false

        source: "FolderViewLayer.qml"
Eike Hein's avatar
Eike Hein committed
421
422
423
424
425
426
427
428
429
430
431
432
433
434

        onFocusChanged: {
            if (!focus && model) {
                model.clearSelection();
            }
        }

        Connections {
            target: folderViewLayer.view

            onPressed: {
                folderViewLayer.focus = true;
            }
        }
435
436
    }

437
438
439
440
    Item {
        id: resultsFlow
        anchors.fill: parent

441
442
443
444
445
446
447
448
449
450
        anchors {
            top: parent.top
            topMargin: 5
            horizontalCenter: parent.horizontalCenter
        }

        visible: isContainment
        enabled: isContainment

        // This is just for event compression when a lot of boxes are created one after the other.
451
452
453
454
455
456
457
458
459
        Timer {
            id: layoutTimer
            repeat: false
            running: false
            interval: 100
            onTriggered: {
                LayoutManager.resetPositions()
                for (var i=0; i<resultsFlow.children.length; ++i) {
                    var child = resultsFlow.children[i]
460
461
                    if (!child.applet)
                        continue
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
                    if (child.enabled) {
                        if (LayoutManager.itemsConfig[child.category]) {
                            var rect = LayoutManager.itemsConfig[child.category]
                            child.x = rect.x
                            child.y = rect.y
                            child.width = rect.width
                            child.height = rect.height
                            child.rotation = rect.rotation
                        } else {
                            child.x = 0
                            child.y = 0
                            child.width = Math.min(470, 32+child.categoryCount*140)
                        }
                        child.visible = true
                        LayoutManager.positionItem(child)
                    } else {
                        child.visible = false
                    }
                }
                LayoutManager.save()
            }
        }
    }

    Item {
487
488
        id: placerHolderWrapper

489
490
491
        anchors.fill: resultsFlow
        z: 0

492
493
494
        visible: isContainment
        enabled: isContainment

495
496
497
        Item {
            id: placeHolder

498
            x: -10000 // Move offscreen initially to avoid flickering.
499
500
501
502
503
504
505
506
507
            width: 100
            height: 100

            property bool animationsEnabled
            property int minimumWidth
            property int minimumHeight
            property Item syncItem

            function syncWithItem(item) {
508
509
510
511
                syncItem = item;
                minimumWidth = item.minimumWidth;
                minimumHeight = item.minimumHeight;
                repositionTimer.running = true;
512
                if (placeHolderPaint.opacity < 1) {
513
                    placeHolder.delayedSyncWithItem();
514
515
516
517
                }
            }

            function delayedSyncWithItem() {
518
519
                placeHolder.x = placeHolder.syncItem.x;
                placeHolder.y = placeHolder.syncItem.y;
520
                placeHolder.width = placeHolder.syncItem.width + (plasmoid.immutable || !syncItem.showAppletHandle ? 0 : syncItem.handleWidth)
521
522
523
524
                placeHolder.height = placeHolder.syncItem.height;
                // Only positionItem here, we don't want to save.
                LayoutManager.positionItem(placeHolder);
                LayoutManager.setSpaceAvailable(placeHolder.x, placeHolder.y, placeHolder.width, placeHolder.height, true);
525
526
527
528
529
530
531
532
533
534
535
536
537
538
            }

            Timer {
                id: repositionTimer
                interval: 100
                repeat: false
                running: false
                onTriggered: placeHolder.delayedSyncWithItem()
            }
        }

        PlasmaComponents.Highlight {
            id: placeHolderPaint

539
540
541
542
            x: placeHolder.x
            y: placeHolder.y
            width: placeHolder.width
            height: placeHolder.height
543
            z: 0
Eike Hein's avatar
Eike Hein committed
544

545
546
            opacity: 0
            visible: opacity > 0
547
548
549
550
551
552
553
554
555
556
557

            Behavior on opacity {
                NumberAnimation {
                    duration: units.longDuration
                    easing.type: Easing.InOutQuad
                }
            }
        }
    }

    Component.onCompleted: {
558
559
560
561
        if (!isContainment) {
            return;
        }

562
563
        // Customize the icon and text to improve discoverability
        plasmoid.setAction("configure", i18n("Configure Desktop"), "preferences-desktop-wallpaper")
564

565
566
567
568
569
570
571
572
        // WORKAROUND: that's the only place where we can inject a sensible size.
        // if root has width defined, it will override the value we set before
        // the component completes
        root.width = plasmoid.width;

        LayoutManager.resultsFlow = resultsFlow;
        LayoutManager.plasmoid = plasmoid;
        updateGridSize();
573

574
        LayoutManager.restore();
575
576

        for (var i = 0; i < plasmoid.applets.length; ++i) {
577
            var applet = plasmoid.applets[i];
578
579
            addApplet(applet, -1, -1);
        }
580
581

        // Clean any eventual invalid chunks in the config.
582
583
584
        LayoutManager.save();
    }
}