some more branding

This commit is contained in:
tumillanino
2026-03-26 14:36:06 +11:00
parent 204c1638b9
commit e7d6daf595
317 changed files with 23146 additions and 1 deletions

View File

@@ -0,0 +1,36 @@
/*
SPDX-FileCopyrightText: 2014-2015 Eike Hein <hein@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import org.kde.plasma.plasmoid
import org.kde.plasma.configuration
ConfigModel {
id: configModel
property bool isFolder: (Plasmoid.pluginName === "org.kde.plasma.folder")
ConfigCategory {
name: i18nc("@title:group for configuration dialog page", "Location")
icon: "inode-directory"
source: "ConfigLocation.qml"
visible: configModel.isFolder
}
ConfigCategory {
name: i18nc("@title:group for configuration dialog page", "Icons")
icon: "preferences-desktop-icons"
source: "ConfigIcons.qml"
visible: configModel.isFolder
}
ConfigCategory {
name: i18nc("@title:group for configuration dialog page", "Filter")
icon: "preferences-desktop-filter"
source: "ConfigFilter.qml"
visible: configModel.isFolder
}
}

View File

@@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
<kcfgfile name=""/>
<group name="General">
<entry name="icon" type="String">
<label>The name of the custom icon to use for the compact representation (e.g. on a small panel) of the Folder View applet. Only used if useCustomIcon is true.</label>
<default>folder-symbolic</default>
</entry>
<entry name="useCustomIcon" type="Bool">
<label>Whether to use a custom icon for the compact representation (e.g. on a small panel) of the Folder View Applet.</label>
<default>false</default>
</entry>
<entry name="ItemsGeometries" type="String" hidden="true">
<label>Encoded geometries of items (resource categories).</label>
</entry>
<entry name="ToolBoxButtonState" type="String">
<label>Position state of the toolbox button.</label>
<default></default>
</entry>
<entry name="ToolBoxButtonX" type="int">
<label>X coordinate of the toolbox.</label>
</entry>
<entry name="ToolBoxButtonY" type="int">
<label>Y coordinate of the toolbox.</label>
</entry>
<entry name="FirstStartup" type="Bool" hidden="true">
<label>First time the containment starts?</label>
<default>true</default>
</entry>
<entry name="positions" type="StringList" hidden="true">
<default></default>
</entry>
<entry name="url" type="String">
<label>URL of the file system location being shown.</label>
<default>desktop:/</default>
</entry>
<entry name="labelMode" type="Int">
<label>How to show the Folder View label: 0 = No label, 1 = Friendly version of path relative to closest Places entry, 2 = Full path, 3 = Custom title</label>
<default>1</default>
</entry>
<entry name="labelText" type="String">
<label>Custom text for the Folder View label. Only used if labelMode is 3.</label>
<default></default>
</entry>
<entry name="arrangement" type="Int">
<label>How Folder View icons are arranged: 0 = Rows, 1 = Columns</label>
<default>0</default>
</entry>
<entry name="alignment" type="Int">
<label>How Folder View icons are aligned: 0 = Left, 1 = Right</label>
<default>0</default>
</entry>
<entry name="locked" type="Bool">
<label>Whether Folder View icons are locked or not. Only used when serving as containment.</label>
<default>false</default>
</entry>
<entry name="sortMode" type="Int">
<label>How Folder View icons are sorted: 0 = Unsorted, 1 = Name, 2 = Size, 3 = Type, 4 = Date</label>
<default>0</default>
</entry>
<entry name="sortDesc" type="Bool">
<label>Whether to sort Folder View icons descending instead of ascending.</label>
<default>false</default>
</entry>
<entry name="sortDirsFirst" type="Bool">
<label>Whether to sort folders before files in Folder View.</label>
<default>true</default>
</entry>
<entry name="toolTips" type="Bool">
<label>Whether to show info tooltips when hovering Folder View icons.</label>
<default>false</default>
</entry>
<entry name="selectionMarkers" type="Bool">
<label>Whether to show selection markers when hovering Folder View icons.</label>
<default>true</default>
</entry>
<entry name="renameInline" type="Bool">
<label>Whether to initiate inline renaming when clicking on the text of an already-selected item, while using the systemwide double-click mode.</label>
<default>true</default>
</entry>
<entry name="popups" type="Bool">
<label>Whether to show a popup preview window for Folder View icons for folders.</label>
<default>true</default>
</entry>
<entry name="previews" type="Bool">
<label>Whether to show preview thumbnails in Folder View.</label>
<default>true</default>
</entry>
<entry name="previewPlugins" type="StringList">
<label>List of ids of the thumbnail preview plugins to use in Folder View. If empty, uses a default set of thumbnailers (cf. KIO::PreviewJob::defaultPlugins)</label>
<default></default>
</entry>
<entry name="viewMode" type="Int">
<label>The Folder View view mode (used only by the widget full representation): 0 = Grid, 1 = List</label>
<default>0</default>
</entry>
<entry name="iconSize" type="Int">
<label>The icon size to use for Folder View icons. 0 = 22px (smallMedium); 1 = 32px (medium); 2 = 48px (large); 3 = 64px (huge); 4 = 96px (large * 2); 5 = 128px (enormous); 6 = 256px (enormous * 2)</label>
<default>3</default>
</entry>
<entry name="labelWidth" type="Int">
<label>The width of the grid cells when using icon view. 0 = Narrow, 1 = Medium, 2 = Wide</label>
<default>1</default>
</entry>
<entry name="textLines" type="Int">
<label>The (maximum) number of lines of text to use below Folder View icons.</label>
<default>2</default>
</entry>
<entry name="textColor" type="String">
<label>The text color to use below Folder View icons. Only used when serving as containment.</label>
<default>white</default>
</entry>
<entry name="filterPattern" type="String">
<label>The pattern to filter files by. Supports wildcards.</label>
<default>*</default>
</entry>
<entry name="filterMode" type="Int">
<label>The file filter mode: 0 = Show All Files, 1 = Show Files Matching, 2 = Hide Files Matching</label>
<default>0</default>
</entry>
<entry name="filterMimeTypes" type="StringList">
<label>List of MIME types to filter by.</label>
<default>all/all</default>
</entry>
<entry name="showHiddenFiles" type="Bool">
<label>Show hidden files.</label>
<default>false</default>
</entry>
</group>
</kcfg>

View File

@@ -0,0 +1,45 @@
/*
SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.plasma.plasmoid
import org.kde.plasma.core as PlasmaCore
import org.kde.kirigami as Kirigami
import org.kde.plasma.components as PC3
PC3.ToolButton {
id: button
property PlasmaCore.Action qAction
readonly property int iconSize: Kirigami.Settings.hasTransientTouchInput
? Kirigami.Units.iconSizes.medium
: Kirigami.Units.iconSizes.small
property alias toolTip: toolTip.text
onClicked: {
if (qAction) {
qAction.trigger()
}
if (!Plasmoid.containment.corona.editMode) {
appletContainer.editMode = false;
}
}
icon.width: iconSize
icon.height: iconSize
PC3.ToolTip {
id: toolTip
text: button.qAction ? button.qAction.text : ""
delay: 0
visible: button.hovered && text.length > 0
Kirigami.Theme.colorSet: Kirigami.Theme.Window
Kirigami.Theme.inherit: false
}
}

View File

@@ -0,0 +1,126 @@
/*
SPDX-FileCopyrightText: 2011-2013 Sebastian Kügler <sebas@kde.org>
SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2014-2015 Eike Hein <hein@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import org.kde.plasma.plasmoid
import org.kde.ksvg as KSvg
import org.kde.plasma.components as PlasmaComponents3
import org.kde.kirigami as Kirigami
KSvg.FrameSvgItem {
id: upButton
width: gridView.cellWidth
height: visible ? gridView.cellHeight : 0
visible: history.length !== 0
property bool ignoreClick: false
property bool containsDrag: false
property alias active: hoverActivateTimer.running
imagePath: "widgets/viewitem"
function handleDragMove() {
containsDrag = true;
hoverActivateTimer.restart();
}
function endDragMove() {
containsDrag = false;
hoverActivateTimer.stop();
}
MouseArea {
id: mouseArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.BackButton
hoverEnabled: true
onContainsMouseChanged: {
gridView.hoveredItem = null;
}
onPressed: mouse => {
if (mouse.buttons & Qt.BackButton) {
if (root.isPopup && dir.resolvedUrl !== dir.resolve(Plasmoid.configuration.url)) {
doBack();
upButton.ignoreClick = true;
}
}
}
onClicked: mouse => {
if (upButton.ignoreClick) {
upButton.ignoreClick = false;
return;
}
doBack();
}
}
Kirigami.Icon {
id: icon
anchors {
left: parent.left
leftMargin: Kirigami.Units.smallSpacing
verticalCenter: parent.verticalCenter
}
width: gridView.iconSize
height: gridView.iconSize
source: "arrow-left"
}
PlasmaComponents3.Label {
id: label
anchors {
left: icon.right
leftMargin: Kirigami.Units.smallSpacing * 2
verticalCenter: parent.verticalCenter
}
width: parent.width - icon.width - (Kirigami.Units.smallSpacing * 4);
height: undefined // Unset PlasmaComponents.Label's default.
textFormat: Text.PlainText
maximumLineCount: root.isPopup ? 1 : Plasmoid.configuration.textLines
wrapMode: Text.Wrap
elide: Text.ElideRight
text: i18nc("@action:button", "Back")
}
Timer {
id: hoverActivateTimer
interval: root.hoverActivateDelay
onTriggered: doBack()
}
states: [
State {
name: "hover"
when: mouseArea.containsMouse || upButton.containsDrag
PropertyChanges {
upButton.prefix: "hover"
}
}
]
}

View File

@@ -0,0 +1,74 @@
/*
SPDX-FileCopyrightText: 2013-2014 Eike Hein <hein@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import org.kde.plasma.plasmoid
import org.kde.plasma.core as PlasmaCore
import org.kde.draganddrop as DragDrop
import org.kde.kirigami as Kirigami
DragDrop.DropArea {
id: compactRoot
readonly property bool inPanel: [
PlasmaCore.Types.TopEdge,
PlasmaCore.Types.LeftEdge,
PlasmaCore.Types.RightEdge,
PlasmaCore.Types.BottomEdge,
].includes(Plasmoid.location)
Layout.minimumWidth: Plasmoid.formFactor === PlasmaCore.Types.Horizontal ? height : Kirigami.Units.iconSizes.small
Layout.minimumHeight: Plasmoid.formFactor === PlasmaCore.Types.Vertical ? width : (Kirigami.Units.iconSizes.small + 2 * Kirigami.Units.iconSizes.sizeForLabels)
property FolderView folderView: null
onContainsDragChanged: contained => {
if (containsDrag) {
hoverActivateTimer.restart();
} else {
hoverActivateTimer.stop();
}
}
onDrop: event => {
folderView.model.dropCwd(event);
}
preventStealing: true
function toggle() {
root.expanded = !root.expanded;
}
Kirigami.Icon {
id: icon
anchors.fill: parent
active: mouseArea.containsMouse
source: Plasmoid.configuration.useCustomIcon ? Plasmoid.configuration.icon : compactRoot.folderView.model.iconName
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: mouse => compactRoot.toggle()
}
Timer {
id: hoverActivateTimer
interval: root.hoverActivateDelay
onTriggered: compactRoot.toggle()
}
}

View File

@@ -0,0 +1,212 @@
/*
SPDX-FileCopyrightText: 2014 Eike Hein <hein@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.private.desktopcontainment.folder as Folder
import org.kde.kitemmodels as KItemModels
import org.kde.kcmutils as KCM
KCM.ScrollViewKCM {
id: configIcons
property alias cfg_filterMode: filterMode.currentIndex
property alias cfg_filterPattern: filterPattern.text
property alias cfg_filterMimeTypes: mimeTypesModel.checkedTypes
property alias cfg_showHiddenFiles: showHiddenFiles.checked
KItemModels.KSortFilterProxyModel {
id: filteredMimeTypesModel
sourceModel: Folder.MimeTypesModel {
id: mimeTypesModel
}
filterRegularExpression: RegExp(mimeFilter.text, "i")
filterRoleName: "name"
sortRoleName: "name"
sortOrder: Qt.AscendingOrder
function checkFiltered() {
var types = [];
for (var i = 0; i < count; ++i) {
types.push(index(i, 0).data(Qt.UserRole));
}
mimeTypesModel.checkedTypes = types;
}
function uncheckFiltered() {
var types = [];
for (var i = 0; i < count; ++i) {
types.push(index(i, 0).data(Qt.UserRole));
}
mimeTypesModel.checkedTypes = mimeTypesModel.checkedTypes
.filter(x => types.indexOf(x) === -1);
}
}
header: Kirigami.FormLayout {
ComboBox {
id: filterMode
Kirigami.FormData.label: i18nc("@label:listbox filter mode", "Files:")
model: [i18nc("@item:inlistbox filter mode", "Show all"), i18nc("@item:inlistbox filter mode", "Show matching"), i18nc("@item:inlistbox filter mode", "Hide matching")]
}
TextField {
id: filterPattern
Kirigami.FormData.label: i18nc("@label:textbox", "File name pattern:")
enabled: (filterMode.currentIndex > 0)
inputMethodHints: Qt.ImhNoPredictiveText
}
Kirigami.SearchField {
id: mimeFilter
Kirigami.FormData.label: i18nc("@label:textbox filter list by", "File types:")
enabled: (filterMode.currentIndex > 0)
}
CheckBox {
id: showHiddenFiles
Kirigami.FormData.label: i18nc("@option:check prefix", "Show hidden files:")
}
}
view: ListView {
id: mimeTypesView
clip: true
enabled: (filterMode.currentIndex > 0)
// Signal the delegates listen to when user presses space to toggle current row.
signal toggleCurrent()
model: filteredMimeTypesModel
property real columnSize: Kirigami.Units.gridUnit * 15
headerPositioning: ListView.OverlayHeader
Keys.onSpacePressed: toggleCurrent()
header: HorizontalHeaderView {
id: headerView
z: 9
implicitWidth: mimeTypesView.width
rowHeightProvider: function () {
return Kirigami.Units.gridUnit * 2
}
clip: true // This removes event handling blocking by the header
model: ListModel {
Component.onCompleted: {
append({ display: i18nc("@title:column", "File Type") });
append({ display: i18nc("@title:column", "Description") });
}
}
interactive: false
columnWidthProvider: function(column) {
if (column === 0) {
return mimeTypesView.columnSize;
} else {
return mimeTypesView.width - mimeTypesView.columnSize;
}
}
}
delegate: ItemDelegate {
id: delegate
width: mimeTypesView.width
required property string name
required property string comment
required property var decoration
contentItem: RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.iconSizes.small
RowLayout {
Layout.preferredWidth: mimeTypesView.columnSize
Layout.maximumWidth: mimeTypesView.columnSize
Layout.fillHeight: true
CheckBox {
Layout.fillHeight: true
checked: mimeTypesModel.checkedTypes.indexOf(delegate.name) >= 0
onToggled: {
let idx = mimeTypesModel.checkedTypes.indexOf(delegate.name);
if (idx >= 0) {
mimeTypesModel.checkedTypes.splice(idx, 1);
} else {
mimeTypesModel.checkedTypes.push(delegate.name)
}
}
}
Kirigami.Icon {
Layout.fillHeight: true
implicitWidth: Kirigami.Units.iconSizes.small
implicitHeight: Kirigami.Units.iconSizes.small
animated: false // TableView reuses delegates, avoid animation when sorting/filtering.
source: delegate.decoration
}
Label {
text: delegate.name
elide: Text.ElideRight
Layout.fillWidth: true
Layout.fillHeight: true
}
}
Label {
text: delegate.comment
elide: Text.ElideRight
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}
}
footer: RowLayout {
id: selectLayout
Button {
id: selectAllButton
enabled: (filterMode.currentIndex > 0)
icon.name: "edit-select-all"
ToolTip.delay: Kirigami.Units.toolTipDelay
ToolTip.visible: (Kirigami.Settings.isMobile ? pressed : hovered) && ToolTip.text.length > 0
ToolTip.text: i18nc("@action:button tooltip only Select all filetypes", "Select All")
onClicked: filteredMimeTypesModel.checkFiltered()
}
Button {
id: deselectAllButton
enabled: (filterMode.currentIndex > 0)
icon.name: "edit-select-none"
ToolTip.delay: Kirigami.Units.toolTipDelay
ToolTip.visible: (Kirigami.Settings.isMobile ? pressed : hovered) && ToolTip.text.length > 0
ToolTip.text: i18nc("@action:button tooltip only Deselect all filetypes", "Deselect All")
onClicked: filteredMimeTypesModel.uncheckFiltered()
}
Button {
enabled: (filterMode.currentIndex > 0)
icon.name: filteredMimeTypesModel.sortOrder === Qt.AscendingOrder ? "view-sort-ascending-symbolic" : "view-sort-descending-symbolic"
ToolTip.delay: Kirigami.Units.toolTipDelay
ToolTip.visible: (Kirigami.Settings.isMobile ? pressed : hovered) && ToolTip.text.length > 0
ToolTip.text: i18nc("@action:button tooltip only, ascending/descending", "Switch Sort Order")
onClicked: {
filteredMimeTypesModel.sortOrder = filteredMimeTypesModel.sortOrder === Qt.AscendingOrder ? Qt.DescendingOrder : Qt.AscendingOrder;
filteredMimeTypesModel.sort(0, filteredMimeTypesModel.sortOrder);
}
}
Item {
Layout.fillWidth: true
}
}
}

View File

@@ -0,0 +1,358 @@
/*
SPDX-FileCopyrightText: 2014 Eike Hein <hein@kde.org>
SPDX-FileCopyrightText: 2015 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import org.kde.plasma.plasmoid
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.extras as PlasmaExtras
import org.kde.iconthemes as KIconThemes
import org.kde.config // for KAuthorized
import org.kde.kirigami as Kirigami
import org.kde.kcmutils as KCM
KCM.SimpleKCM {
id: configIcons
property bool isPopup: (Plasmoid.location !== PlasmaCore.Types.Floating)
property string cfg_icon: Plasmoid.configuration.icon
property alias cfg_useCustomIcon: useCustomIcon.checked
property alias cfg_arrangement: arrangement.currentIndex
property alias cfg_alignment: alignment.currentIndex
property bool cfg_locked
property alias cfg_sortMode: sortMode.mode
property alias cfg_sortDesc: sortDesc.checked
property alias cfg_sortDirsFirst: sortDirsFirst.checked
property alias cfg_toolTips: toolTips.checked
property alias cfg_selectionMarkers: selectionMarkers.checked
property alias cfg_renameInline: renameInline.checked
property alias cfg_popups: popups.checked
property alias cfg_previews: previews.checked
property var cfg_previewPlugins
property alias cfg_viewMode: viewMode.currentIndex
property alias cfg_iconSize: iconSize.value
property alias cfg_labelWidth: labelWidth.currentIndex
property alias cfg_textLines: textLines.value
readonly property bool lockedByKiosk: !KAuthorized.authorize("editable_desktop_icons")
KIconThemes.IconDialog {
id: iconDialog
onIconNameChanged: iconName => configIcons.cfg_icon = iconName || "folder-symbolic";
}
Kirigami.FormLayout {
// Panel button
RowLayout {
spacing: Kirigami.Units.smallSpacing
visible: configIcons.isPopup
Kirigami.FormData.label: i18nc("@title:group prefix for checkbox + button", "Panel button:")
CheckBox {
id: useCustomIcon
visible: configIcons.isPopup
checked: configIcons.cfg_useCustomIcon
text: i18nc("@option:check", "Use a custom icon")
}
Button {
id: iconButton
Layout.minimumWidth: Kirigami.Units.iconSizes.large + Kirigami.Units.smallSpacing * 2
Layout.maximumWidth: Layout.minimumWidth
Layout.minimumHeight: Layout.minimumWidth
Layout.maximumHeight: Layout.minimumWidth
checkable: true
enabled: useCustomIcon.checked
onClicked: {
checked = Qt.binding(() =>
iconMenu.status === PlasmaExtras.Menu.Open);
iconMenu.open(0, height);
}
Kirigami.Icon {
anchors.centerIn: parent
width: Kirigami.Units.iconSizes.large
height: width
source: configIcons.cfg_icon
}
}
PlasmaExtras.Menu {
id: iconMenu
visualParent: iconButton
PlasmaExtras.MenuItem {
text: i18nc("@item:inmenu Open icon chooser dialog", "Choose…")
icon: "document-open-folder"
onClicked: iconDialog.open()
}
PlasmaExtras.MenuItem {
text: i18nc("@item:inmenu Reset icon to default", "Clear Icon")
icon: "edit-clear"
onClicked: configIcons.cfg_icon = "folder-symbolic";
}
}
}
Item {
visible: configIcons.isPopup
Kirigami.FormData.isSection: true
}
// Arrangement section
ComboBox {
id: arrangement
Layout.fillWidth: true
visible: !configIcons.isPopup || viewMode.currentIndex === 1 /* Icons mode */
Kirigami.FormData.label: i18nc("@label:listbox columns/rows", "Arrangement:")
model: [
i18nc("@item:inlistbox arrangement of icons", "In Columns"),
i18nc("@item:inlistbox arrangement of icons", "In Rows"),
]
}
ComboBox {
id: alignment
Layout.fillWidth: true
visible: !configIcons.isPopup || viewMode.currentIndex === 1 /* Icons mode */
Kirigami.FormData.label: i18nc("@label:listbox, LtR/RtL", "Sort Alignment:")
model: {
const ltrText = i18nc("@item:inlistbox alignment of icons", "Left-to-Right");
const rtlText = i18nc("@item:inlistbox alignment of icons", "Right-to-Left");
if (Application.layoutDirection === Qt.LeftToRight) {
return [ltrText, rtlText];
}
else {
return [rtlText, ltrText];
}
}
}
CheckBox {
id: locked
visible: ("containmentType" in Plasmoid)
checked: configIcons.cfg_locked || configIcons.lockedByKiosk
enabled: !configIcons.lockedByKiosk
onCheckedChanged: {
if (!configIcons.lockedByKiosk) {
configIcons.cfg_locked = checked;
}
}
text: i18nc("@option:check lock icon positions", "Lock in place")
}
Item {
Kirigami.FormData.isSection: true
visible: !configIcons.isPopup || viewMode.currentIndex === 1 /* Icons mode */
}
// Sorting section
ComboBox {
id: sortMode
Layout.fillWidth: true
Kirigami.FormData.label: i18nc("@label:listbox sort items by field", "Sort by:")
property int mode
// FIXME TODO HACK: This maps the combo box list model to the KDirModel::ModelColumns
// enum, which should be done in C++.
property var indexToMode: [-1, 0, 1, 6, 2]
property var modeToIndex: {'-1': '0', '0': '1', '1': '2', '6': '3', '2': '4'}
model: [i18nc("@item:inlistbox sort icons manually", "Manual"),
i18nc("@item:inlistbox sort icons by name", "Name"),
i18nc("@item:inlistbox sort icons by size", "Size"),
i18nc("@item:inlistbox sort icons by file type", "Type"),
i18nc("@item:inlistbox sort icons by date", "Date")]
Component.onCompleted: currentIndex = modeToIndex[mode]
onActivated: index => mode = indexToMode[index]
}
CheckBox {
id: sortDesc
enabled: sortMode.currentIndex !== 0
text: i18nc("@option:check sort icons in descending order", "Descending")
}
CheckBox {
id: sortDirsFirst
enabled: sortMode.currentIndex !== 0
text: i18nc("@option:check sort icons with folders first", "Folders first")
}
Item {
Kirigami.FormData.isSection: true
}
// View Mode section (only if we're a pop-up)
ComboBox {
id: viewMode
visible: configIcons.isPopup
Layout.fillWidth: true
Kirigami.FormData.label: i18nc("whether to use icon or list view", "View mode:")
model: [i18nc("@item:inlistbox show icons in a list", "List"),
i18nc("@item:inlistbox show icons in a grid", "Grid")]
}
// Size section
Slider {
id: iconSize
Layout.fillWidth: true
visible: !configIcons.isPopup || viewMode.currentIndex === 1 /* Icons mode */
Kirigami.FormData.label: i18nc("@label:slider", "Icon size:")
from: 0
to: 6
stepSize: 1
snapMode: Slider.SnapAlways
}
RowLayout {
Layout.fillWidth: true
Label {
Layout.alignment: Qt.AlignLeft
visible: !configIcons.isPopup || viewMode.currentIndex === 1 /* Icons mode */
text: i18nc("@item:inrange smallest icon size", "Small")
}
Item {
Layout.fillWidth: true
}
Label {
Layout.alignment: Qt.AlignRight
visible: !configIcons.isPopup || viewMode.currentIndex === 1 /* Icons mode */
text: i18nc("@item:inrange largest icon size", "Large")
}
}
ComboBox {
id: labelWidth
visible: !configIcons.isPopup || viewMode.currentIndex === 1 /* Icons mode */
Layout.fillWidth: true
Kirigami.FormData.label: i18nc("@label:listbox", "Label width:")
model: [
i18nc("@item:inlistbox how long a text label should be", "Narrow"),
i18nc("@item:inlistbox how long a text label should be", "Medium"),
i18nc("@item:inlistbox how long a text label should be", "Wide"),
]
}
SpinBox {
id: textLines
visible: !configIcons.isPopup || viewMode.currentIndex === 1 /* Icons mode */
Kirigami.FormData.label: i18nc("@label:spinbox", "Text lines:")
from: 1
to: 10
stepSize: 1
}
Item {
Kirigami.FormData.isSection: true
}
// Features section
CheckBox {
id: toolTips
Kirigami.FormData.label: i18nc("@title:group prefix for checkbox group", "When hovering over icons:")
text: i18nc("@option:check When hovering over icons…", "Show tooltips")
}
CheckBox {
id: selectionMarkers
visible: Application.styleHints.singleClickActivation
text: i18nc("@option:check When hovering over icons…", "Show selection markers")
}
CheckBox {
id: popups
visible: !configIcons.isPopup
text: i18nc("@option:check When hovering over icons…", "Show folder preview popups")
}
Item {
Kirigami.FormData.isSection: true
}
CheckBox {
id: renameInline
Kirigami.FormData.label: i18nc("@label prefix for checkbox", "Rename:")
visible: !selectionMarkers.visible
text: i18nc("@option:check", "Rename inline by clicking selected item's text")
}
Item {
Kirigami.FormData.isSection: true
visible: renameInline.visible
}
CheckBox {
id: previews
Kirigami.FormData.label: i18nc("@title:group prefix for checkbox and button", "Previews:")
text: i18nc("@option:check", "Show preview thumbnails")
}
Button {
id: previewSettings
Layout.fillWidth: true
icon.name: "configure"
text: i18nc("@action:button opens dialog", "Configure Preview Plugins…")
onClicked: {
const component = Qt.createComponent(Qt.resolvedUrl("FolderItemPreviewPluginsDialog.qml"));
component.incubateObject(configIcons.Window.window.contentItem, {
"previewPlugins": configIcons.cfg_previewPlugins,
}, Qt.Asynchronous);
component.destroy();
}
}
}
}

View File

@@ -0,0 +1,213 @@
/*
SPDX-FileCopyrightText: 2014 Eike Hein <hein@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import org.kde.plasma.plasmoid
import org.kde.kirigami as Kirigami
import org.kde.kcmutils as KCM
import org.kde.private.desktopcontainment.folder as Folder
KCM.SimpleKCM {
id: configLocation
property string cfg_url
property alias cfg_labelMode: labelMode.currentIndex
property alias cfg_labelText: labelText.text
property bool titleVisible: Plasmoid.containment != Plasmoid
onCfg_urlChanged: applyConfig()
function applyConfig(force) {
if (!force && locationGroup.checkedButton !== null) {
return;
}
if (cfg_url === "desktop:/") {
locationDesktop.checked = true;
locationCustomValue.text = "";
} else if (cfg_url === "activities:/current/") {
locationCurrentActivity.checked = true;
locationCustomValue.text = "";
} else {
var placeForUrl = placesModel.indexForUrl(cfg_url);
if (placeForUrl !== -1) {
locationPlaceValue.currentIndex = placeForUrl; // needs to happen before checking the radiobutton
locationPlace.checked = true;
locationCustomValue.text = "";
} else {
locationCustom.checked = true;
locationCustomValue.text = cfg_url;
}
}
locationPlaceValue.enabled = locationPlace.checked;
}
Folder.PlacesModel {
id: placesModel
showDesktopEntry: false
onPlacesChanged: configLocation.applyConfig(true)
}
ButtonGroup {
id: locationGroup
buttons: [locationDesktop, locationCurrentActivity, locationPlace, locationCustom]
onCheckedButtonChanged: {
if (checkedButton === locationDesktop) {
configLocation.cfg_url = "desktop:/";
} else if (checkedButton === locationCurrentActivity) {
configLocation.cfg_url = "activities:/current/";
}
}
}
Kirigami.FormLayout {
RadioButton {
id: locationDesktop
implicitHeight: locationCustomValue.implicitHeight
Kirigami.FormData.label: i18nc("@title:group form label for radiobutton group", "Show:")
text: i18nc("@option:radio", "Desktop folder")
}
RadioButton {
id: locationCurrentActivity
visible: placesModel.activityLinkingEnabled
implicitHeight: locationCustomValue.implicitHeight
text: i18nc("@option:radio", "Files linked to the current activity")
}
RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
RadioButton {
id: locationPlace
text: i18nc("@option:radio also label for combobox", "Places panel item:")
Layout.minimumWidth: Math.max(locationPlace.implicitWidth, locationCustom.implicitWidth, labelMode.implicitWidth)
onCheckedChanged: {
locationPlaceValue.enabled = checked;
}
}
ComboBox {
id: locationPlaceValue
Layout.fillWidth: true
model: placesModel
textRole: "display"
enabled: true
onEnabledChanged: {
if (enabled && currentIndex !== -1) {
configLocation.cfg_url = Folder.DesktopSchemeHelper.getDesktopUrl(placesModel.urlForIndex(currentIndex));
}
}
onActivated: index => {
configLocation.cfg_url = Folder.DesktopSchemeHelper.getDesktopUrl(placesModel.urlForIndex(index));
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
RadioButton {
id: locationCustom
Layout.minimumWidth: Math.max(locationPlace.implicitWidth, locationCustom.implicitWidth, labelMode.implicitWidth)
text: i18nc("@option:radio also label for text field", "Custom location:")
}
TextField {
id: locationCustomValue
enabled: locationCustom.checked
Layout.fillWidth: true
placeholderText: i18nc("@info:placeholder custom location", "Type path or URL…")
inputMethodHints: Qt.ImhNoPredictiveText
onEnabledChanged: {
if (enabled && text !== "") {
configLocation.cfg_url = Folder.DesktopSchemeHelper.getDesktopUrl(text);
}
}
onTextChanged: {
if (enabled) {
configLocation.cfg_url = Folder.DesktopSchemeHelper.getDesktopUrl(text);
}
}
}
Button {
icon.name: "document-open"
enabled: locationCustom.checked
onClicked: {
directoryPicker.open();
}
}
Folder.DirectoryPicker {
id: directoryPicker
onUrlChanged: {
locationCustomValue.text = Folder.DesktopSchemeHelper.getDesktopUrl(url);
}
}
}
Item {
visible: configLocation.titleVisible
Kirigami.FormData.isSection: true
}
RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
visible: configLocation.titleVisible
Kirigami.FormData.label: i18nc("@label:textbox custom widget title", "Title:")
ComboBox {
id: labelMode
Layout.minimumWidth: Math.max(locationPlace.implicitWidth, locationCustom.implicitWidth, labelMode.implicitWidth)
visible: configLocation.titleVisible
model: [
i18nc("@item:inlistbox no title", "None"),
i18nc("@item:inlistbox default title", "Default"),
i18nc("@item:inlistbox full path as title", "Full path"),
i18nc("@item:inlistbox title from text input field", "Custom")
]
}
TextField {
id: labelText
Layout.fillWidth: true
enabled: (labelMode.currentIndex === 3)
placeholderText: i18nc("@info:placeholder custom window title", "Enter title…")
}
}
}
}

View File

@@ -0,0 +1,256 @@
/*
SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import org.kde.plasma.core as PlasmaCore
import org.kde.kirigami as Kirigami
import org.kde.ksvg as KSvg
import org.kde.plasma.private.containmentlayoutmanager as ContainmentLayoutManager
ContainmentLayoutManager.ConfigOverlayWithHandles {
id: overlay
SequentialAnimation {
id: removeAnim
NumberAnimation {
target: overlay.itemContainer
property: "scale"
from: 1
to: 0
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
ScriptAction {
script: {
appletContainer.applet.plasmoid.internalAction("remove").trigger();
appletContainer.editMode = false;
}
}
}
KSvg.FrameSvgItem {
id: frame
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: {
let heightDifference = Math.round((frame.height - overlay.height) / 2)
if (heightDifference > 0) {
if (heightDifference > overlay.topAvailableSpace) {
return heightDifference - overlay.topAvailableSpace
}
if (heightDifference > overlay.bottomAvailableSpace) {
return overlay.bottomAvailableSpace - heightDifference
}
}
return 0
}
x: overlay.rightAvailableSpace > width + Kirigami.Units.gridUnit
? parent.width + Kirigami.Units.gridUnit
: -width - Kirigami.Units.gridUnit
// This MouseArea is used to block input between the applet and the handle, to not make it steal by other applets
MouseArea {
anchors {
top: parent.top
bottom: parent.bottom
}
z: -1
x: overlay.rightAvailableSpace > parent.width + Kirigami.Units.gridUnit ? -Kirigami.Units.gridUnit : 0
width: Kirigami.Units.gridUnit + parent.width
hoverEnabled: true
}
transform: Translate {
x: overlay.open ? 0 : (overlay.rightAvailableSpace > frame.width + Kirigami.Units.gridUnit ? -frame.width : frame.width)
Behavior on x {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
}
width: layout.implicitWidth + margins.left + margins.right
height: Math.max(layout.implicitHeight + margins.top + margins.bottom, parent.height)
imagePath: "widgets/background"
ColumnLayout {
id: layout
anchors {
fill: parent
topMargin: parent.margins.top
leftMargin: parent.margins.left
bottomMargin: parent.margins.bottom
rightMargin: parent.margins.right
}
ActionButton {
id: rotateButton
icon.name: "object-rotate-left-symbolic"
toolTip: !rotateHandle.pressed ? i18nc("@action:button tooltip rotate widget", "Click and drag to rotate") : ""
action: applet ? applet.plasmoid.internalAction("rotate") : null
down: rotateHandle.pressed
Component.onCompleted: {
if (action !== null) {
action.enabled = true;
}
}
MouseArea {
id: rotateHandle
anchors.fill: parent
property int startRotation
property real startCenterRelativeAngle
function pointAngle(pos: point): real {
var r = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
var cosine = pos.x / r;
if (pos.y >= 0) {
return Math.acos(cosine) * (180/Math.PI);
} else {
return -Math.acos(cosine) * (180/Math.PI);
}
}
function centerRelativePos(x: real, y: real): point {
var mousePos = overlay.itemContainer.parent.mapFromItem(rotateButton, x, y);
var centerPos = overlay.itemContainer.parent.mapFromItem(overlay.itemContainer, overlay.itemContainer.width/2, overlay.itemContainer.height/2);
mousePos.x -= centerPos.x;
mousePos.y -= centerPos.y;
return mousePos;
}
onPressed: mouse => {
mouse.accepted = true;
startRotation = overlay.itemContainer.rotation;
startCenterRelativeAngle = pointAngle(centerRelativePos(mouse.x, mouse.y));
}
onPositionChanged: mouse => {
var rot = startRotation % 360;
var snap = 4;
var newRotation = Math.round(pointAngle(centerRelativePos(mouse.x, mouse.y)) - startCenterRelativeAngle + startRotation);
if (newRotation < 0) {
newRotation = newRotation + 360;
} else if (newRotation >= 360) {
newRotation = newRotation % 360;
}
snapIt(0);
snapIt(90);
snapIt(180);
snapIt(270);
function snapIt(snapTo) {
if (newRotation > (snapTo - snap) && newRotation < (snapTo + snap)) {
newRotation = snapTo;
}
}
overlay.itemContainer.rotation = newRotation;
}
onReleased: mouse => {
// save rotation
overlay.itemContainer.layout.save();
}
}
}
ActionButton {
icon.name: "configure"
visible: qAction && qAction.enabled && (applet && applet.plasmoid.hasConfigurationInterface)
qAction: applet ? applet.plasmoid.internalAction("configure") : null
Component.onCompleted: {
if (qAction) {
qAction.enabled = true;
}
}
}
ActionButton {
icon.name: "show-background"
toolTip: checked ? i18nc("@action:button tooltip hide widget background", "Hide Background") : i18nc("@action:button tooltip", "Show Background")
visible: (applet.plasmoid.backgroundHints & PlasmaCore.Types.ConfigurableBackground)
checked: applet.plasmoid.effectiveBackgroundHints & PlasmaCore.Types.StandardBackground || applet.plasmoid.effectiveBackgroundHints & PlasmaCore.Types.TranslucentBackground
checkable: true
onClicked: {
if (checked) {
if (applet.plasmoid.backgroundHints & PlasmaCore.Types.StandardBackground || applet.plasmoid.backgroundHints & PlasmaCore.Types.TranslucentBackground) {
applet.plasmoid.userBackgroundHints = applet.plasmoid.backgroundHints;
} else {
applet.plasmoid.userBackgroundHints = PlasmaCore.Types.StandardBackground;
}
} else {
if (applet.plasmoid.backgroundHints & PlasmaCore.Types.ShadowBackground || applet.plasmoid.backgroundHints & PlasmaCore.Types.NoBackground) {
applet.plasmoid.userBackgroundHints = applet.plasmoid.backgroundHints;
} else {
applet.plasmoid.userBackgroundHints = PlasmaCore.Types.ShadowBackground;
}
}
}
}
MouseArea {
drag.target: overlay.itemContainer
Layout.minimumHeight: Kirigami.Units.gridUnit * 3
Layout.fillHeight: true
Layout.fillWidth: true
cursorShape: containsPress ? Qt.DragMoveCursor : Qt.OpenHandCursor
hoverEnabled: true
onPressed: mouse => {
overlay.itemContainer.layout.releaseSpace(overlay.itemContainer);
}
onPositionChanged: mouse => {
if (!pressed) {
return;
}
overlay.itemContainer.layout.showPlaceHolderForItem(overlay.itemContainer);
var dragPos = mapToItem(overlay.itemContainer, mouse.x, mouse.y);
overlay.itemContainer.userDrag(Qt.point(overlay.itemContainer.x, overlay.itemContainer.y), dragPos);
}
onReleased: mouse => {
overlay.itemContainer.layout.hidePlaceHolder();
overlay.itemContainer.layout.positionItem(overlay.itemContainer);
}
}
ActionButton {
id: closeButton
icon.name: "edit-delete-remove"
toolTip: i18nc("@action:button tooltip remove widget", "Remove")
visible: {
if (!applet) {
return false;
}
var a = applet.plasmoid.internalAction("remove");
return a && a.enabled || false;
}
// we don't set action, since we want to catch the button click,
// animate, and then trigger the "remove" action
// Triggering the action is handled in the overlay.itemContainer, we just
// Q_EMIT a signal here to avoid the applet-gets-removed-before-we-
// can-animate it race condition.
onClicked: {
removeAnim.restart();
}
Component.onCompleted: {
var a = applet.plasmoid.internalAction("remove");
if (a) {
a.enabled = true;
}
}
}
}
}
}

View File

@@ -0,0 +1,77 @@
/*
SPDX-FileCopyrightText: 2014 Eike Hein <hein@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import org.kde.plasma.plasmoid
import org.kde.kirigami as Kirigami
import org.kde.ksvg as KSvg
KSvg.SvgItem {
id: actionButton
width: {
if (!visible) {
return 0;
}
switch (Plasmoid.configuration.iconSize) {
case 0: return Kirigami.Units.iconSizes.small;
case 1: return Kirigami.Units.iconSizes.small;
case 2: return Kirigami.Units.iconSizes.smallMedium;
case 3: return Kirigami.Units.iconSizes.smallMedium;
case 4: return Kirigami.Units.iconSizes.smallMedium;
case 5: return Kirigami.Units.iconSizes.medium;
case 6: return Kirigami.Units.iconSizes.large;
default: return Kirigami.Units.iconSizes.small;
}
}
height: width
signal clicked()
property string element
svg: KSvg.Svg {
imagePath: "widgets/action-overlays"
multipleImages: true
size: Qt.size(16, 16)
}
elementId: element + "-normal"
Behavior on opacity {
NumberAnimation { duration: Kirigami.Units.shortDuration }
}
MouseArea {
id: actionButtonMouseArea
anchors.fill: actionButton
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: mouse => actionButton.clicked()
states: [
State {
name: "hover"
when: actionButtonMouseArea.containsMouse && !actionButtonMouseArea.pressed
PropertyChanges {
actionButton.elementId: actionButton.element + "-hover"
}
},
State {
name: "pressed"
when: actionButtonMouseArea.pressed
PropertyChanges {
actionButton.elementId: actionButton.element + "-pressed"
}
}
]
}
}

View File

@@ -0,0 +1,489 @@
/*
SPDX-FileCopyrightText: 2014-2015 Eike Hein <hein@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Window
import Qt5Compat.GraphicalEffects
import org.kde.plasma.plasmoid
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.extras as PlasmaExtras
import org.kde.kirigami as Kirigami
import org.kde.private.desktopcontainment.folder as Folder
Item {
id: main
required property var model
property int index: model.index
property string name: model.blank ? "" : model.display
property string nameWrapped: model.blank ? "" : model.displayWrapped
property bool blank: model.blank
property bool selected: model.blank ? false : model.selected
property bool isDir: loader.item ? loader.item.isDir : false
property bool isOnRootView: false
property /*FolderViewDialog*/ Folder.SubDialog popupDialog: loader.item ? loader.item.popupDialog : null
property Item iconArea: loader.item ? loader.item.iconArea : null
property Item label: loader.item ? loader.item.label : null
property Item labelArea: loader.item ? loader.item.labelArea : null
property Item actionsOverlay: loader.item ? loader.item.actionsOverlay : null
property Item hoverArea: loader.item ? loader.item.hoverArea : null
property Item frame: loader.item ? loader.item.frame : null
property PlasmaCore.ToolTipArea toolTip: loader.item ? loader.item.toolTip : null
property real contentHeight: loader.item && !root.useListViewMode ? loader.item.contentHeight : null
Accessible.name: name
Accessible.role: Accessible.Canvas
// This MouseArea exists to intercept press and hold; preventing edit mode
// from being triggered when pressing and holding on an icon (if there is one).
MouseArea {
anchors.fill: parent
visible: !main.blank
}
function openPopup() {
if (isDir) {
loader.item.openPopup();
}
}
function closePopup() {
if (popupDialog && popupDialog.allowClosing) {
(popupDialog as FolderViewDialog).requestDestroy();
loader.item.popupDialog = null;
}
}
Loader {
id: loader
// On the desktop we pad our cellSize to avoid a gap at the right/bottom of the screen.
// The padding per item is quite small and causes the delegate to be positioned on fractional pixels
// leading to blurry rendering. The Loader is offset to account for this.
x: -main.x % 1
y: -main.y % 1
width: parent.width
height: parent.height
visible: status === Loader.Ready
active: !main.model.blank
sourceComponent: delegateImplementation
asynchronous: true
}
function updateDragImage() {
if (selected && !blank) {
loader.grabToImage(result => {
dir.addItemDragImage(positioner.map(index), main.x + loader.x, main.y + loader.y, loader.width, loader.height, result.image);
});
}
}
Component {
id: delegateImplementation
Item {
id: impl
anchors.fill: parent
property bool blank: main.model.blank
property bool isDir: main.model.blank ? false : main.model.isDir
property bool hovered: (main.GridView.view.hoveredItem === main)
property /*FolderViewDialog*/ Folder.SubDialog popupDialog: null
property Item iconArea: icon
property Item label: label
property Item labelArea: label
property Item actionsOverlay: actions
property Item hoverArea: toolTip
property Item frame: frameLoader
property alias toolTip: toolTip
property Item selectionButton: selectionButtonComponent.createObject(actions) as FolderItemActionButton
property Item popupButton: null
property int contentHeight: frameLoader.height + frameLoader.y * 2
readonly property bool iconAndLabelsShouldlookSelected: impl.hovered
Connections {
target: main.model
function onSelectedChanged() {
if (dir.usedByContainment && main.model.selected) {
gridView.currentIndex = main.model.index;
}
}
}
onHoveredChanged: {
if (hovered) {
if (Plasmoid.configuration.selectionMarkers && Application.styleHints.singleClickActivation) {
selectionButton.visible = true;
}
if (main.model.isDir) {
if (!main.GridView.view.isRootView || root.containsDrag) {
hoverActivateTimer.restart();
}
if (Plasmoid.configuration.popups && !root.useListViewMode) {
popupButton = popupButtonComponent.createObject(actions);
}
}
} else if (!hovered) {
if (popupDialog != null) {
main.closePopup();
}
selectionButton.visible = false;
if (popupButton) {
popupButton.destroy();
popupButton = null;
}
}
}
function openPopup() {
if (folderViewDialogComponent.status === Component.Ready) {
impl.popupDialog = folderViewDialogComponent.createObject(impl);
impl.popupDialog.visualParent = icon;
impl.popupDialog.url = Folder.DesktopSchemeHelper.getDesktopUrl(main.model.linkDestinationUrl);
impl.popupDialog.visible = true;
}
}
PlasmaCore.ToolTipArea {
id: toolTip
anchors.fill: impl
active: (Plasmoid.configuration.toolTips || label.truncated)
&& impl.popupDialog === null
&& !main.model.blank
interactive: false
location: root.useListViewMode ? (Plasmoid.location === PlasmaCore.Types.LeftEdge ? PlasmaCore.Types.LeftEdge : PlasmaCore.Types.RightEdge) : Plasmoid.location
onContainsMouseChanged: {
if (containsMouse && !main.model.blank) {
if (toolTip.active) {
toolTip.icon = main.model.decoration;
toolTip.mainText = main.model.display;
if (main.model.size !== undefined) {
toolTip.subText = main.model.type + "\n" + main.model.size;
} else {
toolTip.subText = main.model.type;
}
}
Qt.callLater(() => {
// Workaround for Qt Bug: https://bugreports.qt.io/browse/QTBUG-117444
// In some cases the signal order is reversed:
// - first it's delivered to the new object with "true" value
// - next it's delivered to the old object with "false" value
// In this case when the signal is emitted with "false" (to the old object),
// it's also delivered to the "FolderView" which sets the "hoveredItem" to
// "null" right after we set it here.
// The solution is to call later and check again to make sure if we still contains
// mouse and next set the "hoveredItem". In this approach the "FolderView" sets the
// old "hoveredItem" to "null" and next we set it to the new item here.
if (containsMouse && !main.model.blank) {
main.GridView.view.hoveredItem = main;
}
})
}
}
states: [
State { // icon view
when: !root.useListViewMode
AnchorChanges {
target: toolTip
anchors.horizontalCenter: parent.horizontalCenter
}
PropertyChanges {
toolTip.y: frameLoader.y + icon.y
toolTip.width: Math.max(icon.paintedWidth, label.paintedWidth)
toolTip.height: (label.y + label.paintedHeight) - y
}
},
State { // list view
when: root.useListViewMode
AnchorChanges {
target: toolTip
anchors.horizontalCenter: undefined
}
PropertyChanges {
toolTip.x: frameLoader.x
toolTip.y: frameLoader.y
toolTip.width: frameLoader.width
toolTip.height: frameLoader.height
}
}
]
}
Loader {
id: frameLoader
x: root.useListViewMode ? 0 : Kirigami.Units.smallSpacing
y: root.useListViewMode ? 0 : Kirigami.Units.smallSpacing
property Item iconShadow: null
property string prefix: ""
sourceComponent: frameComponent
active: impl.iconAndLabelsShouldlookSelected || (main.model?.selected ?? false)
asynchronous: true
width: {
if (root.useListViewMode) {
if (main.GridView.view.overflowing) {
return parent.width - Kirigami.Units.smallSpacing;
} else {
return parent.width;
}
}
return parent.width - (Kirigami.Units.smallSpacing * 2);
}
height: root.useListViewMode
? parent.height
// the smallSpacings are for padding
: icon.height + label.implicitHeight + (Kirigami.Units.smallSpacing * 3)
Kirigami.Icon {
id: icon
z: 2
states: [
State { // icon view
when: !root.useListViewMode
AnchorChanges {
target: icon
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
}
},
State { // list view
when: root.useListViewMode
AnchorChanges {
target: icon
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
}
}
]
anchors {
topMargin: Kirigami.Units.smallSpacing
leftMargin: Kirigami.Units.smallSpacing
}
width: root.useListViewMode ? main.GridView.view.iconSize : (parent.width - 2 * Kirigami.Units.smallSpacing)
height: main.GridView.view.iconSize
opacity: {
if (root.useListViewMode && impl.selectionButton.visible) {
return 0.3;
}
if (main.model.isHidden) {
return 0.6;
}
return 1.0;
}
animated: false
source: main.model.decoration
}
PlasmaExtras.ShadowedLabel {
id: label
readonly property bool renaming: (editor && editor.targetItem === main)
z: 2 // So it's always above the highlight effect
states: [
State { // icon view
when: !root.useListViewMode
AnchorChanges {
target: label
anchors.top: icon.bottom
anchors.horizontalCenter: parent.horizontalCenter
}
PropertyChanges {
label.anchors.topMargin: Kirigami.Units.smallSpacing
label.width: label.parent.width - Kirigami.Units.smallSpacing
label.maximumLineCount: label.Plasmoid.configuration.textLines
label.horizontalAlignment: Text.AlignHCenter
}
},
State { // list view
when: root.useListViewMode
AnchorChanges {
target: label
anchors.left: icon.right
anchors.verticalCenter: parent.verticalCenter
}
PropertyChanges {
label.anchors.leftMargin: Kirigami.Units.smallSpacing * 2
label.anchors.rightMargin: Kirigami.Units.smallSpacing * 2
label.width: label.parent.width - icon.width - (Kirigami.Units.smallSpacing * 4)
label.maximumLineCount: 1
label.horizontalAlignment: Text.AlignLeft
}
}
]
color: {
if (main.isOnRootView) {
// In this situation there's a shadow or a background rect, both of which are always black
return "white";
}
if (main.model.selected) {
return Kirigami.Theme.highlightedTextColor;
}
return Kirigami.Theme.textColor;
}
visible: !renaming
renderShadow: main.isOnRootView && !renaming
opacity: main.model.isHidden ? 0.6 : 1
text: main.nameWrapped
font.italic: (main.model?.isLink ?? false)
wrapMode: (maximumLineCount === 1) ? Text.NoWrap : Text.Wrap
horizontalAlignment: Text.AlignHCenter
}
Component {
id: frameComponent
PlasmaExtras.Highlight {
// Workaround for a bug where the frameComponent does not
// get unloaded when items are dragged to a different
// place on the desktop.
visible: this === frameLoader.item
hovered: impl.iconAndLabelsShouldlookSelected
pressed: main.model.selected
active: Window.active
}
}
Component {
id: selectionButtonComponent
FolderItemActionButton {
element: main.model.selected ? "remove" : "add"
onClicked: {
dir.toggleSelected(positioner.map(main.index));
main.GridView.view.currentIndex = main.index;
}
}
}
Component {
id: popupButtonComponent
FolderItemActionButton {
visible: main.GridView.view.isRootView && (impl.popupDialog == null)
element: "open"
onClicked: {
dir.setSelected(positioner.map(main.index));
main.GridView.view.currentIndex = main.index;
main.openPopup();
}
}
}
Component {
id: iconShadowComponent
DropShadow {
anchors.fill: icon
z: 1
verticalOffset: 1
radius: 5.0
samples: radius * 2 + 1
spread: 0.05
color: "black"
opacity: main.model.isHidden ? 0.3 : 0.6
source: icon
}
}
}
Column {
id: actions
visible: {
if (main.GridView.view.isRootView && root.containsDrag) {
return false;
}
if (!main.GridView.view.isRootView && main.GridView.view.dialog && main.GridView.view.dialog.containsDrag) {
return false;
}
if (main.popupDialog) {
return false;
}
return true;
}
anchors {
left: frameLoader.left
top: frameLoader.top
leftMargin: root.useListViewMode ? (icon.x + (icon.width / 2)) - (width / 2) : 0
topMargin: root.useListViewMode ? (icon.y + (icon.height / 2)) - (height / 2) : 0
}
width: implicitWidth
height: implicitHeight
}
Component.onCompleted: {
selectionButton.visible = false;
if (Plasmoid.isContainment && main.GridView.view.isRootView && root.GraphicsInfo.api === GraphicsInfo.OpenGL) {
frameLoader.iconShadow = iconShadowComponent.createObject(frameLoader);
}
}
}
}
}

View File

@@ -0,0 +1,53 @@
/*
SPDX-FileCopyrightText: 2014 Eike Hein <hein@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls
import org.kde.private.desktopcontainment.folder as Folder
import org.kde.kirigami as Kirigami
Kirigami.Dialog {
id: dialog
required property var previewPlugins
title: i18nc("@title:window", "Preview Plugins")
preferredWidth: Kirigami.Units.gridUnit * 15
implicitHeight: Math.round(parent.height * 0.8)
standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
onAccepted: {
configIcons.cfg_previewPlugins = previewPluginsModel.checkedPlugins;
dialog.close();
}
onRejected: {
dialog.close();
destroy();
}
onClosed: destroy()
ListView {
model: Folder.PreviewPluginsModel {
id: previewPluginsModel
}
delegate: CheckDelegate {
required property var model // for display, which shadows a delegate property
required checked
width: ListView.view.width
text: model.display
onToggled: model.checked = checked;
}
}
Component.onCompleted: {
previewPluginsModel.checkedPlugins = dialog.previewPlugins;
open();
}
}

View File

@@ -0,0 +1,129 @@
/*
SPDX-FileCopyrightText: 2014-2015 Eike Hein <hein@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import org.kde.plasma.plasmoid
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.extras as PlasmaExtras
import org.kde.kirigami as Kirigami
import org.kde.private.desktopcontainment.folder as Folder
Folder.SubDialog {
id: dialog
visible: false
property bool containsDrag: {
if (folderViewDropArea.containsDrag) {
return true;
}
if (folderView.hoveredItem && folderView.hoveredItem.popupDialog) {
return folderView.hoveredItem.popupDialog.containsDrag;
}
return false;
}
property QtObject closeTimer: closeTimer
property QtObject childDialog: (folderView.hoveredItem !== null) ? folderView.hoveredItem.popupDialog : null
property bool containsMouse: folderView.containsMouse || (childDialog !== null && childDialog.containsMouse)
property alias url: folderView.url
location: PlasmaCore.Types.Floating
hideOnWindowDeactivate: (allowClosing && (childDialog === null))
onContainsMouseChanged: {
if (containsMouse) {
closeTimer.stop();
} else {
closeTimer.start();
}
}
mainItem: FolderViewDropArea {
id: folderViewDropArea
width: folderView.cellWidth * 3 + Kirigami.Units.gridUnit // FIXME HACK: Use actual scrollbar width.
height: folderView.cellHeight * 2
folderView: folderView
FolderView {
id: folderView
anchors.fill: parent
isRootView: false
dialog: dialog
locked: true
sortMode: ((Plasmoid.configuration.sortMode === 0) ? 1 : Plasmoid.configuration.sortMode)
filterMode: 0
// TODO: Bidi.
flow: GridView.FlowLeftToRight
layoutDirection: Qt.LeftToRight
onDragInProgressAnywhereChanged: {
if (!dragInProgressAnywhere && !dialog.visible) {
dialog.destroy();
}
}
onCreatingNewItemsChanged: {
dialog.allowClosing = !creatingNewItems;
}
}
Loader {
anchors.centerIn: parent
width: parent.width - (Kirigami.Units.gridUnit * 4)
active: folderView.view.count === 0
sourceComponent: PlasmaExtras.PlaceholderMessage {
text: i18nc("@info:placeholder", "Folder is empty")
}
}
}
data: [
Timer {
id: closeTimer
interval: Kirigami.Units.humanMoment
onTriggered: {
if (dialog.childDialog !== null) {
dialog.childDialog.closeTimer.stop();
dialog.childDialog.visible = false;
}
dialog.visible = false;
dialog.delayedDestroy();
}
}
]
function requestDestroy() {
if (folderView.dragInProgressAnywhere) {
visible = false;
} else {
destroy();
}
}
function delayedDestroy() {
Qt.callLater(() => itemDialog.destroy());
}
Component.onDestruction: {
closeTimer.stop();
}
}

View File

@@ -0,0 +1,60 @@
/*
SPDX-FileCopyrightText: 2014-2017 Eike Hein <hein@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import org.kde.draganddrop as DragDrop
import org.kde.kirigami as Kirigami
DragDrop.DropArea {
id: dropArea
property FolderView folderView: null
function handleDragMove(folderView, pos) {
// Trigger autoscroll.
folderView.scrollLeft = (pos.x < (Kirigami.Units.gridUnit * 3));
folderView.scrollRight = (pos.x > width - (Kirigami.Units.gridUnit * 3));
folderView.scrollUp = (pos.y < (Kirigami.Units.gridUnit * 3));
folderView.scrollDown = (pos.y > height - (Kirigami.Units.gridUnit * 3));
folderView.handleDragMove(pos.x, pos.y);
}
function handleDragEnd(folderView) {
// Cancel autoscroll.
folderView.scrollLeft = false;
folderView.scrollRight = false;
folderView.scrollUp = false;
folderView.scrollDown = false;
folderView.endDragMove();
}
onDragMove: event => {
// TODO: We should reject drag moves onto file items that don't accept drops
// (cf. QAbstractItemModel::flags() here, but DeclarativeDropArea currently
// is currently incapable of rejecting drag events.
if (folderView) {
handleDragMove(folderView, mapToItem(folderView, event.x, event.y));
}
}
onDragLeave: event => {
if (folderView) {
handleDragEnd(folderView);
}
}
onDrop: event => {
if (folderView) {
handleDragEnd(folderView);
folderView.drop(folderView, event, mapToItem(folderView, event.x, event.y));
}
}
}

View File

@@ -0,0 +1,385 @@
/*
SPDX-FileCopyrightText: 2014-2015 Eike Hein <hein@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import QtQml
import org.kde.plasma.plasmoid
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.components as PlasmaComponents
import org.kde.config // for KAuthorized
import org.kde.kirigami as Kirigami
import org.kde.private.desktopcontainment.folder as Folder
FocusScope {
id: folderViewLayerComponent
// these need to be passed in from main, but required properties with Loaders are awkward
/*required*/ property bool isPopup
/*required*/ property bool useListViewMode
property var sharedActions: ["newMenu", "paste", "undo", "emptyTrash"]
property Component folderViewDialogComponent: Qt.createComponent("FolderViewDialog.qml", Qt.Asynchronous, root)
property FolderView view: folderView
property Item label: null
property int labelHeight: Kirigami.Units.iconSizes.sizeForLabels + (Kirigami.Units.smallSpacing * 2)
property alias model: folderView.model
property alias overflowing: folderView.overflowing
property alias flow: folderView.flow
readonly property bool lockedByKiosk: !KAuthorized.authorize("editable_desktop_icons")
focus: true
function updateContextualActions() {
folderView.model.updateActions();
for (let i = 0, len = sharedActions.length; i < len; i++) {
const actionName = sharedActions[i];
const appletAction = Plasmoid.internalAction(actionName);
if (appletAction) {
modelAction = folderView.model.action(actionName);
appletAction.text = modelAction.text;
appletAction.enabled = modelAction.enabled;
appletAction.visible = modelAction.visible;
}
}
}
function cancelRename() {
folderView.cancelRename();
}
function goHome() {
if (folderView.url !== Plasmoid.configuration.url) {
folderView.url = Qt.binding(() => Plasmoid.configuration.url);
folderView.history = [];
folderView.historyChanged();
}
}
Binding {
target: Plasmoid
property: "title"
value: labelGenerator.displayLabel
restoreMode: Binding.RestoreBinding
}
Folder.LabelGenerator {
id: labelGenerator
folderModel: folderView.model
rtl: (Application.layoutDirection === Qt.RightToLeft)
labelMode: Plasmoid.configuration.labelMode || (Plasmoid.isContainment ? 0 : 1)
labelText: Plasmoid.configuration.labelText
}
Folder.ViewPropertiesMenu {
id: viewPropertiesMenu
showLayoutActions: !folderViewLayerComponent.isPopup
showLockAction: Plasmoid.isContainment
showIconSizeActions: !folderViewLayerComponent.useListViewMode
lockedEnabled: !folderViewLayerComponent.lockedByKiosk
onArrangementChanged: {
Plasmoid.configuration.arrangement = arrangement;
}
onAlignmentChanged: {
Plasmoid.configuration.alignment = alignment;
}
onPreviewsChanged: {
Plasmoid.configuration.previews = previews;
}
onLockedChanged: {
if (!folderViewLayerComponent.lockedByKiosk) {
Plasmoid.configuration.locked = locked;
}
}
onSortModeChanged: {
Plasmoid.configuration.sortMode = sortMode;
}
onSortDescChanged: {
Plasmoid.configuration.sortDesc = sortDesc;
}
onSortDirsFirstChanged: {
Plasmoid.configuration.sortDirsFirst = sortDirsFirst;
}
onIconSizeChanged: {
Plasmoid.configuration.iconSize = iconSize;
}
Component.onCompleted: {
arrangement = Plasmoid.configuration.arrangement;
alignment = Plasmoid.configuration.alignment;
previews = Plasmoid.configuration.previews;
locked = Plasmoid.configuration.locked || folderViewLayerComponent.lockedByKiosk;
sortMode = Plasmoid.configuration.sortMode;
sortDesc = Plasmoid.configuration.sortDesc;
sortDirsFirst = Plasmoid.configuration.sortDirsFirst;
iconSize = Plasmoid.configuration.iconSize;
}
}
PlasmaComponents.Label {
anchors.fill: parent
text: folderView.errorString
textFormat: Text.PlainText
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Connections {
target: root
function onExpandedChanged() {
if (folderViewLayerComponent.isPopup) {
if (root.expanded) {
folderView.currentIndex = -1;
folderView.forceActiveFocus();
folderView.positionViewAtBeginning();
} else {
folderViewLayerComponent.goHome();
folderView.currentIndex = -1;
folderView.model.clearSelection();
folderView.cancelRename();
}
}
}
}
Connections {
target: Plasmoid.configuration
function onArrangementChanged() {
viewPropertiesMenu.arrangement = Plasmoid.configuration.arrangement;
}
function onAlignmentChanged() {
viewPropertiesMenu.alignment = Plasmoid.configuration.alignment;
}
function onLockedChanged() {
viewPropertiesMenu.locked = Plasmoid.configuration.locked;
}
function onSortModeChanged() {
folderView.sortMode = Plasmoid.configuration.sortMode;
viewPropertiesMenu.sortMode = Plasmoid.configuration.sortMode;
}
function onSortDescChanged() {
viewPropertiesMenu.sortDesc = Plasmoid.configuration.sortDesc;
}
function onSortDirsFirstChanged() {
viewPropertiesMenu.sortDirsFirst = Plasmoid.configuration.sortDirsFirst;
}
function onIconSizeChanged() {
viewPropertiesMenu.iconSize = Plasmoid.configuration.iconSize;
}
}
FolderView {
id: folderView
anchors.left: parent.left
anchors.top: parent.top
anchors.topMargin: folderViewLayerComponent.label !== null ? folderViewLayerComponent.label.height : 0
anchors.right: parent.right
anchors.bottom: parent.bottom
focus: true
isRootView: Plasmoid.isContainment
positionerApplet: Plasmoid
url: Plasmoid.configuration.url
locked: (Plasmoid.configuration.locked || !Plasmoid.isContainment || folderViewLayerComponent.lockedByKiosk)
filterMode: Plasmoid.configuration.filterMode
filterPattern: Plasmoid.configuration.filterPattern
filterMimeTypes: Plasmoid.configuration.filterMimeTypes
showHiddenFiles: Plasmoid.configuration.showHiddenFiles
flow: (Plasmoid.configuration.arrangement === 0) ? GridView.FlowLeftToRight : GridView.FlowTopToBottom
layoutDirection: (Plasmoid.configuration.alignment === 0) ? Qt.LeftToRight : Qt.RightToLeft
onSortModeChanged: {
Plasmoid.configuration.sortMode = sortMode;
}
Component.onCompleted: {
folderView.sortMode = Plasmoid.configuration.sortMode;
}
}
Component {
id: labelComponent
Item {
id: label
// If we bind height to visible, it will be invisible initially (since "visible"
// propagates recursively) and that confuses the Label, hence the temp property.
readonly property bool active: (Plasmoid.configuration.labelMode !== 0)
readonly property bool showPin: folderViewLayerComponent.isPopup && root.compactRepresentationItem && root.compactRepresentationItem.visible
width: parent.width
height: active ? folderViewLayerComponent.labelHeight : 0
visible: active
property Item windowPin: null
property Item homeButton: null
onVisibleChanged: {
if (folderViewLayerComponent.isPopup && !visible) {
root.hideOnWindowDeactivate = true;
}
}
onShowPinChanged: {
if (!windowPin && showPin) {
windowPin = windowPinComponent.createObject(label);
} else if (windowPin) {
windowPin.destroy();
windowPin = null;
}
}
Connections {
target: folderView
function onUrlChanged() {
if (!label.homeButton && folderView.url !== Plasmoid.configuration.url) {
label.homeButton = homeButtonComponent.createObject(label);
} else if (label.homeButton && folderView.url === Plasmoid.configuration.url) {
label.homeButton.destroy();
}
}
}
PlasmaComponents.Label {
id: text
anchors {
left: label.homeButton ? label.homeButton.right : parent.left
right: label.windowPin ? label.windowPin.left : parent.right
margins: Kirigami.Units.smallSpacing
}
height: parent.height
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignTop
elide: Text.ElideMiddle
text: labelGenerator.displayLabel
textFormat: Text.PlainText
font.underline: labelMouseArea.containsMouse
}
MouseArea {
id: labelMouseArea
anchors {
top: text.top
horizontalCenter: text.horizontalCenter
}
width: text.contentWidth
height: text.contentHeight
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
Folder.AppLauncher.openUrl(folderView.url)
}
}
Component {
id: windowPinComponent
PlasmaComponents.ToolButton {
id: windowPin
anchors.right: parent.right
visible: label.showPin
width: folderViewLayerComponent.isPopup ? Math.round(Kirigami.Units.gridUnit * 1.25) : 0
height: width
checkable: true
icon.name: "window-pin"
onCheckedChanged: root.hideOnWindowDeactivate = !checked
}
}
Component {
id: homeButtonComponent
PlasmaComponents.ToolButton {
id: homeButton
anchors.left: parent.left
visible: folderViewLayerComponent.isPopup && folderView.url !== Plasmoid.configuration.url
width: folderViewLayerComponent.isPopup ? Math.round(Kirigami.Units.gridUnit * 1.25) : 0
height: width
icon.name: "go-home"
onClicked: folderViewLayerComponent.goHome()
}
}
}
}
PlasmaCore.Action {
id: viewPropertiesAction
text: i18nc("@item:inmenu opens submenu with view options like sorting", "Icons")
icon.name: "view-list-icons"
menu: viewPropertiesMenu.menu
}
PlasmaCore.Action {
id: actionSeparator
isSeparator: true
}
Component.onCompleted: {
if (!Plasmoid.isContainment) {
label = labelComponent.createObject(folderViewLayerComponent);
}
for (let i = 0, len = sharedActions.length; i < len; i++) {
const actionName = sharedActions[i];
const modelAction = folderView.model.action(actionName);
Plasmoid.contextualActions.push(modelAction)
if (actionName === "emptyTrash") {
Plasmoid.contextualActions.push(viewPropertiesAction)
}
}
Plasmoid.contextualActions.push(actionSeparator);
Plasmoid.contextualActionsAboutToShow.connect(updateContextualActions);
Plasmoid.contextualActionsAboutToShow.connect(folderView.model.clearSelection);
}
}

View File

@@ -0,0 +1,206 @@
/*
SPDX-FileCopyrightText: 2014-2015 Eike Hein <hein@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQml
import QtQuick.Controls as QQC2
import org.kde.plasma.plasmoid
import org.kde.plasma.components as PlasmaComponents
import org.kde.kirigami as Kirigami
PlasmaComponents.ScrollView {
id: root
property alias text: editor.text
property alias targetItem: editor.targetItem
signal commit
onFocusChanged: {
if (focus) {
editor.forceActiveFocus();
}
}
// We use QQC2.TextArea here to allow context menu to appear,
// since PlasmaComponents.TextArea does not have a context menu
// BUG:427292
QQC2.TextArea {
id: editor
wrapMode: root.useListViewMode ? TextEdit.NoWrap : TextEdit.Wrap
textMargin: 0
horizontalAlignment: root.useListViewMode ? TextEdit.AlignLeft : TextEdit.AlignHCenter
rightPadding: root.PlasmaComponents.ScrollBar.vertical.visible ? root.PlasmaComponents.ScrollBar.vertical.width : 0
Kirigami.SpellCheck.enabled: false
background: Rectangle {
color: Kirigami.Theme.backgroundColor
radius: Kirigami.Units.cornerRadius
border.color: Kirigami.Theme.highlightColor
border.width: 1
}
property FolderItemDelegate targetItem: null
Binding {
target: editor.background
property: "width"
value: root.width
}
Binding {
target: editor.background
property: "height"
value: root.height
}
Component.onCompleted: root.contentItem.clip = false
onTargetItemChanged: {
if (targetItem !== null) {
var xy = getXY();
root.x = xy[0];
root.y = xy[1];
root.width = getWidth();
root.height = getInitHeight();
text = targetItem.name;
adjustSize();
editor.select(0, dir.fileExtensionBoundary(positioner.map(targetItem.index)));
if (isPopup) {
root.contentItem.contentX = Math.max(root.contentItem.contentWidth - contentItem.width, 0);
} else {
root.contentItem.contentY = Math.max(root.contentItem.contentHeight - contentItem.height, 0);
}
root.visible = true;
} else {
root.x = 0;
root.y = 0;
root.visible = false;
}
}
Keys.onPressed: event => {
switch (event.key) {
case Qt.Key_Return:
case Qt.Key_Enter:
root.commit();
break;
case Qt.Key_Escape:
if (targetItem) {
targetItem = null;
event.accepted = true;
}
break;
case Qt.Key_Home:
if (event.modifiers & Qt.ShiftModifier) {
editor.select(0, cursorPosition);
} else {
editor.select(0, 0);
}
event.accepted = true;
break;
case Qt.Key_End:
if (event.modifiers & Qt.ShiftModifier) {
editor.select(cursorPosition, text.length);
} else {
editor.select(text.length, text.length);
}
event.accepted = true;
break;
default:
adjustSize();
break;
}
}
Keys.onReleased: event => {
adjustSize();
}
function getXY() {
if (!targetItem) {
return [0,0];
}
var pos = main.mapFromItem(targetItem, targetItem.labelArea.x, targetItem.labelArea.y);
var _x, _y;
if (root.useListViewMode) {
_x = targetItem.labelArea.x - editor.leftPadding;
_y = pos.y - editor.topPadding;
} else {
_x = targetItem.x + Math.abs(Math.min(gridView.contentX, gridView.originX));
_x += editor.leftPadding;
_x += scrollArea.viewport.x;
if (root.PlasmaComponents.ScrollBar.vertical.policy === Qt.ScrollBarAlwaysOn
&& gridView.effectiveLayoutDirection === Qt.RightToLeft) {
_x -= root.PlasmaComponents.ScrollBar.vertical.width;
}
_y = pos.y + Kirigami.Units.smallSpacing - editor.topPadding;
}
return [ _x, _y ];
}
function getWidth(addWidthVerticalScroller) {
if (!targetItem) {
return 0;
}
return(targetItem.label.parent.width - Kirigami.Units.smallSpacing +
(root.useListViewMode ? -(editor.leftPadding + editor.rightPadding + Kirigami.Units.smallSpacing) : 0) +
(addWidthVerticalScroller ? root.PlasmaComponents.ScrollBar.vertical.width : 0));
}
function getHeight(addWidthHoriozontalScroller, init) {
if (!targetItem) {
return 0;
}
var _height;
if (isPopup || init) {
_height = targetItem.labelArea.height + editor.topPadding + editor.bottomPadding;
} else {
var realHeight = contentHeight + editor.topPadding + editor.bottomPadding;
var maxHeight = Kirigami.Units.iconSizes.sizeForLabels * (Plasmoid.configuration.textLines + 1) + editor.topPadding + editor.bottomPadding;
_height = Math.min(realHeight, maxHeight);
}
return _height + (addWidthHoriozontalScroller ? root.PlasmaComponents.ScrollBar.horizontal.height : 0);
}
function getInitHeight() {
return getHeight(false, true);
}
function adjustSize() {
if (isPopup) {
if(contentWidth + editor.leftPadding + editor.rightPadding > root.width) {
root.visible = targetItem !== null;
root.PlasmaComponents.ScrollBar.horizontal.policy = Qt.ScrollBarAlwaysOn;
root.height = getHeight(true);
} else {
root.PlasmaComponents.ScrollBar.horizontal.policy = Qt.ScrollBarAlwaysOff;
root.height = getHeight();
}
} else {
root.height = getHeight();
if(contentHeight + editor.topPadding + editor.bottomPadding > root.height) {
root.visible = targetItem !== null;
root.PlasmaComponents.ScrollBar.vertical.policy = Qt.ScrollBarAlwaysOn;
root.width = getWidth(true);
} else {
root.PlasmaComponents.ScrollBar.vertical.policy = Qt.ScrollBarAlwaysOff;
root.width = getWidth();
}
}
var xy = getXY();
root.x = xy[0];
root.y = xy[1];
}
}
}

View File

@@ -0,0 +1,69 @@
/*
SPDX-FileCopyrightText: 2014 Eike Hein <hein@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
var iconSizes = [Kirigami.Units.iconSizes.smallMedium,
Kirigami.Units.iconSizes.medium,
Kirigami.Units.iconSizes.large,
Kirigami.Units.iconSizes.huge,
Kirigami.Units.iconSizes.large*2,
Kirigami.Units.iconSizes.enormous,
Kirigami.Units.iconSizes.enormous*2];
function iconSizeFromTheme(size) {
return iconSizes[size];
}
function effectiveNavDirection(flow, layoutDirection, direction) {
if (direction == Qt.LeftArrow) {
if (flow == GridView.FlowLeftToRight) {
if (layoutDirection == Qt.LeftToRight) {
return Qt.LeftArrow;
} else {
return Qt.RightArrow;
}
} else {
if (layoutDirection == Qt.LeftToRight) {
return Qt.UpArrow;
} else {
return Qt.DownArrow;
}
}
} else if (direction == Qt.RightArrow) {
if (flow == GridView.FlowLeftToRight) {
if (layoutDirection == Qt.LeftToRight) {
return Qt.RightArrow;
} else {
return Qt.LeftArrow;
}
} else {
if (layoutDirection == Qt.LeftToRight) {
return Qt.DownArrow;
} else {
return Qt.UpArrow;
}
}
} else if (direction == Qt.UpArrow) {
if (flow == GridView.FlowLeftToRight) {
return Qt.UpArrow;
} else {
return Qt.LeftArrow;
}
} else if (direction == Qt.DownArrow) {
if (flow == GridView.FlowLeftToRight) {
return Qt.DownArrow;
} else {
return Qt.RightArrow
}
}
}
function isFileDrag(event) {
var taskUrl = event.mimeData.formats.indexOf("text/x-orgkdeplasmataskmanager_taskurl") != -1;
var arkService = event.mimeData.formats.indexOf("application/x-kde-ark-dndextract-service") != -1;
var arkPath = event.mimeData.formats.indexOf("application/x-kde-ark-dndextract-path") != -1;
return (event.mimeData.hasUrls || taskUrl || (arkService && arkPath));
}

View File

@@ -0,0 +1,473 @@
/*
SPDX-FileCopyrightText: 2011-2013 Sebastian Kügler <sebas@kde.org>
SPDX-FileCopyrightText: 2011-2019 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2014-2015 Eike Hein <hein@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import org.kde.plasma.plasmoid
import org.kde.plasma.core as PlasmaCore
import org.kde.ksvg as KSvg
import org.kde.kirigami as Kirigami
import org.kde.private.desktopcontainment.folder as Folder
import org.kde.plasma.private.containmentlayoutmanager as ContainmentLayoutManager
import "code/FolderTools.js" as FolderTools
ContainmentItem {
id: root
switchWidth: { switchSize(); }
switchHeight: { switchSize(); }
// Only exists because the default CompactRepresentation doesn't:
// - open on drag
// - allow defining a custom drop handler
// TODO remove once it gains that feature (perhaps optionally?)
compactRepresentation: (isFolder && !isContainment) ? compactRepresentation : null
objectName: isFolder ? "folder" : "desktop"
width: isPopup ? undefined : preferredWidth(false) // Initial size when adding to e.g. desktop.
height: isPopup ? undefined : preferredHeight(false) // Initial size when adding to e.g. desktop.
function switchSize() {
// Support expanding into the full representation on very thick vertical panels.
if (isPopup && Plasmoid.formFactor === PlasmaCore.Types.Vertical) {
return Kirigami.Units.gridUnit * 8;
}
return 0;
}
LayoutMirroring.enabled: Application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
property bool isFolder: (Plasmoid.pluginName === "org.kde.plasma.folder")
property bool isContainment: Plasmoid.isContainment
property bool isPopup: (Plasmoid.location !== PlasmaCore.Types.Floating)
property bool useListViewMode: isPopup && Plasmoid.configuration.viewMode === 0
property Component appletAppearanceComponent
property int handleDelay: 800
property real haloOpacity: 0.5
readonly property bool isUiReady: Plasmoid.containment.corona.isScreenUiReady(root.screen)
readonly property int hoverActivateDelay: 750 // Magic number that matches Dolphin's auto-expand folders delay.
readonly property FolderViewLayerLoader folderViewLayer: fullRepresentationItem.folderViewLayer
readonly property ContainmentLayoutManager.AppletsLayout appletsLayout: fullRepresentationItem.appletsLayout
// Plasmoid.title is set by a Binding {} in FolderViewLayer
toolTipSubText: ""
Plasmoid.icon: (!Plasmoid.configuration.useCustomIcon && folderViewLayer.ready) ? symbolicizeIconName(folderViewLayer.view?.model.iconName) : Plasmoid.configuration.icon
// We want to do this here rather than in the model because we don't always want
// symbolic icons everywhere, but we do know that we always want them in this
// specific representation right here
function symbolicizeIconName(iconName) {
const symbolicSuffix = "-symbolic";
if (iconName?.endsWith(symbolicSuffix)) {
return iconName;
}
return iconName + symbolicSuffix;
}
function addLauncher(desktopUrl) {
if (!isFolder) {
return;
}
folderViewLayer.view.linkHere(desktopUrl);
}
function preferredWidth(forMinimumSize: bool): real {
if ((isContainment || !folderViewLayer.ready) || (isPopup && !compactRepresentationItem.visible)) {
return -1;
} else if (useListViewMode) {
return (forMinimumSize ? folderViewLayer.view.cellHeight * 4 : Kirigami.Units.gridUnit * 16);
}
return (folderViewLayer.view.cellWidth * (forMinimumSize ? 1 : 3)) + (Kirigami.Units.gridUnit * 2);
}
function preferredHeight(forMinimumSize: bool): real {
let height;
if ((isContainment || !folderViewLayer.ready) || (isPopup && !compactRepresentationItem.visible)) {
return -1;
} else if (useListViewMode) {
height = (folderViewLayer.view.cellHeight * (forMinimumSize ? 1 : 15)) + Kirigami.Units.smallSpacing;
} else {
height = (folderViewLayer.view.cellHeight * (forMinimumSize ? 1 : 2)) + Kirigami.Units.gridUnit;
}
if (Plasmoid.configuration.labelMode !== 0) {
height += (folderViewLayer.item as FolderViewLayer).labelHeight;
}
return height;
}
function isDrag(fromX, fromY, toX, toY) {
const length = Math.abs(fromX - toX) + Math.abs(fromY - toY);
return length >= Application.styleHints.startDragDistance;
}
onFocusChanged: {
if (focus && isFolder) {
(folderViewLayer.item as Item)?.forceActiveFocus();
}
}
onExternalData: (mimetype, data) => {
Plasmoid.configuration.url = data
}
component ShortDropBehavior : Behavior {
NumberAnimation {
duration: Kirigami.Units.shortDuration
easing.type: Easing.InOutQuad
}
}
component LongDropBehavior : Behavior {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
KSvg.FrameSvgItem {
id: highlightItemSvg
visible: false
imagePath: root.isPopup ? "widgets/viewitem" : ""
prefix: "hover"
}
KSvg.FrameSvgItem {
id: listItemSvg
visible: false
imagePath: root.isPopup ? "widgets/viewitem" : ""
prefix: "normal"
}
KSvg.Svg {
id: toolBoxSvg
imagePath: "widgets/toolbox"
property int rightBorder: elementSize("right").width
property int topBorder: elementSize("top").height
property int bottomBorder: elementSize("bottom").height
property int leftBorder: elementSize("left").width
}
// FIXME: the use and existence of this property is a workaround
preloadFullRepresentation: true
fullRepresentation: FolderViewDropArea {
id: dropArea
anchors {
fill: parent
leftMargin: (root.isContainment && root.availableScreenRect) ? root.availableScreenRect.x : 0
topMargin: (root.isContainment && root.availableScreenRect) ? root.availableScreenRect.y : 0
rightMargin: (root.isContainment && root.availableScreenRect && parent)
? (parent.width - root.availableScreenRect.x - root.availableScreenRect.width) : 0
bottomMargin: (root.isContainment && root.availableScreenRect && parent)
? (parent.height - root.availableScreenRect.y - root.availableScreenRect.height) : 0
}
LongDropBehavior on anchors.topMargin { }
LongDropBehavior on anchors.leftMargin { }
LongDropBehavior on anchors.rightMargin { }
LongDropBehavior on anchors.bottomMargin { }
property alias folderViewLayer: folderViewLayer
property alias appletsLayout: appletsLayout
// Layout size bindings are set in Component.onCompleted
preventStealing: true
onDragEnter: event => {
if (root.isContainment && Plasmoid.immutable && !(root.isFolder && FolderTools.isFileDrag(event))) {
event.ignore();
}
// Don't allow any drops while listing.
if (root.isFolder && folderViewLayer.view.status === Folder.FolderModel.Listing) {
event.ignore();
}
// Firefox tabs are regular drags. Since all of our drop handling is asynchronous
// we would accept this drop and have Firefox not spawn a new window. (Bug 337711)
if (event.mimeData.formats.indexOf("application/x-moz-tabbrowser-tab") !== -1) {
event.ignore();
}
}
onDragMove: event => {
// TODO: We should reject drag moves onto file items that don't accept drops
// (cf. QAbstractItemModel::flags() here, but DeclarativeDropArea currently
// is currently incapable of rejecting drag events.
// Trigger autoscroll.
if (root.isFolder && FolderTools.isFileDrag(event)) {
handleDragMove(folderViewLayer.view, mapToItem(folderViewLayer.view, event.x, event.y));
} else if (root.isContainment) {
appletsLayout.showPlaceHolderAt(
Qt.rect(event.x - appletsLayout.minimumItemWidth / 2,
event.y - appletsLayout.minimumItemHeight / 2,
appletsLayout.minimumItemWidth,
appletsLayout.minimumItemHeight)
);
}
}
onDragLeave: event => {
// Cancel autoscroll.
if (root.isFolder) {
handleDragEnd(folderViewLayer.view);
}
if (root.isContainment) {
appletsLayout.hidePlaceHolder();
}
}
onDrop: event => {
if (root.isFolder && FolderTools.isFileDrag(event)) {
handleDragEnd(folderViewLayer.view);
folderViewLayer.view.drop(root, event, mapToItem(folderViewLayer.view, event.x, event.y));
} else if (root.isContainment) {
root.processMimeData(event.mimeData,
event.x - appletsLayout.placeHolder.width / 2,
event.y - appletsLayout.placeHolder.height / 2);
event.accept(event.proposedAction);
appletsLayout.hidePlaceHolder();
}
}
Component {
id: compactRepresentation
CompactRepresentation { folderView: folderViewLayer.view }
}
Connections {
target: Plasmoid.containment.corona
ignoreUnknownSignals: true
function onEditModeChanged() {
appletsLayout.editMode = Plasmoid.containment.corona.editMode;
}
// When adding panels, sizes change. We want to make sure all panels
// are loaded, and when they all are loaded, we tell the folderViewLayer loader to start.
function onScreenUiReadyChanged(screen: int, newLayoutReady: bool) {
if (root.isContainment && root.isFolder && !folderViewLayer.ready && root.screen === screen && newLayoutReady){
// We skip x and y since that is handled by the parent of folderViewLayer
folderViewLayer.active = true;
}
}
}
ContainmentLayoutManager.AppletsLayout {
id: appletsLayout
anchors.fill: parent
relayoutLock: width !== root.availableScreenRect.width || height !== root.availableScreenRect.height
// NOTE: use root.availableScreenRect and not own width and height as they are updated not atomically
configKey: "ItemGeometries-" + Math.round(root.screenGeometry.width) + "x" + Math.round(root.screenGeometry.height)
fallbackConfigKey: root.availableScreenRect.width > root.availableScreenRect.height ? "ItemGeometriesHorizontal" : "ItemGeometriesVertical"
Binding on containment {
value: Plasmoid
when: Plasmoid.isContainment
}
containmentItem: root
editModeCondition: Plasmoid.immutable
? ContainmentLayoutManager.AppletsLayout.Locked
: ContainmentLayoutManager.AppletsLayout.AfterPressAndHold
// Sets the containment in edit mode when we go in edit mode as well
onEditModeChanged: Plasmoid.containment.corona.editMode = editMode;
minimumItemWidth: Kirigami.Units.gridUnit * 3
minimumItemHeight: minimumItemWidth
cellWidth: Kirigami.Units.iconSizes.small
cellHeight: cellWidth
defaultItemWidth: cellWidth * 6
defaultItemHeight: cellHeight * 6
eventManagerToFilter: (folderViewLayer.item as FolderViewLayer)?.view.view ?? null
appletContainerComponent: ContainmentLayoutManager.BasicAppletContainer {
id: appletContainer
editModeCondition: Plasmoid.immutable
? ContainmentLayoutManager.ItemContainer.Locked
: ContainmentLayoutManager.ItemContainer.AfterPressAndHold
configOverlaySource: "ConfigOverlay.qml"
Connections {
target: appletsLayout
function onEditModeChanged(): void {
if (!Plasmoid.containment.corona.editMode) {
appletContainer.cancelEdit();
}
}
}
onAppletChanged: {
applet.visible = true
}
Drag.dragType: Drag.Automatic
Drag.active: false
Drag.supportedActions: Qt.MoveAction
Drag.mimeData: {
"text/x-plasmoidinstanceid": Plasmoid.containment.id+':'+appletContainer.applet.plasmoid.id
}
Drag.onDragFinished: dropEvent => {
if (dropEvent == Qt.MoveAction) {
appletContainer.visible = true
appletContainer.applet.visible = true
//currentApplet.applet.plasmoid.internalAction("remove").trigger()
} else {
appletContainer.visible = true
//appletsModel.insert(configurationArea.draggedItemIndex - 1, {applet: appletContainer.applet});
}
//appletContainer.destroy()
//root.dragAndDropping = false
//root.layoutManager.save()
}
onUserDrag: (newPosition, dragCenter) => {
const pos = mapToItem(root.parent, dragCenter.x, dragCenter.y);
const newCont = root.containmentItemAt(pos.x, pos.y);
// User likely touched screen edges, so ignore that.
if (!newCont) {
return;
}
if (newCont.plasmoid !== Plasmoid) {
// First go out of applet edit mode, get rid of the config overlay, release mouse grabs in preparation of applet reparenting
cancelEdit();
appletsLayout.hidePlaceHolder();
appletContainer.grabToImage(result => {
appletContainer.Drag.imageSource = result.url
appletContainer.visible = false
appletContainer.Drag.active = true
})
}
}
ShortDropBehavior on x { }
ShortDropBehavior on y { }
}
placeHolder: ContainmentLayoutManager.PlaceHolder {}
component FolderViewLayerLoader: Loader {
property bool ready: status === Loader.Ready
property FolderView view: (item as FolderViewLayer)?.view ?? null
property Folder.FolderModel model: (item as FolderViewLayer)?.model ?? null
source: "FolderViewLayer.qml"
}
FolderViewLayerLoader {
id: folderViewLayer
anchors.fill: parent
focus: true
// Do not set this active by default for desktop, and disable it when folderMode is not used
active: {
if (root.isFolder){
if (!root.isContainment) {
// We are a folder widget
return true;
} else {
// For desktop, test if the screen is ready
return root.isUiReady;
}
}
return false;
}
asynchronous: false
onFocusChanged: {
if (!focus && model) {
model.clearSelection();
}
}
Binding {
target: folderViewLayer.item
property: "isPopup"
value: root.isPopup
}
Binding {
target: folderViewLayer.item
property: "useListViewMode"
value: root.useListViewMode
}
Connections {
target: folderViewLayer.view
// `FolderViewDropArea` is not a FocusScope. We need to forward manually.
function onPressed() {
folderViewLayer.forceActiveFocus();
}
}
}
}
PlasmaCore.Action {
id: configAction
text: i18nc("@action:inmenu opens config dialog", "Desktop and Wallpaper")
icon.name: "preferences-desktop-wallpaper"
shortcut:"Ctrl+Shift+D"
onTriggered: Plasmoid.containment.configureRequested(Plasmoid)
}
Component.onCompleted: {
// Layout bindings need to be set delayed; the intermediate steps as the other bindings happen cause loops
Qt.callLater( () => {
dropArea.Layout.minimumWidth = Qt.binding(() => root.preferredWidth(root.isPopup))
dropArea.Layout.minimumHeight = Qt.binding(() => root.preferredHeight(root.isPopup))
dropArea.Layout.preferredWidth = Qt.binding(() => root.preferredWidth(false))
dropArea.Layout.preferredHeight = Qt.binding(() => root.preferredHeight(false))
// Maximum size is intentionally unbounded
})
if (!Plasmoid.isContainment) {
return;
}
Plasmoid.setInternalAction("configure", configAction)
}
}
}