// SPDX-FileCopyrightText: 2023 Devin Lin // SPDX-License-Identifier: GPL-2.0-or-later #include "dragstate.h" #include "favouritesmodel.h" #include "pagelistmodel.h" #include #include // 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 *DelegateDragPosition::folder() const { return m_folder; } void DelegateDragPosition::setFolder(FolioApplicationFolder *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::swipeStateChanged, this, [this]() { if (m_state->swipeState() == HomeScreenState::DraggingDelegate) { onDelegateDraggingStarted(); } }); connect(m_state, &HomeScreenState::delegateDragEnded, this, &DragState::onDelegateDropped); 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 *DragState::dropDelegate() const { return m_dropDelegate; } void DragState::setDropDelegate(FolioDelegate *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(); } 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(); auto *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 *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->pageCellHeight(); column = (delegateX - pageHorizontalMargin) / m_state->pageCellWidth(); } else { // otherwise, we base it on the pointer position row = (y - pageVerticalMargin) / m_state->pageCellHeight(); column = (x - pageHorizontalMargin) / 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 *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 *app = new FolioApplication{m_homeScreen, service}; setDropDelegate(new FolioDelegate{app, m_homeScreen}); } 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); 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 *widget = new FolioWidget{m_homeScreen, -1, 1, 1}; setDropDelegate(new FolioDelegate{widget, m_homeScreen}); // set start location m_startPosition->setLocation(DelegateDragPosition::WidgetList); } void DragState::onDelegateDropped() { if (!m_dropDelegate) { return; } // add dropped delegate bool success = createDropPositionDelegate(); // 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(); // 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::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->swipeState() != HomeScreenState::DraggingDelegate)) { 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); } } 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); } } } void DragState::onOpenFolderTimerFinished() { if (!m_state || m_state->swipeState() != HomeScreenState::DraggingDelegate || m_state->viewState() != HomeScreenState::PageView || (m_candidateDropPosition->location() != DelegateDragPosition::Pages && m_candidateDropPosition->location() != DelegateDragPosition::Favourites)) { return; } FolioApplicationFolder *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 *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 *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); } void DragState::onLeaveFolderTimerFinished() { if (!m_state || (m_state->swipeState() != HomeScreenState::DraggingDelegate) || !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->swipeState() != HomeScreenState::DraggingDelegate) || !m_state->currentFolder()) { return; } auto *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); } } 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); } } } void DragState::onFolderInsertBetweenTimerFinished() { if (!m_state || (m_state->swipeState() != HomeScreenState::DraggingDelegate) || !m_state->currentFolder()) { return; } auto *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 *delegate = new FolioPageDelegate{row, column, m_dropDelegate, m_homeScreen}; // delegate that exists at the drop position FolioPageDelegate *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 *folder = new FolioApplicationFolder(m_homeScreen, DEFAULT_FOLDER_NAME); folder->addDelegate(delegate, 0); folder->addDelegate(existingDelegate, 0); FolioPageDelegate *folderDelegate = new 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 *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 *folder = new FolioApplicationFolder(m_homeScreen, DEFAULT_FOLDER_NAME); folder->addDelegate(m_dropDelegate, 0); folder->addDelegate(existingDelegate, 0); FolioDelegate *folderDelegate = new FolioDelegate{folder, m_homeScreen}; 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: { auto *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(); }