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"