File indexing completed on 2024-12-29 05:06:05
0001 // SPDX-FileCopyrightText: 2022-2023 Devin Lin <devin@kde.org> 0002 // SPDX-License-Identifier: GPL-2.0-or-later 0003 0004 #include "pagemodel.h" 0005 #include "foliosettings.h" 0006 #include "homescreenstate.h" 0007 #include "widgetsmanager.h" 0008 0009 FolioPageDelegate::FolioPageDelegate(int row, int column, QObject *parent) 0010 : FolioDelegate{parent} 0011 , m_row{row} 0012 , m_column{column} 0013 { 0014 init(); 0015 } 0016 0017 FolioPageDelegate::FolioPageDelegate(int row, int column, FolioApplication *application, QObject *parent) 0018 : FolioDelegate{application, parent} 0019 , m_row{row} 0020 , m_column{column} 0021 { 0022 init(); 0023 } 0024 0025 FolioPageDelegate::FolioPageDelegate(int row, int column, FolioApplicationFolder *folder, QObject *parent) 0026 : FolioDelegate{folder, parent} 0027 , m_row{row} 0028 , m_column{column} 0029 { 0030 init(); 0031 } 0032 0033 FolioPageDelegate::FolioPageDelegate(int row, int column, FolioWidget *widget, QObject *parent) 0034 : FolioDelegate{widget, parent} 0035 , m_row{row} 0036 , m_column{column} 0037 { 0038 init(); 0039 } 0040 0041 FolioPageDelegate::FolioPageDelegate(int row, int column, FolioDelegate *delegate, QObject *parent) 0042 : FolioDelegate{parent} 0043 , m_row{row} 0044 , m_column{column} 0045 { 0046 m_type = delegate->type(); 0047 m_application = delegate->application(); 0048 m_folder = delegate->folder(); 0049 m_widget = delegate->widget(); 0050 0051 init(); 0052 } 0053 0054 void FolioPageDelegate::init() 0055 { 0056 // we have to use the "real" rows and columns, so fetch them from FolioSettings instead of HomeScreenState 0057 switch (HomeScreenState::self()->pageOrientation()) { 0058 case HomeScreenState::RegularPosition: 0059 m_realRow = m_row; 0060 m_realColumn = m_column; 0061 break; 0062 case HomeScreenState::RotateClockwise: 0063 m_realRow = HomeScreenState::self()->pageColumns() - m_column - 1; 0064 m_realColumn = m_row; 0065 0066 if (m_widget) { 0067 // since top-left in cw is bottom-left in portrait 0068 m_realRow -= m_widget->realGridHeight() - 1; 0069 } 0070 0071 break; 0072 case HomeScreenState::RotateCounterClockwise: 0073 m_realRow = m_column; 0074 m_realColumn = HomeScreenState::self()->pageRows() - m_row - 1; 0075 0076 if (m_widget) { 0077 // since top-left in ccw is top-right in portrait 0078 m_realColumn -= m_widget->realGridWidth() - 1; 0079 } 0080 0081 break; 0082 case HomeScreenState::RotateUpsideDown: 0083 m_realRow = HomeScreenState::self()->pageRows() - m_row - 1; 0084 m_realColumn = HomeScreenState::self()->pageColumns() - m_column - 1; 0085 0086 if (m_widget) { 0087 // since top-left in upside-down is bottom-right in portrait 0088 m_realRow -= m_widget->realGridHeight() - 1; 0089 m_realColumn -= m_widget->realGridWidth() - 1; 0090 } 0091 0092 break; 0093 } 0094 0095 if (m_widget) { 0096 connect(m_widget, &FolioWidget::realTopLeftPositionChanged, this, [this](int rowOffset, int columnOffset) { 0097 m_realRow += rowOffset; 0098 m_realColumn += columnOffset; 0099 }); 0100 } 0101 0102 connect(HomeScreenState::self(), &HomeScreenState::pageOrientationChanged, this, [this]() { 0103 setRowOnly(getTranslatedTopLeftRow(m_realRow, m_realColumn, this)); 0104 setColumnOnly(getTranslatedTopLeftColumn(m_realRow, m_realColumn, this)); 0105 }); 0106 } 0107 0108 FolioPageDelegate *FolioPageDelegate::fromJson(QJsonObject &obj, QObject *parent) 0109 { 0110 FolioDelegate *fd = FolioDelegate::fromJson(obj, parent); 0111 0112 if (!fd) { 0113 return nullptr; 0114 } 0115 0116 int realRow = obj[QStringLiteral("row")].toInt(); 0117 int realColumn = obj[QStringLiteral("column")].toInt(); 0118 0119 int row = getTranslatedTopLeftRow(realRow, realColumn, fd); 0120 int column = getTranslatedTopLeftColumn(realRow, realColumn, fd); 0121 0122 FolioPageDelegate *delegate = new FolioPageDelegate{row, column, fd, parent}; 0123 fd->deleteLater(); 0124 0125 return delegate; 0126 } 0127 0128 int FolioPageDelegate::getTranslatedTopLeftRow(int realRow, int realColumn, FolioDelegate *fd) 0129 { 0130 int row = getTranslatedRow(realRow, realColumn); 0131 int column = getTranslatedColumn(realRow, realColumn); 0132 0133 // special logic to return "top left" for widgets, since they take more than one tile 0134 if (fd->type() == FolioDelegate::Widget) { 0135 return fd->widget()->topLeftCorner(row, column).row; 0136 } else { 0137 return row; 0138 } 0139 } 0140 0141 int FolioPageDelegate::getTranslatedTopLeftColumn(int realRow, int realColumn, FolioDelegate *fd) 0142 { 0143 int row = getTranslatedRow(realRow, realColumn); 0144 int column = getTranslatedColumn(realRow, realColumn); 0145 0146 // special logic to return "top left" for widgets, since they take more than one tile 0147 if (fd->type() == FolioDelegate::Widget) { 0148 return fd->widget()->topLeftCorner(row, column).column; 0149 } else { 0150 return column; 0151 } 0152 } 0153 0154 int FolioPageDelegate::getTranslatedRow(int realRow, int realColumn) 0155 { 0156 // we have to use the "real" rows and columns, so fetch them from FolioSettings instead of HomeScreenState 0157 switch (HomeScreenState::self()->pageOrientation()) { 0158 case HomeScreenState::RegularPosition: 0159 return realRow; 0160 case HomeScreenState::RotateClockwise: 0161 return realColumn; 0162 case HomeScreenState::RotateCounterClockwise: 0163 return FolioSettings::self()->homeScreenColumns() - realColumn - 1; 0164 case HomeScreenState::RotateUpsideDown: 0165 return FolioSettings::self()->homeScreenRows() - realRow - 1; 0166 } 0167 return realRow; 0168 } 0169 0170 int FolioPageDelegate::getTranslatedColumn(int realRow, int realColumn) 0171 { 0172 // we have to use the "real" rows and columns, so fetch them from FolioSettings instead of HomeScreenState 0173 switch (HomeScreenState::self()->pageOrientation()) { 0174 case HomeScreenState::RegularPosition: 0175 return realColumn; 0176 case HomeScreenState::RotateClockwise: 0177 return FolioSettings::self()->homeScreenRows() - realRow - 1; 0178 case HomeScreenState::RotateCounterClockwise: 0179 return realRow; 0180 case HomeScreenState::RotateUpsideDown: 0181 return FolioSettings::self()->homeScreenColumns() - realColumn - 1; 0182 } 0183 return realRow; 0184 } 0185 0186 QJsonObject FolioPageDelegate::toJson() const 0187 { 0188 QJsonObject o = FolioDelegate::toJson(); 0189 o[QStringLiteral("row")] = m_realRow; 0190 o[QStringLiteral("column")] = m_realColumn; 0191 return o; 0192 } 0193 0194 int FolioPageDelegate::row() 0195 { 0196 return m_row; 0197 } 0198 0199 void FolioPageDelegate::setRow(int row) 0200 { 0201 if (m_row != row) { 0202 // adjust stored data too 0203 switch (HomeScreenState::self()->pageOrientation()) { 0204 case HomeScreenState::RegularPosition: 0205 m_realRow = row; 0206 break; 0207 case HomeScreenState::RotateClockwise: 0208 m_realColumn += row - m_row; 0209 break; 0210 case HomeScreenState::RotateCounterClockwise: 0211 m_realColumn += m_row - row; 0212 break; 0213 case HomeScreenState::RotateUpsideDown: 0214 m_realRow += m_row - row; 0215 break; 0216 } 0217 0218 setRowOnly(row); 0219 } 0220 } 0221 0222 void FolioPageDelegate::setRowOnly(int row) 0223 { 0224 if (m_row != row) { 0225 m_row = row; 0226 Q_EMIT rowChanged(); 0227 } 0228 } 0229 0230 int FolioPageDelegate::column() 0231 { 0232 return m_column; 0233 } 0234 0235 void FolioPageDelegate::setColumn(int column) 0236 { 0237 if (m_column != column) { 0238 // adjust stored data too 0239 switch (HomeScreenState::self()->pageOrientation()) { 0240 case HomeScreenState::RegularPosition: 0241 m_realColumn = column; 0242 break; 0243 case HomeScreenState::RotateClockwise: 0244 m_realRow += m_column - column; 0245 break; 0246 case HomeScreenState::RotateCounterClockwise: 0247 m_realRow += column - m_column; 0248 break; 0249 case HomeScreenState::RotateUpsideDown: 0250 m_realColumn += m_column - column; 0251 break; 0252 } 0253 0254 setColumnOnly(column); 0255 } 0256 } 0257 0258 void FolioPageDelegate::setColumnOnly(int column) 0259 { 0260 if (m_column != column) { 0261 m_column = column; 0262 Q_EMIT columnChanged(); 0263 } 0264 } 0265 0266 PageModel::PageModel(QList<FolioPageDelegate *> delegates, QObject *parent) 0267 : QAbstractListModel{parent} 0268 , m_delegates{delegates} 0269 { 0270 connect(WidgetsManager::self(), &WidgetsManager::widgetRemoved, this, [this](Plasma::Applet *applet) { 0271 if (applet) { 0272 // delete any instance of this widget 0273 for (int i = 0; i < m_delegates.size(); i++) { 0274 auto *delegate = m_delegates[i]; 0275 if (delegate->type() == FolioDelegate::Widget && delegate->widget()->applet() == applet) { 0276 removeDelegate(i); 0277 break; 0278 } 0279 } 0280 } 0281 }); 0282 } 0283 0284 PageModel::~PageModel() = default; 0285 0286 PageModel *PageModel::fromJson(QJsonArray &arr, QObject *parent) 0287 { 0288 QList<FolioPageDelegate *> delegates; 0289 0290 for (QJsonValueRef r : arr) { 0291 QJsonObject obj = r.toObject(); 0292 0293 FolioPageDelegate *delegate = FolioPageDelegate::fromJson(obj, parent); 0294 if (delegate) { 0295 delegates.append(delegate); 0296 } 0297 } 0298 0299 PageModel *model = new PageModel{delegates, parent}; 0300 0301 // ensure delegates can request saves 0302 for (auto *delegate : delegates) { 0303 model->connectSaveRequests(delegate); 0304 } 0305 0306 return model; 0307 } 0308 0309 QJsonArray PageModel::toJson() const 0310 { 0311 QJsonArray arr; 0312 0313 for (FolioPageDelegate *delegate : m_delegates) { 0314 if (!delegate) { 0315 continue; 0316 } 0317 0318 arr.append(delegate->toJson()); 0319 } 0320 0321 return arr; 0322 } 0323 0324 int PageModel::rowCount(const QModelIndex &parent) const 0325 { 0326 Q_UNUSED(parent) 0327 return m_delegates.size(); 0328 } 0329 0330 QVariant PageModel::data(const QModelIndex &index, int role) const 0331 { 0332 if (!index.isValid()) { 0333 return QVariant(); 0334 } 0335 0336 switch (role) { 0337 case DelegateRole: 0338 return QVariant::fromValue(m_delegates.at(index.row())); 0339 } 0340 0341 return QVariant(); 0342 } 0343 0344 QHash<int, QByteArray> PageModel::roleNames() const 0345 { 0346 return {{DelegateRole, "delegate"}}; 0347 } 0348 0349 void PageModel::removeDelegate(int row, int col) 0350 { 0351 for (int i = 0; i < m_delegates.size(); ++i) { 0352 if (m_delegates[i]->row() == row && m_delegates[i]->column() == col) { 0353 removeDelegate(i); 0354 break; 0355 } 0356 } 0357 } 0358 0359 void PageModel::removeDelegate(int index) 0360 { 0361 if (index < 0 || index >= m_delegates.size()) { 0362 return; 0363 } 0364 0365 beginRemoveRows(QModelIndex(), index, index); 0366 // HACK: do not deleteLater(), because the delegate might still be used somewhere else 0367 m_delegates.removeAt(index); 0368 endRemoveRows(); 0369 0370 save(); 0371 } 0372 0373 bool PageModel::canAddDelegate(int row, int column, FolioDelegate *delegate) 0374 { 0375 if (row < 0 || row >= HomeScreenState::self()->pageRows() || column < 0 || column >= HomeScreenState::self()->pageColumns()) { 0376 return false; 0377 } 0378 0379 if (delegate->type() == FolioDelegate::Widget) { 0380 // inserting a widget... 0381 0382 // bounds of widget 0383 int maxRow = row + delegate->widget()->gridHeight() - 1; 0384 int maxColumn = column + delegate->widget()->gridWidth() - 1; 0385 0386 // check bounds 0387 if ((row < 0 || row >= HomeScreenState::self()->pageRows()) || (maxRow < 0 || maxRow >= HomeScreenState::self()->pageRows()) 0388 || (column < 0 || column >= HomeScreenState::self()->pageColumns()) || (maxColumn < 0 || maxColumn >= HomeScreenState::self()->pageColumns())) { 0389 return false; 0390 } 0391 0392 // check if any delegate exists at any of the spots where the widget is being added 0393 for (FolioPageDelegate *d : m_delegates) { 0394 if (delegate->widget()->isInBounds(row, column, d->row(), d->column())) { 0395 return false; 0396 } else if (d->type() == FolioDelegate::Widget) { 0397 // 2 widgets overlapping scenario 0398 if (d->widget()->overlapsWidget(d->row(), d->column(), delegate->widget(), row, column)) { 0399 return false; 0400 } 0401 } 0402 } 0403 0404 } else { 0405 // inserting app or folder... 0406 0407 // check if there already exists a delegate in this space 0408 for (FolioPageDelegate *d : m_delegates) { 0409 if (d->row() == row && d->column() == column) { 0410 return false; 0411 } else if (d->type() == FolioDelegate::Widget && d->widget()->isInBounds(d->row(), d->column(), row, column)) { 0412 return false; 0413 } 0414 } 0415 } 0416 0417 return true; 0418 } 0419 0420 bool PageModel::addDelegate(FolioPageDelegate *delegate) 0421 { 0422 if (!canAddDelegate(delegate->row(), delegate->column(), delegate)) { 0423 return false; 0424 } 0425 0426 beginInsertRows(QModelIndex(), m_delegates.size(), m_delegates.size()); 0427 m_delegates.append(delegate); 0428 endInsertRows(); 0429 0430 // ensure the delegate requests saves 0431 connectSaveRequests(delegate); 0432 save(); 0433 0434 return true; 0435 } 0436 0437 FolioPageDelegate *PageModel::getDelegate(int row, int col) 0438 { 0439 for (FolioPageDelegate *d : m_delegates) { 0440 if (d->row() == row && d->column() == col) { 0441 return d; 0442 } 0443 0444 // check if this is in a widget's space 0445 if (d->type() == FolioDelegate::Widget) { 0446 if (d->widget()->isInBounds(d->row(), d->column(), row, col)) { 0447 return d; 0448 } 0449 } 0450 } 0451 return nullptr; 0452 } 0453 0454 void PageModel::moveAndResizeWidgetDelegate(FolioPageDelegate *delegate, int newRow, int newColumn, int newGridWidth, int newGridHeight) 0455 { 0456 if (delegate->type() != FolioDelegate::Widget) { 0457 return; 0458 } 0459 0460 if (newGridWidth < 1 || newGridHeight < 1) { 0461 return; 0462 } 0463 0464 // test if we can add the delegate with new size and position 0465 FolioWidget *testWidget = new FolioWidget(this, 0, 0, 0); 0466 // we have to use setGridWidth and setGridHeight since it takes into account the page orientation 0467 testWidget->setGridWidth(newGridWidth); 0468 testWidget->setGridHeight(newGridHeight); 0469 FolioDelegate *testDelegate = new FolioDelegate(testWidget, this); 0470 0471 // NOT THREAD SAFE! 0472 // which is fine, because the GUI isn't multithreaded 0473 int index = m_delegates.indexOf(delegate); 0474 m_delegates.remove(index); // remove the delegate temporarily, since we don't want it to check overlapping of itself 0475 bool canAdd = canAddDelegate(newRow, newColumn, testDelegate); 0476 m_delegates.insert(index, delegate); // add it back 0477 0478 // cleanup test delegate 0479 testDelegate->deleteLater(); 0480 testWidget->deleteLater(); 0481 0482 if (!canAdd) { 0483 return; 0484 } 0485 0486 delegate->setRow(newRow); 0487 delegate->setColumn(newColumn); 0488 delegate->widget()->setGridWidth(newGridWidth); 0489 delegate->widget()->setGridHeight(newGridHeight); 0490 } 0491 0492 bool PageModel::isPageEmpty() 0493 { 0494 return m_delegates.size() == 0; 0495 } 0496 0497 void PageModel::connectSaveRequests(FolioDelegate *delegate) 0498 { 0499 if (delegate->type() == FolioDelegate::Folder && delegate->folder()) { 0500 connect(delegate->folder(), &FolioApplicationFolder::saveRequested, this, &PageModel::save); 0501 } else if (delegate->type() == FolioDelegate::Widget && delegate->widget()) { 0502 connect(delegate->widget(), &FolioWidget::saveRequested, this, &PageModel::save); 0503 } 0504 } 0505 0506 void PageModel::save() 0507 { 0508 Q_EMIT saveRequested(); 0509 }