File indexing completed on 2024-12-15 04:54:44
0001 /****************************************************************************** 0002 * 0003 * SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 * 0007 *******************************************************************************/ 0008 0009 #include "utils/configureaggregationsdialog.h" 0010 #include "utils/configureaggregationsdialog_p.h" 0011 0012 #include "core/aggregation.h" 0013 #include "utils/aggregationeditor.h" 0014 0015 #include "core/manager.h" 0016 0017 #include <QFrame> 0018 #include <QGridLayout> 0019 #include <QMap> 0020 #include <QPushButton> 0021 0022 #include <KConfig> 0023 #include <KConfigGroup> 0024 #include <KLocalizedString> 0025 #include <QDialogButtonBox> 0026 #include <QFileDialog> 0027 #include <QIcon> 0028 #include <QVBoxLayout> 0029 0030 namespace MessageList 0031 { 0032 namespace Utils 0033 { 0034 class AggregationListWidgetItem : public QListWidgetItem 0035 { 0036 private: 0037 Core::Aggregation *mAggregation = nullptr; 0038 0039 public: 0040 AggregationListWidgetItem(QListWidget *par, const Core::Aggregation &set) 0041 : QListWidgetItem(set.name(), par) 0042 { 0043 mAggregation = new Core::Aggregation(set); 0044 } 0045 0046 ~AggregationListWidgetItem() override 0047 { 0048 delete mAggregation; 0049 } 0050 0051 public: 0052 [[nodiscard]] Core::Aggregation *aggregation() const 0053 { 0054 return mAggregation; 0055 } 0056 0057 void forgetAggregation() 0058 { 0059 mAggregation = nullptr; 0060 } 0061 }; 0062 0063 /** 0064 * The widget that lists the available Aggregations. 0065 * 0066 * At the moment of writing, derived from QListWidget only to override sizeHint(). 0067 */ 0068 class AggregationListWidget : public QListWidget 0069 { 0070 public: 0071 AggregationListWidget(QWidget *parent) 0072 : QListWidget(parent) 0073 { 0074 } 0075 0076 public: 0077 // need a larger but shorter QListWidget 0078 [[nodiscard]] QSize sizeHint() const override 0079 { 0080 return {450, 128}; 0081 } 0082 }; 0083 } // namespace Utils 0084 } // namespace MessageList 0085 0086 using namespace MessageList::Core; 0087 using namespace MessageList::Utils; 0088 0089 ConfigureAggregationsDialog::ConfigureAggregationsDialog(QWidget *parent) 0090 : QDialog(parent) 0091 , d(new ConfigureAggregationsDialogPrivate(this)) 0092 { 0093 setAttribute(Qt::WA_DeleteOnClose); 0094 auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); 0095 auto mainLayout = new QVBoxLayout(this); 0096 QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); 0097 okButton->setDefault(true); 0098 okButton->setShortcut(Qt::CTRL | Qt::Key_Return); 0099 connect(buttonBox, &QDialogButtonBox::rejected, this, &ConfigureAggregationsDialog::reject); 0100 setWindowTitle(i18nc("@title:window", "Customize Message Aggregation Modes")); 0101 0102 auto base = new QWidget(this); 0103 mainLayout->addWidget(base); 0104 mainLayout->addWidget(buttonBox); 0105 0106 auto g = new QGridLayout(base); 0107 g->setContentsMargins({}); 0108 0109 d->mAggregationList = new AggregationListWidget(base); 0110 d->mAggregationList->setSelectionMode(QAbstractItemView::ExtendedSelection); 0111 d->mAggregationList->setSortingEnabled(true); 0112 g->addWidget(d->mAggregationList, 0, 0, 7, 1); 0113 0114 connect(d->mAggregationList, &AggregationListWidget::itemClicked, this, [this](QListWidgetItem *item) { 0115 d->aggregationListItemClicked(item); 0116 }); 0117 connect(d->mAggregationList, &AggregationListWidget::currentItemChanged, this, [this](QListWidgetItem *item) { 0118 d->aggregationListItemClicked(item); 0119 }); 0120 0121 d->mNewAggregationButton = new QPushButton(i18n("New Aggregation"), base); 0122 d->mNewAggregationButton->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); 0123 g->addWidget(d->mNewAggregationButton, 0, 1); 0124 0125 connect(d->mNewAggregationButton, &QPushButton::clicked, this, [this]() { 0126 d->newAggregationButtonClicked(); 0127 }); 0128 0129 d->mCloneAggregationButton = new QPushButton(i18n("Clone Aggregation"), base); 0130 d->mCloneAggregationButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); 0131 g->addWidget(d->mCloneAggregationButton, 1, 1); 0132 0133 connect(d->mCloneAggregationButton, &QPushButton::clicked, this, [this]() { 0134 d->cloneAggregationButtonClicked(); 0135 }); 0136 0137 auto f = new QFrame(base); 0138 f->setFrameStyle(QFrame::Sunken | QFrame::HLine); 0139 f->setMinimumHeight(24); 0140 g->addWidget(f, 2, 1, Qt::AlignVCenter); 0141 0142 d->mExportAggregationButton = new QPushButton(i18n("Export Aggregation..."), base); 0143 g->addWidget(d->mExportAggregationButton, 3, 1); 0144 0145 connect(d->mExportAggregationButton, &QPushButton::clicked, this, [this]() { 0146 d->exportAggregationButtonClicked(); 0147 }); 0148 0149 d->mImportAggregationButton = new QPushButton(i18n("Import Aggregation..."), base); 0150 g->addWidget(d->mImportAggregationButton, 4, 1); 0151 connect(d->mImportAggregationButton, &QPushButton::clicked, this, [this]() { 0152 d->importAggregationButtonClicked(); 0153 }); 0154 0155 f = new QFrame(base); 0156 f->setFrameStyle(QFrame::Sunken | QFrame::HLine); 0157 f->setMinimumHeight(24); 0158 g->addWidget(f, 5, 1, Qt::AlignVCenter); 0159 0160 d->mDeleteAggregationButton = new QPushButton(i18n("Delete Aggregation"), base); 0161 d->mDeleteAggregationButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); 0162 g->addWidget(d->mDeleteAggregationButton, 6, 1); 0163 0164 connect(d->mDeleteAggregationButton, &QPushButton::clicked, this, [this]() { 0165 d->deleteAggregationButtonClicked(); 0166 }); 0167 0168 d->mEditor = new AggregationEditor(base); 0169 g->addWidget(d->mEditor, 8, 0, 1, 2); 0170 0171 connect(d->mEditor, &AggregationEditor::aggregationNameChanged, this, [this]() { 0172 d->editedAggregationNameChanged(); 0173 }); 0174 0175 g->setColumnStretch(0, 1); 0176 g->setRowStretch(7, 1); 0177 0178 connect(okButton, &QPushButton::clicked, this, [this]() { 0179 d->okButtonClicked(); 0180 }); 0181 0182 d->fillAggregationList(); 0183 } 0184 0185 ConfigureAggregationsDialog::~ConfigureAggregationsDialog() = default; 0186 0187 void ConfigureAggregationsDialog::selectAggregation(const QString &aggregationId) 0188 { 0189 AggregationListWidgetItem *item = d->findAggregationItemById(aggregationId); 0190 if (item) { 0191 d->mAggregationList->setCurrentItem(item); 0192 d->aggregationListItemClicked(item); 0193 } 0194 } 0195 0196 void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::okButtonClicked() 0197 { 0198 if (Manager::instance()) { 0199 commitEditor(); 0200 0201 Manager::instance()->removeAllAggregations(); 0202 0203 const int c = mAggregationList->count(); 0204 int i = 0; 0205 while (i < c) { 0206 auto item = dynamic_cast<AggregationListWidgetItem *>(mAggregationList->item(i)); 0207 if (item) { 0208 Manager::instance()->addAggregation(item->aggregation()); 0209 item->forgetAggregation(); 0210 } 0211 ++i; 0212 } 0213 0214 Manager::instance()->aggregationsConfigurationCompleted(); 0215 } 0216 Q_EMIT q->okClicked(); 0217 q->accept(); // this will delete too 0218 } 0219 0220 void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::commitEditor() 0221 { 0222 Aggregation *editedAggregation = mEditor->editedAggregation(); 0223 if (!editedAggregation) { 0224 return; 0225 } 0226 0227 mEditor->commit(); 0228 0229 AggregationListWidgetItem *editedItem = findAggregationItemByAggregation(editedAggregation); 0230 if (!editedItem) { 0231 return; 0232 } 0233 const QString goodName = uniqueNameForAggregation(editedAggregation->name(), editedAggregation); 0234 editedAggregation->setName(goodName); 0235 editedItem->setText(goodName); 0236 } 0237 0238 void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::editedAggregationNameChanged() 0239 { 0240 Aggregation *set = mEditor->editedAggregation(); 0241 if (!set) { 0242 return; 0243 } 0244 0245 AggregationListWidgetItem *it = findAggregationItemByAggregation(set); 0246 if (!it) { 0247 return; 0248 } 0249 0250 const QString goodName = uniqueNameForAggregation(set->name(), set); 0251 0252 it->setText(goodName); 0253 } 0254 0255 void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::fillAggregationList() 0256 { 0257 if (!Manager::instance()) { 0258 return; 0259 } 0260 const QMap<QString, Aggregation *> &sets = Manager::instance()->aggregations(); 0261 QMap<QString, Aggregation *>::ConstIterator end(sets.constEnd()); 0262 for (QMap<QString, Aggregation *>::ConstIterator it = sets.constBegin(); it != end; ++it) { 0263 (void)new AggregationListWidgetItem(mAggregationList, *(*it)); 0264 } 0265 } 0266 0267 void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::aggregationListItemClicked(QListWidgetItem *cur) 0268 { 0269 commitEditor(); 0270 updateButton(cur); 0271 } 0272 0273 void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::updateButton(QListWidgetItem *cur) 0274 { 0275 const int numberOfSelectedItem(mAggregationList->selectedItems().count()); 0276 0277 AggregationListWidgetItem *item = cur ? dynamic_cast<AggregationListWidgetItem *>(cur) : nullptr; 0278 mDeleteAggregationButton->setEnabled(item && !item->aggregation()->readOnly() && (mAggregationList->count() > 1)); 0279 0280 mCloneAggregationButton->setEnabled(numberOfSelectedItem == 1); 0281 mExportAggregationButton->setEnabled(numberOfSelectedItem > 0); 0282 mEditor->editAggregation(item ? item->aggregation() : nullptr); 0283 if (item && !item->isSelected()) { 0284 item->setSelected(true); // make sure it's true 0285 } 0286 } 0287 0288 AggregationListWidgetItem *ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::findAggregationItemByName(const QString &name, 0289 Aggregation *skipAggregation) 0290 { 0291 const int c = mAggregationList->count(); 0292 int i = 0; 0293 while (i < c) { 0294 auto item = dynamic_cast<AggregationListWidgetItem *>(mAggregationList->item(i)); 0295 if (item) { 0296 if (item->aggregation() != skipAggregation) { 0297 if (item->aggregation()->name() == name) { 0298 return item; 0299 } 0300 } 0301 } 0302 ++i; 0303 } 0304 return nullptr; 0305 } 0306 0307 AggregationListWidgetItem *ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::findAggregationItemById(const QString &aggregationId) 0308 { 0309 const int c = mAggregationList->count(); 0310 int i = 0; 0311 while (i < c) { 0312 auto item = dynamic_cast<AggregationListWidgetItem *>(mAggregationList->item(i)); 0313 if (item) { 0314 if (item->aggregation()->id() == aggregationId) { 0315 return item; 0316 } 0317 } 0318 ++i; 0319 } 0320 return nullptr; 0321 } 0322 0323 AggregationListWidgetItem *ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::findAggregationItemByAggregation(Aggregation *set) 0324 { 0325 const int c = mAggregationList->count(); 0326 int i = 0; 0327 while (i < c) { 0328 auto item = dynamic_cast<AggregationListWidgetItem *>(mAggregationList->item(i)); 0329 if (item) { 0330 if (item->aggregation() == set) { 0331 return item; 0332 } 0333 } 0334 ++i; 0335 } 0336 return nullptr; 0337 } 0338 0339 QString ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::uniqueNameForAggregation(const QString &baseName, Aggregation *skipAggregation) 0340 { 0341 QString ret = baseName; 0342 if (ret.isEmpty()) { 0343 ret = i18n("Unnamed Aggregation"); 0344 } 0345 0346 int idx = 1; 0347 0348 AggregationListWidgetItem *item = findAggregationItemByName(ret, skipAggregation); 0349 while (item) { 0350 idx++; 0351 ret = QStringLiteral("%1 %2").arg(baseName).arg(idx); 0352 item = findAggregationItemByName(ret, skipAggregation); 0353 } 0354 return ret; 0355 } 0356 0357 void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::newAggregationButtonClicked() 0358 { 0359 Aggregation emptyAggregation; 0360 emptyAggregation.setName(uniqueNameForAggregation(i18n("New Aggregation"))); 0361 auto item = new AggregationListWidgetItem(mAggregationList, emptyAggregation); 0362 0363 mAggregationList->clearSelection(); 0364 mAggregationList->setCurrentItem(item); 0365 mDeleteAggregationButton->setEnabled(item && !item->aggregation()->readOnly()); 0366 } 0367 0368 void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::cloneAggregationButtonClicked() 0369 { 0370 auto item = dynamic_cast<AggregationListWidgetItem *>(mAggregationList->currentItem()); 0371 if (!item) { 0372 return; 0373 } 0374 commitEditor(); 0375 item->setSelected(false); 0376 Aggregation copyAggregation(*(item->aggregation())); 0377 copyAggregation.setReadOnly(false); 0378 copyAggregation.generateUniqueId(); // regenerate id so it becomes different 0379 copyAggregation.setName(uniqueNameForAggregation(item->aggregation()->name())); 0380 item = new AggregationListWidgetItem(mAggregationList, copyAggregation); 0381 0382 mAggregationList->setCurrentItem(item); 0383 aggregationListItemClicked(item); 0384 } 0385 0386 void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::deleteAggregationButtonClicked() 0387 { 0388 const QList<QListWidgetItem *> list = mAggregationList->selectedItems(); 0389 if (list.isEmpty()) { 0390 return; 0391 } 0392 0393 mEditor->editAggregation(nullptr); // forget it 0394 for (QListWidgetItem *it : list) { 0395 auto item = dynamic_cast<AggregationListWidgetItem *>(it); 0396 if (!item) { 0397 return; 0398 } 0399 if (!item->aggregation()->readOnly()) { 0400 delete item; // this will trigger aggregationListCurrentItemChanged() 0401 } 0402 if (mAggregationList->count() < 2) { 0403 break; // no way: desperately try to keep at least one option set alive :) 0404 } 0405 } 0406 0407 auto newItem = dynamic_cast<AggregationListWidgetItem *>(mAggregationList->currentItem()); 0408 updateButton(newItem); 0409 } 0410 0411 void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::importAggregationButtonClicked() 0412 { 0413 const QString filename = QFileDialog::getOpenFileName(q, i18n("Import Aggregation")); 0414 if (!filename.isEmpty()) { 0415 KConfig config(filename); 0416 0417 if (config.hasGroup(QStringLiteral("MessageListView::Aggregations"))) { 0418 KConfigGroup grp(&config, QStringLiteral("MessageListView::Aggregations")); 0419 const int cnt = grp.readEntry("Count", 0); 0420 int idx = 0; 0421 while (idx < cnt) { 0422 const QString data = grp.readEntry(QStringLiteral("Set%1").arg(idx), QString()); 0423 if (!data.isEmpty()) { 0424 auto set = new Aggregation(); 0425 if (set->loadFromString(data)) { 0426 set->setReadOnly(false); 0427 set->generateUniqueId(); // regenerate id so it becomes different 0428 set->setName(uniqueNameForAggregation(set->name())); 0429 (void)new AggregationListWidgetItem(mAggregationList, *set); 0430 } else { 0431 delete set; // b0rken 0432 } 0433 } 0434 ++idx; 0435 } 0436 } 0437 } 0438 } 0439 0440 void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::exportAggregationButtonClicked() 0441 { 0442 const QList<QListWidgetItem *> list = mAggregationList->selectedItems(); 0443 if (list.isEmpty()) { 0444 return; 0445 } 0446 const QString filename = QFileDialog::getSaveFileName(q, i18n("Export Aggregation"), QString(), i18n("All Files (*)")); 0447 if (!filename.isEmpty()) { 0448 KConfig config(filename); 0449 0450 KConfigGroup grp(&config, QStringLiteral("MessageListView::Aggregations")); 0451 grp.writeEntry("Count", list.count()); 0452 0453 int idx = 0; 0454 for (QListWidgetItem *item : list) { 0455 auto themeItem = static_cast<AggregationListWidgetItem *>(item); 0456 grp.writeEntry(QStringLiteral("Set%1").arg(idx), themeItem->aggregation()->saveToString()); 0457 ++idx; 0458 } 0459 } 0460 } 0461 0462 #include "moc_configureaggregationsdialog.cpp"