Commit 88718b16 authored by Andras Mantia's avatar Andras Mantia

Make Folder View screen aware

Summary:
When using multiple screen with a Folder View as a desktop containment, we need to make sure a file belongs only to one screen. The patch adds support for this by:

1) Introducing a ScreenMapper object shared acrossed all Folder Views
2) FolderModel registers the screen it resides on if used as a containment
3) FolderModel filters out items not on the current screen (if used as a containment)
4) FolderModel adds mapping for the newly appeared files. The new files will go to the registered screen having the smallest id. This also means by default all files appear on the screen with the smallest id.
5) url/screen mapping is stored in the configuration of each folder view applet. This means duplication of the information, and although they should be in sync unless manually modified, the logic is that the last view's mapping option is used.
6) When removing a screen, the list of items from the removed screen is stored and items get moved to the first screen
7) When a screen is added back, the items are moved back there based on the above list

So far missing:
- correct handling of D&D between folderviews on different screens
- forgetting the saved position from a removed screen if the items are reorganized on the primary (visible) screen

Depends on D8566
Depends on D8567

Reviewers: #plasma, ervin, mlaurent, dvratil, hein, aacid, apol, mwolff

Reviewed By: #plasma, hein, mwolff

Subscribers: anthonyfieroni, ngraham, mwolff, davidedmundson, broulik, mart, plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D8493
parent 35d847df
......@@ -37,6 +37,10 @@
<default></default>
</entry>
<entry name="screenMapping" 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>
......
......@@ -1075,6 +1075,8 @@ Item {
parseDesktopFiles: (plasmoid.configuration.url == "desktop:/")
previews: plasmoid.configuration.previews
previewPlugins: plasmoid.configuration.previewPlugins
screenMapper: Folder.ScreenMapper
appletInterface: plasmoid
onListingCompleted: {
if (!gridView.model && plasmoid.expanded) {
......
......@@ -188,6 +188,18 @@ Item {
onPositionsChanged: {
folderView.positions = plasmoid.configuration.positions;
}
onScreenMappingChanged: {
Folder.ScreenMapper.screenMapping = plasmoid.configuration.screenMapping;
}
}
Connections {
target: Folder.ScreenMapper
onScreenMappingChanged: {
plasmoid.configuration.screenMapping = Folder.ScreenMapper.screenMapping;
}
}
PlasmaCore.ColorScope {
......@@ -227,6 +239,7 @@ Item {
}
Component.onCompleted: {
Folder.ScreenMapper.screenMapping = plasmoid.configuration.screenMapping;
folderView.sortMode = plasmoid.configuration.sortMode;
folderView.positions = plasmoid.configuration.positions;
}
......
......@@ -20,6 +20,7 @@ set(folderplugin_SRCS
viewpropertiesmenu.cpp
wheelinterceptor.cpp
shortcut.cpp
screenmapper.cpp
)
install(FILES qmldir DESTINATION ${QML_INSTALL_DIR}/org/kde/private/desktopcontainment/folder)
......
......@@ -5,6 +5,7 @@ include(ECMAddTests)
include_directories("..";${include_directories})
ecm_add_tests(
screenmappertest.cpp
foldermodeltest.cpp
positionertest.cpp
viewpropertiesmenutest.cpp
......
......@@ -21,6 +21,7 @@
#include "foldermodeltest.h"
#include "foldermodel.h"
#include "screenmapper.h"
#include <QTest>
#include <QTemporaryDir>
......@@ -30,13 +31,11 @@ QTEST_MAIN(FolderModelTest)
static const QLatin1String desktop(QLatin1String("Desktop"));
void FolderModelTest::initTestCase()
void FolderModelTest::createTestFolder(const QString &path)
{
m_folderDir = new QTemporaryDir();
QDir dir(m_folderDir->path());
dir.mkdir(desktop);
dir.cd(desktop);
dir.mkdir(path);
dir.cd(path);
dir.mkdir("firstDir");
QFile f;
for (int i = 1; i < 10; i++) {
......@@ -44,16 +43,12 @@ void FolderModelTest::initTestCase()
f.open(QFile::WriteOnly);
f.close();
}
}
void FolderModelTest::cleanupTestCase()
{
delete m_folderDir;
}
void FolderModelTest::init()
{
m_folderDir = new QTemporaryDir();
createTestFolder(desktop);
m_folderModel = new FolderModel(this);
m_folderModel->setUrl(m_folderDir->path() + QDir::separator() + desktop );
QSignalSpy s(m_folderModel, &FolderModel::listingCompleted);
......@@ -62,6 +57,8 @@ void FolderModelTest::init()
void FolderModelTest::cleanup()
{
delete m_folderDir;
m_folderDir = 0;
delete m_folderModel;
m_folderModel = nullptr;
}
......@@ -265,3 +262,110 @@ void FolderModelTest::tst_lockedChanged()
m_folderModel->setLocked(true);
QCOMPARE(s.count(), 2);
}
void FolderModelTest::tst_multiScreen()
{
auto *screenMapper = ScreenMapper::instance();
m_folderModel->setUsedByContainment(true);
m_folderModel->setScreenMapper(screenMapper);
m_folderModel->setScreen(0);
QSignalSpy s(m_folderModel, &FolderModel::listingCompleted);
s.wait(1000);
const auto count = m_folderModel->rowCount();
for (int i = 0; i < count; i++) {
const auto index = m_folderModel->index(i, 0);
const auto name = index.data(FolderModel::UrlRole).toString();
// all items are on the first screen by default
QCOMPARE(screenMapper->screenForItem(name), 0);
}
// move one file to a new screen
const auto movedItem = m_folderModel->index(0, 0).data(FolderModel::UrlRole).toString();
FolderModel secondFolderModel;
secondFolderModel.setUrl(m_folderDir->path() + QDir::separator() + desktop );
secondFolderModel.setUsedByContainment(true);
secondFolderModel.setScreenMapper(screenMapper);
secondFolderModel.setScreen(1);
QSignalSpy s2(&secondFolderModel, &FolderModel::listingCompleted);
s2.wait(1000);
const auto count2 = secondFolderModel.rowCount();
QCOMPARE(count2, 0);
screenMapper->addMapping(movedItem, 1);
m_folderModel->invalidate();
secondFolderModel.invalidate();
s.wait(1000);
s2.wait(1000);
// we have one less item
QCOMPARE(m_folderModel->rowCount(), count - 1);
QCOMPARE(secondFolderModel.rowCount(), 1);
QCOMPARE(secondFolderModel.index(0,0).data(FolderModel::UrlRole).toString(), movedItem);
QCOMPARE(screenMapper->screenForItem(movedItem), 1);
// remove extra screen, we have all items back
screenMapper->removeScreen(1, m_folderModel->url());
s.wait(500);
QCOMPARE(m_folderModel->rowCount(), count);
QCOMPARE(secondFolderModel.rowCount(), 0);
QCOMPARE(screenMapper->screenForItem(movedItem), 0);
// add back extra screen, the item is moved there
screenMapper->addScreen(1, m_folderModel->url());
s.wait(500);
s2.wait(500);
QCOMPARE(m_folderModel->rowCount(), count - 1);
QCOMPARE(secondFolderModel.rowCount(), 1);
QCOMPARE(secondFolderModel.index(0,0).data(FolderModel::UrlRole).toString(), movedItem);
QCOMPARE(screenMapper->screenForItem(movedItem), 1);
// create a new item, it appears on the first screen
QDir dir(m_folderDir->path());
dir.cd(desktop);
dir.mkdir("secondDir");
dir.cd("secondDir");
s.wait(1000);
QCOMPARE(m_folderModel->rowCount(), count);
QCOMPARE(secondFolderModel.rowCount(), 1);
QCOMPARE(screenMapper->screenForItem("file://" + dir.path()), 0);
}
void FolderModelTest::tst_multiScreenDifferenPath()
{
auto *screenMapper = ScreenMapper::instance();
m_folderModel->setUsedByContainment(true);
m_folderModel->setScreenMapper(screenMapper);
m_folderModel->setScreen(0);
QSignalSpy s(m_folderModel, &FolderModel::listingCompleted);
s.wait(1000);
const auto count = m_folderModel->rowCount();
QCOMPARE(count, 10);
const QLatin1String desktop2(QLatin1String("Desktop2"));
createTestFolder(desktop2);
FolderModel secondFolderModel;
secondFolderModel.setUsedByContainment(true);
secondFolderModel.setScreenMapper(screenMapper);
secondFolderModel.setUrl(m_folderDir->path() + QDir::separator() + desktop2 );
secondFolderModel.setScreen(1);
QSignalSpy s2(&secondFolderModel, &FolderModel::listingCompleted);
s2.wait(1000);
const auto count2 = secondFolderModel.rowCount();
QCOMPARE(count2, 10);
// create a new item, it appears on the first screen
QDir dir(m_folderDir->path());
dir.cd(desktop);
dir.mkdir("secondDir");
s.wait(1000);
QCOMPARE(m_folderModel->rowCount(), count + 1);
QCOMPARE(secondFolderModel.rowCount(), count2);
// create a new item, it appears on the second screen
dir.cd(m_folderDir->path() + QDir::separator() + desktop2);
dir.mkdir("secondDir2");
s.wait(1000);
QCOMPARE(m_folderModel->rowCount(), count + 1);
QCOMPARE(secondFolderModel.rowCount(), count2 + 1);
}
......@@ -32,9 +32,6 @@ class FolderModelTest : public QObject
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void init();
void cleanup();
void tst_listing();
......@@ -48,8 +45,13 @@ private Q_SLOTS:
void tst_defaultValues();
void tst_actionMenu();
void tst_lockedChanged();
void tst_multiScreen();
void tst_multiScreenDifferenPath();
private:
void createTestFolder(const QString &path);
FolderModel *m_folderModel;
QTemporaryDir *m_folderDir;
};
......
/***************************************************************************
* Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company *
* <info@kdab.com> *
* Author: Andras Mantia <andras.mantia@kdab.com> *
* Work sponsored by the LiMux project of the city of Munich. *
* *
* 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 "screenmappertest.h"
#include "screenmapper.h"
#include <QTest>
#include <QSignalSpy>
QTEST_MAIN(ScreenMapperTest)
void ScreenMapperTest::initTestCase()
{
m_screenMapper = ScreenMapper::instance();
}
void ScreenMapperTest::init()
{
m_screenMapper->cleanup();
}
void ScreenMapperTest::tst_addScreens()
{
const auto path = QStringLiteral("desktop:/");
QSignalSpy s(m_screenMapper, &ScreenMapper::screensChanged);
m_screenMapper->addScreen(-1, path);
QCOMPARE(s.count(), 0);
m_screenMapper->addScreen(1, path);
QCOMPARE(s.count(), 1);
m_screenMapper->addScreen(0, path);
QCOMPARE(s.count(), 2);
m_screenMapper->addScreen(1, path);
QCOMPARE(s.count(), 2);
QCOMPARE(m_screenMapper->firstAvailableScreen(path), 0);
}
void ScreenMapperTest::tst_removeScreens()
{
const auto path = QStringLiteral("desktop:/");
addScreens(path);
QSignalSpy s(m_screenMapper, &ScreenMapper::screensChanged);
m_screenMapper->removeScreen(-1, path);
QCOMPARE(s.count(), 0);
m_screenMapper->removeScreen(1, path);
QCOMPARE(s.count(), 1);
QCOMPARE(m_screenMapper->firstAvailableScreen(path), 0);
m_screenMapper->removeScreen(1, path);
QCOMPARE(s.count(), 1);
m_screenMapper->addScreen(3, path);
QCOMPARE(s.count(), 2);
m_screenMapper->removeScreen(0, path);
QCOMPARE(s.count(), 3);
QCOMPARE(m_screenMapper->firstAvailableScreen(path), 2);
}
void ScreenMapperTest::tst_addMapping()
{
const auto path = QStringLiteral("desktop:/");
addScreens(path);
QSignalSpy s(m_screenMapper, &ScreenMapper::screenMappingChanged);
QString file("desktop:/foo%1.txt");
for (int i = 0 ; i < 3; i++) {
const QString name = file.arg(i);
m_screenMapper->addMapping(name, i);
QCOMPARE(s.count(), i + 1);
QCOMPARE(m_screenMapper->screenForItem(name), i);
}
}
void ScreenMapperTest::tst_addRemoveScreenWithItems()
{
const auto path = QStringLiteral("desktop:/");
addScreens(path);
QString file("desktop:/foo%1.txt");
for (int i = 0 ; i < 3; i++) {
const QString name = file.arg(i);
m_screenMapper->addMapping(name, i);
}
// remove one screen
m_screenMapper->removeScreen(1, path);
QCOMPARE(m_screenMapper->screenForItem(file.arg(0)), 0);
QCOMPARE(m_screenMapper->screenForItem(file.arg(1)), -1);
QCOMPARE(m_screenMapper->screenForItem(file.arg(2)), 2);
// add removed screen back, items screen is restored
m_screenMapper->addScreen(1, path);
QCOMPARE(m_screenMapper->screenForItem(file.arg(0)), 0);
QCOMPARE(m_screenMapper->screenForItem(file.arg(1)), 1);
QCOMPARE(m_screenMapper->screenForItem(file.arg(2)), 2);
// remove all screens, firstAvailableScreen changes
m_screenMapper->removeScreen(0, path);
QCOMPARE(m_screenMapper->firstAvailableScreen(path), 1);
m_screenMapper->removeScreen(1, path);
QCOMPARE(m_screenMapper->firstAvailableScreen(path), 2);
m_screenMapper->removeScreen(2, path);
QCOMPARE(m_screenMapper->firstAvailableScreen(path), -1);
QCOMPARE(m_screenMapper->screenForItem(file.arg(0)), -1);
QCOMPARE(m_screenMapper->screenForItem(file.arg(1)), -1);
QCOMPARE(m_screenMapper->screenForItem(file.arg(2)), -1);
// add all screens back, all item's screen is restored
addScreens(path);
QCOMPARE(m_screenMapper->screenForItem(file.arg(0)), 0);
QCOMPARE(m_screenMapper->screenForItem(file.arg(1)), 1);
QCOMPARE(m_screenMapper->screenForItem(file.arg(2)), 2);
// remove one screen and move its item
const QString movedItem = file.arg(1);
m_screenMapper->removeScreen(1, path);
QCOMPARE(m_screenMapper->screenForItem(movedItem), -1);
m_screenMapper->addMapping(movedItem, 0);
QCOMPARE(m_screenMapper->screenForItem(movedItem), 0);
// add back the screen, item goes back to the original place
m_screenMapper->addScreen(1, path);
QCOMPARE(m_screenMapper->screenForItem(movedItem), 1);
}
void ScreenMapperTest::tst_addRemoveScreenDifferentPaths()
{
const auto path = QStringLiteral("desktop:/Foo");
const auto path2 = QStringLiteral("desktop:/Foo2");
m_screenMapper->addScreen(0, path);
QCOMPARE(m_screenMapper->firstAvailableScreen(path), 0);
QCOMPARE(m_screenMapper->firstAvailableScreen(path2), -1);
}
void ScreenMapperTest::addScreens(const QString &path)
{
m_screenMapper->addScreen(0, path);
m_screenMapper->addScreen(1, path);
m_screenMapper->addScreen(2, path);
}
/***************************************************************************
* Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company *
* <info@kdab.com> *
* Author: Andras Mantia <andras.mantia@kdab.com> *
* Work sponsored by the LiMux project of the city of Munich. *
* *
* 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 SCREENMAPPERTEST_H
#define SCREENMAPPERTEST_H
#include <QObject>
class ScreenMapper;
class ScreenMapperTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void init();
void tst_addScreens();
void tst_removeScreens();
void tst_addMapping();
void tst_addRemoveScreenWithItems();
void tst_addRemoveScreenDifferentPaths();
private:
void addScreens(const QString &path);
ScreenMapper *m_screenMapper;
};
#endif // SCREENMAPPERTEST_H
......@@ -24,6 +24,7 @@
#include "foldermodel.h"
#include "itemviewadapter.h"
#include "positioner.h"
#include "screenmapper.h"
#include <QApplication>
#include <QClipboard>
......@@ -70,6 +71,10 @@
#include <KProtocolInfo>
#include <KRun>
#include <Plasma/Applet>
#include <Plasma/Containment>
#include <Plasma/Corona>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
......@@ -154,6 +159,12 @@ FolderModel::FolderModel(QObject *parent) : QSortFilterProxyModel(parent),
FolderModel::~FolderModel()
{
if (m_screenMapper) {
// disconnect so we don't handle signals from the screen mapper when
// removeScreen is called
m_screenMapper->disconnect(this);
m_screenMapper->removeScreen(m_screen, url());
}
}
QHash< int, QByteArray > FolderModel::roleNames() const
......@@ -194,6 +205,8 @@ void FolderModel::setUrl(const QString& url)
return;
}
const auto oldUrl = m_url;
beginResetModel();
m_url = url;
m_isDirCache.clear();
......@@ -225,6 +238,11 @@ void FolderModel::setUrl(const QString& url)
}
emit iconNameChanged();
if (m_screenMapper) {
m_screenMapper->removeScreen(m_screen, oldUrl);
m_screenMapper->addScreen(m_screen, url);
}
}
QUrl FolderModel::resolvedUrl() const
......@@ -518,6 +536,18 @@ void FolderModel::setFilterMimeTypes(const QStringList &mimeList)
}
}
void FolderModel::setScreen(int screen)
{
if (m_screen == screen)
return;
m_screen = screen;
if (m_usedByContainment && m_screenMapper) {
m_screenMapper->addScreen(screen, url());
}
emit screenChanged();
}
KFileItem FolderModel::rootItem() const
{
return m_dirModel->dirLister()->rootItem();
......@@ -1162,6 +1192,9 @@ void FolderModel::statResult(KJob *job)
void FolderModel::evictFromIsDirCache(const KFileItemList& items)
{
foreach (const KFileItem &item, items) {
if (m_screenMapper) {
m_screenMapper->removeFromMap(item.url().toString());
}
m_isDirCache.remove(item.url());
}
}
......@@ -1283,13 +1316,34 @@ inline bool FolderModel::matchPattern(const KFileItem &item) const
bool FolderModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
const KDirModel *dirModel = static_cast<KDirModel*>(sourceModel());
const KFileItem item = dirModel->itemForIndex(dirModel->index(sourceRow, KDirModel::Name, sourceParent));
if (m_usedByContainment && m_screenMapper) {
const QString name = item.url().toString();
const int screen = m_screenMapper->screenForItem(name);
// don't do anything if the folderview is not associated with a screen
if (m_screen != -1) {
if (screen == -1) {
// The item is not associated with a screen, probably because this is the first
// time we see it or the folderview was previously used as a regular applet.
// Associated with this folderview if the view is on the first available screen
if (m_screen == m_screenMapper->firstAvailableScreen(url())) {
m_screenMapper->addMapping(name, m_screen, ScreenMapper::DelayedSignal);
} else {
return false;
}
} else if (m_screen != screen) {
// the item belongs to a different screen, filter it out
return false;
}
}
}
if (m_filterMode == NoFilter) {
return true;
}
const KDirModel *dirModel = static_cast<KDirModel*>(sourceModel());
const KFileItem item = dirModel->itemForIndex(dirModel->index(sourceRow, KDirModel::Name, sourceParent));
if (m_filterMode == FilterShowMatches) {
return (matchPattern(item) && matchMimeType(item));
} else {
......@@ -1617,6 +1671,66 @@ void FolderModel::refresh()
m_dirModel->dirLister()->updateDirectory(m_dirModel->dirLister()->url());
}
ScreenMapper *FolderModel::screenMapper() const
{
return m_screenMapper;