File indexing completed on 2024-04-21 03:56:37

0001 /*
0002     KPeople - Duplicates
0003     SPDX-FileCopyrightText: 2013 Franck Arrecot <franck.arrecot@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-or-later
0006 */
0007 
0008 #include "mergedialog.h"
0009 #include "duplicatesfinder_p.h"
0010 #include "matchessolver_p.h"
0011 #include "mergedelegate.h"
0012 #include "persondata.h"
0013 #include "personsmodel.h"
0014 
0015 #include "kpeople_widgets_debug.h"
0016 
0017 #include <QDialogButtonBox>
0018 #include <QLabel>
0019 #include <QLayout>
0020 #include <QListView>
0021 #include <QPushButton>
0022 #include <QStandardItemModel>
0023 
0024 #include <KLocalizedString>
0025 #include <KPixmapSequence>
0026 #include <KPixmapSequenceWidget>
0027 
0028 using namespace KPeople;
0029 
0030 class MergeDialogPrivate
0031 {
0032 public:
0033     PersonsModel *personsModel;
0034     QListView *view;
0035     MergeDelegate *delegate;
0036 
0037     QStandardItemModel *model;
0038     DuplicatesFinder *duplicatesFinder;
0039     KPixmapSequenceWidget *sequence;
0040 };
0041 
0042 MergeDialog::MergeDialog(QWidget *parent)
0043     : QDialog(parent)
0044     , d_ptr(new MergeDialogPrivate)
0045 {
0046     Q_D(MergeDialog);
0047 
0048     d_ptr->personsModel = nullptr;
0049     d_ptr->delegate = nullptr;
0050     d_ptr->duplicatesFinder = nullptr;
0051 
0052     setWindowTitle(i18n("Duplicates Manager"));
0053 
0054     auto *layout = new QVBoxLayout(this);
0055 
0056     setMinimumSize(450, 350);
0057 
0058     d->model = new QStandardItemModel(this);
0059     d->view = new QListView(this);
0060     d->view->setModel(d->model);
0061     d->view->setEditTriggers(QAbstractItemView::NoEditTriggers);
0062 
0063     QLabel *topLabel = new QLabel(i18n("Select contacts to be merged"));
0064 
0065     QDialogButtonBox *buttons = new QDialogButtonBox(this);
0066     buttons->addButton(QDialogButtonBox::Ok);
0067     buttons->addButton(QDialogButtonBox::Cancel);
0068     connect(buttons, SIGNAL(accepted()), SLOT(onMergeButtonClicked()));
0069     connect(buttons, SIGNAL(rejected()), SLOT(reject()));
0070 
0071     d->sequence = new KPixmapSequenceWidget(this);
0072     d->sequence->setSequence(KPixmapSequence(QStringLiteral("process-working"), 22));
0073     d->sequence->setInterval(100);
0074     d->sequence->setVisible(false);
0075 
0076     layout->addWidget(topLabel);
0077     layout->addWidget(d->view);
0078     layout->addWidget(d->sequence);
0079     layout->addWidget(buttons);
0080 }
0081 
0082 MergeDialog::~MergeDialog()
0083 {
0084     delete d_ptr;
0085 }
0086 
0087 void MergeDialog::setPersonsModel(PersonsModel *model)
0088 {
0089     Q_D(MergeDialog);
0090     d->personsModel = model;
0091     if (d->personsModel) {
0092         searchForDuplicates();
0093         connect(d->personsModel, SIGNAL(modelInitialized(bool)), SLOT(searchForDuplicates()));
0094     }
0095 }
0096 
0097 void MergeDialog::searchForDuplicates()
0098 {
0099     Q_D(MergeDialog);
0100     if (!d->personsModel || !d->personsModel->rowCount() || d->duplicatesFinder) {
0101         qCWarning(KPEOPLE_WIDGETS_LOG) << "MergeDialog failed to launch the duplicates research";
0102         return;
0103     }
0104     d->duplicatesFinder = new DuplicatesFinder(d->personsModel);
0105     connect(d->duplicatesFinder, SIGNAL(result(KJob *)), SLOT(searchForDuplicatesFinished(KJob *)));
0106     d->duplicatesFinder->start();
0107 }
0108 
0109 void MergeDialog::onMergeButtonClicked()
0110 {
0111     Q_D(MergeDialog);
0112     QList<Match> matches;
0113     for (int i = 0, rows = d->model->rowCount(); i < rows; i++) {
0114         QStandardItem *item = d->model->item(i, 0);
0115         if (item->checkState() == Qt::Checked) {
0116             for (int j = 0, contactsCount = item->rowCount(); j < contactsCount; ++j) {
0117                 QStandardItem *matchItem = item->child(j);
0118                 matches << matchItem->data(MergeDialog::MergeReasonRole).value<Match>();
0119             }
0120         }
0121     }
0122 
0123     MatchesSolver *solverJob = new MatchesSolver(matches, d->personsModel, this);
0124     solverJob->start();
0125     d->sequence->setVisible(true);
0126     d->view->setEnabled(false);
0127     connect(solverJob, SIGNAL(finished(KJob *)), this, SLOT(accept()));
0128 }
0129 
0130 void MergeDialog::searchForDuplicatesFinished(KJob *)
0131 {
0132     Q_D(MergeDialog);
0133     feedDuplicateModelFromMatches(d->duplicatesFinder->results());
0134 
0135     d->delegate = new MergeDelegate(d->view);
0136     d->view->setItemDelegate(d->delegate);
0137 
0138     // To extend the selected item
0139     connect(d->view->selectionModel(),
0140             SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
0141             d->delegate,
0142             SLOT(onSelectedContactsChanged(QItemSelection, QItemSelection)));
0143     // To contract an already selected item
0144     connect(d->view, SIGNAL(doubleClicked(QModelIndex)), d->delegate, SLOT(onClickContactParent(QModelIndex)));
0145 }
0146 
0147 void MergeDialog::feedDuplicateModelFromMatches(const QList<Match> &matches)
0148 {
0149     Q_D(MergeDialog);
0150     QHash<QPersistentModelIndex, QList<Match>> compareTable;
0151     QHash<QPersistentModelIndex, QPersistentModelIndex> doneIndexes;
0152 
0153     for (const Match &match : matches) {
0154         QPersistentModelIndex destination = doneIndexes.value(match.indexA, match.indexA);
0155         QHash<QPersistentModelIndex, QList<Match>>::iterator currentValue = compareTable.find(destination);
0156 
0157         if (currentValue == compareTable.end()) { // new parent, create it
0158             compareTable[match.indexA] = QList<Match>() << match;
0159         } else { // know parent, add child
0160             currentValue->append(match);
0161         }
0162         doneIndexes[match.indexB] = destination;
0163     }
0164     // now build the model : 1st dimension = person candidate, 2nd dimension = match
0165     QStandardItem *rootItem = d->model->invisibleRootItem();
0166     QHash<QPersistentModelIndex, QList<Match>>::const_iterator i;
0167 
0168     for (i = compareTable.constBegin(); i != compareTable.constEnd(); ++i) {
0169         // Build the merge Contact in the model
0170         QStandardItem *parent = itemMergeContactFromMatch(true, i->first());
0171         rootItem->appendRow(parent);
0172 
0173         for (const Match &matchChild : std::as_const(*i)) {
0174             parent->appendRow(itemMergeContactFromMatch(false, matchChild));
0175         }
0176     }
0177 
0178     rootItem->sortChildren(0);
0179 }
0180 
0181 QStandardItem *MergeDialog::itemMergeContactFromMatch(bool isParent, const Match &match)
0182 {
0183     QStandardItem *item = new QStandardItem;
0184 
0185     item->setCheckable(true);
0186     item->setCheckState(Qt::Unchecked);
0187     item->setSizeHint(MergeDelegate::pictureSize());
0188     item->setData(true, KExtendableItemDelegate::ShowExtensionIndicatorRole);
0189 
0190     QVariant deco;
0191     if (!isParent) { // child
0192         QString uri = match.indexB.data(PersonsModel::PersonUriRole).toString();
0193         item->setData(uri, UriRole);
0194 
0195         item->setData(QVariant::fromValue<Match>(match), MergeReasonRole);
0196         item->setText(match.indexB.data(Qt::DisplayRole).toString());
0197         deco = match.indexB.data(Qt::DecorationRole);
0198 
0199     } else { // parent
0200         QString uri = match.indexA.data(PersonsModel::PersonUriRole).toString();
0201         item->setData(uri, UriRole);
0202 
0203         item->setText(match.indexA.data(Qt::DisplayRole).toString());
0204         deco = match.indexA.data(Qt::DecorationRole);
0205     }
0206 
0207     QIcon icon;
0208     if (deco.userType() == QMetaType::QIcon) {
0209         icon = deco.value<QIcon>();
0210     } else if (deco.userType() == QMetaType::QPixmap) {
0211         icon = QIcon(deco.value<QPixmap>());
0212     } else if (deco.userType() == QMetaType::QImage) {
0213         icon = QIcon(QPixmap::fromImage(deco.value<QImage>()));
0214     } else {
0215         qCWarning(KPEOPLE_WIDGETS_LOG) << "unknown decoration type" << deco.typeName();
0216     }
0217     item->setIcon(icon);
0218     return item;
0219 }
0220 
0221 #include "moc_mergedialog.cpp"