mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
This is exacerbated now that we have device-specific view margins (to deal with notches and the like), which caused the drag and drop positions to be wildly inaccurate.
980 lines
34 KiB
C++
980 lines
34 KiB
C++
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "dragstate.h"
|
|
#include "favouritesmodel.h"
|
|
#include "pagelistmodel.h"
|
|
|
|
#include <KLocalizedString>
|
|
#include <algorithm>
|
|
|
|
// TODO don't hardcode, use page widths
|
|
const int PAGE_CHANGE_THRESHOLD = 30;
|
|
|
|
const QString DEFAULT_FOLDER_NAME = i18n("Folder");
|
|
|
|
DelegateDragPosition::DelegateDragPosition(QObject *parent)
|
|
: QObject{parent}
|
|
{
|
|
}
|
|
|
|
DelegateDragPosition::~DelegateDragPosition() = default;
|
|
|
|
void DelegateDragPosition::copyFrom(DelegateDragPosition *position)
|
|
{
|
|
setPage(position->page());
|
|
setPageRow(position->pageRow());
|
|
setPageColumn(position->pageColumn());
|
|
setFavouritesPosition(position->favouritesPosition());
|
|
setFolderPosition(position->folderPosition());
|
|
setFolder(position->folder());
|
|
setLocation(position->location());
|
|
}
|
|
|
|
DelegateDragPosition::Location DelegateDragPosition::location() const
|
|
{
|
|
return m_location;
|
|
}
|
|
|
|
void DelegateDragPosition::setLocation(Location location)
|
|
{
|
|
if (m_location != location) {
|
|
m_location = location;
|
|
Q_EMIT locationChanged();
|
|
}
|
|
}
|
|
|
|
int DelegateDragPosition::page() const
|
|
{
|
|
return m_page;
|
|
}
|
|
|
|
void DelegateDragPosition::setPage(int page)
|
|
{
|
|
if (m_page != page) {
|
|
m_page = page;
|
|
Q_EMIT pageChanged();
|
|
}
|
|
}
|
|
|
|
int DelegateDragPosition::pageRow() const
|
|
{
|
|
return m_pageRow;
|
|
}
|
|
|
|
void DelegateDragPosition::setPageRow(int pageRow)
|
|
{
|
|
if (m_pageRow != pageRow) {
|
|
m_pageRow = pageRow;
|
|
Q_EMIT pageRowChanged();
|
|
}
|
|
}
|
|
|
|
int DelegateDragPosition::pageColumn() const
|
|
{
|
|
return m_pageColumn;
|
|
}
|
|
|
|
void DelegateDragPosition::setPageColumn(int pageColumn)
|
|
{
|
|
if (m_pageColumn != pageColumn) {
|
|
m_pageColumn = pageColumn;
|
|
Q_EMIT pageColumnChanged();
|
|
}
|
|
}
|
|
|
|
int DelegateDragPosition::favouritesPosition() const
|
|
{
|
|
return m_favouritesPosition;
|
|
}
|
|
|
|
void DelegateDragPosition::setFavouritesPosition(int favouritesPosition)
|
|
{
|
|
if (m_favouritesPosition != favouritesPosition) {
|
|
m_favouritesPosition = favouritesPosition;
|
|
Q_EMIT favouritesPositionChanged();
|
|
}
|
|
}
|
|
|
|
int DelegateDragPosition::folderPosition() const
|
|
{
|
|
return m_folderPosition;
|
|
}
|
|
|
|
void DelegateDragPosition::setFolderPosition(int folderPosition)
|
|
{
|
|
if (m_folderPosition != folderPosition) {
|
|
m_folderPosition = folderPosition;
|
|
Q_EMIT folderPositionChanged();
|
|
}
|
|
}
|
|
|
|
FolioApplicationFolder::Ptr DelegateDragPosition::folder() const
|
|
{
|
|
return m_folder;
|
|
}
|
|
|
|
FolioApplicationFolder *DelegateDragPosition::folderRaw() const
|
|
{
|
|
return m_folder.get();
|
|
}
|
|
|
|
void DelegateDragPosition::setFolder(FolioApplicationFolder::Ptr folder)
|
|
{
|
|
if (m_folder != folder) {
|
|
m_folder = folder;
|
|
Q_EMIT folderChanged();
|
|
}
|
|
}
|
|
|
|
DragState::DragState(HomeScreenState *state, HomeScreen *parent)
|
|
: QObject{parent}
|
|
, m_homeScreen{parent}
|
|
, m_state{state}
|
|
, m_changePageTimer{new QTimer{this}}
|
|
, m_openFolderTimer{new QTimer{this}}
|
|
, m_leaveFolderTimer{new QTimer{this}}
|
|
, m_changeFolderPageTimer{new QTimer{this}}
|
|
, m_folderInsertBetweenTimer{new QTimer{this}}
|
|
, m_favouritesInsertBetweenTimer{new QTimer{this}}
|
|
, m_candidateDropPosition{new DelegateDragPosition{this}}
|
|
, m_startPosition{new DelegateDragPosition{this}}
|
|
{
|
|
if (!state) {
|
|
return;
|
|
}
|
|
|
|
// 500 ms hold before page timer changes
|
|
m_changePageTimer->setInterval(500);
|
|
m_changePageTimer->setSingleShot(true);
|
|
|
|
m_openFolderTimer->setInterval(1000);
|
|
m_openFolderTimer->setSingleShot(true);
|
|
|
|
m_leaveFolderTimer->setInterval(500);
|
|
m_leaveFolderTimer->setSingleShot(true);
|
|
|
|
m_changeFolderPageTimer->setInterval(500);
|
|
m_changeFolderPageTimer->setSingleShot(true);
|
|
|
|
m_folderInsertBetweenTimer->setInterval(250);
|
|
m_folderInsertBetweenTimer->setSingleShot(true);
|
|
|
|
m_favouritesInsertBetweenTimer->setInterval(250);
|
|
m_favouritesInsertBetweenTimer->setSingleShot(true);
|
|
|
|
connect(m_changePageTimer, &QTimer::timeout, this, &DragState::onChangePageTimerFinished);
|
|
connect(m_openFolderTimer, &QTimer::timeout, this, &DragState::onOpenFolderTimerFinished);
|
|
connect(m_leaveFolderTimer, &QTimer::timeout, this, &DragState::onLeaveFolderTimerFinished);
|
|
connect(m_changeFolderPageTimer, &QTimer::timeout, this, &DragState::onChangeFolderPageTimerFinished);
|
|
connect(m_folderInsertBetweenTimer, &QTimer::timeout, this, &DragState::onFolderInsertBetweenTimerFinished);
|
|
connect(m_favouritesInsertBetweenTimer, &QTimer::timeout, this, &DragState::onFavouritesInsertBetweenTimerFinished);
|
|
|
|
connect(m_state, &HomeScreenState::delegateDragFromPageStarted, this, &DragState::onDelegateDragFromPageStarted);
|
|
connect(m_state, &HomeScreenState::delegateDragFromAppDrawerStarted, this, &DragState::onDelegateDragFromAppDrawerStarted);
|
|
connect(m_state, &HomeScreenState::delegateDragFromFavouritesStarted, this, &DragState::onDelegateDragFromFavouritesStarted);
|
|
connect(m_state, &HomeScreenState::delegateDragFromFolderStarted, this, &DragState::onDelegateDragFromFolderStarted);
|
|
connect(m_state, &HomeScreenState::delegateDragFromWidgetListStarted, this, &DragState::onDelegateDragFromWidgetListStarted);
|
|
connect(m_state, &HomeScreenState::delegateDragStarted, this, &DragState::onDelegateDraggingStarted);
|
|
connect(m_state, &HomeScreenState::delegateDragDropped, this, &DragState::onDelegateDropped);
|
|
connect(m_state, &HomeScreenState::delegateDragCancelled, this, &DragState::onDelegateDraggingCancelled);
|
|
|
|
connect(m_state, &HomeScreenState::pageNumChanged, this, [this]() {
|
|
m_candidateDropPosition->setPageRow(m_state->currentPage());
|
|
});
|
|
|
|
connect(m_state, &HomeScreenState::delegateDragXChanged, this, &DragState::onDelegateDragPositionChanged);
|
|
connect(m_state, &HomeScreenState::delegateDragYChanged, this, &DragState::onDelegateDragPositionChanged);
|
|
|
|
connect(m_state, &HomeScreenState::leftCurrentFolder, this, &DragState::onLeaveCurrentFolder);
|
|
}
|
|
|
|
DelegateDragPosition *DragState::candidateDropPosition() const
|
|
{
|
|
return m_candidateDropPosition;
|
|
}
|
|
|
|
DelegateDragPosition *DragState::startPosition() const
|
|
{
|
|
return m_startPosition;
|
|
}
|
|
|
|
FolioDelegate::Ptr DragState::dropDelegate() const
|
|
{
|
|
return m_dropDelegate;
|
|
}
|
|
|
|
FolioDelegate *DragState::dropDelegateRaw() const
|
|
{
|
|
return m_dropDelegate.get();
|
|
}
|
|
|
|
void DragState::setDropDelegate(FolioDelegate::Ptr dropDelegate)
|
|
{
|
|
m_dropDelegate = dropDelegate;
|
|
Q_EMIT dropDelegateChanged();
|
|
}
|
|
|
|
void DragState::onDelegateDragPositionChanged()
|
|
{
|
|
if (!m_state) {
|
|
return;
|
|
}
|
|
|
|
// we want to update the candidate drop position variable in this function!
|
|
|
|
qreal x = getPointerX();
|
|
qreal y = getPointerY();
|
|
|
|
bool inFolder = m_state->viewState() == HomeScreenState::FolderView;
|
|
bool inFavouritesArea = !inFolder;
|
|
|
|
// the favourites bar can be in different locations, so account for each
|
|
switch (m_state->favouritesBarLocation()) {
|
|
case HomeScreenState::Bottom:
|
|
inFavouritesArea = inFavouritesArea && y > m_state->pageHeight();
|
|
break;
|
|
case HomeScreenState::Left:
|
|
inFavouritesArea = inFavouritesArea && x < m_state->viewWidth() - m_state->pageHeight();
|
|
break;
|
|
case HomeScreenState::Right:
|
|
inFavouritesArea = inFavouritesArea && x > m_state->pageWidth();
|
|
break;
|
|
}
|
|
|
|
// stop the favourites insertion timer if the delegate has moved out
|
|
if (!inFavouritesArea) {
|
|
m_favouritesInsertBetweenTimer->stop();
|
|
|
|
// clear any ghost entries in the favourites model
|
|
m_homeScreen->favouritesModel()->deleteGhostEntry();
|
|
}
|
|
|
|
if (inFavouritesArea || inFolder) {
|
|
m_openFolderTimer->stop();
|
|
}
|
|
|
|
if (m_state->viewState() == HomeScreenState::FolderView) {
|
|
// if we are in a folder
|
|
onDelegateDragPositionOverFolderViewChanged();
|
|
|
|
} else if (inFavouritesArea) {
|
|
// we are in the favourites bar area
|
|
onDelegateDragPositionOverFavouritesChanged();
|
|
} else {
|
|
// we are in the homescreen pages area
|
|
onDelegateDragPositionOverPageViewChanged();
|
|
}
|
|
}
|
|
|
|
void DragState::onDelegateDragPositionOverFolderViewChanged()
|
|
{
|
|
// if the drag position changes while in the folder view
|
|
qreal x = getPointerX();
|
|
qreal y = getPointerY();
|
|
|
|
FolioApplicationFolder::Ptr folder = m_state->currentFolder();
|
|
if (!folder) {
|
|
return;
|
|
}
|
|
|
|
// if the drop position is not in the folder, but outside (going to page view)
|
|
if (folder->isDropPositionOutside(x, y)) {
|
|
if (!m_leaveFolderTimer->isActive()) {
|
|
m_leaveFolderTimer->start();
|
|
}
|
|
return;
|
|
} else if (m_leaveFolderTimer->isActive()) {
|
|
// cancel timer if we are back in the folder
|
|
m_leaveFolderTimer->stop();
|
|
}
|
|
|
|
// the potential folder index that can be dropped at
|
|
int dropIndex = folder->dropInsertPosition(m_state->currentFolderPage(), x, y);
|
|
|
|
// if the delegate has moved to another position, cancel the insert timer
|
|
if (dropIndex != m_folderInsertBetweenIndex) {
|
|
m_folderInsertBetweenTimer->stop();
|
|
}
|
|
|
|
// start the insertion timer (so that the user has time to move the delegate away)
|
|
if (!m_folderInsertBetweenTimer->isActive()) {
|
|
m_folderInsertBetweenTimer->start();
|
|
m_folderInsertBetweenIndex = dropIndex;
|
|
}
|
|
|
|
const qreal leftPagePosition = folder->applications()->leftMarginFromScreenEdge();
|
|
const qreal rightPagePosition = m_state->viewWidth() - leftPagePosition;
|
|
|
|
// determine if the delegate is near the edge of a page (to switch pages).
|
|
// -> start the change page timer if we at the page edge.
|
|
if (x <= leftPagePosition + PAGE_CHANGE_THRESHOLD || x >= rightPagePosition - PAGE_CHANGE_THRESHOLD) {
|
|
if (!m_changeFolderPageTimer->isActive()) {
|
|
m_changeFolderPageTimer->start();
|
|
}
|
|
} else {
|
|
if (m_changeFolderPageTimer->isActive()) {
|
|
m_changeFolderPageTimer->stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
void DragState::onDelegateDragPositionOverFavouritesChanged()
|
|
{
|
|
// the drag position changed while over the favourites strip
|
|
|
|
qreal x = getPointerX();
|
|
qreal y = getPointerY();
|
|
FavouritesModel *favouritesModel = m_homeScreen->favouritesModel();
|
|
int dropIndex = favouritesModel->dropInsertPosition(x, y);
|
|
|
|
// if the drop position changed, cancel the open folder timer
|
|
if (m_candidateDropPosition->location() != DelegateDragPosition::Favourites || m_candidateDropPosition->favouritesPosition() != dropIndex) {
|
|
if (m_openFolderTimer->isActive()) {
|
|
m_openFolderTimer->stop();
|
|
}
|
|
}
|
|
|
|
// if the delegate has moved to another position, cancel the insert timer
|
|
if (dropIndex != m_favouritesInsertBetweenIndex) {
|
|
m_favouritesInsertBetweenTimer->stop();
|
|
}
|
|
|
|
// ignore this event if the favourites area is full already
|
|
if (favouritesModel->isFull()) {
|
|
return;
|
|
}
|
|
|
|
// ignore widget drop delegates (since they can't be placed in the favourites)
|
|
if (m_dropDelegate && m_dropDelegate->type() == FolioDelegate::Widget) {
|
|
return;
|
|
}
|
|
|
|
if (favouritesModel->dropPositionIsEdge(x, y)) {
|
|
// if we need to make space for the delegate
|
|
|
|
// start the insertion timer (so that the user has time to move the delegate away)
|
|
if (!m_favouritesInsertBetweenTimer->isActive()) {
|
|
m_favouritesInsertBetweenTimer->start();
|
|
m_favouritesInsertBetweenIndex = dropIndex;
|
|
}
|
|
} else {
|
|
// if we are hovering over the center of a folder or app
|
|
|
|
// delete ghost entry if there is one
|
|
int ghostEntryPosition = favouritesModel->getGhostEntryPosition();
|
|
if (ghostEntryPosition != -1 && ghostEntryPosition != dropIndex) {
|
|
if (dropIndex > ghostEntryPosition) {
|
|
// correct index if deleting the ghost will change the index
|
|
dropIndex--;
|
|
}
|
|
favouritesModel->deleteGhostEntry();
|
|
}
|
|
|
|
// update the current drop position
|
|
m_candidateDropPosition->setFavouritesPosition(dropIndex);
|
|
m_candidateDropPosition->setLocation(DelegateDragPosition::Favourites);
|
|
|
|
// start folder open timer if hovering over a folder
|
|
// get delegate being hovered over
|
|
FolioDelegate::Ptr delegate = favouritesModel->getEntryAt(dropIndex);
|
|
|
|
// check delegate is a folder and the drop delegate is an app
|
|
if (delegate && delegate->type() == FolioDelegate::Folder && m_dropDelegate && m_dropDelegate->type() == FolioDelegate::Application) {
|
|
if (!m_openFolderTimer->isActive()) {
|
|
m_openFolderTimer->start();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DragState::onDelegateDragPositionOverPageViewChanged()
|
|
{
|
|
// the drag position changed while over the homescreen pages strip
|
|
|
|
qreal delegateX = getDraggedDelegateX();
|
|
qreal delegateY = getDraggedDelegateY();
|
|
qreal x = getPointerX();
|
|
qreal y = getPointerY();
|
|
int page = m_state->currentPage();
|
|
|
|
// calculate the row and column the delegate is over
|
|
qreal pageHorizontalMargin = (m_state->pageWidth() - m_state->pageContentWidth()) / 2;
|
|
qreal pageVerticalMargin = (m_state->pageHeight() - m_state->pageContentHeight()) / 2;
|
|
|
|
int row = 0;
|
|
int column = 0;
|
|
|
|
if (m_dropDelegate && m_dropDelegate->type() == FolioDelegate::Widget) {
|
|
// for widgets, we use their top left position to determine where they are placed (since they are larger than one cell)
|
|
row = (delegateY - pageVerticalMargin - m_state->viewTopPadding()) / m_state->pageCellHeight();
|
|
column = (delegateX - pageHorizontalMargin - m_state->viewLeftPadding()) / m_state->pageCellWidth();
|
|
} else {
|
|
// otherwise, we base it on the pointer position
|
|
row = (y - pageVerticalMargin - m_state->viewTopPadding()) / m_state->pageCellHeight();
|
|
column = (x - pageHorizontalMargin - m_state->viewLeftPadding()) / m_state->pageCellWidth();
|
|
}
|
|
|
|
// ensure it's in bounds
|
|
row = std::max(0, std::min(m_state->pageRows() - 1, row));
|
|
column = std::max(0, std::min(m_state->pageColumns() - 1, column));
|
|
|
|
// if the drop position changed, cancel the open folder timer
|
|
if (m_candidateDropPosition->location() != DelegateDragPosition::Pages || m_candidateDropPosition->pageRow() != row
|
|
|| m_candidateDropPosition->pageColumn() != column) {
|
|
if (m_openFolderTimer->isActive()) {
|
|
m_openFolderTimer->stop();
|
|
}
|
|
}
|
|
|
|
// update the current drop position
|
|
m_candidateDropPosition->setPage(page);
|
|
m_candidateDropPosition->setPageRow(row);
|
|
m_candidateDropPosition->setPageColumn(column);
|
|
m_candidateDropPosition->setLocation(DelegateDragPosition::Pages);
|
|
|
|
// start folder open timer if hovering over a folder
|
|
PageModel *pageModel = m_homeScreen->pageListModel()->getPage(page);
|
|
if (pageModel) {
|
|
// get delegate being hovered over
|
|
FolioDelegate::Ptr delegate = pageModel->getDelegate(row, column);
|
|
|
|
// check delegate is a folder and the drop delegate is an app
|
|
if (delegate && delegate->type() == FolioDelegate::Folder && m_dropDelegate && m_dropDelegate->type() == FolioDelegate::Application) {
|
|
if (!m_openFolderTimer->isActive()) {
|
|
m_openFolderTimer->start();
|
|
}
|
|
}
|
|
}
|
|
|
|
const int leftPagePosition = 0;
|
|
const int rightPagePosition = m_state->pageWidth();
|
|
|
|
// determine if the delegate is near the edge of a page (to switch pages).
|
|
// -> start the change page timer if we at the page edge.
|
|
if (qAbs(leftPagePosition - x) <= PAGE_CHANGE_THRESHOLD || qAbs(rightPagePosition - x) <= PAGE_CHANGE_THRESHOLD) {
|
|
if (!m_changePageTimer->isActive()) {
|
|
m_changePageTimer->start();
|
|
}
|
|
} else {
|
|
if (m_changePageTimer->isActive()) {
|
|
m_changePageTimer->stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
void DragState::onDelegateDraggingStarted()
|
|
{
|
|
// remove the delegate from the model
|
|
// NOTE: we only delete here (and not from the event trigger, ex. onDelegateDragFromPageStarted)
|
|
// because the actual dragging only started when this is called
|
|
deleteStartPositionDelegate();
|
|
}
|
|
|
|
void DragState::onDelegateDragFromPageStarted(int page, int row, int column)
|
|
{
|
|
// fetch delegate at start position
|
|
PageModel *pageModel = m_homeScreen->pageListModel()->getPage(page);
|
|
if (pageModel) {
|
|
setDropDelegate(pageModel->getDelegate(row, column));
|
|
} else {
|
|
setDropDelegate(nullptr);
|
|
}
|
|
|
|
// set start location
|
|
m_startPosition->setPage(page);
|
|
m_startPosition->setPageRow(row);
|
|
m_startPosition->setPageColumn(column);
|
|
m_startPosition->setLocation(DelegateDragPosition::Pages);
|
|
}
|
|
|
|
void DragState::onDelegateDragFromFavouritesStarted(int position)
|
|
{
|
|
// fetch delegate at start position
|
|
setDropDelegate(m_homeScreen->favouritesModel()->getEntryAt(position));
|
|
|
|
// set start location
|
|
m_startPosition->setFavouritesPosition(position);
|
|
m_startPosition->setLocation(DelegateDragPosition::Favourites);
|
|
}
|
|
|
|
void DragState::onDelegateDragFromAppDrawerStarted(QString storageId)
|
|
{
|
|
// fetch delegate at start position
|
|
if (KService::Ptr service = KService::serviceByStorageId(storageId)) {
|
|
FolioApplication::Ptr app = std::make_shared<FolioApplication>(service);
|
|
setDropDelegate(std::make_shared<FolioDelegate>(app));
|
|
} else {
|
|
setDropDelegate(nullptr);
|
|
}
|
|
|
|
// set start location
|
|
m_startPosition->setLocation(DelegateDragPosition::AppDrawer);
|
|
}
|
|
|
|
void DragState::onDelegateDragFromFolderStarted(FolioApplicationFolder *folder, int position)
|
|
{
|
|
// fetch delegate at start position
|
|
setDropDelegate(folder->applications()->getDelegate(position));
|
|
|
|
// set start location
|
|
m_startPosition->setFolder(folder->shared_from_this());
|
|
m_startPosition->setFolderPosition(position);
|
|
m_startPosition->setLocation(DelegateDragPosition::Folder);
|
|
}
|
|
|
|
void DragState::onDelegateDragFromWidgetListStarted(QString appletPluginId)
|
|
{
|
|
// default widget has dimensions of 1x1, and id of -1
|
|
m_createdAppletPluginId = appletPluginId;
|
|
FolioWidget::Ptr widget = std::make_shared<FolioWidget>(m_homeScreen, -1, 1, 1);
|
|
setDropDelegate(std::make_shared<FolioDelegate>(widget));
|
|
|
|
// set start location
|
|
m_startPosition->setLocation(DelegateDragPosition::WidgetList);
|
|
}
|
|
|
|
void DragState::onDelegateDropped()
|
|
{
|
|
if (!m_dropDelegate) {
|
|
return;
|
|
}
|
|
|
|
// add dropped delegate
|
|
bool success = createDropPositionDelegate();
|
|
|
|
// Cleanup timers and state
|
|
dragStopCleanup();
|
|
|
|
// emit corresponding signal
|
|
// -> if we couldn't drop a new delegate at a spot, emit newDelegateDropAbandoned()
|
|
// -> otherwise, emit delegateDroppedAndPlaced()
|
|
if (!success && (m_startPosition->location() == DelegateDragPosition::WidgetList || m_startPosition->location() == DelegateDragPosition::AppDrawer)) {
|
|
Q_EMIT newDelegateDropAbandoned();
|
|
} else {
|
|
Q_EMIT delegateDroppedAndPlaced();
|
|
}
|
|
}
|
|
|
|
void DragState::onDelegateDraggingCancelled()
|
|
{
|
|
if (!m_dropDelegate) {
|
|
return;
|
|
}
|
|
|
|
// Cleanup timers and state
|
|
dragStopCleanup();
|
|
|
|
if (m_startPosition->location() == DelegateDragPosition::WidgetList || m_startPosition->location() == DelegateDragPosition::AppDrawer) {
|
|
// If this is a new delegate, it's abandoned
|
|
Q_EMIT newDelegateDropAbandoned();
|
|
} else {
|
|
// If it's an existing delegate, it simply goes back to its original position
|
|
Q_EMIT delegateDroppedAndPlaced();
|
|
}
|
|
}
|
|
|
|
void DragState::dragStopCleanup()
|
|
{
|
|
// Delete empty pages at the end if they exist
|
|
// (it can be created if user drags app to new page, but doesn't place it there)
|
|
m_homeScreen->pageListModel()->deleteEmptyPagesAtEnd();
|
|
|
|
// Clear ghost position if there is one
|
|
m_homeScreen->favouritesModel()->deleteGhostEntry();
|
|
|
|
// Reset timers
|
|
m_folderInsertBetweenTimer->stop();
|
|
m_changeFolderPageTimer->stop();
|
|
m_leaveFolderTimer->stop();
|
|
m_changePageTimer->stop();
|
|
m_favouritesInsertBetweenTimer->stop();
|
|
}
|
|
|
|
void DragState::onLeaveCurrentFolder()
|
|
{
|
|
if (!m_state) {
|
|
return;
|
|
}
|
|
|
|
// reset timers
|
|
m_folderInsertBetweenTimer->stop();
|
|
m_changeFolderPageTimer->stop();
|
|
m_leaveFolderTimer->stop();
|
|
|
|
if (m_candidateDropPosition->location() == DelegateDragPosition::Folder && m_candidateDropPosition->folder()) {
|
|
// clear ghost entry
|
|
m_candidateDropPosition->folder()->applications()->deleteGhostEntry();
|
|
}
|
|
}
|
|
|
|
void DragState::onChangePageTimerFinished()
|
|
{
|
|
if (!m_state || !m_state->isDraggingDelegate()) {
|
|
return;
|
|
}
|
|
|
|
const int leftPagePosition = 0;
|
|
const int rightPagePosition = m_state->pageWidth();
|
|
|
|
PageListModel *pageListModel = m_homeScreen->pageListModel();
|
|
|
|
qreal x = getPointerX();
|
|
if (qAbs(leftPagePosition - x) <= PAGE_CHANGE_THRESHOLD) {
|
|
// if we are at the left edge, go left
|
|
int page = m_state->currentPage() - 1;
|
|
if (page >= 0) {
|
|
m_state->goToPage(page, false);
|
|
}
|
|
|
|
} else if (qAbs(rightPagePosition - x) <= PAGE_CHANGE_THRESHOLD) {
|
|
// if we are at the right edge, go right
|
|
int page = m_state->currentPage() + 1;
|
|
|
|
// if we are at the right-most page, try to create a new one if the current page isn't empty
|
|
if (page == pageListModel->rowCount() && !pageListModel->isLastPageEmpty()) {
|
|
pageListModel->addPageAtEnd();
|
|
}
|
|
|
|
// go to page if it exists
|
|
if (page < pageListModel->rowCount()) {
|
|
m_state->goToPage(page, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DragState::onOpenFolderTimerFinished()
|
|
{
|
|
if (!m_state || !m_state->isDraggingDelegate() || m_state->viewState() != HomeScreenState::PageView
|
|
|| (m_candidateDropPosition->location() != DelegateDragPosition::Pages && m_candidateDropPosition->location() != DelegateDragPosition::Favourites)) {
|
|
return;
|
|
}
|
|
|
|
FolioApplicationFolder::Ptr folder = nullptr;
|
|
QPointF screenPosition;
|
|
|
|
switch (m_candidateDropPosition->location()) {
|
|
case DelegateDragPosition::Pages: {
|
|
// get current page
|
|
PageModel *page = m_homeScreen->pageListModel()->getPage(m_candidateDropPosition->page());
|
|
if (!page) {
|
|
return;
|
|
}
|
|
|
|
// get delegate being hovered over
|
|
FolioDelegate::Ptr delegate = page->getDelegate(m_candidateDropPosition->pageRow(), m_candidateDropPosition->pageColumn());
|
|
if (!delegate || delegate->type() != FolioDelegate::Folder) {
|
|
return;
|
|
}
|
|
|
|
folder = delegate->folder();
|
|
screenPosition =
|
|
m_state->getPageDelegateScreenPosition(m_candidateDropPosition->page(), m_candidateDropPosition->pageRow(), m_candidateDropPosition->pageColumn());
|
|
break;
|
|
}
|
|
case DelegateDragPosition::Favourites: {
|
|
// get delegate being hovered over in favourites bar
|
|
FolioDelegate::Ptr delegate = m_homeScreen->favouritesModel()->getEntryAt(m_candidateDropPosition->favouritesPosition());
|
|
if (!delegate || delegate->type() != FolioDelegate::Folder) {
|
|
return;
|
|
}
|
|
|
|
folder = delegate->folder();
|
|
screenPosition = m_homeScreen->homeScreenState()->getFavouritesDelegateScreenPosition(m_candidateDropPosition->favouritesPosition());
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// open the folder
|
|
m_state->openFolder(screenPosition.x(), screenPosition.y(), folder.get());
|
|
}
|
|
|
|
void DragState::onLeaveFolderTimerFinished()
|
|
{
|
|
if (!m_state || !m_state->isDraggingDelegate() || !m_state->currentFolder()) {
|
|
return;
|
|
}
|
|
|
|
// check if the drag position is outside of the folder
|
|
if (m_state->currentFolder()->isDropPositionOutside(getPointerX(), getPointerY())) {
|
|
m_state->closeFolder();
|
|
}
|
|
}
|
|
|
|
void DragState::onChangeFolderPageTimerFinished()
|
|
{
|
|
if (!m_state || !m_state->isDraggingDelegate() || !m_state->currentFolder()) {
|
|
return;
|
|
}
|
|
|
|
FolioApplicationFolder::Ptr folder = m_state->currentFolder();
|
|
qreal x = getPointerX();
|
|
qreal y = getPointerY();
|
|
|
|
// check if the drag position is outside of the folder
|
|
if (folder->isDropPositionOutside(x, y)) {
|
|
return;
|
|
}
|
|
|
|
const qreal leftPagePosition = folder->applications()->leftMarginFromScreenEdge();
|
|
const qreal rightPagePosition = m_state->viewWidth() - leftPagePosition;
|
|
|
|
if (x <= leftPagePosition + PAGE_CHANGE_THRESHOLD) {
|
|
// if we are at the left edge, go left
|
|
int page = m_state->currentFolderPage() - 1;
|
|
if (page >= 0) {
|
|
m_state->goToFolderPage(page, false);
|
|
}
|
|
|
|
} else if (x >= rightPagePosition - PAGE_CHANGE_THRESHOLD) {
|
|
// if we are at the right edge, go right
|
|
int page = m_state->currentFolderPage() + 1;
|
|
|
|
// go to page if it exists
|
|
if (page < folder->applications()->numTotalPages()) {
|
|
m_state->goToFolderPage(page, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DragState::onFolderInsertBetweenTimerFinished()
|
|
{
|
|
if (!m_state || !m_state->isDraggingDelegate() || !m_state->currentFolder()) {
|
|
return;
|
|
}
|
|
|
|
FolioApplicationFolder::Ptr folder = m_state->currentFolder();
|
|
|
|
// update the candidate drop position
|
|
m_candidateDropPosition->setFolder(folder);
|
|
m_candidateDropPosition->setFolderPosition(m_folderInsertBetweenIndex);
|
|
m_candidateDropPosition->setLocation(DelegateDragPosition::Folder);
|
|
|
|
// insert it at this position, shifting existing apps to the side
|
|
// TODO the ghost entry may shift the m_folderInsertBetweenIndex, perhaps we should update??
|
|
folder->applications()->setGhostEntry(m_folderInsertBetweenIndex);
|
|
}
|
|
|
|
void DragState::onFavouritesInsertBetweenTimerFinished()
|
|
{
|
|
// update the candidate drop position
|
|
m_candidateDropPosition->setFavouritesPosition(m_favouritesInsertBetweenIndex);
|
|
m_candidateDropPosition->setLocation(DelegateDragPosition::Favourites);
|
|
|
|
// insert it at this position, shifting existing apps to the side
|
|
m_homeScreen->favouritesModel()->setGhostEntry(m_favouritesInsertBetweenIndex);
|
|
}
|
|
|
|
void DragState::deleteStartPositionDelegate()
|
|
{
|
|
// delete the delegate at the start position
|
|
switch (m_startPosition->location()) {
|
|
case DelegateDragPosition::Pages: {
|
|
PageModel *page = m_homeScreen->pageListModel()->getPage(m_startPosition->page());
|
|
if (page) {
|
|
page->removeDelegate(m_startPosition->pageRow(), m_startPosition->pageColumn());
|
|
}
|
|
break;
|
|
}
|
|
case DelegateDragPosition::Favourites:
|
|
m_homeScreen->favouritesModel()->removeEntry(m_startPosition->favouritesPosition());
|
|
break;
|
|
case DelegateDragPosition::Folder:
|
|
m_startPosition->folder()->removeDelegate(m_startPosition->folderPosition());
|
|
break;
|
|
case DelegateDragPosition::AppDrawer:
|
|
case DelegateDragPosition::WidgetList:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool DragState::createDropPositionDelegate()
|
|
{
|
|
if (!m_dropDelegate) {
|
|
return false;
|
|
}
|
|
|
|
// whether the drop goes successfully
|
|
bool added = false;
|
|
|
|
// creates the delegate at the drop position
|
|
switch (m_candidateDropPosition->location()) {
|
|
case DelegateDragPosition::Pages: {
|
|
// locate the page we are dropping on
|
|
PageModel *page = m_homeScreen->pageListModel()->getPage(m_candidateDropPosition->page());
|
|
if (!page) {
|
|
break;
|
|
}
|
|
|
|
int row = m_candidateDropPosition->pageRow();
|
|
int column = m_candidateDropPosition->pageColumn();
|
|
|
|
// delegate to add
|
|
FolioPageDelegate::Ptr delegate = std::make_shared<FolioPageDelegate>(row, column, m_dropDelegate, m_homeScreen);
|
|
|
|
// delegate that exists at the drop position
|
|
FolioPageDelegate::Ptr existingDelegate = page->getDelegate(row, column);
|
|
|
|
// if a delegate already exists at the spot, check if we can insert/create a folder
|
|
if (existingDelegate) {
|
|
if (delegate->type() == FolioDelegate::Application) {
|
|
if (existingDelegate->type() == FolioDelegate::Folder) {
|
|
// add the app to the existing folder
|
|
|
|
auto existingFolder = existingDelegate->folder();
|
|
existingFolder->addDelegate(delegate, existingFolder->applications()->rowCount());
|
|
|
|
added = true;
|
|
break;
|
|
} else if (existingDelegate->type() == FolioDelegate::Application && !isStartPositionEqualDropPosition()) {
|
|
// create a folder from the two apps
|
|
|
|
FolioApplicationFolder::Ptr folder = std::make_shared<FolioApplicationFolder>(m_homeScreen, DEFAULT_FOLDER_NAME);
|
|
folder->addDelegate(delegate, 0);
|
|
folder->addDelegate(existingDelegate, 0);
|
|
FolioPageDelegate::Ptr folderDelegate = std::make_shared<FolioPageDelegate>(row, column, folder, m_homeScreen);
|
|
|
|
page->removeDelegate(row, column);
|
|
page->addDelegate(folderDelegate);
|
|
|
|
added = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// default behavior for widgets, folders or dropping an app at an empty spot
|
|
|
|
added = page->addDelegate(delegate);
|
|
|
|
// if we couldn't add the delegate, try again but at the start position (return to start)
|
|
if (!added && !isStartPositionEqualDropPosition()) {
|
|
m_candidateDropPosition->copyFrom(m_startPosition);
|
|
added = createDropPositionDelegate();
|
|
}
|
|
break;
|
|
}
|
|
case DelegateDragPosition::Favourites: {
|
|
// delegate that exists at the drop position
|
|
FolioDelegate::Ptr existingDelegate = m_homeScreen->favouritesModel()->getEntryAt(m_candidateDropPosition->favouritesPosition());
|
|
|
|
// if a delegate already exists at the spot, check if we can insert/create a folder
|
|
if (existingDelegate) {
|
|
if (m_dropDelegate->type() == FolioDelegate::Application) {
|
|
if (existingDelegate->type() == FolioDelegate::Folder) {
|
|
// add the app to the existing folder
|
|
|
|
auto existingFolder = existingDelegate->folder();
|
|
existingFolder->addDelegate(m_dropDelegate, existingFolder->applications()->rowCount());
|
|
|
|
added = true;
|
|
break;
|
|
} else if (existingDelegate->type() == FolioDelegate::Application && !isStartPositionEqualDropPosition()) {
|
|
// create a folder from the two apps
|
|
|
|
FolioApplicationFolder::Ptr folder = std::make_shared<FolioApplicationFolder>(m_homeScreen, DEFAULT_FOLDER_NAME);
|
|
folder->addDelegate(m_dropDelegate, 0);
|
|
folder->addDelegate(existingDelegate, 0);
|
|
FolioDelegate::Ptr folderDelegate = std::make_shared<FolioDelegate>(folder);
|
|
|
|
m_homeScreen->favouritesModel()->removeEntry(m_candidateDropPosition->favouritesPosition());
|
|
m_homeScreen->favouritesModel()->addEntry(m_candidateDropPosition->favouritesPosition(), folderDelegate);
|
|
|
|
added = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// otherwise, just add the delegate at this position
|
|
|
|
added = m_homeScreen->favouritesModel()->addEntry(m_candidateDropPosition->favouritesPosition(), m_dropDelegate);
|
|
|
|
// if we couldn't add the delegate, try again but at the start position
|
|
if (!added && !isStartPositionEqualDropPosition()) {
|
|
m_candidateDropPosition->copyFrom(m_startPosition);
|
|
added = createDropPositionDelegate();
|
|
}
|
|
|
|
// correct position when we delete from an entry earlier in the favourites
|
|
if (added) {
|
|
if (m_startPosition->location() == DelegateDragPosition::Favourites
|
|
&& m_startPosition->favouritesPosition() > m_candidateDropPosition->favouritesPosition()) {
|
|
m_startPosition->setFavouritesPosition(m_startPosition->favouritesPosition() - 1);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case DelegateDragPosition::Folder: {
|
|
FolioApplicationFolder::Ptr folder = m_candidateDropPosition->folder();
|
|
if (!folder) {
|
|
break;
|
|
}
|
|
|
|
// only support dropping apps into folders
|
|
if (m_dropDelegate->type() != FolioDelegate::Application) {
|
|
break;
|
|
}
|
|
|
|
added = folder->addDelegate(m_dropDelegate, m_candidateDropPosition->folderPosition());
|
|
|
|
// if we couldn't add the delegate, try again but at the start position
|
|
if (!added && !isStartPositionEqualDropPosition()) {
|
|
m_candidateDropPosition->copyFrom(m_startPosition);
|
|
added = createDropPositionDelegate();
|
|
}
|
|
|
|
if (added) {
|
|
folder->applications()->deleteGhostEntry();
|
|
}
|
|
break;
|
|
}
|
|
case DelegateDragPosition::AppDrawer:
|
|
case DelegateDragPosition::WidgetList:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// if we are dropping a new widget, we need to now create the applet in the containment
|
|
if (added && m_startPosition->location() == DelegateDragPosition::WidgetList && m_dropDelegate->type() == FolioDelegate::Widget && m_homeScreen) {
|
|
Plasma::Applet *applet = m_homeScreen->createApplet(m_createdAppletPluginId);
|
|
|
|
// associate the new delegate with the Plasma::Applet
|
|
m_dropDelegate->widget()->setApplet(applet);
|
|
}
|
|
|
|
return added;
|
|
}
|
|
|
|
bool DragState::isStartPositionEqualDropPosition()
|
|
{
|
|
return m_startPosition->location() == m_candidateDropPosition->location() && m_startPosition->page() == m_candidateDropPosition->page()
|
|
&& m_startPosition->pageRow() == m_candidateDropPosition->pageRow() && m_startPosition->pageColumn() == m_candidateDropPosition->pageColumn()
|
|
&& m_startPosition->favouritesPosition() == m_candidateDropPosition->favouritesPosition()
|
|
&& m_startPosition->folder() == m_candidateDropPosition->folder() && m_startPosition->folderPosition() == m_candidateDropPosition->folderPosition();
|
|
}
|
|
|
|
qreal DragState::getDraggedDelegateX()
|
|
{
|
|
// adjust to get the position of the center of the delegate
|
|
return m_state->delegateDragX() + m_state->pageCellWidth() / 2;
|
|
}
|
|
|
|
qreal DragState::getDraggedDelegateY()
|
|
{
|
|
// adjust to get the position of the center of the delegate
|
|
return m_state->delegateDragY() + m_state->pageCellHeight() / 2;
|
|
}
|
|
|
|
qreal DragState::getPointerX()
|
|
{
|
|
return m_state->delegateDragX() + m_state->delegateDragPointerOffsetX();
|
|
}
|
|
|
|
qreal DragState::getPointerY()
|
|
{
|
|
return m_state->delegateDragY() + m_state->delegateDragPointerOffsetY();
|
|
}
|