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"