File indexing completed on 2025-02-16 11:39:48
0001 // clang-format off 0002 /* 0003 * This file is part of KDiff3. 0004 * 0005 * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de 0006 * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com 0007 * SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 // clang-format on 0010 0011 // application specific includes 0012 #include "kdiff3.h" 0013 0014 #include "compat.h" 0015 #include "defmac.h" 0016 #include "difftextwindow.h" 0017 #include "directorymergewindow.h" 0018 #include "fileaccess.h" 0019 #include "guiutils.h" 0020 #include "kdiff3_shell.h" 0021 #include "mergeresultwindow.h" 0022 #include "optiondialog.h" 0023 #include "progress.h" 0024 #include "RLPainter.h" 0025 #include "smalldialogs.h" 0026 #include "Utils.h" 0027 // Standard c/c++ includes 0028 #include <memory> 0029 #ifndef Q_OS_WIN 0030 #include <unistd.h> 0031 #endif 0032 #include <utility> 0033 // Anything else that isn't Qt/Frameworks 0034 #include <boost/signals2.hpp> 0035 // include files for QT 0036 #include <QCheckBox> 0037 #include <QClipboard> 0038 #include <QCommandLineParser> 0039 #include <QDir> 0040 #include <QDockWidget> 0041 #include <QFileDialog> 0042 #include <QLabel> 0043 #include <QLayout> 0044 #include <QLineEdit> 0045 #include <QMenu> 0046 #include <QMenuBar> 0047 #include <QPaintDevice> 0048 #include <QPainter> 0049 #include <QPointer> 0050 #include <QPrintDialog> 0051 #include <QPrinter> 0052 #include <QPushButton> 0053 #include <QScreen> 0054 #include <QShortcut> 0055 #include <QSplitter> 0056 #include <QStatusBar> 0057 #include <QTextEdit> 0058 #include <QTextStream> 0059 #include <QUrl> 0060 // include files for KDE 0061 #include <KActionCollection> 0062 #include <KConfig> 0063 #include <KCrash> 0064 #include <KLocalizedString> 0065 #include <KMessageBox> 0066 #include <KStandardAction> 0067 #include <KToggleAction> 0068 #include <KToolBar> 0069 0070 bool KDiff3App::m_bTripleDiff = false; 0071 std::unique_ptr<Options> gOptions = std::make_unique<Options>(); 0072 0073 boost::signals2::signal<QString(), FirstNonEmpty<QString>> KDiff3App::getSelection; 0074 boost::signals2::signal<bool(), or_> KDiff3App::allowCopy; 0075 boost::signals2::signal<bool(), or_> KDiff3App::allowCut; 0076 0077 /* 0078 To be a constexpr the QLatin1String constructor must be given the size of the string explicitly. 0079 Otherwise it calls strlen which is not a constexpr. 0080 */ 0081 constexpr QLatin1String MAIN_TOOLBAR_NAME = QLatin1String("mainToolBar", sizeof("mainToolBar") - 1); 0082 0083 KActionCollection* KDiff3App::actionCollection() const 0084 { 0085 assert(m_pKDiff3Shell != nullptr); 0086 0087 return m_pKDiff3Shell->actionCollection(); 0088 } 0089 0090 QStatusBar* KDiff3App::statusBar() const 0091 { 0092 assert(m_pKDiff3Shell != nullptr); 0093 0094 return m_pKDiff3Shell->statusBar(); 0095 } 0096 0097 KToolBar* KDiff3App::toolBar(const QLatin1String& toolBarId) const 0098 { 0099 assert(m_pKDiff3Shell != nullptr); 0100 return m_pKDiff3Shell->toolBar(toolBarId); 0101 } 0102 0103 bool KDiff3App::isFileSaved() const 0104 { 0105 return m_bFileSaved; 0106 } 0107 0108 bool KDiff3App::isDirComparison() const 0109 { 0110 return m_bDirCompare; 0111 } 0112 0113 /* 0114 Don't call completeInit from here it will be called in KDiff3Shell as needed. 0115 */ 0116 KDiff3App::KDiff3App(QWidget* pParent, const QString& name, KDiff3Shell* pKDiff3Shell): 0117 QMainWindow(pParent) 0118 { 0119 setWindowFlags(Qt::Widget); 0120 setObjectName(name); 0121 m_pKDiff3Shell = pKDiff3Shell; 0122 0123 m_pCentralWidget = new QWidget(this); 0124 QVBoxLayout* pCentralLayout = new QVBoxLayout(m_pCentralWidget); 0125 pCentralLayout->setContentsMargins(0, 0, 0, 0); 0126 pCentralLayout->setSpacing(0); 0127 setCentralWidget(m_pCentralWidget); 0128 0129 m_pMainWidget = new QWidget(m_pCentralWidget); 0130 m_pMainWidget->setObjectName("MainWidget"); 0131 pCentralLayout->addWidget(m_pMainWidget); 0132 m_pMainWidget->hide(); 0133 0134 setWindowTitle("KDiff3"); 0135 setUpdatesEnabled(false); 0136 0137 // set Disabled to same color as enabled to prevent flicker in DirectoryMergeWindow 0138 QPalette pal; 0139 pal.setBrush(QPalette::Base, pal.brush(QPalette::Active, QPalette::Base)); 0140 pal.setColor(QPalette::Text, pal.color(QPalette::Active, QPalette::Text)); 0141 setPalette(pal); 0142 0143 // Setup progress ProgressDialog. No progress can be shown before this point. 0144 // ProgressProxy will otherwise emit no-ops to disconnected boost signals. 0145 if(g_pProgressDialog == nullptr) 0146 { 0147 g_pProgressDialog = new ProgressDialog(this, statusBar()); 0148 g_pProgressDialog->setStayHidden(true); 0149 } 0150 0151 // All default values must be set before calling readOptions(). 0152 m_pOptionDialog = new OptionDialog(m_pKDiff3Shell != nullptr, this); 0153 chk_connect_a(m_pOptionDialog, &OptionDialog::applyDone, this, &KDiff3App::slotRefresh); 0154 0155 m_pOptionDialog->readOptions(KSharedConfig::openConfig()); 0156 0157 // Option handling. 0158 QtSizeType argCount = KDiff3Shell::getParser()->optionNames().count() + KDiff3Shell::getParser()->positionalArguments().count(); 0159 bool hasArgs = argCount > 0; 0160 if(hasArgs) 0161 { 0162 QString s; 0163 QString title; 0164 if(KDiff3Shell::getParser()->isSet("confighelp")) 0165 { 0166 s = m_pOptionDialog->calcOptionHelp(); 0167 title = i18n("Current Configuration:"); 0168 } 0169 else 0170 { 0171 s = m_pOptionDialog->parseOptions(KDiff3Shell::getParser()->values("cs")); 0172 title = i18n("Config Option Error:"); 0173 } 0174 if(!s.isEmpty()) 0175 { 0176 #ifndef Q_OS_WIN 0177 if(isatty(fileno(stderr)) != 1) 0178 #endif 0179 { 0180 QPointer<QDialog> pDialog = QPointer<QDialog>(new QDialog(this)); 0181 pDialog->setAttribute(Qt::WA_DeleteOnClose); 0182 pDialog->setModal(true); 0183 pDialog->setWindowTitle(title); 0184 QPointer<QVBoxLayout> pVBoxLayout = new QVBoxLayout(pDialog); 0185 QPointer<QTextEdit> pTextEdit = QPointer<QTextEdit>(new QTextEdit(pDialog)); 0186 pTextEdit->setText(s); 0187 pTextEdit->setReadOnly(true); 0188 pTextEdit->setWordWrapMode(QTextOption::NoWrap); 0189 pVBoxLayout->addWidget(pTextEdit); 0190 pDialog->resize(600, 400); 0191 pDialog->exec(); 0192 } 0193 #if !defined(Q_OS_WIN) 0194 else 0195 { 0196 // Launched from a console 0197 QTextStream outStream(stdout); 0198 outStream << title << "\n"; 0199 outStream << s;//newline already appended by parseOptions 0200 } 0201 #endif 0202 exit(1); 0203 } 0204 } 0205 0206 #ifdef ENABLE_AUTO 0207 m_bAutoFlag = hasArgs && KDiff3Shell::getParser()->isSet("auto") && !KDiff3Shell::getParser()->isSet("noauto"); 0208 #else 0209 m_bAutoFlag = false; 0210 #endif 0211 0212 m_bAutoMode = m_bAutoFlag || gOptions->m_bAutoSaveAndQuitOnMergeWithoutConflicts; 0213 if(hasArgs) 0214 { 0215 m_outputFilename = KDiff3Shell::getParser()->value("output"); 0216 0217 if(m_outputFilename.isEmpty()) 0218 m_outputFilename = KDiff3Shell::getParser()->value("out"); 0219 0220 if(!m_outputFilename.isEmpty()) 0221 m_outputFilename = FileAccess(m_outputFilename, true).absoluteFilePath(); 0222 0223 if(m_bAutoMode && m_outputFilename.isEmpty()) 0224 { 0225 if(m_bAutoFlag) 0226 { 0227 QTextStream(stderr) << i18n("Option --auto used, but no output file specified.") << "\n"; 0228 } 0229 m_bAutoMode = false; 0230 } 0231 0232 if(m_outputFilename.isEmpty() && KDiff3Shell::getParser()->isSet("merge")) 0233 { 0234 m_outputFilename = "unnamed.txt"; 0235 m_bDefaultFilename = true; 0236 } 0237 else 0238 { 0239 m_bDefaultFilename = false; 0240 } 0241 0242 QStringList args = KDiff3Shell::getParser()->positionalArguments(); 0243 0244 m_sd1->setFilename(KDiff3Shell::getParser()->value("base")); 0245 if(m_sd1->isEmpty()) 0246 { 0247 if(args.count() > 0) m_sd1->setFilename(args[0]); // args->arg(0) 0248 if(args.count() > 1) m_sd2->setFilename(args[1]); 0249 if(args.count() > 2) m_sd3->setFilename(args[2]); 0250 } 0251 else 0252 { 0253 if(args.count() > 0) m_sd2->setFilename(args[0]); 0254 if(args.count() > 1) m_sd3->setFilename(args[1]); 0255 } 0256 //Set m_bDirCompare flag 0257 m_bDirCompare = m_sd1->isDir(); 0258 0259 QStringList aliasList = KDiff3Shell::getParser()->values("fname"); 0260 QStringList::Iterator ali = aliasList.begin(); 0261 0262 QString an1 = KDiff3Shell::getParser()->value("L1"); 0263 if(!an1.isEmpty()) 0264 { 0265 m_sd1->setAliasName(an1); 0266 } 0267 else if(ali != aliasList.end()) 0268 { 0269 m_sd1->setAliasName(*ali); 0270 ++ali; 0271 } 0272 0273 QString an2 = KDiff3Shell::getParser()->value("L2"); 0274 if(!an2.isEmpty()) 0275 { 0276 m_sd2->setAliasName(an2); 0277 } 0278 else if(ali != aliasList.end()) 0279 { 0280 m_sd2->setAliasName(*ali); 0281 ++ali; 0282 } 0283 0284 QString an3 = KDiff3Shell::getParser()->value("L3"); 0285 if(!an3.isEmpty()) 0286 { 0287 m_sd3->setAliasName(an3); 0288 } 0289 else if(ali != aliasList.end()) 0290 { 0291 m_sd3->setAliasName(*ali); 0292 ++ali; 0293 } 0294 } 0295 else 0296 { 0297 m_bDefaultFilename = false; 0298 } 0299 g_pProgressDialog->setStayHidden(m_bAutoMode); 0300 0301 /////////////////////////////////////////////////////////////////// 0302 // call inits to invoke all other construction parts 0303 // Warning: Call this before connecting KDiff3App::slotUpdateAvailabilities or 0304 // calling KXMLGUIClient::setXMLFile or KXMLGUIClient::createGUI 0305 initActions(actionCollection()); 0306 0307 initStatusBar(); 0308 0309 m_pFindDialog = new FindDialog(this); 0310 chk_connect_a(m_pFindDialog, &FindDialog::findNext, this, &KDiff3App::slotEditFindNext); 0311 0312 mEscapeAction->setEnabled(gOptions->m_bEscapeKeyQuits); 0313 autoAdvance->setChecked(gOptions->m_bAutoAdvance); 0314 showWhiteSpaceCharacters->setChecked(gOptions->m_bShowWhiteSpaceCharacters); 0315 showWhiteSpace->setChecked(gOptions->m_bShowWhiteSpace); 0316 showWhiteSpaceCharacters->setEnabled(gOptions->m_bShowWhiteSpace); 0317 showLineNumbers->setChecked(gOptions->m_bShowLineNumbers); 0318 wordWrap->setChecked(gOptions->wordWrapOn()); 0319 0320 /* 0321 No need to restore window size and position here that is done later. 0322 See KDiff3App::completeInit 0323 */ 0324 viewStatusBar->setChecked(gOptions->isStatusBarVisible()); 0325 slotViewStatusBar(); 0326 0327 KToolBar* mainToolBar = toolBar(MAIN_TOOLBAR_NAME); 0328 if(mainToolBar != nullptr) 0329 { 0330 mainToolBar->mainWindow()->addToolBar(Qt::TopToolBarArea, mainToolBar); 0331 } 0332 0333 slotRefresh(); 0334 0335 m_pDirectoryMergeDock = new QDockWidget(i18n("Directory merge"), this); 0336 m_pDirectoryMergeWindow = new DirectoryMergeWindow(m_pDirectoryMergeDock, *this); 0337 m_pDirectoryMergeDock->setObjectName("DirectoryMergeDock"); 0338 m_pDirectoryMergeDock->setWidget(m_pDirectoryMergeWindow); 0339 m_pDirectoryMergeDock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); 0340 m_pDirectoryMergeInfoDock = new QDockWidget(i18n("Merge info"), this); 0341 m_pDirectoryMergeInfoDock->setObjectName("MergeInfoDock"); 0342 m_pDirectoryMergeInfo = new DirectoryMergeInfo(m_pDirectoryMergeInfoDock); 0343 m_pDirectoryMergeInfoDock->setWidget(m_pDirectoryMergeInfo); 0344 m_pDirectoryMergeInfoDock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); 0345 m_pDirectoryMergeWindow->setDirectoryMergeInfo(m_pDirectoryMergeInfo); 0346 //Warning: Make sure DirectoryMergeWindow::initActions is called before this point or we can crash when selectionChanged is sent. 0347 m_pDirectoryMergeWindow->setupConnections(this); 0348 addDockWidget(Qt::LeftDockWidgetArea, m_pDirectoryMergeDock); 0349 splitDockWidget(m_pDirectoryMergeDock, m_pDirectoryMergeInfoDock, Qt::Vertical); 0350 0351 chk_connect_a(QApplication::clipboard(), &QClipboard::dataChanged, this, &KDiff3App::slotClipboardChanged); 0352 chk_connect_q(this, &KDiff3App::sigRecalcWordWrap, this, &KDiff3App::slotRecalcWordWrap); 0353 chk_connect_a(this, &KDiff3App::finishDrop, this, &KDiff3App::slotFinishDrop); 0354 0355 connections.push_back(allowCut.connect(boost::bind(&KDiff3App::canCut, this))); 0356 connections.push_back(allowCopy.connect(boost::bind(&KDiff3App::canCopy, this))); 0357 0358 m_pDirectoryMergeWindow->initDirectoryMergeActions(this, actionCollection()); 0359 0360 if(qApp != nullptr) 0361 chk_connect_a(qApp, &QApplication::focusChanged, this, &KDiff3App::slotFocusChanged); 0362 0363 chk_connect_a(this, &KDiff3App::updateAvailabilities, this, &KDiff3App::slotUpdateAvailabilities); 0364 } 0365 0366 /* 0367 This function is only concerned with qt objects that don't support canCut. 0368 allowCut() or's the results from all canCut signals 0369 0370 returns true if a QLineEdit or QLineEdit is in focus because Qt handles these internally. 0371 */ 0372 bool KDiff3App::canCut() 0373 { 0374 QWidget* focus = focusWidget(); 0375 0376 return (qobject_cast<QLineEdit*>(focus) != nullptr || qobject_cast<QTextEdit*>(focus) != nullptr); 0377 } 0378 0379 /* 0380 Please do not add logic for MergeResultWindow or DiffTextWindow here they have their own handlers. 0381 This function is only concerned with qt objects that don't support canCopy. 0382 allowCopy() or's the results from all canCopy signals sent via boost. 0383 0384 returns true if a QLineEdit or QLineEdit is in focus because Qt handles these internally. 0385 */ 0386 bool KDiff3App::canCopy() 0387 { 0388 QWidget* focus = focusWidget(); 0389 0390 return (qobject_cast<QLineEdit*>(focus) != nullptr || qobject_cast<QTextEdit*>(focus) != nullptr); 0391 } 0392 /* 0393 Make sure Edit menu tracks focus correctly. 0394 */ 0395 void KDiff3App::slotFocusChanged([[maybe_unused]] QWidget* old, [[maybe_unused]] QWidget* now) 0396 { 0397 qCDebug(kdiffMain) << "[KDiff3App::slotFocusChanged] old = " << old << ", new =" << now; 0398 //This needs to be called after the focus change is complete 0399 QMetaObject::invokeMethod(this, &KDiff3App::updateAvailabilities, Qt::QueuedConnection); 0400 } 0401 0402 // Restore and show mainWindow. 0403 void KDiff3App::showMainWindow() 0404 { 0405 if(!m_pKDiff3Shell->isVisible() && !restoreWindow(KSharedConfig::openConfig())) 0406 { 0407 /* 0408 Set default state/geometry from config file. 0409 This will no longer be updated but serves as a fallback. 0410 Qt's restoreState/saveState can handle multiple screens this won't. 0411 */ 0412 if(gOptions->isFullScreen()) 0413 m_pKDiff3Shell->showFullScreen(); 0414 else if(gOptions->isMaximized()) 0415 m_pKDiff3Shell->showMaximized(); 0416 0417 QSize size = gOptions->getGeometry(); 0418 QPoint pos = gOptions->getPosition(); 0419 0420 if(!size.isEmpty()) 0421 { 0422 m_pKDiff3Shell->resize(size); 0423 0424 QRect visibleRect = QRect(pos, size) & m_pKDiff3Shell->screen()->availableGeometry(); 0425 if(visibleRect.width() > 100 && visibleRect.height() > 100) 0426 m_pKDiff3Shell->move(pos); 0427 } 0428 } 0429 0430 m_pKDiff3Shell->show(); 0431 } 0432 0433 // Do file comparision. 0434 void KDiff3App::doFileCompare() 0435 { 0436 improveFilenames(); 0437 m_pDirectoryMergeDock->hide(); 0438 m_pDirectoryMergeInfoDock->hide(); 0439 0440 mainInit(m_totalDiffStatus); 0441 } 0442 0443 void KDiff3App::completeInit(const QString& fn1, const QString& fn2, const QString& fn3) 0444 { 0445 bool openError = false; 0446 bool bSuccess = true; 0447 0448 if(!fn1.isEmpty()) 0449 { 0450 m_sd1->setFilename(fn1); 0451 m_bDirCompare = m_sd1->isDir(); 0452 } 0453 if(!fn2.isEmpty()) 0454 { 0455 m_sd2->setFilename(fn2); 0456 } 0457 if(!fn3.isEmpty()) 0458 { 0459 m_sd3->setFilename(fn3); 0460 } 0461 0462 //Should not fail ever. 0463 assert(m_bDirCompare == m_sd1->isDir()); 0464 if((m_bDirCompare && (!m_sd2->isDir() || !(m_sd3->isValid() && m_sd3->isDir()))) || 0465 (!m_bDirCompare && (m_sd2->isDir() || m_sd3->isDir()))) 0466 { 0467 KMessageBox::error(this, i18nc("Error message", "Can't compare file with folder."), 0468 i18nc("Title error message box", "Bad comparison attempt")); 0469 0470 bSuccess = false; 0471 m_bDirCompare = false; 0472 openError = true; 0473 } 0474 0475 if(m_bAutoFlag && m_bAutoMode && m_bDirCompare) 0476 { 0477 QTextStream(stderr) << i18n("Option --auto ignored for folder comparison.") << "\n"; 0478 m_bAutoMode = false; 0479 } 0480 0481 if(!m_bAutoMode) 0482 showMainWindow(); 0483 0484 g_pProgressDialog->setStayHidden(false); 0485 //initView does first time setup for ui. 0486 initView(); 0487 0488 if(bSuccess) 0489 { 0490 if(m_bDirCompare) 0491 bSuccess = doDirectoryCompare(false); 0492 else 0493 { 0494 doFileCompare(); 0495 if(m_totalDiffStatus->getUnsolvedConflicts() != 0) 0496 bSuccess = false; 0497 0498 if(m_bAutoMode && m_totalDiffStatus->getUnsolvedConflicts() == 0) 0499 { 0500 QSharedPointer<SourceData> pSD = nullptr; 0501 if(m_sd3->isEmpty()) 0502 { 0503 if(m_totalDiffStatus->isBinaryEqualAB()) 0504 { 0505 pSD = m_sd1; 0506 } 0507 } 0508 else 0509 { 0510 if(m_totalDiffStatus->isBinaryEqualBC() || m_totalDiffStatus->isBinaryEqualAB()) 0511 { 0512 //if B==C (assume A is old), if A==B then C has changed 0513 pSD = m_sd3; 0514 } 0515 else if(m_totalDiffStatus->isBinaryEqualAC()) 0516 { 0517 pSD = m_sd2; // assuming B has changed 0518 } 0519 } 0520 0521 if(pSD != nullptr) 0522 { 0523 // Save this file directly, not via the merge result window. 0524 FileAccess fa(m_outputFilename); 0525 if(gOptions->m_bDmCreateBakFiles && fa.exists()) 0526 { 0527 fa.createBackup(".orig"); 0528 } 0529 0530 bSuccess = pSD->saveNormalDataAs(m_outputFilename); 0531 if(!bSuccess) 0532 KMessageBox::error(this, i18n("Saving failed.")); 0533 } 0534 else if(m_pMergeResultWindow->getNumberOfUnsolvedConflicts() == 0) 0535 { 0536 bSuccess = m_pMergeResultWindow->saveDocument(m_pMergeResultWindowTitle->getFileName(), m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle()); 0537 } 0538 if(bSuccess) 0539 { 0540 qApp->exit(0); 0541 } 0542 } 0543 } 0544 } 0545 0546 if(bSuccess && m_bAutoMode) return; 0547 if(m_bAutoMode) 0548 showMainWindow(); 0549 0550 0551 m_bAutoMode = false; 0552 0553 if(statusBar() != nullptr) 0554 statusBar()->setSizeGripEnabled(true); 0555 0556 slotClipboardChanged(); // For initialisation. 0557 0558 Q_EMIT updateAvailabilities(); 0559 0560 if(!m_bDirCompare) 0561 { 0562 if((!m_sd1->getErrors().isEmpty()) || 0563 (!m_sd2->getErrors().isEmpty()) || 0564 (!m_sd3->getErrors().isEmpty())) 0565 { 0566 QString text(i18n("Opening of these files failed:")); 0567 text += "\n\n"; 0568 if(!m_sd1->getErrors().isEmpty()) 0569 text += " - " + m_sd1->getAliasName() + '\n' + m_sd1->getErrors().join('\n') + '\n'; 0570 if(!m_sd2->getErrors().isEmpty()) 0571 text += " - " + m_sd2->getAliasName() + '\n' + m_sd2->getErrors().join('\n') + '\n'; 0572 if(!m_sd3->getErrors().isEmpty()) 0573 text += " - " + m_sd3->getAliasName() + '\n' + m_sd3->getErrors().join('\n') + '\n'; 0574 0575 KMessageBox::error(this, text, i18n("File open error")); 0576 0577 openError = true; 0578 } 0579 0580 if(m_sd1->isEmpty() || m_sd2->isEmpty() || openError) 0581 slotFileOpen(); 0582 } 0583 else if(!bSuccess) // Directory open failed 0584 { 0585 slotFileOpen(); 0586 } 0587 } 0588 0589 KDiff3App::~KDiff3App() 0590 { 0591 delete m_totalDiffStatus; 0592 0593 if(mRunnablesStarted) 0594 { 0595 g_pProgressDialog->cancel(ProgressDialog::eExit); 0596 } 0597 0598 // Prevent spurious focus change signals from Qt from being picked up by KDiff3App during destruction. 0599 QObject::disconnect(qApp, &QApplication::focusChanged, this, &KDiff3App::slotFocusChanged); 0600 }; 0601 0602 /** 0603 * Helper function used to create actions into the ac collection 0604 */ 0605 0606 void KDiff3App::initActions(KActionCollection* ac) 0607 { 0608 if(Q_UNLIKELY(ac == nullptr)) 0609 { 0610 KMessageBox::error(nullptr, "actionCollection==0"); 0611 exit(-1); //we cannot recover from this. 0612 } 0613 fileOpen = KStandardAction::open(this, &KDiff3App::slotFileOpen, ac); 0614 fileOpen->setStatusTip(i18n("Opens documents for comparison...")); 0615 0616 fileReload = GuiUtils::createAction<QAction>(i18n("Reload"), QKeySequence(QKeySequence::Refresh), this, &KDiff3App::slotReload, ac, u8"file_reload"); 0617 0618 fileSave = KStandardAction::save(this, &KDiff3App::slotFileSave, ac); 0619 fileSave->setStatusTip(i18n("Saves the merge result. All conflicts must be solved!")); 0620 fileSaveAs = KStandardAction::saveAs(this, &KDiff3App::slotFileSaveAs, ac); 0621 fileSaveAs->setStatusTip(i18n("Saves the current document as...")); 0622 #ifndef QT_NO_PRINTER 0623 filePrint = KStandardAction::print(this, &KDiff3App::slotFilePrint, ac); 0624 filePrint->setStatusTip(i18n("Print the differences")); 0625 #endif 0626 fileQuit = KStandardAction::quit(this, &KDiff3App::slotFileQuit, ac); 0627 fileQuit->setStatusTip(i18n("Quits the application")); 0628 0629 editUndo = KStandardAction::undo(this, &KDiff3App::slotEditUndo, ac); 0630 editUndo->setShortcuts(QKeySequence::Undo); 0631 editUndo->setStatusTip(i18n("Undo last action.")); 0632 0633 editCut = KStandardAction::cut(this, &KDiff3App::slotEditCut, ac); 0634 editCut->setShortcuts(QKeySequence::Cut); 0635 editCut->setStatusTip(i18n("Cuts the selected section and puts it to the clipboard")); 0636 editCopy = KStandardAction::copy(this, &KDiff3App::slotEditCopy, ac); 0637 editCopy->setShortcut(QKeySequence::Copy); 0638 editCopy->setStatusTip(i18n("Copies the selected section to the clipboard")); 0639 editPaste = KStandardAction::paste(this, &KDiff3App::slotEditPaste, ac); 0640 editPaste->setStatusTip(i18n("Pastes the clipboard contents to current position")); 0641 editPaste->setShortcut(QKeySequence::Paste); 0642 editSelectAll = KStandardAction::selectAll(this, &KDiff3App::slotEditSelectAll, ac); 0643 editSelectAll->setStatusTip(i18n("Select everything in current window")); 0644 editFind = KStandardAction::find(this, &KDiff3App::slotEditFind, ac); 0645 editFind->setShortcut(QKeySequence::Find); 0646 editFind->setStatusTip(i18n("Search for a string")); 0647 editFindNext = KStandardAction::findNext(this, &KDiff3App::slotEditFindNext, ac); 0648 editFindNext->setStatusTip(i18n("Search again for the string")); 0649 0650 viewStatusBar = KStandardAction::showStatusbar(this, &KDiff3App::slotViewStatusBar, ac); 0651 viewStatusBar->setStatusTip(i18n("Enables/disables the statusbar")); 0652 KStandardAction::keyBindings(this, &KDiff3App::slotConfigureKeys, ac); 0653 QAction* pAction = KStandardAction::preferences(this, &KDiff3App::slotConfigure, ac); 0654 pAction->setText(i18n("Configure KDiff3...")); 0655 0656 #include "xpm/autoadvance.xpm" 0657 #include "xpm/currentpos.xpm" 0658 #include "xpm/down1arrow.xpm" 0659 #include "xpm/down2arrow.xpm" 0660 #include "xpm/downend.xpm" 0661 #include "xpm/gotoline.xpm" 0662 #include "xpm/iconA.xpm" 0663 #include "xpm/iconB.xpm" 0664 #include "xpm/iconC.xpm" 0665 #include "xpm/nextunsolved.xpm" 0666 #include "xpm/prevunsolved.xpm" 0667 #include "xpm/showlinenumbers.xpm" 0668 #include "xpm/showwhitespace.xpm" 0669 #include "xpm/showwhitespacechars.xpm" 0670 #include "xpm/up1arrow.xpm" 0671 #include "xpm/up2arrow.xpm" 0672 #include "xpm/upend.xpm" 0673 0674 mGoCurrent = GuiUtils::createAction<QAction>(i18n("Go to Current Delta"), QIcon(QPixmap(currentpos)), i18n("Current\nDelta"), QKeySequence(Qt::CTRL | Qt::Key_Space), this, &KDiff3App::slotGoCurrent, ac, "go_current"); 0675 0676 mGoTop = GuiUtils::createAction<QAction>(i18n("Go to First Delta"), QIcon(QPixmap(upend)), i18n("First\nDelta"), this, &KDiff3App::slotGoTop, ac, "go_top"); 0677 0678 mGoBottom = GuiUtils::createAction<QAction>(i18n("Go to Last Delta"), QIcon(QPixmap(downend)), i18n("Last\nDelta"), this, &KDiff3App::slotGoBottom, ac, "go_bottom"); 0679 0680 QString omitsWhitespace = ".\n" + i18nc("Tooltip explanation text", "(Skips white space differences when \"Show White Space\" is disabled.)"); 0681 QString includeWhitespace = ".\n" + i18nc("Tooltip explanation text", "(Does not skip white space differences even when \"Show White Space\" is disabled.)"); 0682 mGoPrevDelta = GuiUtils::createAction<QAction>(i18n("Go to Previous Delta"), QIcon(QPixmap(up1arrow)), i18n("Prev\nDelta"), QKeySequence(Qt::CTRL | Qt::Key_Up), this, &KDiff3App::slotGoPrevDelta, ac, "go_prev_delta"); 0683 mGoPrevDelta->setToolTip(mGoPrevDelta->text() + omitsWhitespace); 0684 mGoNextDelta = GuiUtils::createAction<QAction>(i18n("Go to Next Delta"), QIcon(QPixmap(down1arrow)), i18n("Next\nDelta"), QKeySequence(Qt::CTRL | Qt::Key_Down), this, &KDiff3App::slotGoNextDelta, ac, "go_next_delta"); 0685 mGoNextDelta->setToolTip(mGoNextDelta->text() + omitsWhitespace); 0686 mGoPrevConflict = GuiUtils::createAction<QAction>(i18n("Go to Previous Conflict"), QIcon(QPixmap(up2arrow)), i18n("Prev\nConflict"), QKeySequence(Qt::CTRL | Qt::Key_PageUp), this, &KDiff3App::slotGoPrevConflict, ac, "go_prev_conflict"); 0687 mGoPrevConflict->setToolTip(mGoPrevConflict->text() + omitsWhitespace); 0688 mGoNextConflict = GuiUtils::createAction<QAction>(i18n("Go to Next Conflict"), QIcon(QPixmap(down2arrow)), i18n("Next\nConflict"), QKeySequence(Qt::CTRL | Qt::Key_PageDown), this, &KDiff3App::slotGoNextConflict, ac, "go_next_conflict"); 0689 mGoNextConflict->setToolTip(mGoNextConflict->text() + omitsWhitespace); 0690 mGoPrevUnsolvedConflict = GuiUtils::createAction<QAction>(i18n("Go to Previous Unsolved Conflict"), QIcon(QPixmap(prevunsolved)), i18n("Prev\nUnsolved"), QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_PageUp), this, &KDiff3App::slotGoPrevUnsolvedConflict, ac, "go_prev_unsolved_conflict"); 0691 mGoPrevUnsolvedConflict->setToolTip(mGoPrevUnsolvedConflict->text() + includeWhitespace); 0692 mGoNextUnsolvedConflict = GuiUtils::createAction<QAction>(i18n("Go to Next Unsolved Conflict"), QIcon(QPixmap(nextunsolved)), i18n("Next\nUnsolved"), QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_PageDown), this, &KDiff3App::slotGoNextUnsolvedConflict, ac, "go_next_unsolved_conflict"); 0693 mGoNextUnsolvedConflict->setToolTip(mGoNextUnsolvedConflict->text() + includeWhitespace); 0694 mGotoLine = GuiUtils::createAction<QAction>(i18nc("Title for menu item", "Go to Line"), QIcon(QPixmap(gotoline)), i18nc("Text used for toolbar button.", "Go\nLine"), QKeySequence(Qt::CTRL | Qt::Key_G), this, &KDiff3App::slotGoToLine, ac, "go_to_line"); 0695 mGotoLine->setToolTip(mGoNextUnsolvedConflict->text() + ".\n" + i18nc("Tooltip Text", "Goto specified line.")); 0696 chooseA = GuiUtils::createAction<KToggleAction>(i18nc("Title for menu item", "Select Line(s) From A"), QIcon(QPixmap(iconA)), i18nc("Text used for select A toolbar button.", "Choose\nA"), QKeySequence(Qt::CTRL | Qt::Key_1), this, &KDiff3App::slotChooseA, ac, "merge_choose_a"); 0697 chooseB = GuiUtils::createAction<KToggleAction>(i18nc("Title for menu item", "Select Line(s) From B"), QIcon(QPixmap(iconB)), i18nc("Text used for select B when toolbar button.", "Choose\nB"), QKeySequence(Qt::CTRL | Qt::Key_2), this, &KDiff3App::slotChooseB, ac, "merge_choose_b"); 0698 chooseC = GuiUtils::createAction<KToggleAction>(i18nc("Title for menu item", "Select Line(s) From C"), QIcon(QPixmap(iconC)), i18nc("Text used for select C toolbar button.", "Choose\nC"), QKeySequence(Qt::CTRL | Qt::Key_3), this, &KDiff3App::slotChooseC, ac, "merge_choose_c"); 0699 autoAdvance = GuiUtils::createAction<KToggleAction>(i18nc("Title for menu item", "Automatically Go to Next Unsolved Conflict After Source Selection"), QIcon(QPixmap(autoadvance)), i18nc("Auto goto next unsolved toolbar text.", "Auto\nNext"), this, &KDiff3App::slotAutoAdvanceToggled, ac, "merge_autoadvance"); 0700 0701 showWhiteSpaceCharacters = GuiUtils::createAction<KToggleAction>(i18n("Show Space && Tabulator Characters"), QIcon(QPixmap(showwhitespacechars)), i18nc("Show whitespace toolbar text.", "White\nCharacters"), this, &KDiff3App::slotShowWhiteSpaceToggled, ac, "diff_show_whitespace_characters"); 0702 showWhiteSpace = GuiUtils::createAction<KToggleAction>(i18n("Show White Space"), QIcon(QPixmap(showwhitespace)), i18nc("Show whitespace changes toolbar text.", "White\nDeltas"), this, &KDiff3App::slotShowWhiteSpaceToggled, ac, "diff_show_whitespace"); 0703 0704 showLineNumbers = GuiUtils::createAction<KToggleAction>(i18n("Show Line Numbers"), QIcon(QPixmap(showlinenumbers)), i18nc("Show line numbers toolbar text", "Line\nNumbers"), this, &KDiff3App::slotShowLineNumbersToggled, ac, "diff_showlinenumbers"); 0705 0706 mAutoSolve = GuiUtils::createAction<QAction>(i18n("Automatically Solve Simple Conflicts"), this, &KDiff3App::slotAutoSolve, ac, "merge_autosolve"); 0707 mUnsolve = GuiUtils::createAction<QAction>(i18n("Set Deltas to Conflicts"), this, &KDiff3App::slotUnsolve, ac, "merge_autounsolve"); 0708 mergeRegExp = GuiUtils::createAction<QAction>(i18n("Run Regular Expression Auto Merge"), this, &KDiff3App::slotRegExpAutoMerge, ac, "merge_regexp_automerge"); 0709 mMergeHistory = GuiUtils::createAction<QAction>(i18n("Automatically Solve History Conflicts"), this, &KDiff3App::slotMergeHistory, ac, "merge_versioncontrol_history"); 0710 splitDiff = GuiUtils::createAction<QAction>(i18n("Split Diff At Selection"), this, &KDiff3App::slotSplitDiff, ac, "merge_splitdiff"); 0711 joinDiffs = GuiUtils::createAction<QAction>(i18n("Join Selected Diffs"), this, &KDiff3App::slotJoinDiffs, ac, "merge_joindiffs"); 0712 0713 showWindowA = GuiUtils::createAction<KToggleAction>(i18n("Show Window A"), this, &KDiff3App::slotShowWindowAToggled, ac, "win_show_a"); 0714 showWindowB = GuiUtils::createAction<KToggleAction>(i18n("Show Window B"), this, &KDiff3App::slotShowWindowBToggled, ac, "win_show_b"); 0715 showWindowC = GuiUtils::createAction<KToggleAction>(i18n("Show Window C"), this, &KDiff3App::slotShowWindowCToggled, ac, "win_show_c"); 0716 0717 overviewModeNormal = GuiUtils::createAction<KToggleAction>(i18n("Normal Overview"), this, &KDiff3App::slotOverviewNormal, ac, "diff_overview_normal"); 0718 overviewModeAB = GuiUtils::createAction<KToggleAction>(i18n("A vs. B Overview"), this, &KDiff3App::slotOverviewAB, ac, "diff_overview_ab"); 0719 overviewModeAC = GuiUtils::createAction<KToggleAction>(i18n("A vs. C Overview"), this, &KDiff3App::slotOverviewAC, ac, "diff_overview_ac"); 0720 overviewModeBC = GuiUtils::createAction<KToggleAction>(i18n("B vs. C Overview"), this, &KDiff3App::slotOverviewBC, ac, "diff_overview_bc"); 0721 wordWrap = GuiUtils::createAction<KToggleAction>(i18n("Word Wrap Diff Windows"), this, &KDiff3App::slotWordWrapToggled, ac, "diff_wordwrap"); 0722 addManualDiffHelp = GuiUtils::createAction<QAction>(i18n("Add Manual Diff Alignment"), QKeySequence(Qt::CTRL | Qt::Key_Y), this, &KDiff3App::slotAddManualDiffHelp, ac, "diff_add_manual_diff_help"); 0723 clearManualDiffHelpList = GuiUtils::createAction<QAction>(i18n("Clear All Manual Diff Alignments"), QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Y), this, &KDiff3App::slotClearManualDiffHelpList, ac, "diff_clear_manual_diff_help_list"); 0724 0725 winFocusNext = GuiUtils::createAction<QAction>(i18n("Focus Next Window"), QKeySequence(Qt::ALT | Qt::Key_Right), this, &KDiff3App::slotWinFocusNext, ac, "win_focus_next"); 0726 winFocusPrev = GuiUtils::createAction<QAction>(i18n("Focus Prev Window"), QKeySequence(Qt::ALT | Qt::Key_Left), this, &KDiff3App::slotWinFocusPrev, ac, "win_focus_prev"); 0727 winToggleSplitOrientation = GuiUtils::createAction<QAction>(i18n("Toggle Split Orientation"), this, &KDiff3App::slotWinToggleSplitterOrientation, ac, "win_toggle_split_orientation"); 0728 0729 dirShowBoth = GuiUtils::createAction<KToggleAction>(i18n("Folder && Text Split Screen View"), this, &KDiff3App::slotDirShowBoth, ac, "win_dir_show_both"); 0730 dirShowBoth->setChecked(true); 0731 dirViewToggle = GuiUtils::createAction<QAction>(i18n("Toggle Between Folder && Text View"), this, &KDiff3App::slotDirViewToggle, ac, "win_dir_view_toggle"); 0732 0733 m_pMergeEditorPopupMenu = new QMenu(this); 0734 m_pMergeEditorPopupMenu->addAction(chooseA); 0735 m_pMergeEditorPopupMenu->addAction(chooseB); 0736 m_pMergeEditorPopupMenu->addAction(chooseC); 0737 0738 mEscapeAction = new QShortcut(Qt::Key_Escape, this, this, &KDiff3App::slotFileQuit); 0739 0740 MergeResultWindow::initActions(ac); 0741 } 0742 0743 void KDiff3App::showPopupMenu(const QPoint& point) 0744 { 0745 m_pMergeEditorPopupMenu->popup(point); 0746 } 0747 0748 void KDiff3App::initStatusBar() 0749 { 0750 /////////////////////////////////////////////////////////////////// 0751 // STATUSBAR 0752 if(statusBar() != nullptr) 0753 statusBar()->showMessage(i18n("Ready.")); 0754 } 0755 0756 void KDiff3App::saveWindow(const KSharedConfigPtr config) 0757 { 0758 KConfigGroup group = config->group(KDIFF3_CONFIG_GROUP); 0759 group.writeEntry("mainWindow-geometry", saveGeometry()); 0760 group.writeEntry("mainWindow-state", saveState(1)); 0761 group.writeEntry("shell-geometry", m_pKDiff3Shell->saveGeometry()); 0762 group.writeEntry("shell-state", m_pKDiff3Shell->saveState()); 0763 } 0764 0765 bool KDiff3App::restoreWindow(const KSharedConfigPtr config) 0766 { 0767 KConfigGroup group = config->group(KDIFF3_CONFIG_GROUP); 0768 if(m_pKDiff3Shell->restoreState(group.readEntry("mainWindow-state", QVariant(QByteArray())).toByteArray())) 0769 { 0770 bool r = m_pKDiff3Shell->restoreGeometry(group.readEntry("mainWindow-geometry", QVariant(QByteArray())).toByteArray()); 0771 group.deleteEntry("mainWindow-state"); 0772 group.deleteEntry("mainWindow-geometry"); 0773 saveWindow(config); 0774 return r; 0775 } 0776 0777 return (restoreGeometry(group.readEntry("mainWindow-geometry", QVariant(QByteArray())).toByteArray()) && 0778 restoreState(group.readEntry("mainWindow-state", QVariant(QByteArray())).toByteArray(), 1)) && 0779 (m_pKDiff3Shell->restoreGeometry(group.readEntry("shell-geometry", QVariant(QByteArray())).toByteArray()) && 0780 m_pKDiff3Shell->restoreState(group.readEntry("shell-state", QVariant(QByteArray())).toByteArray())); 0781 } 0782 0783 void KDiff3App::saveOptions(KSharedConfigPtr config) 0784 { 0785 if(!m_bAutoMode) 0786 { 0787 saveWindow(config); 0788 m_pOptionDialog->saveOptions(std::move(config)); 0789 } 0790 } 0791 0792 bool KDiff3App::queryClose() 0793 { 0794 saveOptions(KSharedConfig::openConfig()); 0795 0796 if(m_bOutputModified) 0797 { 0798 KMessageBox::ButtonCode result = Compat::warningTwoActionsCancel(this, 0799 i18n("The merge result has not been saved."), 0800 i18nc("Error dialog title", "Warning"), 0801 KGuiItem(i18n("Save && Quit")), 0802 KGuiItem(i18n("Quit Without Saving"))); 0803 if(result == KMessageBox::Cancel) 0804 return false; 0805 else if(result == Compat::PrimaryAction) 0806 { 0807 slotFileSave(); 0808 if(m_bOutputModified) 0809 { 0810 KMessageBox::error(this, i18n("Saving the merge result failed."), i18nc("Error dialog title", "Warning")); 0811 return false; 0812 } 0813 } 0814 } 0815 0816 m_bOutputModified = false; 0817 0818 if(m_pDirectoryMergeWindow->isDirectoryMergeInProgress()) 0819 { 0820 qint32 result = Compat::warningTwoActions(this, 0821 i18n("You are currently doing a folder merge. Are you sure, you want to abort?"), 0822 i18nc("Error dialog title", "Warning"), 0823 KStandardGuiItem::quit(), 0824 KStandardGuiItem::cont() /* i18n("Continue Merging") */); 0825 if(result != Compat::PrimaryAction) 0826 return false; 0827 } 0828 0829 return true; 0830 } 0831 0832 ///////////////////////////////////////////////////////////////////// 0833 // SLOT IMPLEMENTATION 0834 ///////////////////////////////////////////////////////////////////// 0835 0836 void KDiff3App::slotFileSave() 0837 { 0838 if(m_bDefaultFilename) 0839 { 0840 slotFileSaveAs(); 0841 } 0842 else 0843 { 0844 slotStatusMsg(i18n("Saving file...")); 0845 0846 bool bSuccess = m_pMergeResultWindow->saveDocument(m_outputFilename, m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle()); 0847 if(bSuccess) 0848 { 0849 m_bFileSaved = true; 0850 m_bOutputModified = false; 0851 if(m_bDirCompare) 0852 m_pDirectoryMergeWindow->mergeResultSaved(m_outputFilename); 0853 } 0854 0855 slotStatusMsg(i18n("Ready.")); 0856 } 0857 } 0858 0859 void KDiff3App::slotFileSaveAs() 0860 { 0861 slotStatusMsg(i18n("Saving file with a new filename...")); 0862 0863 QString s = QFileDialog::getSaveFileUrl(this, i18n("Save As..."), QUrl::fromLocalFile(QDir::currentPath())).url(QUrl::PreferLocalFile); 0864 if(!s.isEmpty()) 0865 { 0866 m_outputFilename = s; 0867 m_pMergeResultWindowTitle->setFileName(m_outputFilename); 0868 bool bSuccess = m_pMergeResultWindow->saveDocument(m_outputFilename, m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle()); 0869 if(bSuccess) 0870 { 0871 m_bOutputModified = false; 0872 if(m_bDirCompare) 0873 m_pDirectoryMergeWindow->mergeResultSaved(m_outputFilename); 0874 } 0875 //setWindowTitle(url.fileName(),doc->isModified()); 0876 0877 m_bDefaultFilename = false; 0878 } 0879 0880 slotStatusMsg(i18n("Ready.")); 0881 } 0882 0883 void KDiff3App::slotFilePrint() 0884 { 0885 if(m_pDiffTextWindow1 == nullptr || m_pDiffTextWindow2 == nullptr) 0886 return; 0887 #ifdef QT_NO_PRINTER 0888 slotStatusMsg(i18n("Printing not implemented.")); 0889 #else 0890 QPrinter printer; 0891 QPointer<QPrintDialog> printDialog = QPointer<QPrintDialog>(new QPrintDialog(&printer, this)); 0892 0893 LineRef firstSelectionD3LIdx; 0894 LineRef lastSelectionD3LIdx; 0895 0896 m_pDiffTextWindow1->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); 0897 0898 if(!firstSelectionD3LIdx.isValid()) 0899 { 0900 m_pDiffTextWindow2->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); 0901 } 0902 if(!firstSelectionD3LIdx.isValid() && m_pDiffTextWindow3 != nullptr) 0903 { 0904 m_pDiffTextWindow3->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); 0905 } 0906 0907 printDialog->setOption(QPrintDialog::PrintCurrentPage); 0908 0909 if(firstSelectionD3LIdx.isValid()) 0910 { 0911 printDialog->setOption(QPrintDialog::PrintSelection); 0912 printDialog->setPrintRange(QAbstractPrintDialog::Selection); 0913 } 0914 0915 if(!firstSelectionD3LIdx.isValid()) 0916 printDialog->setPrintRange(QAbstractPrintDialog::AllPages); 0917 printDialog->setFromTo(0, 0); 0918 0919 qint32 currentFirstLine = m_pDiffTextWindow1->getFirstLine(); 0920 qint32 currentFirstD3LIdx = m_pDiffTextWindow1->convertLineToDiff3LineIdx(currentFirstLine); 0921 0922 // do some printer initialization 0923 printer.setFullPage(false); 0924 0925 // initialize the printer using the print dialog 0926 if(printDialog->exec() == QDialog::Accepted) 0927 { 0928 slotStatusMsg(i18n("Printing...")); 0929 // create a painter to paint on the printer object 0930 RLPainter painter(&printer, gOptions->m_bRightToLeftLanguage, width(), fontMetrics().horizontalAdvance('W')); 0931 0932 QPaintDevice* pPaintDevice = painter.device(); 0933 qint32 dpiy = pPaintDevice->logicalDpiY(); 0934 qint32 columnDistance = qRound((0.5 / 2.54) * dpiy); // 0.5 cm between the columns 0935 0936 qint32 columns = m_bTripleDiff ? 3 : 2; 0937 qint32 columnWidth = (pPaintDevice->width() - (columns - 1) * columnDistance) / columns; 0938 0939 QFont f = gOptions->defaultFont(); 0940 f.setPointSizeF(f.pointSizeF() - 1); // Print with slightly smaller font. 0941 painter.setFont(f); 0942 QFontMetrics fm = painter.fontMetrics(); 0943 0944 QString topLineText = i18n("Top line"); 0945 0946 qint32 headerWidth = fm.horizontalAdvance(m_sd1->getAliasName() + ", " + topLineText + ": 01234567"); 0947 qint32 headerLines = headerWidth / columnWidth + 1; 0948 0949 qint32 headerMargin = headerLines * fm.height() + 3; // Text + one horizontal line 0950 qint32 footerMargin = fm.height() + 3; 0951 0952 QRect view(0, headerMargin, pPaintDevice->width(), pPaintDevice->height() - (headerMargin + footerMargin)); 0953 QRect view1(0 * (columnWidth + columnDistance), view.top(), columnWidth, view.height()); 0954 QRect view2(1 * (columnWidth + columnDistance), view.top(), columnWidth, view.height()); 0955 QRect view3(2 * (columnWidth + columnDistance), view.top(), columnWidth, view.height()); 0956 0957 LineType linesPerPage = view.height() / fm.lineSpacing(); 0958 0959 m_pEventLoopForPrinting = QPointer<QEventLoop>(new QEventLoop()); 0960 if(gOptions->wordWrapOn()) 0961 { 0962 // For printing the lines are wrapped differently (this invalidates the first line) 0963 recalcWordWrap(columnWidth); 0964 m_pEventLoopForPrinting->exec(); 0965 } 0966 0967 LineType totalNofLines = std::max(m_pDiffTextWindow1->getNofLines(), m_pDiffTextWindow2->getNofLines()); 0968 0969 if(m_bTripleDiff && m_pDiffTextWindow3 != nullptr) 0970 totalNofLines = std::max(totalNofLines, m_pDiffTextWindow3->getNofLines()); 0971 0972 bool bPrintCurrentPage = false; 0973 bool bFirstPrintedPage = false; 0974 0975 bool bPrintSelection = false; 0976 quint32 totalNofPages = (totalNofLines + linesPerPage - 1) / linesPerPage; 0977 LineRef line; 0978 LineRef selectionEndLine; 0979 quint32 from = 0, to = 0; 0980 0981 if(printer.printRange() == QPrinter::AllPages) 0982 { 0983 to = totalNofPages; 0984 from = 1; 0985 } 0986 else if(printer.printRange() == QPrinter::PageRange) 0987 { 0988 from = printer.fromPage(), to = printer.toPage(); 0989 /* 0990 Per Qt docs QPrinter::fromPage and QPrinter::toPage return 0 to indicate they are not set. 0991 Account for this and other invalid settings the user may try. 0992 */ 0993 if(from == 0) from = 1; 0994 if(from > totalNofPages) from = totalNofPages; 0995 if(to == 0 || to > totalNofPages) to = totalNofPages; 0996 if(from > to) to = from; 0997 } 0998 else if(printer.printRange() == QPrinter::CurrentPage) 0999 { 1000 bPrintCurrentPage = true; 1001 // Detect the first visible line in the window. 1002 to = from = line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(currentFirstD3LIdx); 1003 } 1004 else if(printer.printRange() == QPrinter::Selection) 1005 { 1006 bPrintSelection = true; 1007 if(firstSelectionD3LIdx.isValid()) 1008 { 1009 line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(firstSelectionD3LIdx); 1010 selectionEndLine = m_pDiffTextWindow1->convertDiff3LineIdxToLine(lastSelectionD3LIdx + 1); 1011 totalNofPages = (selectionEndLine - line + linesPerPage - 1) / linesPerPage; 1012 } 1013 } 1014 1015 qint32 page = 1; 1016 1017 ProgressScope pp; 1018 ProgressProxy::setMaxNofSteps(totalNofPages); 1019 quint32 i = from; 1020 1021 while(bPrintCurrentPage || 1022 (!bPrintSelection && i <= to) || 1023 (bPrintSelection && line < selectionEndLine)) 1024 { 1025 assert(!(bPrintCurrentPage && i > from)); 1026 ProgressProxy::setInformation(i18nc("Status message", "Printing page %1 of %2", page, totalNofPages), false); 1027 ProgressProxy::setCurrent(page - 1); 1028 if(ProgressProxy::wasCancelled()) 1029 { 1030 printer.abort(); 1031 break; 1032 } 1033 1034 if(!bPrintSelection && !bPrintCurrentPage) 1035 { 1036 page = i; 1037 line = (page - 1) * linesPerPage; 1038 } 1039 else if(bPrintSelection) 1040 { 1041 assert(line < selectionEndLine); 1042 1043 if(selectionEndLine - line < linesPerPage) 1044 linesPerPage = selectionEndLine - line; 1045 } 1046 1047 if(line.isValid() && line < totalNofLines) 1048 { 1049 if(bFirstPrintedPage) 1050 printer.newPage(); 1051 1052 painter.setClipping(true); 1053 1054 painter.setPen(gOptions->aColor()); 1055 QString headerText1 = m_sd1->getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow1->calcTopLineInFile(line) + 1); 1056 m_pDiffTextWindow1->printWindow(painter, view1, headerText1, line, linesPerPage, gOptions->foregroundColor()); 1057 1058 painter.setPen(gOptions->bColor()); 1059 QString headerText2 = m_sd2->getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow2->calcTopLineInFile(line) + 1); 1060 m_pDiffTextWindow2->printWindow(painter, view2, headerText2, line, linesPerPage, gOptions->foregroundColor()); 1061 1062 if(m_bTripleDiff && m_pDiffTextWindow3 != nullptr) 1063 { 1064 painter.setPen(gOptions->cColor()); 1065 QString headerText3 = m_sd3->getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow3->calcTopLineInFile(line) + 1); 1066 m_pDiffTextWindow3->printWindow(painter, view3, headerText3, line, linesPerPage, gOptions->foregroundColor()); 1067 } 1068 painter.setClipping(false); 1069 1070 painter.setPen(gOptions->foregroundColor()); 1071 painter.drawLine(0, view.bottom() + 3, view.width(), view.bottom() + 3); 1072 QString s = bPrintCurrentPage ? QString("") 1073 : QString::number(page) + '/' + QString::number(totalNofPages); 1074 if(bPrintSelection) s += i18n(" (Selection)"); 1075 painter.drawText((view.right() - painter.fontMetrics().horizontalAdvance(s)) / 2, 1076 view.bottom() + painter.fontMetrics().ascent() + 5, s); 1077 1078 bFirstPrintedPage = true; 1079 if(bPrintCurrentPage) break; 1080 } 1081 1082 if(bPrintSelection) 1083 { 1084 line += linesPerPage; 1085 ++page; 1086 } 1087 else 1088 { 1089 ++i; 1090 } 1091 } 1092 1093 painter.end(); 1094 1095 if(gOptions->wordWrapOn()) 1096 { 1097 recalcWordWrap(); 1098 m_pEventLoopForPrinting->exec(); 1099 DiffTextWindow::mVScrollBar->setValue(m_pDiffTextWindow1->convertDiff3LineIdxToLine(currentFirstD3LIdx)); 1100 } 1101 m_pEventLoopForPrinting.clear(); 1102 1103 slotStatusMsg(i18n("Printing completed.")); 1104 } 1105 else 1106 { 1107 slotStatusMsg(i18n("Printing aborted.")); 1108 } 1109 #endif 1110 } 1111 1112 void KDiff3App::slotFileQuit() 1113 { 1114 slotStatusMsg(i18n("Exiting...")); 1115 1116 if(!queryClose()) 1117 return; // Don't quit 1118 1119 QApplication::exit(isFileSaved() || isDirComparison() ? 0 : 1); 1120 } 1121 1122 void KDiff3App::slotViewStatusBar() 1123 { 1124 slotStatusMsg(i18n("Toggle the statusbar...")); 1125 gOptions->setStatusBarState(viewStatusBar->isChecked()); 1126 /////////////////////////////////////////////////////////////////// 1127 //turn Statusbar on or off 1128 if(statusBar() != nullptr) 1129 { 1130 if(!viewStatusBar->isChecked()) 1131 { 1132 statusBar()->hide(); 1133 } 1134 else 1135 { 1136 statusBar()->show(); 1137 } 1138 } 1139 1140 slotStatusMsg(i18n("Ready.")); 1141 } 1142 1143 void KDiff3App::slotStatusMsg(const QString& text) 1144 { 1145 /////////////////////////////////////////////////////////////////// 1146 // change status message permanently 1147 if(statusBar() != nullptr) 1148 { 1149 statusBar()->clearMessage(); 1150 statusBar()->showMessage(text); 1151 } 1152 } 1153 1154 //#include "kdiff3.moc"