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"