[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-commits
Subject:    [plasma-desktop] /: DND in/out/within, rubber band, autoscroll (drag and rubber band).
From:       Eike Hein <hein () kde ! org>
Date:       2014-07-10 10:52:50
Message-ID: E1X5By6-0004Go-Ur () scm ! kde ! org
[Download RAW message or body]

Git commit 85c20929b059ee2329124b1c13ed9f91b1cc6485 by Eike Hein.
Committed on 09/07/2014 at 20:15.
Pushed by hein into branch 'master'.

DND in/out/within, rubber band, autoscroll (drag and rubber band).

Known issues:
- Drag pixmap generation is great on Mesa but sometimes a bit
  flakey on nVidia; 5.4's grabImage() will fix this.
- Custom positioning currently causes keyboard navigation to
  be disabled, spatial navigation had a bug I couldn't fix in
  time. Merge soon.
- There are a few more cases where we want to transform posi-
  tioning data rather than throw it away.

BUG:334890

M  +14   -0    CMakeLists.txt
M  +23   -0    containments/folder/CMakeLists.txt
M  +2    -2    containments/folder/package/contents/code/tools.js
M  +1    -1    containments/folder/package/contents/config/config.qml
M  +5    -1    containments/folder/package/contents/config/main.xml
M  +10   -1    containments/folder/package/contents/ui/ConfigIcons.qml
M  +182  -118  containments/folder/package/contents/ui/ItemDelegate.qml
M  +437  -26   containments/folder/package/contents/ui/ItemView.qml
M  +3    -0    containments/folder/package/contents/ui/ItemViewDialog.qml
M  +83   -19   containments/folder/package/contents/ui/main.qml
M  +356  -30   containments/folder/plugin/foldermodel.cpp
M  +49   -3    containments/folder/plugin/foldermodel.h
M  +6    -0    containments/folder/plugin/folderplugin.cpp
A  +108  -0    containments/folder/plugin/itemgrabber.cpp     [License: GPL (v2+)]
C  +24   -16   containments/folder/plugin/itemgrabber.h [from: \
containments/folder/plugin/systemsettings.h - 066% similarity] A  +798  -0    \
containments/folder/plugin/positioner.cpp     [License: GPL (v2+)] A  +118  -0    \
containments/folder/plugin/positioner.h     [License: GPL (v2+)] C  +25   -18   \
containments/folder/plugin/rubberband.cpp [from: \
containments/folder/package/contents/config/config.qml - 067% similarity] C  +16   -5 \
containments/folder/plugin/rubberband.h [from: \
containments/folder/package/contents/code/tools.js - 084% similarity] M  +7    -1    \
containments/folder/plugin/systemsettings.cpp M  +2    -0    \
containments/folder/plugin/systemsettings.h

http://commits.kde.org/plasma-desktop/85c20929b059ee2329124b1c13ed9f91b1cc6485

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 48fc9b9..fd36808 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -88,6 +88,20 @@ set_package_properties(XCB PROPERTIES TYPE REQUIRED)
 add_feature_info("XCB-XKB" XCB_XKB_FOUND "Required for building kcm/keyboard")
 add_feature_info("libxft" X11_Xft_FOUND "X FreeType interface library required for \
font installation")  
+if(${Qt5Gui_OPENGL_IMPLEMENTATION} STREQUAL "GL")
+    find_package(OpenGL)
+    set_package_properties(OpenGL PROPERTIES DESCRIPTION "The OpenGL libraries"
+                        URL "http://www.opengl.org"
+                        TYPE REQUIRED
+                        )
+else()
+    find_package(OpenGLES)
+    set_package_properties(OpenGLES PROPERTIES DESCRIPTION "The OpenGLES libraries"
+                        URL "http://www.khronos.org/opengles"
+                        TYPE REQUIRED
+                        )
+endif()
+
 include_directories("${CMAKE_CURRENT_BINARY_DIR}")
 
 include(ConfigureChecks.cmake)
diff --git a/containments/folder/CMakeLists.txt b/containments/folder/CMakeLists.txt
index 9ece00c..1e7229d 100644
--- a/containments/folder/CMakeLists.txt
+++ b/containments/folder/CMakeLists.txt
@@ -8,11 +8,14 @@ set(folderplugin_SRCS
     plugin/directorypicker.cpp
     plugin/foldermodel.cpp
     plugin/folderplugin.cpp
+    plugin/itemgrabber.cpp
     plugin/itemviewadapter.cpp
     plugin/labelgenerator.cpp
     plugin/mimetypesmodel.cpp
     plugin/placesmodel.cpp
+    plugin/positioner.cpp
     plugin/previewpluginsmodel.cpp
+    plugin/rubberband.cpp
     plugin/subdialog.cpp
     plugin/systemsettings.cpp
     plugin/internallibkonq/konq_copytomenu.cpp
@@ -40,4 +43,24 @@ target_link_libraries(folderplugin
                       KF5::KDELibs4Support
                       KF5::PlasmaQuick)
 
+set(FOLDER_BUILD_OPENGL FALSE)
+set(FOLDER_BUILD_OPENGLES FALSE)
+if(OPENGL_FOUND AND (${Qt5Gui_OPENGL_IMPLEMENTATION} STREQUAL "GL"))
+    set(FOLDER_BUILD_OPENGL TRUE)
+endif()
+if(OPENGLES_FOUND AND (${Qt5Gui_OPENGL_IMPLEMENTATION} STREQUAL "GLESv2"))
+    set(FOLDER_BUILD_OPENGLES TRUE)
+endif()
+
+if(FOLDER_BUILD_OPENGL)
+  target_link_libraries(folderplugin ${OPENGL_gl_LIBRARY})
+  # -ldl used by OpenGL code
+  find_library(DL_LIBRARY dl)
+  if (DL_LIBRARY)
+    target_link_libraries(folderplugin ${DL_LIBRARY})
+  endif()
+elseif(FOLDER_BUILD_OPENGLES)
+  target_link_libraries(folderplugin ${OPENGLES_LIBRARIES})
+endif()
+
 install(TARGETS folderplugin DESTINATION \
                ${QML_INSTALL_DIR}/org/kde/plasma/private/folder)
diff --git a/containments/folder/package/contents/code/tools.js \
b/containments/folder/package/contents/code/tools.js index fc48a4f..910480d 100644
--- a/containments/folder/package/contents/code/tools.js
+++ b/containments/folder/package/contents/code/tools.js
@@ -17,9 +17,9 @@
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
  ***************************************************************************/
 
-// TODO: Avoid hardcoding the prop names, e.g. take them from the object.
+// FIXME TODO: Avoid hardcoding the prop names, e.g. take them from the object.
 var iconSizes = new Array("small", "smallMedium", "medium", "large", "huge", \
"enormous");  
 function iconSizeFromTheme(size) {
     return units.iconSizes[iconSizes[size]];
-}
\ No newline at end of file
+}
diff --git a/containments/folder/package/contents/config/config.qml \
b/containments/folder/package/contents/config/config.qml index acccf08..98bcfea \
                100644
--- a/containments/folder/package/contents/config/config.qml
+++ b/containments/folder/package/contents/config/config.qml
@@ -33,7 +33,7 @@ ConfigModel {
          icon: "preferences-desktop-icons"
          source: "ConfigIcons.qml"
     }
-    
+
     ConfigCategory {
          name: "Filter"
          icon: "view-filter"
diff --git a/containments/folder/package/contents/config/main.xml \
b/containments/folder/package/contents/config/main.xml index 60b298c..bb43dc4 100644
--- a/containments/folder/package/contents/config/main.xml
+++ b/containments/folder/package/contents/config/main.xml
@@ -27,6 +27,10 @@
       <default>true</default>
     </entry>
 
+    <entry name="positions" type="StringList">
+        <default></default>
+    </entry>
+
     <entry name="url" type="String">
       <default>desktop:/</default>
     </entry>
@@ -47,7 +51,7 @@
       <default>false</default>
     </entry>
     <entry name="sortMode" type="Int">
-      <default>0</default>
+      <default>1</default>
     </entry>
     <entry name="sortDesc" type="Bool">
       <default>false</default>
diff --git a/containments/folder/package/contents/ui/ConfigIcons.qml \
b/containments/folder/package/contents/ui/ConfigIcons.qml index cb13378..b3ad7f4 \
                100644
--- a/containments/folder/package/contents/ui/ConfigIcons.qml
+++ b/containments/folder/package/contents/ui/ConfigIcons.qml
@@ -80,6 +80,8 @@ GroupBox {
                 CheckBox {
                     id: locked
 
+                    visible: !("containmentType" in plasmoid)
+
                     text: i18n("Lock in place")
                 }
             }
@@ -101,19 +103,23 @@ GroupBox {
                     ComboBox {
                         id: sortMode
 
-                        model: [i18n("Name"), i18n("Size"), i18n("Type"), \
i18n("Date")] +                        model: [i18n("Unsorted"), i18n("Name"), \
i18n("Size"), i18n("Type"), i18n("Date")]  }
                 }
 
                 CheckBox {
                     id: sortDesc
 
+                    enabled: (sortMode.currentIndex != 0)
+
                     text: i18n("Descending")
                 }
 
                 CheckBox {
                     id: sortDirsFirst
 
+                    enabled: (sortMode.currentIndex != 0)
+
                     text: i18n("Folders first")
                 }
             }
@@ -166,6 +172,9 @@ GroupBox {
 
             ColumnLayout {
                 RowLayout {
+                    // FIXME TODO: Hide for now until position roundtripping works \
properly for this. +                    visible: false
+
                     Label {
                         text: i18n("Size:")
                     }
diff --git a/containments/folder/package/contents/ui/ItemDelegate.qml \
b/containments/folder/package/contents/ui/ItemDelegate.qml index 4e50e6e..4c17b36 \
                100644
--- a/containments/folder/package/contents/ui/ItemDelegate.qml
+++ b/containments/folder/package/contents/ui/ItemDelegate.qml
@@ -17,7 +17,7 @@
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
  ***************************************************************************/
 
-import QtQuick 2.0
+import QtQuick 2.2
 import org.kde.plasma.plasmoid 2.0
 
 import org.kde.plasma.core 2.0 as PlasmaCore
@@ -25,17 +25,61 @@ import org.kde.plasma.components 2.0 as PlasmaComponents
 import org.kde.kquickcontrolsaddons 2.0
 import org.kde.kcoreaddons 1.0 as KCoreAddons
 
+//FIXME
+import org.kde.plasma.private.folder 0.1 as Folder
+
 Item {
     id: main
 
     property int index: model.index
-    property string name: model.display
+    property string name: model.blank ? "" : model.display
     property QtObject popupDialog: null
     property Item label: label
     property Item labelArea: textBackground
     property Item actionsOverlay: actions
+    property Item hoverArea: toolTip
     property bool hovered: (GridView.view.hoveredItem == main)
-    property bool selected: model.selected
+    property bool selected: model.blank ? false : model.selected
+    property bool blank: model.blank
+    property variant snapshot: grabber.image
+    property Item snapshotSource: main.snapshotSource
+
+    // FIXME TODO: Replace with Qt 5.4's item-to-image API.
+    Folder.ItemGrabber {
+        id: grabber
+
+        item: main.snapshotSource
+    }
+
+    onBlankChanged: {
+        if (snapshotSource) {
+            snapshotSource.visible = false;
+        }
+    }
+
+    onSelectedChanged: {
+        if (snapshotSource) {
+            snapshotSource.visible = false;
+            snapshotSource.destroy();
+        }
+
+        if (selected && !blank) {
+            snapshotTimer.start();
+        } else {
+            snapshotTimer.stop();
+        }
+    }
+
+    onSnapshotChanged: {
+        if (!grabber.null) {
+            dir.addItemDragImage(positioner.map(index), x, y, main.width, \
main.height, snapshot); +        }
+
+        if (snapshotSource) {
+            snapshotSource.visible = false;
+            snapshotSource.destroy();
+        }
+    }
 
     onHoveredChanged: {
         if (hovered && !main.GridView.view.isRootView && model.isDir) {
@@ -69,162 +113,182 @@ Item {
         }
     }
 
+    Timer {
+        id: snapshotTimer
+        repeat: false
+        interval: 0
+
+        onTriggered: {
+            dir.addItemDragImage(positioner.map(index), x, y, main.width, \
main.height, snapshot); +            main.snapshotSource = Qt.createQmlObject("import \
QtQuick 2.2; ShaderEffectSource { anchors.fill: main; live: false; sourceItem: main; \
}", main); +        }
+    }
+
+    PlasmaCore.ToolTipArea {
+        id: toolTip
+
+        x: frame.x + Math.min(icon.x, label.x)
+        y: frame.y + icon.y
+
+        width: Math.max(icon.width, label.width)
+        height: (label.y + label.paintedHeight)
+
+        mainItem: toolTipDelegate
+        active: (popupDialog == null && !model.blank)
+        interactive: false
+        location: plasmoid.location
+
+        onContainsMouseChanged:  {
+            if (containsMouse && !model.blank) {
+                toolTip.icon = model.decoration;
+                toolTip.mainText = model.display;
+                toolTip.subText = model.type + "\n" + \
KCoreAddons.Format.formatByteSize(model.size) +                \
main.GridView.view.hoveredItem = main; +            }
+        }
+    }
+
     PlasmaCore.FrameSvgItem {
         id: frame
 
-        x: units.smallSpacing
-        y: units.smallSpacing
+        x: units.smallSpacing * 2
+        y: units.smallSpacing * 2
 
         width: parent.width - (2 * units.smallSpacing)
         height: (icon.height + (2 * units.smallSpacing) + (label.lineCount
             * theme.mSize(theme.defaultFont).height) + (2 * units.largeSpacing))
 
-        imagePath: "widgets/viewitem"
-
-        PlasmaCore.ToolTipArea {
-            id: toolTip
+        visible: !model.blank
+        enabled: visible
 
-            anchors.fill: parent
+        imagePath: "widgets/viewitem"
 
-            mainItem: toolTipDelegate
-            active: (popupDialog == null)
-            interactive: false
-            location: plasmoid.location
+        QIconItem {
+            id: icon
 
-            onContainsMouseChanged:  {
-                if (containsMouse) {
-                    toolTip.icon = model.decoration;
-                    toolTip.mainText = model.display;
-                    toolTip.subText = model.type + "\n" + \
                KCoreAddons.Format.formatByteSize(model.size)
-                    main.GridView.view.hoveredItem = main;
-                }
+            anchors {
+                top: parent.top
+                topMargin: units.largeSpacing
+                horizontalCenter: parent.horizontalCenter
             }
 
-            QIconItem {
-                id: icon
-
-                anchors {
-                    top: parent.top
-                    topMargin: units.largeSpacing
-                    horizontalCenter: parent.horizontalCenter
-                }
+            width: main.GridView.view.iconSize
+            height: main.GridView.view.iconSize
 
-                width: main.GridView.view.iconSize
-                height: main.GridView.view.iconSize
+            icon: model.decoration
+        }
 
-                icon: model.decoration
+        Rectangle {
+            id: textBackground
+
+            anchors {
+                left: label.left
+                leftMargin: -units.smallSpacing
+                top: label.top
+                topMargin: -units.smallSpacing
+                right: label.right
+                rightMargin: -units.smallSpacing
+                bottom: label.bottom
+                bottomMargin: -units.smallSpacing
             }
 
-            Rectangle {
-                id: textBackground
-                anchors {
-                    left: label.left
-                    leftMargin: -units.smallSpacing
-                    top: label.top
-                    topMargin: -units.smallSpacing
-                    right: label.right
-                    rightMargin: -units.smallSpacing
-                    bottom: label.bottom
-                    bottomMargin: -units.smallSpacing
-                }
-                color: (root.isContainment && main.GridView.view.isRootView) ? \
                theme.textColor : "transparent"
-                radius: units.smallSpacing
-                opacity: 0.4
-            }
+            color: (root.isContainment && main.GridView.view.isRootView) ? \
theme.textColor : "transparent" +            radius: units.smallSpacing
+            opacity: 0.4
+        }
 
-            PlasmaComponents.Label {
-                id: label
+        PlasmaComponents.Label {
+            id: label
 
-                anchors {
-                    top: icon.bottom
-                    topMargin: 2 * units.smallSpacing
-                    horizontalCenter: parent.horizontalCenter
-                }
+            anchors {
+                top: icon.bottom
+                topMargin: 2 * units.smallSpacing
+                horizontalCenter: parent.horizontalCenter
+            }
 
-                width: Math.min(paintedWidth, parent.width - units.smallSpacing * 8) \
                // TODO: Pick a frame prefix margin to cache and use instead.
-                height: undefined // Unset PlasmaComponents.Label's default.
+            width: Math.min(paintedWidth, parent.width - units.smallSpacing * 8) // \
FIXME TODO: Pick a frame prefix margin to cache and use instead. +            height: \
undefined // Unset PlasmaComponents.Label's default.  
-                textFormat: Text.PlainText
+            textFormat: Text.PlainText
 
-                maximumLineCount: plasmoid.configuration.textLines
-                wrapMode: Text.Wrap
-                elide: Text.ElideRight
+            maximumLineCount: plasmoid.configuration.textLines
+            wrapMode: Text.Wrap
+            elide: Text.ElideRight
 
-                horizontalAlignment: Text.AlignHCenter
+            horizontalAlignment: Text.AlignHCenter
 
-                color: (root.isContainment && main.GridView.view.isRootView) ? \
theme.backgroundColor : theme.textColor +            color: (root.isContainment && \
main.GridView.view.isRootView) ? theme.backgroundColor : theme.textColor  
-                text: model.display
-            }
+            text: model.blank ? "" : model.display
+        }
 
-            Column {
-                id: actions
+        Column {
+            id: actions
 
-                x: units.smallSpacing * 3 // TODO: Pick a frame prefix margin to \
                cache and use instead.
-                y: units.smallSpacing * 3 // TODO: Pick a frame prefix margin to \
cache and use instead. +            x: units.smallSpacing * 3 // FIXME TODO: Pick a \
frame prefix margin to cache and use instead. +            y: units.smallSpacing * 3 \
// FIXME TODO: Pick a frame prefix margin to cache and use instead.  
-                width: implicitWidth
-                height: implicitHeight
+            width: implicitWidth
+            height: implicitHeight
 
-                ItemActionButton {
-                    width: systemSettings.singleClick ? units.iconSizes.small : 0
-                    height: width
+            ItemActionButton {
+                width: systemSettings.singleClick ? units.iconSizes.small : 0
+                height: width
 
-                    visible: systemSettings.singleClick
-                    opacity: (systemSettings.singleClick && main.hovered) ? 1.0 : \
0.0 +                visible: systemSettings.singleClick
+                opacity: (systemSettings.singleClick && main.hovered) ? 1.0 : 0.0
 
-                    element: model.selected ? "remove" : "add"
+                element: model.selected ? "remove" : "add"
 
-                    onClicked: dir.toggleSelected(model.index)
-                }
+                onClicked: dir.toggleSelected(positioner.map(model.index))
             }
+        }
 
-            Component {
-                id: popupButtonComponent
+        Component {
+            id: popupButtonComponent
 
-                ItemActionButton {
-                    opacity: (plasmoid.configuration.popups && main.hovered && \
main.popupDialog == null) ? 1.0 : 0.0 +            ItemActionButton {
+                opacity: (plasmoid.configuration.popups && main.hovered && \
main.popupDialog == null) ? 1.0 : 0.0  
-                    element: "open"
+                element: "open"
 
-                    onClicked: main.openPopup()
-                }
+                onClicked: main.openPopup()
             }
+        }
 
-            states: [
-                State {
-                    name: "selected"
-                    when: model.selected
-
-                    PropertyChanges {
-                        target: frame
-                        prefix: "selected"
-                    }
-                },
-                State {
-                    name: "hover"
-                    when: hovered && !model.selected
-
-                    PropertyChanges {
-                        target: frame
-                        prefix: "hover"
-                    }
-                },
-                State {
-                    name: "selected+hover"
-                    when: hovered && model.selected
-
-                    PropertyChanges {
-                        target: frame
-                        prefix: "selected+hover"
-                    }
-                }
-            ]
+        states: [
+            State {
+                name: "selected"
+                when: model.selected
 
-            Component.onCompleted: {
-                if (main.GridView.view.isRootView && model.isDir) {
-                    popupButtonComponent.createObject(actions);
+                PropertyChanges {
+                    target: frame
+                    prefix: "selected"
+                }
+            },
+            State {
+                name: "hover"
+                when: hovered && !model.selected
+
+                PropertyChanges {
+                    target: frame
+                    prefix: "hover"
                 }
+            },
+            State {
+                name: "selected+hover"
+                when: hovered && model.selected
+
+                PropertyChanges {
+                    target: frame
+                    prefix: "selected+hover"
+                }
+            }
+        ]
+
+        Component.onCompleted: {
+            if (main.GridView.view.isRootView && model.isDir) {
+                popupButtonComponent.createObject(actions);
             }
         }
     }
diff --git a/containments/folder/package/contents/ui/ItemView.qml \
b/containments/folder/package/contents/ui/ItemView.qml index 2ac07cd..a40672e 100644
--- a/containments/folder/package/contents/ui/ItemView.qml
+++ b/containments/folder/package/contents/ui/ItemView.qml
@@ -17,7 +17,7 @@
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
  ***************************************************************************/
 
-import QtQuick 2.2
+import QtQuick 2.3
 import QtQuick.Layouts 1.1
 import org.kde.plasma.plasmoid 2.0
 
@@ -33,9 +33,15 @@ import "../code/tools.js" as FolderTools
 FocusScope {
     id: main
 
+    property QtObject model: dir
+    property Item rubberBand: null
+
     property alias isRootView: gridView.isRootView
     property alias url: dir.url
+    property alias positions: positioner.positions
     property alias errorString: dir.errorString
+    property alias locked: dir.locked
+    property alias sortMode: dir.sortMode
     property alias filterMode: dir.filterMode
     property alias filterPattern: dir.filterPattern
     property alias filterMimeTypes: dir.filterMimeTypes
@@ -43,11 +49,36 @@ FocusScope {
     property alias layoutDirection: gridView.layoutDirection
     property alias cellWidth: gridView.cellWidth
     property alias cellHeight: gridView.cellHeight
+    property alias scrollLeft: gridView.scrollLeft
+    property alias scrollRight: gridView.scrollRight
+    property alias scrollUp: gridView.scrollUp
+    property alias scrollDown: gridView.scrollDown
 
     function linkHere(sourceUrl) {
         dir.linkHere(sourceUrl);
     }
 
+    function indexAt(pos) {
+        var cPos = mapToItem(gridView.contentItem, pos.x, pos.y);
+        var item = gridView.itemAt(cPos.x, cPos.y);
+
+        if (item) {
+            if (item.blank) {
+                return -1;
+            }
+
+            var hPos = mapToItem(item.hoverArea, pos.x, pos.y);
+
+            if ((hPos.x < 0 || hPos.y < 0 || hPos.x > item.hoverArea.width || hPos.y \
> item.hoverArea.height)) { +                return -1;
+            } else {
+                return positioner.map(item.index);
+            }
+        }
+
+        return -1;
+    }
+
     onFocusChanged: {
         if (!focus) {
             dir.clearSelection();
@@ -61,11 +92,23 @@ FocusScope {
 
         property alias hoveredItem: gridView.hoveredItem
 
+        property Item pressedItem: null
+        property int pressX: -1
+        property int pressY: -1
+        property variant cPress: null
         property bool doubleClickInProgress: false
 
         acceptedButtons: (hoveredItem == null) ? Qt.LeftButton : (Qt.LeftButton | \
Qt.RightButton)  hoverEnabled: true
 
+        onPressXChanged: {
+            cPress = mapToItem(gridView.contentItem, pressX, pressY);
+        }
+
+        onPressYChanged: {
+            cPress = mapToItem(gridView.contentItem, pressX, pressY);
+        }
+
         onPressed: {
             scrollArea.focus = true;
             main.focus = true;
@@ -74,25 +117,38 @@ FocusScope {
                 editor.targetItem = null;
             }
 
-            if (!hoveredItem) {
-                dir.clearSelection();
+            pressX = mouse.x;
+            pressY = mouse.y;
+
+            if (!hoveredItem || hoveredItem.blank) {
+                if (!gridView.ctrlPressed) {
+                    dir.clearSelection();
+                }
 
                 return;
             }
 
-            var pos = mapToItem(hoveredItem.actionsOverlay, mouse.x, mouse.y);
+            pressedItem = hoveredItem;
 
+            var pos = mapToItem(hoveredItem.actionsOverlay, mouse.x, mouse.y);
 
             if (!(pos.x <= hoveredItem.actionsOverlay.width && pos.y <= \
                hoveredItem.actionsOverlay.height)) {
                 if (gridView.shiftPressed && gridView.currentIndex != -1) {
-                    dir.setRangeSelected(gridView.anchorIndex, hoveredItem.index);
+                    dir.setRangeSelected(positioner.map(gridView.anchorIndex), \
positioner.map(hoveredItem.index));  } else {
-                    if (!(mouse.buttons & Qt.RightButton) && !gridView.ctrlPressed
-                        || (mouse.buttons & Qt.RightButton) && \
!dir.isSelected(hoveredItem.index)) { +                    // FIXME TODO: Clicking \
one item with others selected should deselect the others, +                    // \
which doesn't happen right now because initiating a drag after the press should +     \
// still drag all of them: The deselect needs to happen on release instead so we +    \
// can distinguish. +                    if (!gridView.ctrlPressed && \
!dir.isSelected(positioner.map(hoveredItem.index))) {  dir.clearSelection();
                     }
 
-                    dir.setSelected(hoveredItem.index);
+                    if (gridView.ctrlPressed) {
+                        dir.toggleSelected(positioner.map(hoveredItem.index));
+                    } else {
+                        dir.setSelected(positioner.map(hoveredItem.index));
+                    }
                 }
 
                 gridView.currentIndex = hoveredItem.index;
@@ -103,12 +159,29 @@ FocusScope {
             }
         }
 
+        onReleased: {
+            if (main.rubberBand) {
+                main.rubberBand.visible = false;
+                main.rubberBand.enabled = false;
+                main.rubberBand.destroy();
+                main.rubberBand = null;
+                gridView.interactive = true;
+                gridView.cachedRectangleSelection = null;
+                dir.unpinSelection();
+            }
+
+            clearPressState();
+            gridView.cancelAutoscroll();
+        }
+
         onClicked: {
+            clearPressState();
+
             if (mouse.buttons & Qt.RightButton) {
                 return;
             }
 
-            if (!hoveredItem || gridView.currentIndex == -1 || gridView.ctrlPressed \
|| gridView.shiftPressed) { +            if (!hoveredItem || hoveredItem.blank || \
gridView.currentIndex == -1 || gridView.ctrlPressed || gridView.shiftPressed) {  \
return;  }
 
@@ -116,7 +189,7 @@ FocusScope {
 
             if (!(pos.x <= hoveredItem.actionsOverlay.width && pos.y <= \
                hoveredItem.actionsOverlay.height)) {
                 if (systemSettings.singleClick || doubleClickInProgress) {
-                    dir.run(gridView.currentIndex);
+                    dir.run(positioner.map(gridView.currentIndex));
                 } else {
                     doubleClickInProgress = true;
                     doubleClickTimer.interval = \
systemSettings.doubleClickInterval(); @@ -126,11 +199,79 @@ FocusScope {
         }
 
         onPositionChanged: {
-            var pos = mapToItem(gridView.contentItem, mouse.x, mouse.y);
-            var item = gridView.itemAt(pos.x, pos.y);
+            gridView.ctrlPressed = (mouse.modifiers & Qt.ControlModifier);
+            gridView.shiftPressed = (mouse.modifiers & Qt.ShiftModifier);
+
+            var cPos = mapToItem(gridView.contentItem, mouse.x, mouse.y);
+            var item = gridView.itemAt(cPos.x, cPos.y);
 
             if (!item) {
                 gridView.hoveredItem = null;
+            } else {
+                var aPos = mapToItem(item.actionsOverlay, mouse.x, mouse.y);
+                var hPos = mapToItem(item.hoverArea, mouse.x, mouse.y);
+
+                if ((hPos.x < 0 || hPos.y < 0 || hPos.x > item.hoverArea.width || \
hPos.y > item.hoverArea.height) +                    && aPos.x < 0) {
+                    gridView.hoveredItem = null;
+                }
+            }
+
+            // Trigger autoscroll.
+            if (pressX != -1) {
+                gridView.scrollLeft = (mouse.x <= 0 && gridView.contentX > 0);
+                gridView.scrollRight = (mouse.x >= gridView.width
+                    && gridView.contentX < gridView.contentItem.width - \
gridView.width); +                gridView.scrollUp = (mouse.y <= 0 && \
gridView.contentY > 0); +                gridView.scrollDown = (mouse.y >= \
gridView.height +                    && gridView.contentY < \
gridView.contentItem.height - gridView.height); +            }
+
+            // Update rubberband geometry.
+            if (main.rubberBand) {
+                var rB = main.rubberBand;
+
+                if (cPos.x < cPress.x) {
+                    rB.x = Math.max(0, cPos.x);
+                    rB.width = Math.abs(rB.x - cPress.x);
+                } else {
+                    rB.x = cPress.x;
+                    var ceil = Math.max(gridView.width, gridView.contentItem.width);
+                    rB.width = Math.min(ceil - rB.x, Math.abs(rB.x - cPos.x));
+                }
+
+                if (cPos.y < cPress.y) {
+                    rB.y = Math.max(0, cPos.y);
+                    rB.height = Math.abs(rB.y - cPress.y);
+                } else {
+                    rB.y = cPress.y;
+                    var ceil = Math.max(gridView.height, \
gridView.contentItem.height); +                    rB.height = Math.min(ceil - rB.y, \
Math.abs(rB.y - cPos.y)); +                }
+
+                gridView.rectangleSelect(rB.x, rB.y, rB.width, rB.height);
+
+                return;
+            }
+
+            // Drag initiation.
+            if (pressX != -1 && systemSettings.isDrag(pressX, pressY, mouse.x, \
mouse.y)) { +                if (pressedItem != null && \
dir.isSelected(positioner.map(pressedItem.index))) { +                    \
dir.dragSelected(mouse.x, mouse.y); +                    clearPressState();
+                } else {
+                    dir.pinSelection();
+                    main.rubberBand = Qt.createQmlObject("import QtQuick 2.0; import \
org.kde.plasma.private.folder 0.1 as Folder;" +                        + \
"Folder.RubberBand { x: " + cPress.x + "; y: " + cPress.y + "; width: 0; height: 0; \
z: 99999; }", +                        gridView.contentItem);
+                    gridView.interactive = false;
+                }
+            }
+        }
+
+        onContainsMouseChanged: {
+            if (!containsMouse && !main.rubberBand) {
+                clearPressState();
             }
         }
 
@@ -138,11 +279,17 @@ FocusScope {
             doubleClickInProgress = false;
         }
 
+        function clearPressState() {
+            pressedItem = null;
+            pressX = -1;
+            pressY = -1;
+        }
+
         Timer {
             id: doubleClickTimer
 
             onTriggered: {
-                doubleClickInProgress = false;
+                listener.doubleClickInProgress = false;
             }
         }
 
@@ -157,51 +304,238 @@ FocusScope {
                 id: gridView
 
                 property bool isRootView: false
+
                 property int iconSize: \
FolderTools.iconSizeFromTheme(plasmoid.configuration.iconSize) // TODO: DPI can \
change at runtime. Invalidate. +
                 property Item hoveredItem: null
+
                 property int anchorIndex: 0
                 property bool ctrlPressed: false
                 property bool shiftPressed: false
 
+                property bool scrollLeft: false
+                property bool scrollRight: false
+                property bool scrollUp: false
+                property bool scrollDown: false
+
+                property variant cachedRectangleSelection: null
+
                 currentIndex: -1
 
-                interactive: true
                 keyNavigationWraps: false
                 boundsBehavior: Flickable.StopAtBounds
 
                 cellWidth: iconSize + (4 * units.largeSpacing)
-                cellHeight: (iconSize + (theme.mSize(theme.defaultFont).height * \
plasmoid.configuration.textLines) + (2 * units.largeSpacing) + (2 * \
units.smallSpacing)) +                cellHeight: (iconSize + \
(theme.mSize(theme.defaultFont).height * plasmoid.configuration.textLines) +          \
+ (2 * units.largeSpacing) + (2 * units.smallSpacing))  
-                model: dir
+                model: positioner
 
                 delegate: ItemDelegate {
                     width: gridView.cellWidth
                     height: gridView.cellHeight
                 }
 
+                onContentXChanged: {
+                    dir.setDragHotSpotScrollOffset(contentX, contentY);
+
+                    if (contentX == 0) {
+                        scrollLeft = false;
+                    }
+
+                    if (contentX == contentItem.width - width) {
+                        scrollRight = false;
+                    }
+
+                    // Update rubberband geomety.
+                    if (main.rubberBand) {
+                        var rB = main.rubberBand;
+
+                        if (scrollLeft) {
+                            rB.x = 0;
+                            rB.width = listener.cPress.x;
+                        }
+
+                        if (scrollRight) {
+                            var lastCol = gridView.contentX + gridView.width;
+                            rB.width = lastCol - rB.x;
+                        }
+
+                        gridView.rectangleSelect(rB.x, rB.y, rB.width, rB.height);
+                    }
+                }
+
+                onContentYChanged: {
+                    dir.setDragHotSpotScrollOffset(contentX, contentY);
+
+                    if (contentY == 0) {
+                        scrollUp = false;
+                    }
+
+                    if (contentY == contentItem.height - height) {
+                        scrollDown = false;
+                    }
+
+                    // Update rubberband geomety.
+                    if (main.rubberBand) {
+                        var rB = main.rubberBand;
+
+                        if (scrollUp) {
+                            rB.y = 0;
+                            rB.height = listener.cPress.y;
+                        }
+
+                        if (scrollDown) {
+                            var lastRow = gridView.contentY + gridView.height;
+                            rB.height = lastRow - rB.y;
+                        }
+
+                        gridView.rectangleSelect(rB.x, rB.y, rB.width, rB.height);
+                    }
+                }
+
+                onScrollLeftChanged: {
+                    if (scrollLeft && gridView.visibleArea.widthRatio < 1.0) {
+                        smoothX.enabled = true;
+                        contentX = 0;
+                    } else {
+                        contentX = contentX;
+                        smoothX.enabled = false;
+                    }
+                }
+
+                onScrollRightChanged: {
+                    if (scrollRight && gridView.visibleArea.widthRatio < 1.0) {
+                        smoothX.enabled = true;
+                        contentX = contentItem.width - width;
+                    } else {
+                        contentX = contentX;
+                        smoothX.enabled = false;
+                    }
+                }
+
+                onScrollUpChanged: {
+                    if (scrollUp && gridView.visibleArea.heightRatio < 1.0) {
+                        smoothY.enabled = true;
+                        contentY = 0;
+                    } else {
+                        contentY = contentY;
+                        smoothY.enabled = false;
+                    }
+                }
+
+                onScrollDownChanged: {
+                    if (scrollDown && gridView.visibleArea.heightRatio < 1.0) {
+                        smoothY.enabled = true;
+                        contentY = contentItem.height - height;
+                    } else {
+                        contentY = contentY;
+                        smoothY.enabled = false;
+                    }
+                }
+
+                onFlowChanged: {
+                    // FIXME TODO: Preserve positions.
+                    if (positioner.enabled) {
+                        positioner.reset();
+                    }
+                }
+
+                onLayoutDirectionChanged: {
+                    // FIXME TODO: Preserve positions.
+                    if (positioner.enabled) {
+                        positioner.reset();
+                    }
+                }
+
                 onCurrentIndexChanged: {
                     positionViewAtIndex(currentIndex, GridView.Contain);
                 }
 
+                onCachedRectangleSelectionChanged: {
+                    if (cachedRectangleSelection) {
+                        dir.updateSelection(cachedRectangleSelection, \
gridView.ctrlPressed); +                    }
+                }
+
                 function updateSelection(modifier) {
                     if (modifier & Qt.ShiftModifier) {
-                        dir.setRangeSelected(anchorIndex, currentIndex);
+                        dir.setRangeSelected(positioner.map(anchorIndex), \
positioner.map(currentIndex));  } else {
                         dir.clearSelection();
-                        dir.setSelected(currentIndex);
+                        dir.setSelected(positioner.map(currentIndex));
+                    }
+                }
+
+                function cancelAutoscroll() {
+                    scrollLeft = false;
+                    scrollRight = false;
+                    scrollUp = false;
+                    scrollDown = false;
+                }
+
+                function rectangleSelect(x, y, width, height) {
+                    if (!main.rubberBand) {
+                        return;
                     }
+
+                    // TODO Factor out pos<->index.
+                    var rows = (gridView.flow == GridView.FlowLeftToRight);
+                    var axis = rows ? gridView.width : gridView.height;
+                    var step = rows ? cellWidth : cellHeight;
+                    var midWidth = gridView.cellWidth / 2;
+                    var midHeight = gridView.cellHeight / 2;
+                    var perStripe = Math.floor(axis / step);
+                    var stripes = Math.ceil(gridView.count / perStripe);
+                    var index = 0;
+                    var itemX = 0;
+                    var itemY = 0;
+                    var indices = [];
+
+                    for (var s = 0; s < stripes; s++) {
+                        for (var i = 0; i < perStripe; i++) {
+                            index = (s * perStripe) + i;
+
+                            if (index >= gridView.count) {
+                                break;
+                            }
+
+                            if (positioner.isBlank(index)) {
+                                continue;
+                            }
+
+                            itemX = (((rows ? i : s) + 1) * gridView.cellWidth) - \
midWidth; +                            itemY = (((rows ? s : i) + 1) * \
gridView.cellHeight) - midHeight; +
+                            if (gridView.layoutDirection == Qt.RightToLeft) {
+                                itemX = (rows ? gridView.width : \
gridView.contentItem.width) - itemX; +                            }
+
+                            if (itemX > x && itemX < (x + width) && itemY > y && \
itemY < (y + height)) { +                                \
indices.push(positioner.map(index)); +                            }
+                        }
+                    }
+
+                    gridView.cachedRectangleSelection = indices;
                 }
 
+                Behavior on contentX { id: smoothX; SmoothedAnimation { velocity: \
700 } } +                Behavior on contentY { id: smoothY; SmoothedAnimation { \
velocity: 700 } } +
                 Keys.onReturnPressed: {
                     if (currentIndex != -1) {
-                        dir.run(currentIndex);
+                        dir.run(positioner.map(currentIndex));
                     }
                 }
 
                 Keys.onMenuPressed: {
+                     // FIXME TODO: Correct popup position.
+                    return;
+
                     if (currentIndex != -1) {
-                        dir.setSelected(currentIndex);
-                        dir.openContextMenu(); // FIXME TODO: Position.
+                        dir.setSelected(positioner.map(currentIndex));
+                        dir.openContextMenu();
                     }
                 }
 
@@ -227,6 +561,11 @@ FocusScope {
                 }
 
                 Keys.onLeftPressed: {
+                    // FIXME TODO: Merge keyboard nav for custom positions.
+                    if (positioner.enabled) {
+                        return;
+                    }
+
                     var oldIndex = currentIndex;
 
                     moveCurrentIndexLeft();
@@ -239,6 +578,11 @@ FocusScope {
                 }
 
                 Keys.onRightPressed: {
+                    // FIXME TODO: Merge keyboard nav for custom positions.
+                    if (positioner.enabled) {
+                        return;
+                    }
+
                     var oldIndex = currentIndex;
 
                     moveCurrentIndexRight();
@@ -251,6 +595,11 @@ FocusScope {
                 }
 
                 Keys.onUpPressed: {
+                    // FIXME TODO: Merge keyboard nav for custom positions.
+                    if (positioner.enabled) {
+                        return;
+                    }
+
                     var oldIndex = currentIndex;
 
                     moveCurrentIndexUp();
@@ -263,6 +612,11 @@ FocusScope {
                 }
 
                 Keys.onDownPressed: {
+                    // FIXME TODO: Merge keyboard nav for custom positions.
+                    if (positioner.enabled) {
+                        return;
+                    }
+
                     var oldIndex = currentIndex;
 
                     moveCurrentIndexDown();
@@ -280,19 +634,76 @@ FocusScope {
             id: dir
 
             usedByContainment: root.isContainment
-            sortMode: plasmoid.configuration.sortMode
             sortDesc: plasmoid.configuration.sortDesc
             sortDirsFirst: plasmoid.configuration.sortDirsFirst
             parseDesktopFiles: (url == "desktop:/")
             previews: plasmoid.configuration.previews
             previewPlugins: plasmoid.configuration.previewPlugins
+
+            onMove: {
+                // TODO Factor out pos<->index.
+                var rows = (gridView.flow == GridView.FlowLeftToRight);
+                var axis = rows ? gridView.width : gridView.height;
+                var step = rows ? cellWidth : cellHeight;
+                var perStripe = Math.floor(axis / step);
+                var dropPos = mapToItem(gridView.contentItem, x, y);
+
+                var itemX = -1;
+                var itemY = -1;
+                var col = -1;
+                var row = -1;
+                var from = -1;
+                var to = -1;
+
+                for (var i = 0; i < urls.length; i++) {
+                    from = positioner.indexForUrl(urls[i]);
+                    to = -1;
+
+                    if (from == -1) {
+                        continue;
+                    }
+
+                    var offset = dir.dragCursorOffset(positioner.map(from));
+
+                    if (offset.x == -1) {
+                        continue;
+                    }
+
+                    itemX = dropPos.x + offset.x;
+                    itemY = dropPos.y + offset.y;
+                    col = Math.floor(itemX / gridView.cellWidth);
+                    row = Math.floor(itemY / gridView.cellHeight);
+
+                    if ((rows ? col : row) < perStripe) {
+                        to = ((rows ? row : col) * perStripe) + (rows ? col : row);
+                    }
+
+                    positioner.move(from, to);
+                }
+
+                // HACK Forget drag pixmaps.
+                dir.clearSelection();
+            }
+        }
+
+        Folder.Positioner {
+            id: positioner
+
+            enabled: (isContainment && dir.sortMode == 0)
+
+            folderModel: dir
+
+            // TODO Factor out pos<->index.
+            perStripe: Math.floor(((gridView.flow == GridView.FlowLeftToRight)
+                ? gridView.width : gridView.height) / ((gridView.flow == \
GridView.FlowLeftToRight) +                ? gridView.cellWidth : \
gridView.cellHeight));  }
 
         Folder.ItemViewAdapter {
             id: viewAdapter
 
             adapterView: gridView
-            adapterModel: dir;
+            adapterModel: positioner
             adapterIconSize: gridView.iconSize;
             adapterVisibleArea: Qt.rect(gridView.contentX, gridView.contentY, \
gridView.width, gridView.height)  
@@ -319,8 +730,8 @@ FocusScope {
             onTargetItemChanged: {
                 if (targetItem != null) {
                     var pos = main.mapFromItem(targetItem, targetItem.labelArea.x, \
                targetItem.labelArea.y);
-                    x = pos.x + units.smallSpacing;
-                    y = pos.y + units.smallSpacing;
+                    x = pos.x + (units.smallSpacing * 2);
+                    y = pos.y + (units.smallSpacing * 2);
                     width = targetItem.labelArea.width;
                     height = targetItem.labelArea.height;
                     text = targetItem.label.text;
@@ -342,7 +753,7 @@ FocusScope {
             }
 
             onAccepted: {
-                dir.rename(targetItem.index, text);
+                dir.rename(positioner.map(targetItem.index), text);
                 targetItem = null;
             }
         }
diff --git a/containments/folder/package/contents/ui/ItemViewDialog.qml \
b/containments/folder/package/contents/ui/ItemViewDialog.qml index 4759ba1..ddf14fc \
                100644
--- a/containments/folder/package/contents/ui/ItemViewDialog.qml
+++ b/containments/folder/package/contents/ui/ItemViewDialog.qml
@@ -55,6 +55,9 @@ Folder.SubDialog {
 
         isRootView: false
 
+        locked: true
+
+        sortMode: ((plasmoid.configuration.sortMode == 0) ? 1 : \
plasmoid.configuration.sortMode)  filterMode: 0
 
         // TODO: Bidi.
diff --git a/containments/folder/package/contents/ui/main.qml \
b/containments/folder/package/contents/ui/main.qml index d7ac731..ad70c7e 100644
--- a/containments/folder/package/contents/ui/main.qml
+++ b/containments/folder/package/contents/ui/main.qml
@@ -34,7 +34,7 @@ DragDrop.DropArea {
     objectName: "folder"
 
     width: isContainment ? undefined : (itemView.cellWidth * 3) + \
                (units.largeSpacing * 3)
-    height: isContainment ? undefined: (itemView.cellHeight * 2) + \
(units.largeSpacing * 2) +    height: isContainment ? undefined : \
(itemView.cellHeight * 2) + (units.largeSpacing * 2)  
     property bool isContainment: ("containmentType" in plasmoid)
     property Item label: null
@@ -50,7 +50,8 @@ DragDrop.DropArea {
     property int iconWidth: iconSize
     property int iconHeight: iconWidth
 
-    enabled: isContainment
+    preventStealing: true
+    enabled: true
 
     onIconHeightChanged: updateGridSize()
 
@@ -140,32 +141,69 @@ DragDrop.DropArea {
     }
 
     onDragEnter: {
-        placeHolder.width = LayoutManager.defaultAppletSize.width;
-        placeHolder.height = LayoutManager.defaultAppletSize.height;
-        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;
+        if (isContainment && !event.mimeData.urls.length) {
+            placeHolder.width = LayoutManager.defaultAppletSize.width;
+            placeHolder.height = LayoutManager.defaultAppletSize.height;
+            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; +        }
     }
 
     onDragMove: {
-        placeHolder.width = LayoutManager.defaultAppletSize.width;
-        placeHolder.height = LayoutManager.defaultAppletSize.height;
-        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;
+        // Trigger autoscroll.
+        if (event.mimeData.urls.length) {
+            itemView.scrollLeft = (event.x < (units.largeSpacing * 3));
+            itemView.scrollRight = (event.x > width - (units.largeSpacing * 3));
+            itemView.scrollUp = (event.y < (units.largeSpacing * 3));
+            itemView.scrollDown = (event.y > height - (units.largeSpacing * 3));
+        }
+
+        if (isContainment && !event.mimeData.urls.length) {
+            placeHolder.width = LayoutManager.defaultAppletSize.width;
+            placeHolder.height = LayoutManager.defaultAppletSize.height;
+            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; +        }
     }
 
     onDragLeave: {
-        placeHolderPaint.opacity = 0;
+        // Cancel autoscroll.
+        if (event.mimeData.urls.length) {
+            itemView.scrollLeft = false;
+            itemView.scrollRight = false;
+            itemView.scrollUp = false;
+            itemView.scrollDown = false;
+        }
+
+        if (isContainment) {
+            placeHolderPaint.opacity = 0;
+        }
     }
 
     onDrop: {
-        placeHolderPaint.opacity = 0;
-        plasmoid.processMimeData(event.mimeData, event.x - placeHolder.width / 2, \
event.y - placeHolder.height / 2); +        // Cancel autoscroll.
+        if (event.mimeData.urls.length) {
+            itemView.scrollLeft = false;
+            itemView.scrollRight = false;
+            itemView.scrollUp = false;
+            itemView.scrollDown = false;
+        }
+
+        if (event.mimeData.urls.length) {
+            var pos = mapToItem(itemView, event.x, event.y);
+            itemView.model.drop(root, event, itemView.indexAt(pos));
+        }
+
+        if (isContainment && !event.mimeData.urls.length) {
+            placeHolderPaint.opacity = 0;
+            plasmoid.processMimeData(event.mimeData, event.x - placeHolder.width / \
2, event.y - placeHolder.height / 2); +        }
     }
 
     Connections {
@@ -229,6 +267,18 @@ DragDrop.DropArea {
         visible: false
     }
 
+    Connections {
+        target: plasmoid.configuration
+
+        onSortModeChanged: {
+            itemView.sortMode = plasmoid.configuration.sortMode;
+        }
+
+        onPositionsChanged: {
+            itemView.positions = plasmoid.configuration.positions;
+        }
+    }
+
     ItemView {
         id: itemView
 
@@ -241,12 +291,26 @@ DragDrop.DropArea {
         isRootView: true
 
         url: plasmoid.configuration.url
+        locked: plasmoid.configuration.locked
         filterMode: plasmoid.configuration.filterMode
         filterPattern: plasmoid.configuration.filterPattern
         filterMimeTypes: plasmoid.configuration.filterMimeTypes
 
         flow: (plasmoid.configuration.arrangement == 0) ? GridView.FlowLeftToRight : \
                GridView.FlowTopToBottom
         layoutDirection: (plasmoid.configuration.alignment == 0) ? Qt.LeftToRight : \
Qt.RightToLeft +
+        onSortModeChanged: {
+            plasmoid.configuration.sortMode = sortMode;
+        }
+
+        onPositionsChanged: {
+            plasmoid.configuration.positions = itemView.positions;
+        }
+
+        Component.onCompleted: {
+            itemView.sortMode = plasmoid.configuration.sortMode;
+            itemView.positions = plasmoid.configuration.positions;
+        }
     }
 
     Item {
diff --git a/containments/folder/plugin/foldermodel.cpp \
b/containments/folder/plugin/foldermodel.cpp index a677870..6a6b594 100644
--- a/containments/folder/plugin/foldermodel.cpp
+++ b/containments/folder/plugin/foldermodel.cpp
@@ -1,5 +1,7 @@
 /***************************************************************************
+ *   Copyright (C) 2006 David Faure <faure@kde.org>                        *
  *   Copyright (C) 2008 Fredrik Höglund <fredrik@kde.org>                  *
+ *   Copyright (C) 2008 Rafael Fernández López <ereslibre@kde.org>           *
  *   Copyright (C) 2011 Marco Martin <mart@kde.org>                        *
  *   Copyright (C) 2014 by Eike Hein <hein@kde.org>                        *
  *                                                                         *
@@ -20,12 +22,21 @@
  ***************************************************************************/
 
 #include "foldermodel.h"
+#include "itemviewadapter.h"
+#include "positioner.h"
 
 #include <QApplication>
 #include <QClipboard>
-#include <QDesktopWidget>
 #include <QCollator>
+#include <QDesktopWidget>
+#include <QDrag>
+#include <QImage>
 #include <QItemSelectionModel>
+#include <QMimeData>
+#include <QPainter>
+#include <QPixmap>
+#include <QQuickItem>
+#include <QQuickWindow>
 #include <qplatformdefs.h>
 #include <QDebug>
 
@@ -42,6 +53,7 @@
 #include <KSharedConfig>
 #include <KShell>
 #include <KUrl>
+#include <KUrlMimeData>
 
 #include <KDesktopFile>
 #include <KDirModel>
@@ -72,12 +84,14 @@ void DirLister::handleError(KIO::Job *job)
 }
 
 FolderModel::FolderModel(QObject *parent) : QSortFilterProxyModel(parent),
+    m_dragInProgress(false),
     m_previewGenerator(0),
     m_viewAdapter(0),
     m_actionCollection(this),
     m_newMenu(0),
     m_usedByContainment(false),
-    m_sortMode(0),
+    m_locked(true),
+    m_sortMode(1),
     m_sortDesc(false),
     m_sortDirsFirst(true),
     m_parseDesktopFiles(false),
@@ -103,7 +117,9 @@ FolderModel::FolderModel(QObject *parent) : \
QSortFilterProxyModel(parent),  setFilterCaseSensitivity(Qt::CaseInsensitive);
     setDynamicSortFilter(true);
 
-    sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder);
+    sort(m_sortMode - 1, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder);
+
+    setSupportedDragActions(Qt::CopyAction | Qt::MoveAction | Qt::LinkAction);
 
     createActions();
 }
@@ -117,11 +133,12 @@ QHash< int, QByteArray > FolderModel::roleNames() const
     QHash<int, QByteArray> roleNames = m_dirModel->roleNames();
     roleNames[Qt::DisplayRole] = "display";
     roleNames[Qt::DecorationRole] = "decoration";
-    roleNames[Qt::UserRole + 1] = "selected";
-    roleNames[Qt::UserRole + 2] = "isDir";
-    roleNames[Qt::UserRole + 3] = "url";
-    roleNames[Qt::UserRole + 4] = "size";
-    roleNames[Qt::UserRole + 5] = "type";
+    roleNames[BlankRole] = "blank";
+    roleNames[SelectedRole] = "selected";
+    roleNames[IsDirRole] = "isDir";
+    roleNames[UrlRole] = "url";
+    roleNames[SizeRole] = "size";
+    roleNames[TypeRole] = "type";
 
     return roleNames;
 }
@@ -150,6 +167,7 @@ void FolderModel::setUrl(const QString& _url)
     beginResetModel();
     m_url = _url;
     m_dirModel->dirLister()->openUrl(url);
+    clearDragImages();
     endResetModel();
 
     emit urlChanged();
@@ -177,6 +195,20 @@ void FolderModel::setUsedByContainment(bool used)
     }
 }
 
+bool FolderModel::locked() const
+{
+    return m_locked;
+}
+
+void FolderModel::setLocked(bool locked)
+{
+    if (m_locked != locked) {
+        m_locked = locked;
+
+        emit lockedChanged();
+    }
+}
+
 void FolderModel::dirListFailed(const QString& error)
 {
     m_errorString = error;
@@ -193,8 +225,13 @@ void FolderModel::setSortMode(int mode)
     if (m_sortMode != mode) {
         m_sortMode = mode;
 
-        invalidate();
-        sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder);
+        if (mode == 0 /* Unsorted */) {
+            setDynamicSortFilter(false);
+        } else {
+            setDynamicSortFilter(true);
+            invalidate();
+            sort(m_sortMode - 1, m_sortDesc ? Qt::DescendingOrder : \
Qt::AscendingOrder); +        }
 
         emit sortModeChanged();
     }
@@ -210,8 +247,10 @@ void FolderModel::setSortDesc(bool desc)
     if (m_sortDesc != desc) {
         m_sortDesc = desc;
 
-        invalidate();
-        sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder);
+        if (m_sortMode != 0) {
+            invalidate();
+            sort(m_sortMode - 1, m_sortDesc ? Qt::DescendingOrder : \
Qt::AscendingOrder); +        }
 
         emit sortDescChanged();
     }
@@ -227,8 +266,10 @@ void FolderModel::setSortDirsFirst(bool enable)
     if (m_sortDirsFirst != enable) {
         m_sortDirsFirst = enable;
 
-        invalidate();
-        sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder);
+        if (m_sortMode != 0) {
+            invalidate();
+            sort(m_sortMode - 1, m_sortDesc ? Qt::DescendingOrder : \
Qt::AscendingOrder); +        }
 
         emit sortDirsFirstChanged();
     }
@@ -371,6 +412,10 @@ void FolderModel::setFilterMimeTypes(const QStringList \
&mimeList)  
 void FolderModel::run(int row)
 {
+    if (row < 0) {
+        return;
+    }
+
     KFileItem item = itemForIndex(index(row, 0));
 
     QUrl url(item.targetUrl());
@@ -383,34 +428,290 @@ void FolderModel::run(int row)
 
 void FolderModel::rename(int row, const QString& name)
 {
+    if (row < 0) {
+        return;
+    }
+
     QModelIndex idx = index(row, 0);
     m_dirModel->setData(mapToSource(idx), name, Qt::EditRole);
 }
 
 bool FolderModel::isSelected(int row)
 {
+    if (row < 0) {
+        return false;
+    }
+
     return m_selectionModel->isSelected(index(row, 0));
 }
 
 void FolderModel::setSelected(int row)
 {
+    if (row < 0) {
+        return;
+    }
+
     m_selectionModel->select(index(row, 0), QItemSelectionModel::Select);
 }
 
+void FolderModel::toggleSelected(int row)
+{
+    if (row < 0) {
+        return;
+    }
+
+    m_selectionModel->select(index(row, 0), QItemSelectionModel::Toggle);
+}
+
 void FolderModel::setRangeSelected(int startRow, int endRow)
 {
+    if (startRow < 0 || endRow < 0) {
+        return;
+    }
+
     QItemSelection selection(index(startRow, 0), index(endRow, 0));
     m_selectionModel->select(selection, QItemSelectionModel::ClearAndSelect);
 }
 
-void FolderModel::toggleSelected(int row)
+void FolderModel::updateSelection(const QVariantList &rows, bool toggle)
 {
-    m_selectionModel->select(index(row, 0), QItemSelectionModel::Toggle);
+    QItemSelection newSelection;
+
+    int iRow = -1;
+
+    foreach (const QVariant &row, rows) {
+        iRow = row.toInt();
+
+        if (iRow < 0) {
+            return;
+        }
+
+        const QModelIndex &idx = index(iRow, 0);
+        newSelection.select(idx, idx);
+    }
+
+    if (toggle) {
+        QItemSelection pinnedSelection = m_pinnedSelection;
+        pinnedSelection.merge(newSelection, QItemSelectionModel::Toggle);
+        m_selectionModel->select(pinnedSelection, \
QItemSelectionModel::ClearAndSelect); +    } else {
+        m_selectionModel->select(newSelection, QItemSelectionModel::ClearAndSelect);
+    }
 }
 
 void FolderModel::clearSelection()
 {
-    m_selectionModel->clear();
+    if (m_selectionModel->hasSelection()) {
+        m_selectionModel->clear();
+    }
+}
+
+void FolderModel::pinSelection()
+{
+    m_pinnedSelection = m_selectionModel->selection();
+}
+
+void FolderModel::unpinSelection()
+{
+    m_pinnedSelection = QItemSelection();
+}
+
+void FolderModel::addItemDragImage(int row, int x, int y, int width, int height, \
const QVariant &image) +{
+    if (row < 0) {
+        return;
+    }
+
+    DragImage *dragImage = 0;
+
+    if (m_dragImages.contains(row)) {
+        dragImage = m_dragImages.value(row);
+        delete dragImage;
+        m_dragImages.remove(row);
+    }
+
+    dragImage = new DragImage();
+    dragImage->row = row;
+    dragImage->rect = QRect(x, y, width, height);
+    dragImage->image = image.value<QImage>();
+    dragImage->blank = false;
+
+    m_dragImages.insert(row, dragImage);
+}
+
+void FolderModel::clearDragImages()
+{
+    if (!m_dragImages.isEmpty()) {
+        foreach (DragImage *image, m_dragImages) {
+            delete image;
+        }
+
+        m_dragImages.clear();
+    }
+}
+
+void FolderModel::setDragHotSpotScrollOffset(int x, int y)
+{
+    m_dragHotSpotScrollOffset.setX(x);
+    m_dragHotSpotScrollOffset.setY(y);
+}
+
+QPoint FolderModel::dragCursorOffset(int row)
+{
+    if (!m_dragImages.contains(row)) {
+        return QPoint(-1, -1);
+    }
+
+    return m_dragImages.value(row)->cursorOffset;
+}
+
+void FolderModel::addDragImage(QDrag *drag, int x, int y)
+{
+    if (!drag || m_dragImages.isEmpty()) {
+        return;
+    }
+
+    QRegion region;
+
+    foreach (DragImage *image, m_dragImages) {
+        image->blank = isBlank(image->row);
+        image->rect.translate(-m_dragHotSpotScrollOffset.x(), \
-m_dragHotSpotScrollOffset.y()); +        if (!image->blank && \
!image->image.isNull()) { +            region = region.united(image->rect);
+        }
+    }
+
+    QRect rect = region.boundingRect();
+    QPoint offset = rect.topLeft();
+    rect.translate(-offset.x(), -offset.y());
+
+    QImage dragImage(rect.size(), QImage::Format_RGBA8888);
+    dragImage.fill(Qt::transparent);
+
+    QPainter painter(&dragImage);
+
+    QPoint pos;
+
+    foreach (DragImage *image, m_dragImages) {
+        if (!image->blank && !image->image.isNull()) {
+            pos = image->rect.translated(-offset.x(), -offset.y()).topLeft();
+            image->cursorOffset.setX(pos.x());
+            image->cursorOffset.setY(pos.y());
+
+            painter.drawImage(pos, image->image);
+        }
+
+        // FIXME HACK: Operate on copy.
+        image->rect.translate(m_dragHotSpotScrollOffset.x(), \
m_dragHotSpotScrollOffset.y()); +    }
+
+    drag->setPixmap(QPixmap::fromImage(dragImage));
+    drag->setHotSpot(QPoint(x - offset.x(), y - offset.y()));
+}
+
+void FolderModel::dragSelected(int x, int y)
+{
+    //FIXME Don't drag blank ones.
+
+    if (!m_viewAdapter || !m_selectionModel->hasSelection()) {
+        return;
+    }
+
+    ItemViewAdapter *adapter = qobject_cast<ItemViewAdapter *>(m_viewAdapter);
+    QQuickItem *item = qobject_cast<QQuickItem *>(adapter->adapterView());
+
+    QDrag *drag = new QDrag(item);
+
+    addDragImage(drag, x, y);
+
+    m_dragIndexes = m_selectionModel->selectedIndexes();
+
+    qSort(m_dragIndexes.begin(), m_dragIndexes.end());
+
+    // TODO: Optimize to emit contiguous groups.
+    emit dataChanged(m_dragIndexes.first(), m_dragIndexes.last(), QVector<int>() << \
BlankRole); +
+    QModelIndexList sourceDragIndexes;
+
+    foreach (const QModelIndex &index, m_dragIndexes) {
+        sourceDragIndexes.append(mapToSource(index));
+    }
+
+    drag->setMimeData(m_dirModel->mimeData(sourceDragIndexes));
+
+    item->grabMouse();
+    m_dragInProgress = true;
+    drag->exec(supportedDragActions());
+    m_dragInProgress = false;
+    item->ungrabMouse();
+
+    const QModelIndex first(m_dragIndexes.first());
+    const QModelIndex last(m_dragIndexes.last());
+    m_dragIndexes.clear();
+    // TODO: Optimize to emit contiguous groups.
+    emit dataChanged(first, last, QVector<int>() << BlankRole);
+}
+
+void FolderModel::drop(QQuickItem *target, QObject* dropEvent, int row)
+{
+    QMimeData *mimeData = qobject_cast<QMimeData \
*>(dropEvent->property("mimeData").value<QObject *>()); +
+    if (!mimeData) {
+        return;
+    }
+
+    if (m_dragInProgress) {
+        if (m_locked || mimeData->urls().isEmpty()) {
+            return;
+        }
+
+        setSortMode(0);
+
+        emit move(dropEvent->property("x").toInt(), \
dropEvent->property("y").toInt(), +            mimeData->urls());
+
+        return;
+    }
+
+    KFileItem item;
+
+    if (row > -1 && row < rowCount()) {
+         item = itemForIndex(index(row, 0));
+    }
+
+    if (item.isNull() &&
+        mimeData->hasFormat(QLatin1String("application/x-kde-ark-dndextract-service")) \
&& +        mimeData->hasFormat(QLatin1String("application/x-kde-ark-dndextract-service"))) \
{ +        const QString remoteDBusClient = \
mimeData->data(QLatin1String("application/x-kde-ark-dndextract-service")); +        \
const QString remoteDBusPath = \
mimeData->data(QLatin1String("application/x-kde-ark-dndextract-path")); +
+        QDBusMessage message =
+            QDBusMessage::createMethodCall(remoteDBusClient, remoteDBusPath,
+                                            QLatin1String("org.kde.ark.DndExtract"),
+                                            \
QLatin1String("extractSelectedFilesTo")); +        \
message.setArguments(QVariantList() << \
m_dirModel->dirLister()->url().adjusted(QUrl::PreferLocalFile)); +
+        QDBusConnection::sessionBus().call(message);
+
+        return;
+    }
+
+    QPoint pos;
+    pos.setX(dropEvent->property("x").toInt());
+    pos.setY(dropEvent->property("y").toInt());
+
+    pos = target->mapToScene(pos).toPoint();
+    pos = target->window()->mapToGlobal(pos);
+
+    Qt::DropAction proposedAction((Qt::DropAction)dropEvent->property("proposedAction").toInt());
 +    Qt::DropActions \
possibleActions(dropEvent->property("possibleActions").toInt()); +    \
Qt::MouseButtons buttons(dropEvent->property("buttons").toInt()); +    \
Qt::KeyboardModifiers modifiers(dropEvent->property("modifiers").toInt()); +
+    QDropEvent ev(pos, possibleActions, mimeData, buttons, modifiers);
+    ev.setDropAction(proposedAction);
+
+    KonqOperations::doDrop(item, m_dirModel->dirLister()->url(), &ev, 0, \
QList<QAction *>());  }
 
 void FolderModel::selectionChanged(QItemSelection selected, QItemSelection \
deselected) @@ -419,11 +720,32 @@ void FolderModel::selectionChanged(QItemSelection \
selected, QItemSelection desel  indices.append(deselected.indexes());
 
     QVector<int> roles;
-    roles.append(Qt::UserRole + 1);
+    roles.append(SelectedRole);
 
     foreach(const QModelIndex index, indices) {
         emit dataChanged(index, index, roles);
     }
+
+    if (!m_selectionModel->hasSelection()) {
+        clearDragImages();
+    } else {
+        foreach (const QModelIndex &idx, deselected.indexes()) {
+            if (m_dragImages.contains(idx.row())) {
+                DragImage *image = m_dragImages.value(idx.row());
+                delete image;
+                m_dragImages.remove(idx.row());
+            }
+        }
+    }
+}
+
+bool FolderModel::isBlank(int row) const
+{
+    if (row < 0) {
+        return true;
+    }
+
+    return data(index(row, 0), BlankRole).toBool();
 }
 
 QVariant FolderModel::data(const QModelIndex& index, int role) const
@@ -432,26 +754,30 @@ QVariant FolderModel::data(const QModelIndex& index, int role) \
const  return QVariant();
     }
 
-    if (role == Qt::UserRole + 1) {
+    if (role == BlankRole) {
+        return m_dragIndexes.contains(index);
+    } else if (role == SelectedRole) {
         return m_selectionModel->isSelected(index);
-    } else if (role == Qt::UserRole + 2) {
-        bool moo = isDir(mapToSource(index), m_dirModel);
-
-        return moo;
-    } else if (role == Qt::UserRole + 3) {
-        KFileItem item = itemForIndex(index);
-
-        return item.url();
-    } else if (role == Qt::UserRole + 4) {
+    } else if (role == IsDirRole) {
+        return isDir(mapToSource(index), m_dirModel);
+    } else if (role == UrlRole) {
+        return itemForIndex(index).url();
+    } else if (role == SizeRole) {
         return m_dirModel->data(mapToSource(QSortFilterProxyModel::index(index.row(), \
                1)), Qt::DisplayRole);
-    }
-    else if (role == Qt::UserRole + 5) {
+    } else if (role == TypeRole) {
         return m_dirModel->data(mapToSource(QSortFilterProxyModel::index(index.row(), \
6)), Qt::DisplayRole); +    } else if (role == FileNameRole) {
+        return itemForIndex(index).url().fileName();
     }
 
     return QSortFilterProxyModel::data(index, role);
 }
 
+int FolderModel::indexForUrl(const QUrl& url) const
+{
+    return mapFromSource(m_dirModel->indexForUrl(url)).row();
+}
+
 KFileItem FolderModel::itemForIndex(const QModelIndex &index) const
 {
     return m_dirModel->itemForIndex(mapToSource(index));
diff --git a/containments/folder/plugin/foldermodel.h \
b/containments/folder/plugin/foldermodel.h index 3babc09..b6ec94b 100644
--- a/containments/folder/plugin/foldermodel.h
+++ b/containments/folder/plugin/foldermodel.h
@@ -22,6 +22,7 @@
 #ifndef FOLDERMODEL_H
 #define FOLDERMODEL_H
 
+#include <QImage>
 #include <QItemSelection>
 #include <QPointer>
 #include <QSortFilterProxyModel>
@@ -34,7 +35,9 @@
 #include <KFilePreviewGenerator>
 #include <KDirLister>
 
+class QDrag;
 class QItemSelectionModel;
+class QQuickItem;
 
 class KActionCollection;
 class KDirModel;
@@ -63,6 +66,7 @@ class FolderModel : public QSortFilterProxyModel
     Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged)
     Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged)
     Q_PROPERTY(bool usedByContainment READ usedByContainment WRITE \
setUsedByContainment NOTIFY usedByContainmentChanged); +    Q_PROPERTY(bool locked \
                READ locked WRITE setLocked NOTIFY lockedChanged)
     Q_PROPERTY(int sortMode READ sortMode WRITE setSortMode NOTIFY sortModeChanged)
     Q_PROPERTY(bool sortDesc READ sortDesc WRITE setSortDesc NOTIFY sortDescChanged)
     Q_PROPERTY(bool sortDirsFirst READ sortDirsFirst WRITE setSortDirsFirst NOTIFY \
sortDirsFirstChanged) @@ -75,6 +79,16 @@ class FolderModel : public \
                QSortFilterProxyModel
     Q_PROPERTY(QStringList filterMimeTypes READ filterMimeTypes WRITE \
setFilterMimeTypes NOTIFY filterMimeTypesChanged)  
     public:
+        enum DataRole {
+            BlankRole = Qt::UserRole + 1,
+            SelectedRole,
+            IsDirRole,
+            UrlRole,
+            SizeRole,
+            TypeRole,
+            FileNameRole
+        };
+
         enum FilterMode {
             NoFilter = 0,
             FilterShowMatches,
@@ -94,6 +108,9 @@ class FolderModel : public QSortFilterProxyModel
         bool usedByContainment() const;
         void setUsedByContainment(bool used);
 
+        bool locked() const;
+        void setLocked(bool locked);
+
         int sortMode() const;
         void setSortMode(int mode);
 
@@ -130,15 +147,28 @@ class FolderModel : public QSortFilterProxyModel
 
         Q_INVOKABLE bool isSelected(int row);
         Q_INVOKABLE void setSelected(int row);
-        Q_INVOKABLE void setRangeSelected(int startRow, int endRow);
         Q_INVOKABLE void toggleSelected(int row);
+        Q_INVOKABLE void setRangeSelected(int startRow, int endRow);
+        Q_INVOKABLE void updateSelection(const QVariantList &rows, bool replace);
         Q_INVOKABLE void clearSelection();
+        Q_INVOKABLE void pinSelection();
+        Q_INVOKABLE void unpinSelection();
+
+        Q_INVOKABLE void addItemDragImage(int row, int x, int y, int width, int \
height, const QVariant &image); +        Q_INVOKABLE void clearDragImages();
+        Q_INVOKABLE void setDragHotSpotScrollOffset(int x, int y); // FIXME TODO: \
Propify. +        Q_INVOKABLE QPoint dragCursorOffset(int row);
+        Q_INVOKABLE void dragSelected(int x, int y);
+        Q_INVOKABLE void drop(QQuickItem *target, QObject *dropEvent, int row);
+
+        Q_INVOKABLE bool isBlank(int row) const;
 
         Q_INVOKABLE void openContextMenu();
 
         Q_INVOKABLE void linkHere(const QUrl &sourceUrl);
 
         QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+        int indexForUrl(const QUrl &url) const;
         KFileItem itemForIndex(const QModelIndex &index) const;
         bool isDir(const QModelIndex &index, const KDirModel *dirModel) const;
         bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
@@ -147,6 +177,7 @@ class FolderModel : public QSortFilterProxyModel
         void urlChanged() const;
         void errorStringChanged() const;
         void usedByContainmentChanged() const;
+        void lockedChanged() const;
         void sortModeChanged() const;
         void sortDescChanged() const;
         void sortDirsFirstChanged() const;
@@ -158,6 +189,7 @@ class FolderModel : public QSortFilterProxyModel
         void filterPatternChanged() const;
         void filterMimeTypesChanged() const;
         void requestRename() const;
+        void move(int x, int y, QList<QUrl> urls);
 
     protected:
         bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
@@ -179,19 +211,34 @@ class FolderModel : public QSortFilterProxyModel
         void undoTextChanged(const QString &text);
 
     private:
+        struct DragImage {
+            int row;
+            QRect rect;
+            QPoint cursorOffset;
+            QImage image;
+            bool blank;
+        };
+
         void createActions();
         void updatePasteAction();
+        void addDragImage(QDrag *drag, int x, int y);
         QList<QUrl> selectedUrls(bool forTrash) const;
         KDirModel *m_dirModel;
         QString m_url;
         QItemSelectionModel *m_selectionModel;
+        QItemSelection m_pinnedSelection;
+        QModelIndexList m_dragIndexes;
+        QHash<int, DragImage *> m_dragImages;
+        QPoint m_dragHotSpotScrollOffset;
+        bool m_dragInProgress;
         QPointer<KFilePreviewGenerator> m_previewGenerator;
         QPointer<KAbstractViewAdapter> m_viewAdapter;
         KActionCollection m_actionCollection;
         KNewFileMenu *m_newMenu;
         QString m_errorString;
         bool m_usedByContainment;
-        int m_sortMode;
+        bool m_locked;
+        int m_sortMode; // FIXME TODO: Enumify.
         bool m_sortDesc;
         bool m_sortDirsFirst;
         bool m_parseDesktopFiles;
@@ -202,7 +249,6 @@ class FolderModel : public QSortFilterProxyModel
         bool m_filterPatternMatchAll;
         QSet<QString> m_mimeSet;
         QList<QRegExp> m_regExps;
-
 };
 
 #endif
diff --git a/containments/folder/plugin/folderplugin.cpp \
b/containments/folder/plugin/folderplugin.cpp index bf0c568..e32d9c2 100644
--- a/containments/folder/plugin/folderplugin.cpp
+++ b/containments/folder/plugin/folderplugin.cpp
@@ -20,11 +20,14 @@
 #include "folderplugin.h"
 #include "directorypicker.h"
 #include "foldermodel.h"
+#include "itemgrabber.h"
 #include "itemviewadapter.h"
 #include "labelgenerator.h"
 #include "mimetypesmodel.h"
 #include "placesmodel.h"
+#include "positioner.h"
 #include "previewpluginsmodel.h"
+#include "rubberband.h"
 #include "subdialog.h"
 #include "systemsettings.h"
 
@@ -35,11 +38,14 @@ void FolderPlugin::registerTypes(const char *uri)
     Q_ASSERT(uri == QLatin1String("org.kde.plasma.private.folder"));
     qmlRegisterType<DirectoryPicker>(uri, 0, 1, "DirectoryPicker");
     qmlRegisterType<FolderModel>(uri, 0, 1, "FolderModel");
+    qmlRegisterType<ItemGrabber>(uri, 0, 1, "ItemGrabber");
     qmlRegisterType<ItemViewAdapter>(uri, 0, 1, "ItemViewAdapter");
     qmlRegisterType<LabelGenerator>(uri, 0, 1, "LabelGenerator");
     qmlRegisterType<FilterableMimeTypesModel>(uri, 0, 1, \
"FilterableMimeTypesModel");  qmlRegisterType<PlacesModel>(uri, 0, 1, "PlacesModel");
+    qmlRegisterType<Positioner>(uri, 0, 1, "Positioner");
     qmlRegisterType<PreviewPluginsModel>(uri, 0, 1, "PreviewPluginsModel");
+    qmlRegisterType<RubberBand>(uri, 0, 1, "RubberBand");
     qmlRegisterType<SubDialog>(uri, 0, 1, "SubDialog");
     qmlRegisterType<SystemSettings>(uri, 0, 1, "SystemSettings");
 }
diff --git a/containments/folder/plugin/itemgrabber.cpp \
b/containments/folder/plugin/itemgrabber.cpp new file mode 100644
index 0000000..13681d2
--- /dev/null
+++ b/containments/folder/plugin/itemgrabber.cpp
@@ -0,0 +1,108 @@
+/***************************************************************************
+ *   Copyright (C) 2014 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 .        *
+ ***************************************************************************/
+
+#include "itemgrabber.h"
+
+#include <QQuickItem>
+#include <QQuickWindow>
+#include <QSGTexture>
+#include <QSGTextureProvider>
+#include <QOpenGLFunctions>
+
+ItemGrabber::ItemGrabber(QObject *parent) : QObject(parent)
+{
+}
+
+ItemGrabber::~ItemGrabber()
+{
+}
+
+QQuickItem *ItemGrabber::item() const
+{
+    return m_item;
+}
+
+void ItemGrabber::setItem(QQuickItem *item)
+{
+    if (m_item != item) {
+        m_item = item;
+
+        emit itemChanged();
+    }
+
+    if (m_item && m_item->window()) {
+        connect(m_item->window(), SIGNAL(afterRendering()), this, SLOT(grab()), \
Qt::DirectConnection); +    } else {
+        m_image = QImage();
+        emit nullChanged();
+    }
+}
+
+bool ItemGrabber::null() const
+{
+    return m_image.isNull();
+}
+
+QImage ItemGrabber::image() const
+{
+    return m_image;
+}
+
+void ItemGrabber::grab()
+{
+    if (!m_item || !m_item->window()) {
+        return;
+    }
+
+    QSGTextureProvider *provider = m_item->textureProvider();
+
+    if (!provider) {
+        return;
+    }
+
+    QSGTexture *texture = provider->texture();
+
+    if (!texture) {
+        return;
+    }
+
+    QOpenGLFunctions *f = m_item->window()->openglContext()->functions();
+
+    m_image = QImage(m_item->width(), m_item->height(), QImage::Format_RGBA8888);
+    m_image.fill(Qt::transparent);
+
+    GLint prevfbo;
+    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prevfbo);
+
+    GLuint fbo;
+    f->glGenFramebuffers(1, &fbo);
+    f->glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+    f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, \
texture->textureId(), 0); +    glReadPixels(0, 0, m_image.width(), m_image.height(), \
GL_RGBA, GL_UNSIGNED_BYTE, m_image.bits()); +    f->glDeleteFramebuffers(1, &fbo);
+
+    f->glBindFramebuffer(GL_FRAMEBUFFER, prevfbo);
+
+    emit nullChanged();
+    emit imageChanged();
+
+    if (m_item && m_item->window()) {
+        disconnect(m_item->window(), SIGNAL(afterRendering()), this, SLOT(grab()));
+    }
+}
diff --git a/containments/folder/plugin/systemsettings.h \
b/containments/folder/plugin/itemgrabber.h similarity index 66%
copy from containments/folder/plugin/systemsettings.h
copy to containments/folder/plugin/itemgrabber.h
index 2e58d27..81ad80d 100644
--- a/containments/folder/plugin/systemsettings.h
+++ b/containments/folder/plugin/itemgrabber.h
@@ -17,36 +17,44 @@
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
  ***************************************************************************/
 
-#ifndef SYSTEMSETTINGS_H
-#define SYSTEMSETTINGS_H
+#ifndef ITEMGRABBER_H
+#define ITEMGRABBER_H
 
+#include <QImage>
 #include <QObject>
+#include <QPointer>
+#include <QQuickItem>
 
-class QWidget;
-
-class SystemSettings : public QObject
+class ItemGrabber : public QObject
 {
     Q_OBJECT
 
-    Q_PROPERTY(bool singleClick READ singleClick NOTIFY singleClickChanged)
+    Q_PROPERTY(bool null READ null NOTIFY nullChanged)
+    Q_PROPERTY(QImage image READ image NOTIFY imageChanged)
+    Q_PROPERTY(QQuickItem* item READ item WRITE setItem NOTIFY itemChanged)
 
     public:
-        SystemSettings(QObject *parent = 0);
-        ~SystemSettings();
+        ItemGrabber(QObject *parent = 0);
+        ~ItemGrabber();
+
+        bool null() const;
 
-        bool singleClick() const;
-        Q_INVOKABLE int doubleClickInterval() const;
+        QImage image() const;
 
-        bool eventFilter(QObject *watched, QEvent *event);
+        QQuickItem *item() const;
+        void setItem(QQuickItem *item);
 
     Q_SIGNALS:
-        void singleClickChanged();
+        void nullChanged() const;
+        void imageChanged() const;
+        void itemChanged() const;
 
-    private:
-        // Keeping our own widget around is ugly, but beats filtering all
-        // events on something busy like the main window.
-        QWidget *m_monitoredWidget;
+    private Q_SLOTS:
+        void grab();
 
+    private:
+        QPointer<QQuickItem> m_item;
+        QImage m_image;
 };
 
 #endif
diff --git a/containments/folder/plugin/positioner.cpp \
b/containments/folder/plugin/positioner.cpp new file mode 100644
index 0000000..378f880
--- /dev/null
+++ b/containments/folder/plugin/positioner.cpp
@@ -0,0 +1,798 @@
+/***************************************************************************
+ *   Copyright (C) 2014 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 .        *
+ ***************************************************************************/
+
+#include "positioner.h"
+#include "foldermodel.h"
+
+#include <QDebug>
+
+Positioner::Positioner(QObject *parent): QAbstractItemModel(parent)
+, m_enabled(false)
+, m_folderModel(0)
+, m_perStripe(0)
+, m_pendingPositionsUpdate(false)
+, m_applyPositions(false)
+, m_resetting(false)
+, m_lastIndex(-1)
+, m_ignoreNextTransaction(false)
+{
+}
+
+Positioner::~Positioner()
+{
+}
+
+bool Positioner::enabled() const
+{
+    return m_enabled;
+}
+
+void Positioner::setEnabled(bool enabled)
+{
+    if (m_enabled != enabled) {
+        beginResetModel();
+
+        m_enabled = enabled;
+
+        if (enabled) {
+            initMaps();
+        }
+
+        endResetModel();
+
+        emit enabledChanged();
+
+        if (!m_pendingPositionsUpdate && m_folderModel->rowCount()) {
+            m_pendingPositionsUpdate = true;
+            QMetaObject::invokeMethod(this, "updatePositions", \
Qt::QueuedConnection); +        }
+    }
+}
+
+FolderModel* Positioner::folderModel() const
+{
+    return m_folderModel;
+}
+
+void Positioner::setFolderModel(QObject *folderModel)
+{
+    if (m_folderModel != folderModel) {
+        beginResetModel();
+
+        if (m_folderModel) {
+            disconnectSignals(m_folderModel);
+        }
+
+        m_folderModel = qobject_cast<FolderModel *>(folderModel);
+        connect(m_folderModel, SIGNAL(urlChanged()), this, SLOT(reset()), \
Qt::UniqueConnection); +
+        if (m_folderModel) {
+            connectSignals(m_folderModel);
+        }
+
+        endResetModel();
+
+        emit folderModelChanged();
+    }
+}
+
+int Positioner::perStripe() const
+{
+    return m_perStripe;
+}
+
+void Positioner::setPerStripe(int perStripe)
+{
+    if (m_perStripe != perStripe) {
+        m_perStripe = perStripe;
+
+        emit perStripeChanged();
+
+        if (m_enabled && m_perStripe > 0) {
+            applyPositions();
+
+            if (!m_pendingPositionsUpdate) {
+                m_pendingPositionsUpdate = true;
+                QMetaObject::invokeMethod(this, "updatePositions", \
Qt::QueuedConnection); +            }
+        }
+    }
+}
+
+QStringList Positioner::positions() const
+{
+    return m_positions;
+}
+
+void Positioner::setPositions(QStringList positions)
+{
+    if (m_positions != positions) {
+        m_positions = positions;
+
+        emit positionsChanged();
+    }
+}
+
+int Positioner::map(int row) const
+{
+    if (m_enabled) {
+        if (m_proxyToSource.contains(row)) {
+            return m_proxyToSource.value(row);
+        } else {
+            return -1;
+        }
+    }
+
+    return row;
+}
+
+bool Positioner::isBlank(int row) const
+{
+    if (!m_enabled && m_folderModel) {
+        return m_folderModel->isBlank(row);
+    }
+
+    if (m_proxyToSource.contains(row) && \
!m_folderModel->isBlank(m_proxyToSource.value(row))) { +        return false;
+    }
+
+    return true;
+}
+
+int Positioner::indexForUrl(const QUrl &url) const
+{
+    if (!m_folderModel) {
+        return -1;
+    }
+
+    const QString &name = url.fileName();
+
+    int sourceIndex = -1;
+
+    // TODO Optimize.
+    for (int i = 0; i < m_folderModel->rowCount(); ++i) {
+        if (m_folderModel->data(m_folderModel->index(i, 0), \
FolderModel::FileNameRole).toString() == name) { +            sourceIndex = i;
+
+            break;
+        }
+    }
+
+    if (m_sourceToProxy.contains(sourceIndex)) {
+        return m_sourceToProxy.value(sourceIndex);
+    }
+
+    return -1;
+}
+
+QHash< int, QByteArray > Positioner::roleNames() const
+{
+    if (m_folderModel) {
+        return m_folderModel->roleNames();
+    }
+
+    return QAbstractItemModel::roleNames();
+}
+
+QModelIndex Positioner::index(int row, int column, const QModelIndex &parent) const
+{
+    if (parent.isValid()) {
+        return QModelIndex();
+    }
+
+    return createIndex(row, column);
+}
+
+QModelIndex Positioner::parent(const QModelIndex &index) const
+{
+    if (m_folderModel) {
+        m_folderModel->parent(index);
+    }
+
+    return QModelIndex();
+}
+
+QVariant Positioner::data(const QModelIndex &index, int role) const
+{
+    if (!index.isValid()) {
+        return QVariant();
+    }
+
+    if (m_folderModel) {
+        if (m_enabled) {
+            if (m_proxyToSource.contains(index.row())) {
+                return \
m_folderModel->data(m_folderModel->index(m_proxyToSource.value(index.row()), 0), \
role); +            } else if (role == FolderModel::BlankRole) {
+                return true;
+            }
+        } else {
+            return m_folderModel->data(m_folderModel->index(index.row(), 0), role);
+        }
+    }
+
+    return QVariant();
+}
+
+int Positioner::rowCount(const QModelIndex& parent) const
+{
+    if (m_folderModel) {
+        if (m_enabled) {
+            return lastIndex() + 1;
+        } else {
+            return m_folderModel->rowCount(parent);
+        }
+    }
+
+    return 0;
+}
+
+int Positioner::columnCount(const QModelIndex& parent) const
+{
+    Q_UNUSED(parent)
+
+    if (m_folderModel) {
+        return 1;
+    }
+
+    return 0;
+}
+
+void Positioner::reset()
+{
+    m_resetting = true;
+
+    beginResetModel();
+
+    initMaps();
+
+    m_positions = QStringList();
+    emit positionsChanged();
+
+    endResetModel();
+}
+
+void Positioner::move(int from, int to) {
+    if (!m_proxyToSource.contains(from) || from == to) {
+        return;
+    }
+
+    if (to == -1) {
+        to = firstFreeRow();
+
+        if (to == -1) {
+            to = lastIndex() + 1;
+        }
+    } else if (!isBlank(to)) {
+        return;
+    }
+
+    int oldCount = rowCount();
+
+    int sourceRow = m_proxyToSource.value(from);
+    m_proxyToSource.remove(from);
+    m_proxyToSource.insert(to, sourceRow);
+    m_lastIndex = -1;
+    m_sourceToProxy.insert(sourceRow, to);
+
+    const QModelIndex &fromIdx = index(from, 0);
+    emit dataChanged(fromIdx, fromIdx);
+
+    if (to < oldCount) {
+        const QModelIndex &toIdx = index(to, 0);
+        emit dataChanged(toIdx, toIdx);
+    } else {
+        beginInsertRows(QModelIndex(), oldCount, to);
+        endInsertRows();
+    }
+
+    int newCount = rowCount();
+
+    if (newCount < oldCount) {
+        beginRemoveRows(QModelIndex(), newCount, oldCount - 1);
+        endRemoveRows();
+    }
+
+    if (!m_pendingPositionsUpdate) {
+        m_pendingPositionsUpdate = true;
+        QMetaObject::invokeMethod(this, "updatePositions", Qt::QueuedConnection);
+    }
+}
+
+void Positioner::updatePositions()
+{
+    if (m_perStripe < 1) {
+        m_pendingPositionsUpdate = false;
+
+        return;
+    }
+
+    QStringList positions;
+
+    if (m_enabled && !m_proxyToSource.isEmpty()) {
+        positions.append(QString::number((1 + ((rowCount() - 1) / m_perStripe))));
+        positions.append(QString::number(m_perStripe));
+
+        QHashIterator<int, int> it(m_proxyToSource);
+
+        while (it.hasNext()) {
+            it.next();
+
+            const QString &name = \
m_folderModel->data(m_folderModel->index(it.value(), 0), +                \
FolderModel::UrlRole).toString(); +
+            if (name.isEmpty()) {
+                qDebug() << it.value() << "Source model doesn't know this index!";
+
+                return;
+            }
+
+            positions.append(name);
+            positions.append(QString::number(qMax(0, it.key() / m_perStripe)));
+            positions.append(QString::number(qMax(0, it.key() % m_perStripe)));
+        }
+    }
+
+    setPositions(positions);
+
+    m_pendingPositionsUpdate = false;
+}
+
+void Positioner::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex \
&bottomRight, +    const QVector<int>& roles)
+{
+    if (m_enabled) {
+        int start = topLeft.row();
+        int end = bottomRight.row();
+
+        for (int i = start; i <= end; ++i) {
+            if (m_sourceToProxy.contains(i)) {
+                const QModelIndex &idx = index(m_sourceToProxy.value(i), 0);
+
+                emit dataChanged(idx, idx);
+            }
+        }
+    } else {
+        emit dataChanged(topLeft, bottomRight, roles);
+    }
+}
+
+void Positioner::sourceModelAboutToBeReset()
+{
+    emit beginResetModel();
+}
+
+void Positioner::sourceModelReset()
+{
+    emit endResetModel();
+}
+
+void Positioner::sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, \
int end) +{
+    if (m_enabled) {
+        if (m_sourceToProxy.isEmpty()) {
+            if (m_positions.isEmpty()) {
+                emit beginInsertRows(parent, start, end);
+
+                initMaps(end);
+            } else {
+                m_applyPositions = true;
+            }
+
+            return;
+        }
+
+        int free = -1;
+        int rest = -1;
+
+        for (int i = start; i <= end; ++i) {
+            free = firstFreeRow();
+
+            if (free != -1) {
+                m_proxyToSource.insert(free, i);
+                m_lastIndex = -1;
+                m_sourceToProxy.insert(i, free);
+                m_pendingChanges << createIndex(free, 0);
+            } else {
+                rest = i;
+                break;
+            }
+        }
+
+        if (rest != -1) {
+            int firstNew = lastIndex() + 1;
+            int remainder = (end - rest);
+
+            beginInsertRows(parent, firstNew, firstNew + remainder);
+
+            for (int i = 0; i <= remainder; ++i) {
+                m_proxyToSource.insert(firstNew + i, rest +i);
+                m_lastIndex = -1;
+                m_sourceToProxy.insert(rest + i, firstNew + i);
+            }
+        } else {
+            m_ignoreNextTransaction = true;
+        }
+    } else {
+        emit beginInsertRows(parent, start, end);
+    }
+}
+
+void Positioner::sourceRowsAboutToBeMoved(const QModelIndex &sourceParent, int \
sourceStart, +    int sourceEnd, const QModelIndex& destinationParent, int \
destinationRow) +{
+    qDebug();
+
+    emit beginMoveRows(sourceParent, sourceStart, sourceEnd, destinationParent, \
destinationRow); +}
+
+void Positioner::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int first, \
int last) +{
+    if (m_enabled) {
+        int oldLast = lastIndex();
+
+        for (int i = first; i <= last; ++i) {
+            int proxyRow = m_sourceToProxy.take(i);
+            m_proxyToSource.remove(proxyRow);
+
+            QHash<int, int> newProxyToSource;
+            QHashIterator<int, int> it(m_proxyToSource);
+
+            while (it.hasNext()) {
+                it.next();
+
+                if (it.value() > i) {
+                    newProxyToSource.insert(it.key(), it.value() - 1);
+                } else {
+                    newProxyToSource.insert(it.key(), it.value());
+                }
+            }
+
+            m_proxyToSource = newProxyToSource;
+            m_lastIndex = -1;
+
+            QHash<int, int> newSourceToProxy;
+            QHashIterator<int, int> it2(m_sourceToProxy);
+
+            while (it2.hasNext()) {
+                it2.next();
+
+                if (it2.key() > i) {
+                    newSourceToProxy.insert(it2.key() - 1, it2.value());
+                } else {
+                    newSourceToProxy.insert(it2.key(), it2.value());
+                }
+            }
+
+            m_sourceToProxy = newSourceToProxy;
+
+            m_pendingChanges << createIndex(proxyRow, 0);
+        }
+
+        int newLast = lastIndex();
+
+        if (oldLast > newLast) {
+            int diff = oldLast - newLast;
+            beginRemoveRows(QModelIndex(), ((oldLast - diff) + 1), oldLast);
+        } else {
+            m_ignoreNextTransaction = true;
+        }
+    } else {
+        emit beginRemoveRows(parent, first, last);
+    }
+}
+
+void Positioner::sourceRowsInserted(const QModelIndex &parent, int first, int last)
+{
+    Q_UNUSED(parent)
+    Q_UNUSED(first)
+    Q_UNUSED(last)
+
+    if (!m_ignoreNextTransaction) {
+        if (!m_applyPositions) {
+            emit endInsertRows();
+        } else {
+            if (m_perStripe > 0) {
+                applyPositions();
+            }
+        }
+    } else {
+        m_ignoreNextTransaction = false;
+    }
+
+    flushPendingChanges();
+
+    if (!m_pendingPositionsUpdate && !m_applyPositions) {
+        m_pendingPositionsUpdate = true;
+        QMetaObject::invokeMethod(this, "updatePositions", Qt::QueuedConnection);
+    }
+}
+
+void Positioner::sourceRowsMoved(const QModelIndex &sourceParent, int sourceStart,
+    int sourceEnd, const QModelIndex &destinationParent, int destinationRow)
+{
+    qDebug();
+
+    Q_UNUSED(sourceParent)
+    Q_UNUSED(sourceStart)
+    Q_UNUSED(sourceEnd)
+    Q_UNUSED(destinationParent)
+    Q_UNUSED(destinationRow)
+
+    emit endMoveRows();
+}
+
+void Positioner::sourceRowsRemoved(const QModelIndex &parent, int first, int last)
+{
+    Q_UNUSED(parent)
+    Q_UNUSED(first)
+    Q_UNUSED(last)
+
+    if (!m_ignoreNextTransaction) {
+        emit endRemoveRows();
+    } else {
+        m_ignoreNextTransaction = false;
+    }
+
+    flushPendingChanges();
+
+    if (!m_pendingPositionsUpdate) {
+        m_pendingPositionsUpdate = true;
+        QMetaObject::invokeMethod(this, "updatePositions", Qt::QueuedConnection);
+    }
+}
+
+void Positioner::initMaps(int size)
+{
+    m_proxyToSource.clear();
+    m_sourceToProxy.clear();
+
+    if (size == -1) {
+        size = m_folderModel->rowCount() - 1;
+    }
+
+    if (!size) {
+        return;
+    }
+
+    for (int i = 0; i <= size; ++i) {
+        m_proxyToSource.insert(i, i);
+        m_sourceToProxy.insert(i, i);
+    }
+
+    m_lastIndex = -1;
+}
+
+int Positioner::lastIndex() const
+{
+    if (!m_proxyToSource.isEmpty()) {
+        if (m_lastIndex != -1) {
+            return m_lastIndex;
+        } else {
+            QList<int> keys = m_proxyToSource.keys();
+            qSort(keys);
+            return keys.last();
+        }
+    }
+
+    return 0;
+}
+
+int Positioner::firstFreeRow() const
+{
+    if (!m_proxyToSource.isEmpty()) {
+        int last = lastIndex();
+
+        for (int i = 0; i <= last; ++i) {
+            if (!m_proxyToSource.contains(i)) {
+                return i;
+            }
+        }
+    }
+
+    return -1;
+}
+
+void Positioner::applyPositions()
+{
+    if (m_positions.size() < 5) {
+        if (m_folderModel) {
+            if (m_resetting) {
+                m_resetting = false;
+            } else {
+                beginResetModel();
+                m_proxyToSource.clear();
+                m_sourceToProxy.clear();
+                endResetModel();
+
+                initMaps();
+                beginInsertRows(QModelIndex(), 0, lastIndex());
+                endInsertRows();
+            }
+        }
+
+        return;
+    }
+
+    beginResetModel();
+    m_proxyToSource.clear();
+    m_sourceToProxy.clear();
+    endResetModel();
+
+    QStringList positions(m_positions);
+
+    bool ok = false;
+
+    int stripes = positions.takeFirst().toInt(&ok);
+    if (!ok) { return; }
+    int perStripe = positions.takeFirst().toInt(&ok);
+    if (!ok) { return; }
+
+    Q_UNUSED(stripes)
+    Q_UNUSED(perStripe)
+
+    QString name;
+    int stripe = -1;
+    int pos = -1;
+    int sourceIndex = -1;
+    int index = -1;
+
+    QHash<QString, int> sourceIndices;
+
+    for (int i = 0; i < m_folderModel->rowCount(); ++i) {
+        sourceIndices.insert(m_folderModel->data(m_folderModel->index(i, 0),
+            FolderModel::UrlRole).toString(), i);
+    }
+
+    int items = positions.count() / 3;
+
+    QStringList leftOvers;
+
+    for (int i = 0; i < items; ++i) {
+        name = positions.takeFirst();
+        stripe = positions.takeFirst().toInt(&ok);
+        if (!ok) { return; }
+        pos = positions.takeFirst().toInt(&ok);
+        if (!ok) { return; }
+
+        if (pos <= m_perStripe) {
+            if (!sourceIndices.contains(name)) {
+                continue;
+            } else {
+                sourceIndex = sourceIndices.take(name);
+            }
+
+            index = (stripe * m_perStripe) + pos;
+
+            m_proxyToSource.insert(index, sourceIndex);
+            m_lastIndex = -1;
+            m_sourceToProxy.insert(sourceIndex, index);
+        } else {
+            leftOvers.append(name);
+            leftOvers.append(QString::number(stripe));
+            leftOvers.append(QString::number(pos));
+        }
+    }
+
+    items = leftOvers.count() / 3;
+
+    for (int i = 0; i < items; ++i) {
+        name = leftOvers.takeFirst();
+        stripe = leftOvers.takeFirst().toInt(&ok);
+        if (!ok) { return; }
+        pos = leftOvers.takeFirst().toInt(&ok);
+        if (!ok) { return; }
+
+        if (!sourceIndices.contains(name)) {
+            continue;
+        } else {
+            sourceIndex = sourceIndices.take(name);
+        }
+
+        index = firstFreeRow();
+
+        if (index == -1) {
+            index = lastIndex() + 1;
+        }
+
+        m_proxyToSource.insert(index, sourceIndex);
+        m_lastIndex = -1;
+        m_sourceToProxy.insert(sourceIndex, index);
+    }
+
+    QHashIterator<QString, int> it(sourceIndices);
+
+    while (it.hasNext()) {
+        it.next();
+
+        index = firstFreeRow();
+
+        if (index == -1) {
+            index = lastIndex() + 1;
+        }
+
+        m_proxyToSource.insert(index, it.value());
+        m_lastIndex = -1;
+        m_sourceToProxy.insert(it.value(), index);
+    }
+
+    m_applyPositions = false;
+
+    beginInsertRows(QModelIndex(), 0, lastIndex());
+    endInsertRows();
+}
+
+void Positioner::flushPendingChanges()
+{
+    if (m_pendingChanges.isEmpty()) {
+        return;
+    }
+
+    int last = lastIndex();
+
+    foreach (const QModelIndex &idx, m_pendingChanges) {
+        if (idx.row() <= last) {
+            emit dataChanged(idx, idx);
+        }
+    }
+
+    m_pendingChanges.clear();
+}
+
+void Positioner::connectSignals(FolderModel* model)
+{
+    connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)),
+            this, SLOT(sourceDataChanged(QModelIndex,QModelIndex,QVector<int>)),
+            Qt::UniqueConnection);
+    connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
+            this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)),
+            Qt::UniqueConnection);
+    connect(model, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)),
+            this, SLOT(sourceRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)),
 +            Qt::UniqueConnection);
+    connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
+            this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)),
+            Qt::UniqueConnection);
+    connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
+            this, SLOT(sourceRowsInserted(QModelIndex,int,int)),
+            Qt::UniqueConnection);
+    connect(model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
+            this, SLOT(sourceRowsMoved(QModelIndex,int,int,QModelIndex,int)),
+            Qt::UniqueConnection);
+    connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
+            this, SLOT(sourceRowsRemoved(QModelIndex,int,int)),
+            Qt::UniqueConnection);
+}
+
+void Positioner::disconnectSignals(FolderModel* model)
+{
+    disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)),
+            this, SLOT(sourceDataChanged(QModelIndex,QModelIndex,QVector<int>)));
+    disconnect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
+            this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)));
+    disconnect(model, \
SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), +            this, \
SLOT(sourceRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); +    \
disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), +            \
this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int))); +    disconnect(model, \
SIGNAL(rowsInserted(QModelIndex,int,int)), +            this, \
SLOT(sourceRowsInserted(QModelIndex,int,int))); +    disconnect(model, \
SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), +            this, \
SLOT(sourceRowsMoved(QModelIndex,int,int,QModelIndex,int))); +    disconnect(model, \
SIGNAL(rowsRemoved(QModelIndex,int,int)), +            this, \
SLOT(sourceRowsRemoved(QModelIndex,int,int))); +}
diff --git a/containments/folder/plugin/positioner.h \
b/containments/folder/plugin/positioner.h new file mode 100644
index 0000000..b549eed
--- /dev/null
+++ b/containments/folder/plugin/positioner.h
@@ -0,0 +1,118 @@
+/***************************************************************************
+ *   Copyright (C) 2014 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 .        *
+ ***************************************************************************/
+
+#ifndef POSITIONER_H
+#define POSITIONER_H
+
+#include <QAbstractItemModel>
+
+class FolderModel;
+
+class Positioner : public QAbstractItemModel
+{
+    Q_OBJECT
+
+    Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
+    Q_PROPERTY(FolderModel* folderModel READ folderModel WRITE setFolderModel NOTIFY \
folderModelChanged) +    Q_PROPERTY(int perStripe READ perStripe WRITE setPerStripe \
NOTIFY perStripeChanged) +    Q_PROPERTY(QStringList positions READ positions WRITE \
setPositions NOTIFY positionsChanged) +
+    public:
+        Positioner(QObject *parent = 0);
+        ~Positioner();
+
+        bool enabled() const;
+        void setEnabled(bool enabled);
+
+        FolderModel *folderModel() const;
+        void setFolderModel(QObject *folderModel);
+
+        int perStripe() const;
+        void setPerStripe(int perStripe);
+
+        QStringList positions() const;
+        void setPositions(QStringList positions);
+
+        Q_INVOKABLE int map(int row) const;
+        Q_INVOKABLE bool isBlank(int row) const;
+        Q_INVOKABLE int indexForUrl(const QUrl &url) const;
+
+        Q_INVOKABLE void reset();
+
+        Q_INVOKABLE void move(int from, int to);
+
+        QHash<int, QByteArray> roleNames() const;
+
+        QModelIndex index(int row, int column, const QModelIndex &parent = \
QModelIndex()) const; +        QModelIndex parent(const QModelIndex &index) const;
+
+        QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+
+        int rowCount(const QModelIndex &parent = QModelIndex()) const;
+        int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+    Q_SIGNALS:
+        void enabledChanged() const;
+        void folderModelChanged() const;
+        void perStripeChanged() const;
+        void positionsChanged() const;
+
+    private Q_SLOTS:
+        void updatePositions();
+        void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex \
&bottomRight, +            const QVector<int> &roles);
+        void sourceModelAboutToBeReset();
+        void sourceModelReset();
+        void sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int \
end); +        void sourceRowsAboutToBeMoved(const QModelIndex &sourceParent, int \
sourceStart, +            int sourceEnd, const QModelIndex &destinationParent, int \
destinationRow); +        void sourceRowsAboutToBeRemoved(const QModelIndex &parent, \
int first, int last); +        void sourceRowsInserted(const QModelIndex &parent, int \
first, int last); +        void sourceRowsMoved(const QModelIndex &sourceParent, int \
sourceStart, +            int sourceEnd, const QModelIndex &destinationParent, int \
destinationRow); +        void sourceRowsRemoved(const QModelIndex &parent, int \
first, int last); +
+    private:
+        void initMaps(int size = -1);
+        int lastIndex() const;
+        int firstFreeRow() const;
+        void applyPositions();
+        void flushPendingChanges();
+        void connectSignals(FolderModel *model);
+        void disconnectSignals(FolderModel *model);
+
+        bool m_enabled;
+        FolderModel *m_folderModel;
+
+        int m_perStripe;
+        QStringList m_positions;
+        bool m_pendingPositionsUpdate;
+        bool m_applyPositions;
+        bool m_resetting; // TODO FIXME: Cleanup.
+        int m_lastIndex;
+        bool m_lastIndexDirty;
+
+        QHash<int, int> m_proxyToSource;
+        QHash<int, int> m_sourceToProxy;
+
+        QModelIndexList m_pendingChanges;
+        bool m_ignoreNextTransaction;
+};
+
+#endif
diff --git a/containments/folder/package/contents/config/config.qml \
b/containments/folder/plugin/rubberband.cpp similarity index 67%
copy from containments/folder/package/contents/config/config.qml
copy to containments/folder/plugin/rubberband.cpp
index acccf08..a646144 100644
--- a/containments/folder/package/contents/config/config.qml
+++ b/containments/folder/plugin/rubberband.cpp
@@ -17,26 +17,33 @@
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
  ***************************************************************************/
 
-import QtQuick 2.0
+#include "rubberband.h"
 
-import org.kde.plasma.configuration 2.0
+#include <QApplication>
+#include <QStyleOptionRubberBand>
 
-ConfigModel {
-    ConfigCategory {
-         name: "Location"
-         icon: "folder"
-         source: "ConfigLocation.qml"
-    }
+RubberBand::RubberBand(QQuickItem *parent) : QQuickPaintedItem(parent)
+{
+}
 
-    ConfigCategory {
-         name: "Icons"
-         icon: "preferences-desktop-icons"
-         source: "ConfigIcons.qml"
-    }
-    
-    ConfigCategory {
-         name: "Filter"
-         icon: "view-filter"
-         source: "ConfigFilter.qml"
+RubberBand::~RubberBand()
+{
+}
+
+void RubberBand::paint(QPainter *painter)
+{
+    if (!qApp || !qApp->style()) {
+        return;
     }
+
+    QStyleOptionRubberBand opt;
+    opt.state = QStyle::State_None;
+    opt.direction = qApp->layoutDirection();
+    opt.fontMetrics = qApp->fontMetrics();
+    opt.styleObject = this;
+    opt.palette = qApp->palette();
+    opt.shape = QRubberBand::Rectangle;
+    opt.opaque = false;
+    opt.rect = contentsBoundingRect().toRect();
+    qApp->style()->drawControl(QStyle::CE_RubberBand, &opt, painter);
 }
diff --git a/containments/folder/package/contents/code/tools.js \
b/containments/folder/plugin/rubberband.h similarity index 84%
copy from containments/folder/package/contents/code/tools.js
copy to containments/folder/plugin/rubberband.h
index fc48a4f..ce207f0 100644
--- a/containments/folder/package/contents/code/tools.js
+++ b/containments/folder/plugin/rubberband.h
@@ -17,9 +17,20 @@
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
  ***************************************************************************/
 
-// TODO: Avoid hardcoding the prop names, e.g. take them from the object.
-var iconSizes = new Array("small", "smallMedium", "medium", "large", "huge", \
"enormous"); +#ifndef RUBBERBAND_H
+#define RUBBERBAND_H
 
-function iconSizeFromTheme(size) {
-    return units.iconSizes[iconSizes[size]];
-}
\ No newline at end of file
+#include <QQuickPaintedItem>
+
+class RubberBand : public QQuickPaintedItem
+{
+    Q_OBJECT
+
+    public:
+        RubberBand(QQuickItem *parent = 0);
+        ~RubberBand();
+
+        void paint(QPainter* painter);
+};
+
+#endif
diff --git a/containments/folder/plugin/systemsettings.cpp \
b/containments/folder/plugin/systemsettings.cpp index 173196f..ffeb643 100644
--- a/containments/folder/plugin/systemsettings.cpp
+++ b/containments/folder/plugin/systemsettings.cpp
@@ -24,7 +24,7 @@
 #include <QStyle>
 #include <QWidget>
 
-SystemSettings::SystemSettings(QObject* parent) : QObject(parent),
+SystemSettings::SystemSettings(QObject *parent) : QObject(parent),
     m_monitoredWidget(new QWidget())
 {
     m_monitoredWidget->resize(0, 0);
@@ -52,6 +52,12 @@ int SystemSettings::doubleClickInterval() const
     return qApp->doubleClickInterval();
 }
 
+bool SystemSettings::isDrag(int oldX, int oldY, int newX, int newY) const
+{
+    return ((QPoint(oldX, oldY) - QPoint(newX, newY)).manhattanLength() >= \
QApplication::startDragDistance()); +}
+
+
 bool SystemSettings::eventFilter(QObject *watched, QEvent *event)
 {
     Q_UNUSED(watched)
diff --git a/containments/folder/plugin/systemsettings.h \
b/containments/folder/plugin/systemsettings.h index 2e58d27..fc37bcb 100644
--- a/containments/folder/plugin/systemsettings.h
+++ b/containments/folder/plugin/systemsettings.h
@@ -37,6 +37,8 @@ class SystemSettings : public QObject
         bool singleClick() const;
         Q_INVOKABLE int doubleClickInterval() const;
 
+        Q_INVOKABLE bool isDrag(int oldX, int oldY, int newX, int newY) const;
+
         bool eventFilter(QObject *watched, QEvent *event);
 
     Q_SIGNALS:


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic