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 }