File indexing completed on 2024-04-28 05:42:48
0001 /* 0002 SPDX-FileCopyrightText: 2001-2004, 2009 Otto Bruggeman <bruggie@gmail.com> 0003 SPDX-FileCopyrightText: 2001-2003 John Firebaugh <jfirebaugh@kde.org> 0004 SPDX-FileCopyrightText: 2007-2011 Kevin Kofler <kevin.kofler@chello.at> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "kompare_shell.h" 0010 0011 #include <QTextStream> 0012 #include <QDockWidget> 0013 #include <QEventLoopLocker> 0014 #include <QFileDialog> 0015 #include <QMimeDatabase> 0016 #include <QPushButton> 0017 #include <QStatusBar> 0018 0019 #include <KTextEditor/Document> 0020 #include <KTextEditor/View> 0021 #include <KParts/PartLoader> 0022 #include <KEditToolBar> 0023 #include <KFile> 0024 #include <KShortcutsDialog> 0025 #include <KLocalizedString> 0026 #include <KMessageBox> 0027 #include <KSqueezedTextLabel> 0028 #include <KStandardAction> 0029 #include <KSharedConfig> 0030 #include <KToggleAction> 0031 #include <KActionCollection> 0032 #include <KConfigGroup> 0033 0034 #include "kompareinterface.h" 0035 #include "kompareurldialog.h" 0036 0037 #define ID_N_OF_N_DIFFERENCES 0 0038 #define ID_N_OF_N_FILES 1 0039 #define ID_GENERAL 2 0040 0041 KompareShell::KompareShell() 0042 : KParts::MainWindow(), 0043 m_textViewPart(nullptr), 0044 m_textViewWidget(nullptr), 0045 m_eventLoopLocker(new QEventLoopLocker()) 0046 { 0047 resize(800, 480); 0048 0049 // set the shell's ui resource file 0050 setXMLFile(QStringLiteral("kompareui.rc")); 0051 0052 // then, setup our actions 0053 setupActions(); 0054 setupStatusBar(); 0055 0056 const auto viewPartLoadResult = KPluginFactory::instantiatePlugin<KParts::ReadWritePart>(KPluginMetaData(QStringLiteral("kf" QT_STRINGIFY(QT_VERSION_MAJOR) "/parts/komparepart")), this); 0057 0058 if (viewPartLoadResult) 0059 { 0060 m_viewPart = viewPartLoadResult.plugin; 0061 setCentralWidget(m_viewPart->widget()); 0062 // and integrate the part's GUI with the shell's 0063 createGUI(m_viewPart); 0064 } 0065 else 0066 { 0067 // if we couldn't load our Part, we exit since the Shell by 0068 // itself can't do anything useful 0069 KMessageBox::error(this, i18n("Could not load our KompareViewPart: %1", viewPartLoadResult.errorString)); 0070 exit(2); 0071 } 0072 0073 m_navTreeDock = new QDockWidget(i18nc("@title:window", "Navigation"), this); 0074 m_navTreeDock->setObjectName(QStringLiteral("Navigation")); 0075 0076 // This part is implemented in KompareNavTreePart 0077 0078 const auto navPartLoadResult = KPluginFactory::instantiatePlugin<KParts::ReadOnlyPart>(KPluginMetaData(QStringLiteral("kf" QT_STRINGIFY(QT_VERSION_MAJOR) "/parts/komparenavtreepart")), m_navTreeDock); 0079 0080 if (navPartLoadResult) 0081 { 0082 m_navTreePart = navPartLoadResult.plugin; 0083 m_navTreeDock->setWidget(m_navTreePart->widget()); 0084 addDockWidget(Qt::TopDockWidgetArea, m_navTreeDock); 0085 // m_navTreeDock->manualDock( m_mainViewDock, KDockWidget::DockTop, 20 ); 0086 } 0087 else 0088 { 0089 // if we couldn't load our Part, we exit since the Shell by 0090 // itself can't do anything useful 0091 KMessageBox::error(this, i18n("Could not load our KompareNavigationPart: %1", navPartLoadResult.errorString)); 0092 exit(4); 0093 } 0094 0095 // Hook up the inter part communication 0096 connect(m_viewPart, SIGNAL(modelsChanged(const KompareDiff2::DiffModelList*)), 0097 m_navTreePart, SLOT(slotModelsChanged(const KompareDiff2::DiffModelList*))); 0098 0099 connect(m_viewPart, SIGNAL(kompareInfo(KompareDiff2::Info*)), 0100 m_navTreePart, SLOT(slotKompareInfo(KompareDiff2::Info*))); 0101 0102 connect(m_navTreePart, SIGNAL(selectionChanged(const KompareDiff2::DiffModel*,const KompareDiff2::Difference*)), 0103 m_viewPart, SIGNAL(selectionChanged(const KompareDiff2::DiffModel*,const KompareDiff2::Difference*))); 0104 connect(m_viewPart, SIGNAL(setSelection(const KompareDiff2::DiffModel*,const KompareDiff2::Difference*)), 0105 m_navTreePart, SLOT(slotSetSelection(const KompareDiff2::DiffModel*,const KompareDiff2::Difference*))); 0106 0107 connect(m_navTreePart, SIGNAL(selectionChanged(const KompareDiff2::Difference*)), 0108 m_viewPart, SIGNAL(selectionChanged(const KompareDiff2::Difference*))); 0109 connect(m_viewPart, SIGNAL(setSelection(const KompareDiff2::Difference*)), 0110 m_navTreePart, SLOT(slotSetSelection(const KompareDiff2::Difference*))); 0111 0112 // This is the interpart interface, it is signal and slot based so no "real" nterface here 0113 // All you have to do is connect the parts from your application. 0114 // These just point to the method with the same name in the ModelList or get called 0115 // from the method with the same name in ModelList. 0116 0117 // There is currently no applying possible from the navtreepart to the viewpart 0118 connect(m_viewPart, SIGNAL(applyDifference(bool)), 0119 m_navTreePart, SLOT(slotApplyDifference(bool))); 0120 connect(m_viewPart, SIGNAL(applyAllDifferences(bool)), 0121 m_navTreePart, SLOT(slotApplyAllDifferences(bool))); 0122 connect(m_viewPart, SIGNAL(applyDifference(const KompareDiff2::Difference*,bool)), 0123 m_navTreePart, SLOT(slotApplyDifference(const KompareDiff2::Difference*,bool))); 0124 0125 // Hook up the KomparePart -> KompareShell communication 0126 connect(m_viewPart, SIGNAL(setStatusBarModelInfo(int,int,int,int,int)), 0127 this, SLOT(slotUpdateStatusBar(int,int,int,int,int))); 0128 connect(m_viewPart, SIGNAL(setStatusBarText(QString)), 0129 this, SLOT(slotSetStatusBarText(QString))); 0130 0131 connect(m_viewPart, SIGNAL(diffString(QString)), 0132 this, SLOT(slotSetDiffString(QString))); 0133 0134 // Read basic main-view settings, and set to autosave 0135 setAutoSaveSettings(QStringLiteral("General Options")); 0136 } 0137 0138 KompareShell::~KompareShell() 0139 { 0140 delete m_eventLoopLocker; 0141 m_eventLoopLocker = nullptr; 0142 } 0143 0144 bool KompareShell::queryClose() 0145 { 0146 bool rv = m_viewPart->queryClose(); 0147 if (rv) 0148 { 0149 close(); 0150 } 0151 return rv; 0152 } 0153 0154 void KompareShell::openDiff(const QUrl& url) 0155 { 0156 qCDebug(KOMPARESHELL) << "Url = " << url.toDisplayString(); 0157 m_diffURL = url; 0158 viewPart()->openDiff(url); 0159 } 0160 0161 void KompareShell::openStdin() 0162 { 0163 qCDebug(KOMPARESHELL) << "Using stdin to read the diff" ; 0164 QFile file; 0165 file.open(stdin, QIODevice::ReadOnly); 0166 QTextStream stream(&file); 0167 0168 QString diff = stream.readAll(); 0169 0170 file.close(); 0171 0172 viewPart()->openDiff(diff); 0173 0174 } 0175 0176 void KompareShell::compare(const QUrl& source, const QUrl& destination) 0177 { 0178 m_sourceURL = source; 0179 m_destinationURL = destination; 0180 0181 viewPart()->compare(source, destination); 0182 } 0183 0184 void KompareShell::blend(const QUrl& url1, const QUrl& diff) 0185 { 0186 m_sourceURL = url1; 0187 m_destinationURL = diff; 0188 0189 viewPart()->openDirAndDiff(url1, diff); 0190 } 0191 0192 void KompareShell::setupActions() 0193 { 0194 QAction* a; 0195 a = KStandardAction::open(this, &KompareShell::slotFileOpen, actionCollection()); 0196 a->setText(i18nc("@action", "&Open Diff...")); 0197 a = actionCollection()->addAction(QStringLiteral("file_compare_files"), this, &KompareShell::slotFileCompareFiles); 0198 a->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); 0199 a->setText(i18nc("@action", "&Compare Files...")); 0200 actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_C)); 0201 a = actionCollection()->addAction(QStringLiteral("file_blend_url"), this, &KompareShell::slotFileBlendURLAndDiff); 0202 a->setText(i18nc("@action", "&Blend URL with Diff...")); 0203 actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_B)); 0204 KStandardAction::quit(this, &KompareShell::slotFileClose, actionCollection()); 0205 0206 createStandardStatusBarAction(); 0207 setStandardToolBarMenuEnabled(true); 0208 m_showTextView = new KToggleAction(i18nc("@action", "Show T&ext View"), this); 0209 // needs a KGuiItem, also the doc says explicitly not to do this 0210 // m_showTextView->setCheckedState(i18n("Hide T&ext View")); 0211 actionCollection()->addAction(QStringLiteral("options_show_text_view"), m_showTextView); 0212 connect(m_showTextView, &KToggleAction::triggered, this, &KompareShell::slotShowTextView); 0213 0214 KStandardAction::keyBindings(this, &KompareShell::optionsConfigureKeys, actionCollection()); 0215 KStandardAction::configureToolbars(this, &KompareShell::optionsConfigureToolbars, actionCollection()); 0216 } 0217 0218 void KompareShell::setupStatusBar() 0219 { 0220 // Made these entries permanent so they will appear on the right side 0221 m_differencesLabel = new QLabel(i18n(" 0 of 0 differences ")); 0222 m_filesLabel = new QLabel(i18n(" 0 of 0 files ")); 0223 statusBar()->insertPermanentWidget(ID_N_OF_N_DIFFERENCES, m_differencesLabel, 0); 0224 statusBar()->insertPermanentWidget(ID_N_OF_N_FILES, m_filesLabel, 0); 0225 0226 m_generalLabel = new KSqueezedTextLabel(QString(), nullptr); 0227 statusBar()->addWidget(m_generalLabel, 1); 0228 m_generalLabel->setAlignment(Qt::AlignLeft); 0229 } 0230 0231 void KompareShell::slotUpdateStatusBar(int modelIndex, int differenceIndex, int modelCount, int differenceCount, int appliedCount) 0232 { 0233 qCDebug(KOMPARESHELL) << "KompareShell::updateStatusBar()" ; 0234 0235 QString fileStr; 0236 QString diffStr; 0237 0238 if (modelIndex >= 0) 0239 fileStr = i18np(" %2 of %1 file ", " %2 of %1 files ", modelCount, modelIndex + 1); 0240 else 0241 fileStr = i18np(" %1 file ", " %1 files ", modelCount); 0242 0243 if (differenceIndex >= 0) 0244 diffStr = i18np(" %2 of %1 difference, %3 applied ", " %2 of %1 differences, %3 applied ", differenceCount , 0245 differenceIndex + 1, appliedCount); 0246 else 0247 diffStr = i18np(" %1 difference ", " %1 differences ", differenceCount); 0248 0249 m_filesLabel->setText(fileStr); 0250 m_differencesLabel->setText(diffStr); 0251 } 0252 0253 void KompareShell::slotSetStatusBarText(const QString& text) 0254 { 0255 m_generalLabel->setText(text); 0256 } 0257 0258 void KompareShell::saveProperties(KConfigGroup& config) 0259 { 0260 // The 'config' object points to the session managed 0261 // config file. Anything you write here will be available 0262 // later when this app is restored 0263 if (m_mode == KompareDiff2::ComparingFiles) 0264 { 0265 config.writeEntry("Mode", "ComparingFiles"); 0266 config.writePathEntry("SourceUrl", m_sourceURL.url()); 0267 config.writePathEntry("DestinationUrl", m_destinationURL.url()); 0268 } 0269 else if (m_mode == KompareDiff2::ShowingDiff) 0270 { 0271 config.writeEntry("Mode", "ShowingDiff"); 0272 config.writePathEntry("DiffUrl", m_diffURL.url()); 0273 } 0274 0275 viewPart()->saveProperties(config.config()); 0276 } 0277 0278 void KompareShell::readProperties(const KConfigGroup& config) 0279 { 0280 // The 'config' object points to the session managed 0281 // config file. This function is automatically called whenever 0282 // the app is being restored. Read in here whatever you wrote 0283 // in 'saveProperties' 0284 0285 QString mode = config.readEntry("Mode", "ComparingFiles"); 0286 if (mode == QLatin1String("ComparingFiles")) 0287 { 0288 m_mode = KompareDiff2::ComparingFiles; 0289 m_sourceURL = QUrl::fromLocalFile(config.readPathEntry("SourceUrl", QString())); 0290 m_destinationURL = QUrl::fromLocalFile(config.readPathEntry("DestinationFile", QString())); 0291 0292 viewPart()->readProperties(const_cast<KConfig*>(config.config())); 0293 0294 viewPart()->compareFiles(m_sourceURL, m_destinationURL); 0295 } 0296 else if (mode == QLatin1String("ShowingDiff")) 0297 { 0298 m_mode = KompareDiff2::ShowingDiff; 0299 m_diffURL = QUrl::fromLocalFile(config.readPathEntry("DiffUrl", QString())); 0300 0301 viewPart()->readProperties(const_cast<KConfig*>(config.config())); 0302 0303 m_viewPart->openUrl(m_diffURL); 0304 } 0305 else 0306 { // just in case something weird has happened, don't restore the diff then 0307 // Bruggie: or when some idiot like me changes the possible values for mode 0308 // IOW, a nice candidate for a kconf_update thingy :) 0309 viewPart()->readProperties(const_cast<KConfig*>(config.config())); 0310 } 0311 } 0312 0313 void KompareShell::slotFileOpen() 0314 { 0315 // FIXME: use different filedialog which gets encoding 0316 QUrl url = QFileDialog::getOpenFileUrl(this, QString(), QUrl(), QMimeDatabase().mimeTypeForName(QStringLiteral("text/x-patch")).filterString()); 0317 if (!url.isEmpty()) { 0318 KompareShell* shell = new KompareShell(); 0319 shell->show(); 0320 shell->openDiff(url); 0321 } 0322 } 0323 0324 void KompareShell::slotFileBlendURLAndDiff() 0325 { 0326 KompareURLDialog dialog(this); 0327 0328 dialog.setWindowTitle(i18nc("@title:window", "Blend File/Folder with diff Output")); 0329 dialog.setFirstGroupBoxTitle(i18nc("@title:group", "File/Folder")); 0330 dialog.setSecondGroupBoxTitle(i18nc("@title:group", "Diff Output")); 0331 0332 QPushButton* okButton = dialog.button(QDialogButtonBox::Ok); 0333 okButton->setText(i18nc("@action:button", "Blend")); 0334 okButton->setToolTip(i18nc("@info:tooltip", "Blend this file or folder with the diff output")); 0335 okButton->setWhatsThis(i18nc("@infor:whatsthis", "If you have entered a file or folder name and a file that contains diff output in the fields in this dialog then this button will be enabled and pressing it will open kompare's main view where the output of the entered file or files from the folder are mixed with the diff output so you can then apply the difference(s) to a file or to the files. ")); 0336 0337 dialog.setGroup(QStringLiteral("Recent Blend Files")); 0338 0339 dialog.setFirstURLRequesterMode(KFile::File | KFile::Directory | KFile::ExistingOnly); 0340 // diff output can not be a directory 0341 dialog.setSecondURLRequesterMode(KFile::File | KFile::ExistingOnly); 0342 if (dialog.exec() == QDialog::Accepted) 0343 { 0344 m_sourceURL = dialog.getFirstURL(); 0345 m_destinationURL = dialog.getSecondURL(); 0346 // Leak??? 0347 KompareShell* shell = new KompareShell(); 0348 shell->show(); 0349 shell->viewPart()->setEncoding(dialog.encoding()); 0350 shell->blend(m_sourceURL, m_destinationURL); 0351 } 0352 } 0353 0354 void KompareShell::slotFileCompareFiles() 0355 { 0356 KompareURLDialog dialog(this); 0357 0358 dialog.setWindowTitle(i18nc("@title:window", "Compare Files or Folders")); 0359 dialog.setFirstGroupBoxTitle(i18nc("@title:group", "Source")); 0360 dialog.setSecondGroupBoxTitle(i18nc("@title:group", "Destination")); 0361 0362 QPushButton* okButton = dialog.button(QDialogButtonBox::Ok); 0363 okButton->setText(i18nc("@action:button", "Compare")); 0364 okButton->setToolTip(i18nc("@info:tooltip", "Compare these files or folders")); 0365 okButton->setWhatsThis(i18nc("@info:whatsthis", "If you have entered 2 filenames or 2 folders in the fields in this dialog then this button will be enabled and pressing it will start a comparison of the entered files or folders. ")); 0366 0367 dialog.setGroup(QStringLiteral("Recent Compare Files")); 0368 0369 dialog.setFirstURLRequesterMode(KFile::File | KFile::Directory | KFile::ExistingOnly); 0370 dialog.setSecondURLRequesterMode(KFile::File | KFile::Directory | KFile::ExistingOnly); 0371 0372 if (dialog.exec() == QDialog::Accepted) 0373 { 0374 m_sourceURL = dialog.getFirstURL(); 0375 m_destinationURL = dialog.getSecondURL(); 0376 KompareShell* shell = new KompareShell(); 0377 shell->show(); 0378 qCDebug(KOMPARESHELL) << "The encoding is: " << dialog.encoding() ; 0379 shell->viewPart()->setEncoding(dialog.encoding()); 0380 shell->compare(m_sourceURL, m_destinationURL); 0381 } 0382 } 0383 0384 void KompareShell::slotFileClose() 0385 { 0386 if (m_viewPart->queryClose()) 0387 { 0388 close(); 0389 } 0390 } 0391 0392 void KompareShell::slotShowTextView() 0393 { 0394 if (!m_textViewWidget) 0395 { 0396 QString error; 0397 0398 // FIXME: proper error checking 0399 m_textViewWidget = new QDockWidget(i18nc("@title:window", "Text View"), this); 0400 m_textViewWidget->setObjectName(QStringLiteral("Text View")); 0401 // m_textViewWidget = createDockWidget(i18n("Text View"), SmallIcon("text-x-generic")); 0402 0403 const auto result = KParts::PartLoader::instantiatePart<KTextEditor::Document>(KPluginMetaData(QStringLiteral("kf" QT_STRINGIFY(QT_VERSION_MAJOR) "/parts/katepart")), this, this); 0404 0405 if (result) 0406 { 0407 m_textViewPart = result.plugin; 0408 m_textView = qobject_cast<KTextEditor::View*>(m_textViewPart->createView(this)); 0409 m_textViewWidget->setWidget(static_cast<QWidget*>(m_textView)); 0410 m_textViewPart->setHighlightingMode(QStringLiteral("Diff")); 0411 m_textViewPart->setText(m_diffString); 0412 } 0413 m_textViewWidget->show(); 0414 connect(m_textViewWidget, &QDockWidget::visibilityChanged, 0415 this, &KompareShell::slotVisibilityChanged); 0416 } 0417 else if (m_textViewWidget->isVisible()) 0418 m_textViewWidget->hide(); 0419 else 0420 m_textViewWidget->show(); 0421 0422 addDockWidget(Qt::BottomDockWidgetArea, m_textViewWidget); 0423 // m_textViewWidget->manualDock(m_mainViewDock, KDockWidget:: DockCenter); 0424 } 0425 0426 void KompareShell::slotVisibilityChanged(bool visible) 0427 { 0428 m_showTextView->setChecked(visible); 0429 } 0430 0431 void KompareShell::slotSetDiffString(const QString& diffString) 0432 { 0433 if (m_textViewPart) 0434 m_textViewPart->setText(diffString); 0435 m_diffString = diffString; 0436 } 0437 0438 void KompareShell::optionsConfigureKeys() 0439 { 0440 KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this); 0441 0442 dlg.addCollection(actionCollection()); 0443 if (m_viewPart) 0444 dlg.addCollection(m_viewPart->actionCollection()); 0445 0446 dlg.configure(); 0447 } 0448 0449 void KompareShell::optionsConfigureToolbars() 0450 { 0451 KConfigGroup group(KSharedConfig::openConfig(), autoSaveGroup()); 0452 saveMainWindowSettings(group); 0453 // use the standard toolbar editor 0454 KEditToolBar dlg(factory()); 0455 connect(&dlg, &KEditToolBar::newToolBarConfig, this, &KompareShell::newToolbarConfig); 0456 dlg.exec(); 0457 } 0458 0459 void KompareShell::newToolbarConfig() 0460 { 0461 KConfigGroup group(KSharedConfig::openConfig(), autoSaveGroup()); 0462 applyMainWindowSettings(group); 0463 } 0464 0465 KompareInterface* KompareShell::viewPart() const 0466 { 0467 return qobject_cast<KompareInterface*>(m_viewPart); 0468 } 0469 0470 #include "moc_kompare_shell.cpp"