File indexing completed on 2024-12-29 05:06:03

0001 // SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
0002 // SPDX-License-Identifier: GPL-2.0-or-later
0003 
0004 #include "dragstate.h"
0005 #include "favouritesmodel.h"
0006 #include "pagelistmodel.h"
0007 
0008 #include <KLocalizedString>
0009 #include <algorithm>
0010 
0011 // TODO don't hardcode, use page widths
0012 const int PAGE_CHANGE_THRESHOLD = 30;
0013 
0014 const QString DEFAULT_FOLDER_NAME = i18n("Folder");
0015 
0016 DelegateDragPosition::DelegateDragPosition(QObject *parent)
0017     : QObject{parent}
0018 {
0019 }
0020 
0021 DelegateDragPosition::~DelegateDragPosition() = default;
0022 
0023 void DelegateDragPosition::copyFrom(DelegateDragPosition *position)
0024 {
0025     setPage(position->page());
0026     setPageRow(position->pageRow());
0027     setPageColumn(position->pageColumn());
0028     setFavouritesPosition(position->favouritesPosition());
0029     setFolderPosition(position->folderPosition());
0030     setFolder(position->folder());
0031     setLocation(position->location());
0032 }
0033 
0034 DelegateDragPosition::Location DelegateDragPosition::location() const
0035 {
0036     return m_location;
0037 }
0038 
0039 void DelegateDragPosition::setLocation(Location location)
0040 {
0041     if (m_location != location) {
0042         m_location = location;
0043         Q_EMIT locationChanged();
0044     }
0045 }
0046 
0047 int DelegateDragPosition::page() const
0048 {
0049     return m_page;
0050 }
0051 
0052 void DelegateDragPosition::setPage(int page)
0053 {
0054     if (m_page != page) {
0055         m_page = page;
0056         Q_EMIT pageChanged();
0057     }
0058 }
0059 
0060 int DelegateDragPosition::pageRow() const
0061 {
0062     return m_pageRow;
0063 }
0064 
0065 void DelegateDragPosition::setPageRow(int pageRow)
0066 {
0067     if (m_pageRow != pageRow) {
0068         m_pageRow = pageRow;
0069         Q_EMIT pageRowChanged();
0070     }
0071 }
0072 
0073 int DelegateDragPosition::pageColumn() const
0074 {
0075     return m_pageColumn;
0076 }
0077 
0078 void DelegateDragPosition::setPageColumn(int pageColumn)
0079 {
0080     if (m_pageColumn != pageColumn) {
0081         m_pageColumn = pageColumn;
0082         Q_EMIT pageColumnChanged();
0083     }
0084 }
0085 
0086 int DelegateDragPosition::favouritesPosition() const
0087 {
0088     return m_favouritesPosition;
0089 }
0090 
0091 void DelegateDragPosition::setFavouritesPosition(int favouritesPosition)
0092 {
0093     if (m_favouritesPosition != favouritesPosition) {
0094         m_favouritesPosition = favouritesPosition;
0095         Q_EMIT favouritesPositionChanged();
0096     }
0097 }
0098 
0099 int DelegateDragPosition::folderPosition() const
0100 {
0101     return m_folderPosition;
0102 }
0103 
0104 void DelegateDragPosition::setFolderPosition(int folderPosition)
0105 {
0106     if (m_folderPosition != folderPosition) {
0107         m_folderPosition = folderPosition;
0108         Q_EMIT folderPositionChanged();
0109     }
0110 }
0111 
0112 FolioApplicationFolder *DelegateDragPosition::folder() const
0113 {
0114     return m_folder;
0115 }
0116 
0117 void DelegateDragPosition::setFolder(FolioApplicationFolder *folder)
0118 {
0119     if (m_folder != folder) {
0120         m_folder = folder;
0121         Q_EMIT folderChanged();
0122     }
0123 }
0124 
0125 DragState::DragState(HomeScreenState *state, QObject *parent)
0126     : QObject{parent}
0127     , m_changePageTimer{new QTimer{this}}
0128     , m_openFolderTimer{new QTimer{this}}
0129     , m_leaveFolderTimer{new QTimer{this}}
0130     , m_changeFolderPageTimer{new QTimer{this}}
0131     , m_folderInsertBetweenTimer{new QTimer{this}}
0132     , m_favouritesInsertBetweenTimer{new QTimer{this}}
0133     , m_candidateDropPosition{new DelegateDragPosition{this}}
0134     , m_startPosition{new DelegateDragPosition{this}}
0135     , m_state{state}
0136 {
0137     if (!state) {
0138         return;
0139     }
0140 
0141     // 500 ms hold before page timer changes
0142     m_changePageTimer->setInterval(500);
0143     m_changePageTimer->setSingleShot(true);
0144 
0145     m_openFolderTimer->setInterval(1000);
0146     m_openFolderTimer->setSingleShot(true);
0147 
0148     m_leaveFolderTimer->setInterval(500);
0149     m_leaveFolderTimer->setSingleShot(true);
0150 
0151     m_changeFolderPageTimer->setInterval(500);
0152     m_changeFolderPageTimer->setSingleShot(true);
0153 
0154     m_folderInsertBetweenTimer->setInterval(250);
0155     m_folderInsertBetweenTimer->setSingleShot(true);
0156 
0157     m_favouritesInsertBetweenTimer->setInterval(250);
0158     m_favouritesInsertBetweenTimer->setSingleShot(true);
0159 
0160     connect(m_changePageTimer, &QTimer::timeout, this, &DragState::onChangePageTimerFinished);
0161     connect(m_openFolderTimer, &QTimer::timeout, this, &DragState::onOpenFolderTimerFinished);
0162     connect(m_leaveFolderTimer, &QTimer::timeout, this, &DragState::onLeaveFolderTimerFinished);
0163     connect(m_changeFolderPageTimer, &QTimer::timeout, this, &DragState::onChangeFolderPageTimerFinished);
0164     connect(m_folderInsertBetweenTimer, &QTimer::timeout, this, &DragState::onFolderInsertBetweenTimerFinished);
0165     connect(m_favouritesInsertBetweenTimer, &QTimer::timeout, this, &DragState::onFavouritesInsertBetweenTimerFinished);
0166 
0167     connect(m_state, &HomeScreenState::delegateDragFromPageStarted, this, &DragState::onDelegateDragFromPageStarted);
0168     connect(m_state, &HomeScreenState::delegateDragFromAppDrawerStarted, this, &DragState::onDelegateDragFromAppDrawerStarted);
0169     connect(m_state, &HomeScreenState::delegateDragFromFavouritesStarted, this, &DragState::onDelegateDragFromFavouritesStarted);
0170     connect(m_state, &HomeScreenState::delegateDragFromFolderStarted, this, &DragState::onDelegateDragFromFolderStarted);
0171     connect(m_state, &HomeScreenState::delegateDragFromWidgetListStarted, this, &DragState::onDelegateDragFromWidgetListStarted);
0172     connect(m_state, &HomeScreenState::swipeStateChanged, this, [this]() {
0173         if (HomeScreenState::self()->swipeState() == HomeScreenState::DraggingDelegate) {
0174             onDelegateDraggingStarted();
0175         }
0176     });
0177     connect(m_state, &HomeScreenState::delegateDragEnded, this, &DragState::onDelegateDropped);
0178 
0179     connect(m_state, &HomeScreenState::pageNumChanged, this, [this]() {
0180         m_candidateDropPosition->setPageRow(m_state->currentPage());
0181     });
0182 
0183     connect(m_state, &HomeScreenState::delegateDragXChanged, this, &DragState::onDelegateDragPositionChanged);
0184     connect(m_state, &HomeScreenState::delegateDragYChanged, this, &DragState::onDelegateDragPositionChanged);
0185 
0186     connect(m_state, &HomeScreenState::leftCurrentFolder, this, &DragState::onLeaveCurrentFolder);
0187 }
0188 
0189 DelegateDragPosition *DragState::candidateDropPosition() const
0190 {
0191     return m_candidateDropPosition;
0192 }
0193 
0194 DelegateDragPosition *DragState::startPosition() const
0195 {
0196     return m_startPosition;
0197 }
0198 
0199 FolioDelegate *DragState::dropDelegate() const
0200 {
0201     return m_dropDelegate;
0202 }
0203 
0204 void DragState::setDropDelegate(FolioDelegate *dropDelegate)
0205 {
0206     m_dropDelegate = dropDelegate;
0207     Q_EMIT dropDelegateChanged();
0208 }
0209 
0210 void DragState::onDelegateDragPositionChanged()
0211 {
0212     if (!m_state) {
0213         return;
0214     }
0215 
0216     // we want to update the candidate drop position variable in this function!
0217 
0218     qreal x = getPointerX();
0219     qreal y = getPointerY();
0220 
0221     bool inFolder = m_state->viewState() == HomeScreenState::FolderView;
0222     bool inFavouritesArea = !inFolder;
0223 
0224     // the favourites bar can be in different locations, so account for each
0225     switch (m_state->favouritesBarLocation()) {
0226     case HomeScreenState::Bottom:
0227         inFavouritesArea = inFavouritesArea && y > m_state->pageHeight();
0228         break;
0229     case HomeScreenState::Left:
0230         inFavouritesArea = inFavouritesArea && x < m_state->viewWidth() - m_state->pageHeight();
0231         break;
0232     case HomeScreenState::Right:
0233         inFavouritesArea = inFavouritesArea && x > m_state->pageWidth();
0234         break;
0235     }
0236 
0237     // stop the favourites insertion timer if the delegate has moved out
0238     if (!inFavouritesArea) {
0239         m_favouritesInsertBetweenTimer->stop();
0240     }
0241 
0242     if (inFavouritesArea || inFolder) {
0243         m_openFolderTimer->stop();
0244     }
0245 
0246     if (m_state->viewState() == HomeScreenState::FolderView) {
0247         // if we are in a folder
0248         onDelegateDragPositionOverFolderViewChanged();
0249 
0250     } else if (inFavouritesArea) {
0251         // we are in the favourites bar area
0252         onDelegateDragPositionOverFavouritesChanged();
0253     } else {
0254         // we are in the homescreen pages area
0255         onDelegateDragPositionOverPageViewChanged();
0256     }
0257 }
0258 
0259 void DragState::onDelegateDragPositionOverFolderViewChanged()
0260 {
0261     // if the drag position changes while in the folder view
0262     qreal x = getPointerX();
0263     qreal y = getPointerY();
0264 
0265     auto *folder = m_state->currentFolder();
0266     if (!folder) {
0267         return;
0268     }
0269 
0270     // if the drop position is not in the folder, but outside (going to page view)
0271     if (folder->isDropPositionOutside(x, y)) {
0272         if (!m_leaveFolderTimer->isActive()) {
0273             m_leaveFolderTimer->start();
0274         }
0275         return;
0276     } else if (m_leaveFolderTimer->isActive()) {
0277         // cancel timer if we are back in the folder
0278         m_leaveFolderTimer->stop();
0279     }
0280 
0281     // the potential folder index that can be dropped at
0282     int dropIndex = folder->dropInsertPosition(m_state->currentFolderPage(), x, y);
0283 
0284     // if the delegate has moved to another position, cancel the insert timer
0285     if (dropIndex != m_folderInsertBetweenIndex) {
0286         m_folderInsertBetweenTimer->stop();
0287     }
0288 
0289     // start the insertion timer (so that the user has time to move the delegate away)
0290     if (!m_folderInsertBetweenTimer->isActive()) {
0291         m_folderInsertBetweenTimer->start();
0292         m_folderInsertBetweenIndex = dropIndex;
0293     }
0294 
0295     const qreal leftPagePosition = folder->applications()->leftMarginFromScreenEdge();
0296     const qreal rightPagePosition = m_state->viewWidth() - leftPagePosition;
0297 
0298     // determine if the delegate is near the edge of a page (to switch pages).
0299     // -> start the change page timer if we at the page edge.
0300     if (x <= leftPagePosition + PAGE_CHANGE_THRESHOLD || x >= rightPagePosition - PAGE_CHANGE_THRESHOLD) {
0301         if (!m_changeFolderPageTimer->isActive()) {
0302             m_changeFolderPageTimer->start();
0303         }
0304     } else {
0305         if (m_changeFolderPageTimer->isActive()) {
0306             m_changeFolderPageTimer->stop();
0307         }
0308     }
0309 }
0310 
0311 void DragState::onDelegateDragPositionOverFavouritesChanged()
0312 {
0313     // the drag position changed while over the favourites strip
0314 
0315     qreal x = getPointerX();
0316     qreal y = getPointerY();
0317     int dropIndex = FavouritesModel::self()->dropInsertPosition(x, y);
0318 
0319     // if the drop position changed, cancel the open folder timer
0320     if (m_candidateDropPosition->location() != DelegateDragPosition::Favourites || m_candidateDropPosition->favouritesPosition() != dropIndex) {
0321         if (m_openFolderTimer->isActive()) {
0322             m_openFolderTimer->stop();
0323         }
0324     }
0325 
0326     // if the delegate has moved to another position, cancel the insert timer
0327     if (dropIndex != m_favouritesInsertBetweenIndex) {
0328         m_favouritesInsertBetweenTimer->stop();
0329     }
0330 
0331     // ignore widget drop delegates (since they can't be placed in the favourites)
0332     if (m_dropDelegate && m_dropDelegate->type() == FolioDelegate::Widget) {
0333         return;
0334     }
0335 
0336     if (FavouritesModel::self()->dropPositionIsEdge(x, y)) {
0337         // if we need to make space for the delegate
0338 
0339         // start the insertion timer (so that the user has time to move the delegate away)
0340         if (!m_favouritesInsertBetweenTimer->isActive()) {
0341             m_favouritesInsertBetweenTimer->start();
0342             m_favouritesInsertBetweenIndex = dropIndex;
0343         }
0344     } else {
0345         // if we are hovering over the center of a folder or app
0346 
0347         // delete ghost entry if there is one
0348         int ghostEntryPosition = FavouritesModel::self()->getGhostEntryPosition();
0349         if (ghostEntryPosition != -1 && ghostEntryPosition != dropIndex) {
0350             if (dropIndex > ghostEntryPosition) {
0351                 // correct index if deleting the ghost will change the index
0352                 dropIndex--;
0353             }
0354             FavouritesModel::self()->deleteGhostEntry();
0355         }
0356 
0357         // update the current drop position
0358         m_candidateDropPosition->setFavouritesPosition(dropIndex);
0359         m_candidateDropPosition->setLocation(DelegateDragPosition::Favourites);
0360 
0361         // start folder open timer if hovering over a folder
0362         // get delegate being hovered over
0363         FolioDelegate *delegate = FavouritesModel::self()->getEntryAt(dropIndex);
0364 
0365         // check delegate is a folder and the drop delegate is an app
0366         if (delegate && delegate->type() == FolioDelegate::Folder && m_dropDelegate && m_dropDelegate->type() == FolioDelegate::Application) {
0367             if (!m_openFolderTimer->isActive()) {
0368                 m_openFolderTimer->start();
0369             }
0370         }
0371     }
0372 }
0373 
0374 void DragState::onDelegateDragPositionOverPageViewChanged()
0375 {
0376     // the drag position changed while over the homescreen pages strip
0377 
0378     qreal delegateX = getDraggedDelegateX();
0379     qreal delegateY = getDraggedDelegateY();
0380     qreal x = getPointerX();
0381     qreal y = getPointerY();
0382     int page = m_state->currentPage();
0383 
0384     // calculate the row and column the delegate is over
0385     qreal pageHorizontalMargin = (m_state->pageWidth() - m_state->pageContentWidth()) / 2;
0386     qreal pageVerticalMargin = (m_state->pageHeight() - m_state->pageContentHeight()) / 2;
0387 
0388     int row = 0;
0389     int column = 0;
0390 
0391     if (m_dropDelegate && m_dropDelegate->type() == FolioDelegate::Widget) {
0392         // for widgets, we use their top left position to determine where they are placed (since they are larger than one cell)
0393         row = (delegateY - pageVerticalMargin) / m_state->pageCellHeight();
0394         column = (delegateX - pageHorizontalMargin) / m_state->pageCellWidth();
0395     } else {
0396         // otherwise, we base it on the pointer position
0397         row = (y - pageVerticalMargin) / m_state->pageCellHeight();
0398         column = (x - pageHorizontalMargin) / m_state->pageCellWidth();
0399     }
0400 
0401     // ensure it's in bounds
0402     row = std::max(0, std::min(m_state->pageRows() - 1, row));
0403     column = std::max(0, std::min(m_state->pageColumns() - 1, column));
0404 
0405     // if the drop position changed, cancel the open folder timer
0406     if (m_candidateDropPosition->location() != DelegateDragPosition::Pages || m_candidateDropPosition->pageRow() != row
0407         || m_candidateDropPosition->pageColumn() != column) {
0408         if (m_openFolderTimer->isActive()) {
0409             m_openFolderTimer->stop();
0410         }
0411     }
0412 
0413     // update the current drop position
0414     m_candidateDropPosition->setPage(page);
0415     m_candidateDropPosition->setPageRow(row);
0416     m_candidateDropPosition->setPageColumn(column);
0417     m_candidateDropPosition->setLocation(DelegateDragPosition::Pages);
0418 
0419     // start folder open timer if hovering over a folder
0420     PageModel *pageModel = PageListModel::self()->getPage(page);
0421     if (pageModel) {
0422         // get delegate being hovered over
0423         FolioDelegate *delegate = pageModel->getDelegate(row, column);
0424 
0425         // check delegate is a folder and the drop delegate is an app
0426         if (delegate && delegate->type() == FolioDelegate::Folder && m_dropDelegate && m_dropDelegate->type() == FolioDelegate::Application) {
0427             if (!m_openFolderTimer->isActive()) {
0428                 m_openFolderTimer->start();
0429             }
0430         }
0431     }
0432 
0433     const int leftPagePosition = 0;
0434     const int rightPagePosition = m_state->pageWidth();
0435 
0436     // determine if the delegate is near the edge of a page (to switch pages).
0437     // -> start the change page timer if we at the page edge.
0438     if (qAbs(leftPagePosition - x) <= PAGE_CHANGE_THRESHOLD || qAbs(rightPagePosition - x) <= PAGE_CHANGE_THRESHOLD) {
0439         if (!m_changePageTimer->isActive()) {
0440             m_changePageTimer->start();
0441         }
0442     } else {
0443         if (m_changePageTimer->isActive()) {
0444             m_changePageTimer->stop();
0445         }
0446     }
0447 }
0448 
0449 void DragState::onDelegateDraggingStarted()
0450 {
0451     // remove the delegate from the model
0452     // NOTE: we only delete here (and not from the event trigger, ex. onDelegateDragFromPageStarted)
0453     //       because the actual dragging only started when this is called
0454     deleteStartPositionDelegate();
0455 }
0456 
0457 void DragState::onDelegateDragFromPageStarted(int page, int row, int column)
0458 {
0459     // fetch delegate at start position
0460     PageModel *pageModel = PageListModel::self()->getPage(page);
0461     if (pageModel) {
0462         setDropDelegate(pageModel->getDelegate(row, column));
0463     } else {
0464         setDropDelegate(nullptr);
0465     }
0466 
0467     // set start location
0468     m_startPosition->setPage(page);
0469     m_startPosition->setPageRow(row);
0470     m_startPosition->setPageColumn(column);
0471     m_startPosition->setLocation(DelegateDragPosition::Pages);
0472 }
0473 
0474 void DragState::onDelegateDragFromFavouritesStarted(int position)
0475 {
0476     // fetch delegate at start position
0477     setDropDelegate(FavouritesModel::self()->getEntryAt(position));
0478 
0479     // set start location
0480     m_startPosition->setFavouritesPosition(position);
0481     m_startPosition->setLocation(DelegateDragPosition::Favourites);
0482 }
0483 
0484 void DragState::onDelegateDragFromAppDrawerStarted(QString storageId)
0485 {
0486     // fetch delegate at start position
0487     if (KService::Ptr service = KService::serviceByStorageId(storageId)) {
0488         FolioApplication *app = new FolioApplication{this, service};
0489         setDropDelegate(new FolioDelegate{app, this});
0490     } else {
0491         setDropDelegate(nullptr);
0492     }
0493 
0494     // set start location
0495     m_startPosition->setLocation(DelegateDragPosition::AppDrawer);
0496 }
0497 
0498 void DragState::onDelegateDragFromFolderStarted(FolioApplicationFolder *folder, int position)
0499 {
0500     // fetch delegate at start position
0501     setDropDelegate(folder->applications()->getDelegate(position));
0502 
0503     // set start location
0504     m_startPosition->setFolder(folder);
0505     m_startPosition->setFolderPosition(position);
0506     m_startPosition->setLocation(DelegateDragPosition::Folder);
0507 }
0508 
0509 void DragState::onDelegateDragFromWidgetListStarted(QString appletPluginId)
0510 {
0511     // default widget has dimensions of 1x1, and id of -1
0512     m_createdAppletPluginId = appletPluginId;
0513     FolioWidget *widget = new FolioWidget{this, -1, 1, 1};
0514     setDropDelegate(new FolioDelegate{widget, this});
0515 
0516     // set start location
0517     m_startPosition->setLocation(DelegateDragPosition::WidgetList);
0518 }
0519 
0520 void DragState::onDelegateDropped()
0521 {
0522     if (!m_dropDelegate) {
0523         return;
0524     }
0525 
0526     // add dropped delegate
0527     bool success = createDropPositionDelegate();
0528 
0529     // delete empty pages at the end if they exist
0530     // (it can be created if user drags app to new page, but doesn't place it there)
0531     while (PageListModel::self()->isLastPageEmpty() && PageListModel::self()->rowCount() > 1) {
0532         PageListModel::self()->removePage(PageListModel::self()->rowCount() - 1);
0533     }
0534 
0535     // clear ghost position if there is one
0536     FavouritesModel::self()->deleteGhostEntry();
0537 
0538     // reset timers
0539     m_folderInsertBetweenTimer->stop();
0540     m_changeFolderPageTimer->stop();
0541     m_leaveFolderTimer->stop();
0542     m_changePageTimer->stop();
0543     m_favouritesInsertBetweenTimer->stop();
0544 
0545     // emit corresponding signal
0546     // -> if we couldn't drop a new delegate at a spot, emit newDelegateDropAbandoned()
0547     // -> otherwise, emit delegateDroppedAndPlaced()
0548     if (!success && (m_startPosition->location() == DelegateDragPosition::WidgetList || m_startPosition->location() == DelegateDragPosition::AppDrawer)) {
0549         Q_EMIT newDelegateDropAbandoned();
0550     } else {
0551         Q_EMIT delegateDroppedAndPlaced();
0552     }
0553 }
0554 
0555 void DragState::onLeaveCurrentFolder()
0556 {
0557     if (!m_state) {
0558         return;
0559     }
0560 
0561     // reset timers
0562     m_folderInsertBetweenTimer->stop();
0563     m_changeFolderPageTimer->stop();
0564     m_leaveFolderTimer->stop();
0565 
0566     if (m_candidateDropPosition->location() == DelegateDragPosition::Folder && m_candidateDropPosition->folder()) {
0567         // clear ghost entry
0568         m_candidateDropPosition->folder()->applications()->deleteGhostEntry();
0569     }
0570 }
0571 
0572 void DragState::onChangePageTimerFinished()
0573 {
0574     if (!m_state || (m_state->swipeState() != HomeScreenState::DraggingDelegate)) {
0575         return;
0576     }
0577 
0578     const int leftPagePosition = 0;
0579     const int rightPagePosition = m_state->pageWidth();
0580 
0581     qreal x = getPointerX();
0582     if (qAbs(leftPagePosition - x) <= PAGE_CHANGE_THRESHOLD) {
0583         // if we are at the left edge, go left
0584         int page = m_state->currentPage() - 1;
0585         if (page >= 0) {
0586             m_state->goToPage(page);
0587         }
0588 
0589     } else if (qAbs(rightPagePosition - x) <= PAGE_CHANGE_THRESHOLD) {
0590         // if we are at the right edge, go right
0591         int page = m_state->currentPage() + 1;
0592 
0593         // if we are at the right-most page, try to create a new one if the current page isn't empty
0594         if (page == PageListModel::self()->rowCount() && !PageListModel::self()->isLastPageEmpty()) {
0595             PageListModel::self()->addPageAtEnd();
0596         }
0597 
0598         // go to page if it exists
0599         if (page < PageListModel::self()->rowCount()) {
0600             m_state->goToPage(page);
0601         }
0602     }
0603 }
0604 
0605 void DragState::onOpenFolderTimerFinished()
0606 {
0607     if (!m_state || m_state->swipeState() != HomeScreenState::DraggingDelegate || m_state->viewState() != HomeScreenState::PageView
0608         || (m_candidateDropPosition->location() != DelegateDragPosition::Pages && m_candidateDropPosition->location() != DelegateDragPosition::Favourites)) {
0609         return;
0610     }
0611 
0612     FolioApplicationFolder *folder = nullptr;
0613     QPointF screenPosition;
0614 
0615     switch (m_candidateDropPosition->location()) {
0616     case DelegateDragPosition::Pages: {
0617         // get current page
0618         PageModel *page = PageListModel::self()->getPage(m_candidateDropPosition->page());
0619         if (!page) {
0620             return;
0621         }
0622 
0623         // get delegate being hovered over
0624         FolioDelegate *delegate = page->getDelegate(m_candidateDropPosition->pageRow(), m_candidateDropPosition->pageColumn());
0625         if (!delegate || delegate->type() != FolioDelegate::Folder) {
0626             return;
0627         }
0628 
0629         folder = delegate->folder();
0630         screenPosition = HomeScreenState::self()->getPageDelegateScreenPosition(m_candidateDropPosition->page(),
0631                                                                                 m_candidateDropPosition->pageRow(),
0632                                                                                 m_candidateDropPosition->pageColumn());
0633         break;
0634     }
0635     case DelegateDragPosition::Favourites: {
0636         // get delegate being hovered over in favourites bar
0637         FolioDelegate *delegate = FavouritesModel::self()->getEntryAt(m_candidateDropPosition->favouritesPosition());
0638         if (!delegate || delegate->type() != FolioDelegate::Folder) {
0639             return;
0640         }
0641 
0642         folder = delegate->folder();
0643         screenPosition = HomeScreenState::self()->getFavouritesDelegateScreenPosition(m_candidateDropPosition->favouritesPosition());
0644         break;
0645     }
0646     default:
0647         break;
0648     }
0649 
0650     // open the folder
0651     m_state->openFolder(screenPosition.x(), screenPosition.y(), folder);
0652 }
0653 
0654 void DragState::onLeaveFolderTimerFinished()
0655 {
0656     if (!m_state || (m_state->swipeState() != HomeScreenState::DraggingDelegate) || !m_state->currentFolder()) {
0657         return;
0658     }
0659 
0660     // check if the drag position is outside of the folder
0661     if (m_state->currentFolder()->isDropPositionOutside(getPointerX(), getPointerY())) {
0662         m_state->closeFolder();
0663     }
0664 }
0665 
0666 void DragState::onChangeFolderPageTimerFinished()
0667 {
0668     if (!m_state || (m_state->swipeState() != HomeScreenState::DraggingDelegate) || !m_state->currentFolder()) {
0669         return;
0670     }
0671 
0672     auto *folder = m_state->currentFolder();
0673     qreal x = getPointerX();
0674     qreal y = getPointerY();
0675 
0676     // check if the drag position is outside of the folder
0677     if (folder->isDropPositionOutside(x, y)) {
0678         return;
0679     }
0680 
0681     const qreal leftPagePosition = folder->applications()->leftMarginFromScreenEdge();
0682     const qreal rightPagePosition = m_state->viewWidth() - leftPagePosition;
0683 
0684     if (x <= leftPagePosition + PAGE_CHANGE_THRESHOLD) {
0685         // if we are at the left edge, go left
0686         int page = m_state->currentFolderPage() - 1;
0687         if (page >= 0) {
0688             m_state->goToFolderPage(page);
0689         }
0690 
0691     } else if (x >= rightPagePosition - PAGE_CHANGE_THRESHOLD) {
0692         // if we are at the right edge, go right
0693         int page = m_state->currentFolderPage() + 1;
0694 
0695         // go to page if it exists
0696         if (page < folder->applications()->numTotalPages()) {
0697             m_state->goToFolderPage(page);
0698         }
0699     }
0700 }
0701 
0702 void DragState::onFolderInsertBetweenTimerFinished()
0703 {
0704     if (!m_state || (m_state->swipeState() != HomeScreenState::DraggingDelegate) || !m_state->currentFolder()) {
0705         return;
0706     }
0707 
0708     auto *folder = m_state->currentFolder();
0709 
0710     // update the candidate drop position
0711     m_candidateDropPosition->setFolder(folder);
0712     m_candidateDropPosition->setFolderPosition(m_folderInsertBetweenIndex);
0713     m_candidateDropPosition->setLocation(DelegateDragPosition::Folder);
0714 
0715     // insert it at this position, shifting existing apps to the side
0716     // TODO the ghost entry may shift the m_folderInsertBetweenIndex, perhaps we should update??
0717     folder->applications()->setGhostEntry(m_folderInsertBetweenIndex);
0718 }
0719 
0720 void DragState::onFavouritesInsertBetweenTimerFinished()
0721 {
0722     // update the candidate drop position
0723     m_candidateDropPosition->setFavouritesPosition(m_favouritesInsertBetweenIndex);
0724     m_candidateDropPosition->setLocation(DelegateDragPosition::Favourites);
0725 
0726     // insert it at this position, shifting existing apps to the side
0727     FavouritesModel::self()->setGhostEntry(m_favouritesInsertBetweenIndex);
0728 }
0729 
0730 void DragState::deleteStartPositionDelegate()
0731 {
0732     // delete the delegate at the start position
0733     switch (m_startPosition->location()) {
0734     case DelegateDragPosition::Pages: {
0735         PageModel *page = PageListModel::self()->getPage(m_startPosition->page());
0736         if (page) {
0737             page->removeDelegate(m_startPosition->pageRow(), m_startPosition->pageColumn());
0738         }
0739         break;
0740     }
0741     case DelegateDragPosition::Favourites:
0742         FavouritesModel::self()->removeEntry(m_startPosition->favouritesPosition());
0743         break;
0744     case DelegateDragPosition::Folder:
0745         m_startPosition->folder()->removeDelegate(m_startPosition->folderPosition());
0746         break;
0747     case DelegateDragPosition::AppDrawer:
0748     case DelegateDragPosition::WidgetList:
0749     default:
0750         break;
0751     }
0752 }
0753 
0754 bool DragState::createDropPositionDelegate()
0755 {
0756     if (!m_dropDelegate) {
0757         return false;
0758     }
0759 
0760     // whether the drop goes successfully
0761     bool added = false;
0762 
0763     // creates the delegate at the drop position
0764     switch (m_candidateDropPosition->location()) {
0765     case DelegateDragPosition::Pages: {
0766         // locate the page we are dropping on
0767         PageModel *page = PageListModel::self()->getPage(m_candidateDropPosition->page());
0768         if (!page) {
0769             break;
0770         }
0771 
0772         int row = m_candidateDropPosition->pageRow();
0773         int column = m_candidateDropPosition->pageColumn();
0774 
0775         // delegate to add
0776         FolioPageDelegate *delegate = new FolioPageDelegate{row, column, m_dropDelegate, page};
0777 
0778         // delegate that exists at the drop position
0779         FolioPageDelegate *existingDelegate = page->getDelegate(row, column);
0780 
0781         // if a delegate already exists at the spot, check if we can insert/create a folder
0782         if (existingDelegate) {
0783             if (delegate->type() == FolioDelegate::Application) {
0784                 if (existingDelegate->type() == FolioDelegate::Folder) {
0785                     // add the app to the existing folder
0786 
0787                     auto existingFolder = existingDelegate->folder();
0788                     existingFolder->addDelegate(delegate, existingFolder->applications()->rowCount());
0789 
0790                     added = true;
0791                     break;
0792                 } else if (existingDelegate->type() == FolioDelegate::Application && !isStartPositionEqualDropPosition()) {
0793                     // create a folder from the two apps
0794 
0795                     FolioApplicationFolder *folder = new FolioApplicationFolder(this, DEFAULT_FOLDER_NAME);
0796                     folder->addDelegate(delegate, 0);
0797                     folder->addDelegate(existingDelegate, 0);
0798                     FolioPageDelegate *folderDelegate = new FolioPageDelegate{row, column, folder, this};
0799 
0800                     page->removeDelegate(row, column);
0801                     page->addDelegate(folderDelegate);
0802 
0803                     added = true;
0804                     break;
0805                 }
0806             }
0807         }
0808 
0809         // default behavior for widgets, folders or dropping an app at an empty spot
0810 
0811         added = page->addDelegate(delegate);
0812 
0813         // if we couldn't add the delegate, try again but at the start position (return to start)
0814         if (!added && !isStartPositionEqualDropPosition()) {
0815             m_candidateDropPosition->copyFrom(m_startPosition);
0816             added = createDropPositionDelegate();
0817         }
0818         break;
0819     }
0820     case DelegateDragPosition::Favourites: {
0821         // delegate that exists at the drop position
0822         FolioDelegate *existingDelegate = FavouritesModel::self()->getEntryAt(m_candidateDropPosition->favouritesPosition());
0823 
0824         // if a delegate already exists at the spot, check if we can insert/create a folder
0825         if (existingDelegate) {
0826             if (m_dropDelegate->type() == FolioDelegate::Application) {
0827                 if (existingDelegate->type() == FolioDelegate::Folder) {
0828                     // add the app to the existing folder
0829 
0830                     auto existingFolder = existingDelegate->folder();
0831                     existingFolder->addDelegate(m_dropDelegate, existingFolder->applications()->rowCount());
0832 
0833                     added = true;
0834                     break;
0835                 } else if (existingDelegate->type() == FolioDelegate::Application && !isStartPositionEqualDropPosition()) {
0836                     // create a folder from the two apps
0837 
0838                     FolioApplicationFolder *folder = new FolioApplicationFolder(this, DEFAULT_FOLDER_NAME);
0839                     folder->addDelegate(m_dropDelegate, 0);
0840                     folder->addDelegate(existingDelegate, 0);
0841                     FolioDelegate *folderDelegate = new FolioDelegate{folder, this};
0842 
0843                     FavouritesModel::self()->removeEntry(m_candidateDropPosition->favouritesPosition());
0844                     FavouritesModel::self()->addEntry(m_candidateDropPosition->favouritesPosition(), folderDelegate);
0845 
0846                     added = true;
0847                     break;
0848                 }
0849             }
0850         }
0851 
0852         // otherwise, just add the delegate at this position
0853 
0854         added = FavouritesModel::self()->addEntry(m_candidateDropPosition->favouritesPosition(), m_dropDelegate);
0855 
0856         // if we couldn't add the delegate, try again but at the start position
0857         if (!added && !isStartPositionEqualDropPosition()) {
0858             m_candidateDropPosition->copyFrom(m_startPosition);
0859             added = createDropPositionDelegate();
0860         }
0861 
0862         // correct position when we delete from an entry earlier in the favourites
0863         if (added) {
0864             if (m_startPosition->location() == DelegateDragPosition::Favourites
0865                 && m_startPosition->favouritesPosition() > m_candidateDropPosition->favouritesPosition()) {
0866                 m_startPosition->setFavouritesPosition(m_startPosition->favouritesPosition() - 1);
0867             }
0868         }
0869         break;
0870     }
0871     case DelegateDragPosition::Folder: {
0872         auto *folder = m_candidateDropPosition->folder();
0873         if (!folder) {
0874             break;
0875         }
0876 
0877         // only support dropping apps into folders
0878         if (m_dropDelegate->type() != FolioDelegate::Application) {
0879             break;
0880         }
0881 
0882         added = folder->addDelegate(m_dropDelegate, m_candidateDropPosition->folderPosition());
0883 
0884         // if we couldn't add the delegate, try again but at the start position
0885         if (!added && !isStartPositionEqualDropPosition()) {
0886             m_candidateDropPosition->copyFrom(m_startPosition);
0887             added = createDropPositionDelegate();
0888         }
0889 
0890         if (added) {
0891             folder->applications()->deleteGhostEntry();
0892         }
0893         break;
0894     }
0895     case DelegateDragPosition::AppDrawer:
0896     case DelegateDragPosition::WidgetList:
0897     default:
0898         break;
0899     }
0900 
0901     // if we are dropping a new widget, we need to now create the applet in the containment
0902     if (added && m_startPosition->location() == DelegateDragPosition::WidgetList && m_dropDelegate->type() == FolioDelegate::Widget && m_state->containment()) {
0903         Plasma::Applet *applet = m_state->containment()->createApplet(m_createdAppletPluginId);
0904 
0905         // associate the new delegate with the Plasma::Applet
0906         m_dropDelegate->widget()->setApplet(applet);
0907     }
0908 
0909     return added;
0910 }
0911 
0912 bool DragState::isStartPositionEqualDropPosition()
0913 {
0914     return m_startPosition->location() == m_candidateDropPosition->location() && m_startPosition->page() == m_candidateDropPosition->page()
0915         && m_startPosition->pageRow() == m_candidateDropPosition->pageRow() && m_startPosition->pageColumn() == m_candidateDropPosition->pageColumn()
0916         && m_startPosition->favouritesPosition() == m_candidateDropPosition->favouritesPosition()
0917         && m_startPosition->folder() == m_candidateDropPosition->folder() && m_startPosition->folderPosition() == m_candidateDropPosition->folderPosition();
0918 }
0919 
0920 qreal DragState::getDraggedDelegateX()
0921 {
0922     // adjust to get the position of the center of the delegate
0923     return m_state->delegateDragX() + m_state->pageCellWidth() / 2;
0924 }
0925 
0926 qreal DragState::getDraggedDelegateY()
0927 {
0928     // adjust to get the position of the center of the delegate
0929     return m_state->delegateDragY() + m_state->pageCellHeight() / 2;
0930 }
0931 
0932 qreal DragState::getPointerX()
0933 {
0934     return m_state->delegateDragX() + m_state->delegateDragPointerOffsetX();
0935 }
0936 
0937 qreal DragState::getPointerY()
0938 {
0939     return m_state->delegateDragY() + m_state->delegateDragPointerOffsetY();
0940 }