File indexing completed on 2024-04-28 05:49:32
0001 /* 0002 SPDX-License-Identifier: LGPL-2.0-only 0003 SPDX-FileCopyrightText: 2004 Anders Lund <anders@alweb.dk> 0004 SPDX-FileCopyrightText: 2023 Waqar Ahmed <waqar.17a@gmail.com> 0005 */ 0006 0007 #include "katemwmodonhddialog.h" 0008 0009 #include "diffwidget.h" 0010 #include "gitprocess.h" 0011 #include "hostprocess.h" 0012 #include "kateapp.h" 0013 #include "katedocmanager.h" 0014 #include "katemainwindow.h" 0015 #include "ktexteditor_utils.h" 0016 0017 #include <KLocalizedString> 0018 #include <KMessageBox> 0019 #include <KTextEditor/Document> 0020 0021 #include <QDir> 0022 #include <QHeaderView> 0023 #include <QLabel> 0024 #include <QPushButton> 0025 #include <QStandardPaths> 0026 #include <QStyle> 0027 #include <QTemporaryFile> 0028 #include <QTextStream> 0029 #include <QTreeWidget> 0030 #include <QTreeWidgetItem> 0031 0032 class KateDocItem : public QTreeWidgetItem 0033 { 0034 public: 0035 KateDocItem(KTextEditor::Document *doc, const QString &status, QTreeWidget *tw) 0036 : QTreeWidgetItem(tw) 0037 , document(doc) 0038 { 0039 setText(0, doc->url().toString()); 0040 setText(1, status); 0041 if (!doc->isModified()) { 0042 setCheckState(0, Qt::Checked); 0043 } else { 0044 setCheckState(0, Qt::Unchecked); 0045 } 0046 } 0047 QPointer<KTextEditor::Document> document; 0048 }; 0049 0050 KateMwModOnHdDialog::KateMwModOnHdDialog(const QList<KTextEditor::Document *> &docs, QWidget *parent, const char *name) 0051 : QDialog(parent) 0052 , m_blockAddDocument(false) 0053 { 0054 setWindowTitle(i18n("Documents Modified on Disk")); 0055 setObjectName(QString::fromLatin1(name)); 0056 setModal(true); 0057 0058 QVBoxLayout *mainLayout = new QVBoxLayout; 0059 setLayout(mainLayout); 0060 0061 // Message 0062 QHBoxLayout *hb = new QHBoxLayout; 0063 mainLayout->addLayout(hb); 0064 0065 // dialog text 0066 QLabel *icon = new QLabel(this); 0067 hb->addWidget(icon); 0068 icon->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(style()->pixelMetric(QStyle::PM_LargeIconSize, nullptr, this))); 0069 0070 QLabel *t = new QLabel(i18n("<qt>The documents listed below have changed on disk.<p>Select one " 0071 "or more at once, and press an action button until the list is empty.</p></qt>"), 0072 this); 0073 hb->addWidget(t); 0074 hb->setStretchFactor(t, 1000); 0075 0076 // Document list 0077 docsTreeWidget = new QTreeWidget(this); 0078 mainLayout->addWidget(docsTreeWidget); 0079 QStringList header; 0080 header << i18n("Filename") << i18n("Status on Disk"); 0081 docsTreeWidget->setHeaderLabels(header); 0082 docsTreeWidget->setSelectionMode(QAbstractItemView::SingleSelection); 0083 docsTreeWidget->setRootIsDecorated(false); 0084 0085 m_stateTexts << QString() << i18n("Modified") << i18n("Created") << i18n("Deleted"); 0086 for (auto &doc : docs) { 0087 const auto docInfo = KateApp::self()->documentManager()->documentInfo(doc); 0088 if (!docInfo) { 0089 qWarning() << "Unexpected null doc info"; 0090 continue; 0091 } 0092 new KateDocItem(doc, m_stateTexts[static_cast<uint>(docInfo->modifiedOnDiscReason)], docsTreeWidget); 0093 0094 // ensure proper cleanups to avoid dangling pointers, we can arrive here multiple times, use unique connection 0095 connect(doc, &KTextEditor::Document::destroyed, this, &KateMwModOnHdDialog::removeDocument, Qt::UniqueConnection); 0096 } 0097 docsTreeWidget->header()->setStretchLastSection(false); 0098 docsTreeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch); 0099 docsTreeWidget->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); 0100 0101 connect(docsTreeWidget, &QTreeWidget::currentItemChanged, this, &KateMwModOnHdDialog::slotSelectionChanged); 0102 connect(docsTreeWidget, &QTreeWidget::itemChanged, this, &KateMwModOnHdDialog::slotCheckedFilesChanged); 0103 0104 // Diff line 0105 hb = new QHBoxLayout; 0106 mainLayout->addLayout(hb); 0107 0108 btnDiff = new QPushButton(QIcon::fromTheme(QStringLiteral("document-preview")), i18n("&View Difference"), this); 0109 btnDiff->setWhatsThis(i18n("Calculates the difference between the editor contents and the disk file for the selected document.")); 0110 hb->addWidget(btnDiff); 0111 connect(btnDiff, &QPushButton::clicked, this, &KateMwModOnHdDialog::slotDiff); 0112 0113 // Dialog buttons 0114 dlgButtons = new QDialogButtonBox(this); 0115 mainLayout->addWidget(dlgButtons); 0116 0117 QPushButton *ignoreButton = new QPushButton(QIcon::fromTheme(QStringLiteral("dialog-warning")), i18n("&Ignore Changes")); 0118 ignoreButton->setToolTip(i18n("Remove modified flag from selected documents")); 0119 dlgButtons->addButton(ignoreButton, QDialogButtonBox::RejectRole); 0120 connect(ignoreButton, &QPushButton::clicked, this, &KateMwModOnHdDialog::slotIgnore); 0121 0122 QPushButton *overwriteButton = new QPushButton; 0123 KGuiItem::assign(overwriteButton, KStandardGuiItem::overwrite()); 0124 overwriteButton->setToolTip(i18n("Overwrite selected documents, discarding disk changes")); 0125 dlgButtons->addButton(overwriteButton, QDialogButtonBox::DestructiveRole); 0126 connect(overwriteButton, &QPushButton::clicked, this, &KateMwModOnHdDialog::slotOverwrite); 0127 0128 QPushButton *reloadButton = new QPushButton(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("&Reload")); 0129 reloadButton->setDefault(true); 0130 reloadButton->setToolTip(i18n("Reload selected documents from disk")); 0131 dlgButtons->addButton(reloadButton, QDialogButtonBox::DestructiveRole); 0132 connect(reloadButton, &QPushButton::clicked, this, &KateMwModOnHdDialog::slotReload); 0133 0134 // butons will only be enabled when items are checked. see slotCheckedFilesChanged() 0135 dlgButtons->setEnabled(false); 0136 slotCheckedFilesChanged(nullptr, 0); 0137 0138 slotSelectionChanged(nullptr, nullptr); 0139 } 0140 0141 KateMwModOnHdDialog::~KateMwModOnHdDialog() 0142 { 0143 // ensure no cleanup of documents during shutdown 0144 disconnect(); 0145 0146 KateMainWindow::unsetModifiedOnDiscDialogIfIf(this); 0147 0148 // if there are any living processes, disconnect them now before we get destroyed 0149 const auto children = findChildren<QProcess *>(); 0150 for (QProcess *child : children) { 0151 disconnect(child, nullptr, nullptr, nullptr); 0152 } 0153 } 0154 0155 void KateMwModOnHdDialog::slotIgnore() 0156 { 0157 handleSelected(Ignore); 0158 } 0159 0160 void KateMwModOnHdDialog::slotOverwrite() 0161 { 0162 handleSelected(Overwrite); 0163 } 0164 0165 void KateMwModOnHdDialog::slotReload() 0166 { 0167 handleSelected(Reload); 0168 } 0169 0170 void KateMwModOnHdDialog::handleSelected(int action) 0171 { 0172 // don't alter the treewidget via addDocument, we modify it here! 0173 m_blockAddDocument = true; 0174 0175 // collect all items we can remove 0176 std::vector<QTreeWidgetItem *> itemsToDelete; 0177 for (QTreeWidgetItemIterator it(docsTreeWidget); *it; ++it) { 0178 KateDocItem *item = static_cast<KateDocItem *>(*it); 0179 auto docInfo = KateApp::self()->documentManager()->documentInfo(item->document); 0180 if (!item->document || !docInfo) { 0181 itemsToDelete.push_back(item); 0182 continue; 0183 } 0184 0185 if (item->checkState(0) == Qt::Checked) { 0186 KTextEditor::Document::ModifiedOnDiskReason reason = docInfo->modifiedOnDiscReason; 0187 bool success = true; 0188 item->document->setModifiedOnDisk(KTextEditor::Document::OnDiskUnmodified); 0189 0190 switch (action) { 0191 case Overwrite: 0192 success = item->document->save(); 0193 if (!success) { 0194 KMessageBox::error(this, i18n("Could not save the document \n'%1'", item->document->url().toString())); 0195 } 0196 break; 0197 0198 case Reload: 0199 item->document->documentReload(); 0200 break; 0201 0202 default: 0203 break; 0204 } 0205 0206 if (success) { 0207 itemsToDelete.push_back(item); 0208 } else { 0209 item->document->setModifiedOnDisk(reason); 0210 } 0211 } 0212 } 0213 0214 // remove the marked items, addDocument is blocked, this is save! 0215 qDeleteAll(itemsToDelete); 0216 0217 // any documents left unhandled? 0218 if (!docsTreeWidget->topLevelItemCount()) { 0219 accept(); 0220 } 0221 0222 // update the dialog buttons 0223 slotCheckedFilesChanged(nullptr, 0); 0224 0225 // allow addDocument again 0226 m_blockAddDocument = false; 0227 } 0228 0229 void KateMwModOnHdDialog::slotSelectionChanged(QTreeWidgetItem *current, QTreeWidgetItem *) 0230 { 0231 KateDocItem *currentDocItem = static_cast<KateDocItem *>(current); 0232 if (currentDocItem && currentDocItem->document) { 0233 auto *docInfo = KateApp::self()->documentManager()->documentInfo(currentDocItem->document); 0234 // set the diff button enabled 0235 btnDiff->setEnabled(docInfo && docInfo->modifiedOnDiscReason != KTextEditor::Document::OnDiskDeleted); 0236 } 0237 } 0238 0239 void KateMwModOnHdDialog::slotCheckedFilesChanged(QTreeWidgetItem *, int column) 0240 { 0241 if (column != 0) { 0242 // we only need to react when the checkbox (in column 0) changes 0243 return; 0244 } 0245 0246 for (QTreeWidgetItemIterator it(docsTreeWidget); *it; ++it) { 0247 KateDocItem *item = static_cast<KateDocItem *>(*it); 0248 if (item->checkState(0) == Qt::Checked) { 0249 // at least 1 item is checked so we enable the buttons 0250 dlgButtons->setEnabled(true); 0251 return; 0252 } 0253 } 0254 0255 // no itmes checked, disable all buttons 0256 dlgButtons->setEnabled(false); 0257 } 0258 0259 // ### the code below is slightly modified from kdelibs/kate/part/katedialogs, 0260 // class KateModOnHdPrompt. 0261 void KateMwModOnHdDialog::slotDiff() 0262 { 0263 if (!btnDiff->isEnabled()) { // diff button already pressed, proc not finished yet 0264 return; 0265 } 0266 0267 if (!docsTreeWidget->currentItem()) { 0268 return; 0269 } 0270 0271 QPointer<KTextEditor::Document> doc = (static_cast<KateDocItem *>(docsTreeWidget->currentItem()))->document; 0272 auto docInfo = KateApp::self()->documentManager()->documentInfo(doc); 0273 if (!doc || !docInfo) { 0274 return; 0275 } 0276 0277 // don't try to diff a deleted file 0278 if (docInfo->modifiedOnDiscReason == KTextEditor::Document::OnDiskDeleted) { 0279 return; 0280 } 0281 0282 auto f = new QTemporaryFile(this); 0283 f->open(); 0284 f->write(doc->text().toUtf8().constData()); 0285 f->flush(); 0286 0287 QProcess *p = new QProcess(this); 0288 QStringList args = {QStringLiteral("diff"), QStringLiteral("--no-color"), QStringLiteral("--no-index")}; 0289 args << f->fileName(); 0290 args << doc->url().toLocalFile(); 0291 setupGitProcess(*p, QDir::currentPath(), args); 0292 0293 connect(p, &QProcess::finished, this, [p, f, this, doc](int, QProcess::ExitStatus) { 0294 f->deleteLater(); 0295 p->deleteLater(); 0296 slotGitDiffDone(p, doc); 0297 }); 0298 setCursor(Qt::WaitCursor); 0299 btnDiff->setEnabled(false); 0300 startHostProcess(*p); 0301 } 0302 0303 void KateMwModOnHdDialog::slotGitDiffDone(QProcess *p, KTextEditor::Document *doc) 0304 { 0305 setCursor(Qt::ArrowCursor); 0306 slotSelectionChanged(docsTreeWidget->currentItem(), nullptr); 0307 0308 const QProcess::ExitStatus es = p->exitStatus(); 0309 0310 if (es != QProcess::NormalExit) { 0311 KMessageBox::error(this, 0312 i18n("The diff command failed. Please make sure that " 0313 "git is installed and in your PATH."), 0314 i18n("Error Creating Diff")); 0315 return; 0316 } 0317 0318 const QByteArray out = p->readAllStandardOutput(); 0319 if (!out.isEmpty()) { 0320 DiffParams params; 0321 QString s = QFileInfo(doc->url().toLocalFile()).fileName() + i18n("[OLD]"); 0322 QString t = QFileInfo(doc->url().toLocalFile()).fileName() + i18n("[NEW]"); 0323 params.tabTitle = s + QStringLiteral("..") + t; 0324 params.workingDir = p->workingDirectory(); 0325 auto mw = static_cast<KateMainWindow *>(parent()); 0326 DiffWidgetManager::openDiff(out, params, mw->wrapper()); 0327 } 0328 } 0329 0330 void KateMwModOnHdDialog::addDocument(KTextEditor::Document *doc) 0331 { 0332 // guard this e.g. during handleSelected 0333 if (m_blockAddDocument || !KateApp::self()->documentManager()->documentInfo(doc)) { 0334 return; 0335 } 0336 0337 // avoid double occurrences, we want a fresh item 0338 removeDocument(doc); 0339 0340 uint reason = static_cast<uint>(KateApp::self()->documentManager()->documentInfo(doc)->modifiedOnDiscReason); 0341 if (reason) { 0342 new KateDocItem(doc, m_stateTexts[reason], docsTreeWidget); 0343 connect(doc, &KTextEditor::Document::destroyed, this, &KateMwModOnHdDialog::removeDocument, Qt::UniqueConnection); 0344 } 0345 0346 if (!docsTreeWidget->topLevelItemCount()) { 0347 accept(); 0348 } 0349 } 0350 0351 void KateMwModOnHdDialog::removeDocument(QObject *doc) 0352 { 0353 for (QTreeWidgetItemIterator it(docsTreeWidget); *it; ++it) { 0354 KateDocItem *item = static_cast<KateDocItem *>(*it); 0355 if (item->document == static_cast<KTextEditor::Document *>(doc)) { 0356 disconnect(item->document, nullptr, this, nullptr); 0357 delete item; 0358 break; 0359 } 0360 } 0361 } 0362 0363 void KateMwModOnHdDialog::keyPressEvent(QKeyEvent *event) 0364 { 0365 if (event->modifiers() == 0) { 0366 if (event->key() == Qt::Key_Escape) { 0367 event->accept(); 0368 return; 0369 } 0370 } 0371 QDialog::keyPressEvent(event); 0372 } 0373 0374 void KateMwModOnHdDialog::closeEvent(QCloseEvent *e) 0375 { 0376 if (!docsTreeWidget->topLevelItemCount()) { 0377 QDialog::closeEvent(e); 0378 } else { 0379 e->ignore(); 0380 } 0381 } 0382 0383 #include "moc_katemwmodonhddialog.cpp"