[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