1 /*
2 * KDiff3 - Text Diff And Merge Tool
3 *
4 * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de
5 * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9
10 #include "directorymergewindow.h"
11
12 #include "DirectoryInfo.h"
13 #include "MergeFileInfos.h"
14 #include "PixMapUtils.h"
15 #include "Utils.h"
16 #include "guiutils.h"
17 #include "kdiff3.h"
18 #include "options.h"
19 #include "progress.h"
20
21 #include <algorithm>
22 #include <map>
23 #include <vector>
24
25 #include <QAction>
26 #include <QApplication>
27 #include <QDialogButtonBox>
28 #include <QDir>
29 #include <QElapsedTimer>
30 #include <QFileDialog>
31 #include <QImage>
32 #include <QKeyEvent>
33 #include <QLabel>
34 #include <QLayout>
35 #include <QMenu>
36 #include <QPainter>
37 #include <QPushButton>
38 #include <QRegExp>
39 #include <QSplitter>
40 #include <QStyledItemDelegate>
41 #include <QTextEdit>
42 #include <QTextStream>
43
44 #include <KLocalizedString>
45 #include <KMessageBox>
46 #include <KToggleAction>
47
48 struct DirectoryMergeWindow::t_ItemInfo {
49 bool bExpanded;
50 bool bOperationComplete;
51 QString status;
52 e_MergeOperation eMergeOperation;
53 };
54
55 class StatusInfo : public QDialog
56 {
57 private:
58 QTextEdit* m_pTextEdit;
59
60 public:
StatusInfo(QWidget * pParent)61 explicit StatusInfo(QWidget* pParent): QDialog(pParent)
62 {
63 QVBoxLayout* pVLayout = new QVBoxLayout(this);
64 m_pTextEdit = new QTextEdit(this);
65 pVLayout->addWidget(m_pTextEdit);
66 setObjectName("StatusInfo");
67 setWindowFlags(Qt::Dialog);
68 m_pTextEdit->setWordWrapMode(QTextOption::NoWrap);
69 m_pTextEdit->setReadOnly(true);
70 QDialogButtonBox* box = new QDialogButtonBox(QDialogButtonBox::Close, this);
71 chk_connect(box, &QDialogButtonBox::rejected, this, &QDialog::accept);
72 pVLayout->addWidget(box);
73 }
74
isEmpty()75 bool isEmpty()
76 {
77 return m_pTextEdit->toPlainText().isEmpty();
78 }
79
addText(const QString & s)80 void addText(const QString& s)
81 {
82 m_pTextEdit->append(s);
83 }
84
clear()85 void clear()
86 {
87 m_pTextEdit->clear();
88 }
89
setVisible(bool bVisible)90 void setVisible(bool bVisible) override
91 {
92 if(bVisible)
93 {
94 m_pTextEdit->moveCursor(QTextCursor::End);
95 m_pTextEdit->moveCursor(QTextCursor::StartOfLine);
96 m_pTextEdit->ensureCursorVisible();
97 }
98
99 QDialog::setVisible(bVisible);
100 if(bVisible)
101 setWindowState(windowState() | Qt::WindowMaximized);
102 }
103 };
104
105 enum Columns
106 {
107 s_NameCol = 0,
108 s_ACol = 1,
109 s_BCol = 2,
110 s_CCol = 3,
111 s_OpCol = 4,
112 s_OpStatusCol = 5,
113 s_UnsolvedCol = 6, // Number of unsolved conflicts (for 3 input files)
114 s_SolvedCol = 7, // Number of auto-solvable conflicts (for 3 input files)
115 s_NonWhiteCol = 8, // Number of nonwhite deltas (for 2 input files)
116 s_WhiteCol = 9 // Number of white deltas (for 2 input files)
117 };
118
119 static Qt::CaseSensitivity s_eCaseSensitivity = Qt::CaseSensitive;
120
121 class DirectoryMergeWindow::DirectoryMergeWindowPrivate : public QAbstractItemModel
122 {
123 friend class DirMergeItem;
124
125 public:
DirectoryMergeWindowPrivate(DirectoryMergeWindow * pDMW)126 explicit DirectoryMergeWindowPrivate(DirectoryMergeWindow* pDMW)
127 {
128 mWindow = pDMW;
129 m_pStatusInfo = new StatusInfo(mWindow);
130 m_pStatusInfo->hide();
131 }
~DirectoryMergeWindowPrivate()132 ~DirectoryMergeWindowPrivate() override
133 {
134 delete m_pRoot;
135 }
136
137 bool init(const QSharedPointer<DirectoryInfo>& dirInfo, bool bDirectoryMerge, bool bReload);
138
139 // Implement QAbstractItemModel
140 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
141
142 //Qt::ItemFlags flags ( const QModelIndex & index ) const
parent(const QModelIndex & index) const143 QModelIndex parent(const QModelIndex& index) const override
144 {
145 MergeFileInfos* pMFI = getMFI(index);
146 if(pMFI == nullptr || pMFI == m_pRoot || pMFI->parent() == m_pRoot)
147 return QModelIndex();
148
149 MergeFileInfos* pParentsParent = pMFI->parent()->parent();
150 return createIndex(pParentsParent->children().indexOf(pMFI->parent()), 0, pMFI->parent());
151 }
152
rowCount(const QModelIndex & parent=QModelIndex ()) const153 int rowCount(const QModelIndex& parent = QModelIndex()) const override
154 {
155 MergeFileInfos* pParentMFI = getMFI(parent);
156 if(pParentMFI != nullptr)
157 return pParentMFI->children().count();
158 else
159 return m_pRoot->children().count();
160 }
161
columnCount(const QModelIndex &) const162 int columnCount(const QModelIndex& /*parent*/) const override
163 {
164 return 10;
165 }
166
index(int row,int column,const QModelIndex & parent) const167 QModelIndex index(int row, int column, const QModelIndex& parent) const override
168 {
169 MergeFileInfos* pParentMFI = getMFI(parent);
170 if(pParentMFI == nullptr && row < m_pRoot->children().count())
171 return createIndex(row, column, m_pRoot->children()[row]);
172 else if(pParentMFI != nullptr && row < pParentMFI->children().count())
173 return createIndex(row, column, pParentMFI->children()[row]);
174 else
175 return QModelIndex();
176 }
177
178 QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
179
180 void sort(int column, Qt::SortOrder order) override;
181
182 void selectItemAndColumn(const QModelIndex& mi, bool bContextMenu);
183
setOpStatus(const QModelIndex & mi,e_OperationStatus eOpStatus)184 void setOpStatus(const QModelIndex& mi, e_OperationStatus eOpStatus)
185 {
186 if(MergeFileInfos* pMFI = getMFI(mi))
187 {
188 pMFI->setOpStatus(eOpStatus);
189 Q_EMIT dataChanged(mi, mi);
190 }
191 }
192
193 QModelIndex nextSibling(const QModelIndex& mi);
194
195 // private data and helper methods
getMFI(const QModelIndex & mi) const196 MergeFileInfos* getMFI(const QModelIndex& mi) const
197 {
198 if(mi.isValid())
199 return (MergeFileInfos*)mi.internalPointer();
200 else
201 return nullptr;
202 }
203
isThreeWay() const204 bool isThreeWay() const
205 {
206 if(rootMFI() == nullptr) return false;
207 return rootMFI()->isThreeWay();
208 }
209
rootMFI() const210 MergeFileInfos* rootMFI() const { return m_pRoot; }
211
212 void calcDirStatus(bool bThreeDirs, const QModelIndex& mi,
213 int& nofFiles, int& nofDirs, int& nofEqualFiles, int& nofManualMerges);
214
215 void mergeContinue(bool bStart, bool bVerbose);
216
217 void prepareListView(ProgressProxy& pp);
218 void calcSuggestedOperation(const QModelIndex& mi, e_MergeOperation eDefaultMergeOp);
219 void setAllMergeOperations(e_MergeOperation eDefaultOperation);
220
221 bool canContinue();
222 QModelIndex treeIterator(QModelIndex mi, bool bVisitChildren = true, bool bFindInvisible = false);
223 void prepareMergeStart(const QModelIndex& miBegin, const QModelIndex& miEnd, bool bVerbose);
224 bool executeMergeOperation(MergeFileInfos& mfi, bool& bSingleFileMerge);
225
226 void scanDirectory(const QString& dirName, t_DirectoryList& dirList);
227 void scanLocalDirectory(const QString& dirName, t_DirectoryList& dirList);
228
229 void setMergeOperation(const QModelIndex& mi, e_MergeOperation eMergeOp, bool bRecursive = true);
230 bool isDir(const QModelIndex& mi) const;
231 QString getFileName(const QModelIndex& mi) const;
232
233 bool copyFLD(const QString& srcName, const QString& destName);
234 bool deleteFLD(const QString& name, bool bCreateBackup);
235 bool makeDir(const QString& name, bool bQuiet = false);
236 bool renameFLD(const QString& srcName, const QString& destName);
237 bool mergeFLD(const QString& nameA, const QString& nameB, const QString& nameC,
238 const QString& nameDest, bool& bSingleFileMerge);
239
240 void buildMergeMap(const QSharedPointer<DirectoryInfo>& dirInfo);
241
242 private:
243 class FileKey
244 {
245 private:
246 const FileAccess* m_pFA;
247
248 public:
FileKey(const FileAccess & fa)249 explicit FileKey(const FileAccess& fa)
250 : m_pFA(&fa) {}
251
getParents(const FileAccess * pFA,const FileAccess * v[],quint32 maxSize) const252 quint32 getParents(const FileAccess* pFA, const FileAccess* v[], quint32 maxSize) const
253 {
254 quint32 s = 0;
255 for(s = 0; pFA->parent() != nullptr; pFA = pFA->parent(), ++s)
256 {
257 if(s == maxSize)
258 break;
259 v[s] = pFA;
260 }
261 return s;
262 }
263
264 // This is essentially the same as
265 // int r = filePath().compare( fa.filePath() )
266 // if ( r<0 ) return true;
267 // if ( r==0 ) return m_col < fa.m_col;
268 // return false;
operator <(const FileKey & fk) const269 bool operator<(const FileKey& fk) const
270 {
271 const FileAccess* v1[100];
272 const FileAccess* v2[100];
273 quint32 v1Size = getParents(m_pFA, v1, 100);
274 quint32 v2Size = getParents(fk.m_pFA, v2, 100);
275
276 for(quint32 i = 0; i < v1Size && i < v2Size; ++i)
277 {
278 int r = v1[v1Size - i - 1]->fileName().compare(v2[v2Size - i - 1]->fileName(), s_eCaseSensitivity);
279 if(r < 0)
280 return true;
281 else if(r > 0)
282 return false;
283 }
284
285 return v1Size < v2Size;
286 }
287 };
288
289 typedef QMap<FileKey, MergeFileInfos> t_fileMergeMap;
290
291 MergeFileInfos* m_pRoot = new MergeFileInfos();
292
293 t_fileMergeMap m_fileMergeMap;
294
295 public:
296 DirectoryMergeWindow* mWindow;
297 QSharedPointer<Options> m_pOptions = nullptr;
298
299 bool m_bFollowDirLinks = false;
300 bool m_bFollowFileLinks = false;
301 bool m_bSimulatedMergeStarted = false;
302 bool m_bRealMergeStarted = false;
303 bool m_bError = false;
304 bool m_bSyncMode = false;
305 bool m_bDirectoryMerge = false; // if true, then merge is the default operation, otherwise it's diff.
306 bool m_bCaseSensitive = true;
307 bool m_bUnfoldSubdirs = false;
308 bool m_bSkipDirStatus = false;
309 bool m_bScanning = false; // true while in init()
310
311 DirectoryMergeInfo* m_pDirectoryMergeInfo = nullptr;
312 StatusInfo* m_pStatusInfo = nullptr;
313
314 typedef std::list<QModelIndex> MergeItemList; // linked list
315 MergeItemList m_mergeItemList;
316 MergeItemList::iterator m_currentIndexForOperation;
317
318 QModelIndex m_selection1Index;
319 QModelIndex m_selection2Index;
320 QModelIndex m_selection3Index;
321
322 QPointer<QAction> m_pDirStartOperation;
323 QPointer<QAction> m_pDirRunOperationForCurrentItem;
324 QPointer<QAction> m_pDirCompareCurrent;
325 QPointer<QAction> m_pDirMergeCurrent;
326 QPointer<QAction> m_pDirRescan;
327 QPointer<QAction> m_pDirChooseAEverywhere;
328 QPointer<QAction> m_pDirChooseBEverywhere;
329 QPointer<QAction> m_pDirChooseCEverywhere;
330 QPointer<QAction> m_pDirAutoChoiceEverywhere;
331 QPointer<QAction> m_pDirDoNothingEverywhere;
332 QPointer<QAction> m_pDirFoldAll;
333 QPointer<QAction> m_pDirUnfoldAll;
334
335 KToggleAction* m_pDirShowIdenticalFiles;
336 KToggleAction* m_pDirShowDifferentFiles;
337 KToggleAction* m_pDirShowFilesOnlyInA;
338 KToggleAction* m_pDirShowFilesOnlyInB;
339 KToggleAction* m_pDirShowFilesOnlyInC;
340
341 KToggleAction* m_pDirSynchronizeDirectories;
342 KToggleAction* m_pDirChooseNewerFiles;
343
344 QPointer<QAction> m_pDirCompareExplicit;
345 QPointer<QAction> m_pDirMergeExplicit;
346
347 QPointer<QAction> m_pDirCurrentDoNothing;
348 QPointer<QAction> m_pDirCurrentChooseA;
349 QPointer<QAction> m_pDirCurrentChooseB;
350 QPointer<QAction> m_pDirCurrentChooseC;
351 QPointer<QAction> m_pDirCurrentMerge;
352 QPointer<QAction> m_pDirCurrentDelete;
353
354 QPointer<QAction> m_pDirCurrentSyncDoNothing;
355 QPointer<QAction> m_pDirCurrentSyncCopyAToB;
356 QPointer<QAction> m_pDirCurrentSyncCopyBToA;
357 QPointer<QAction> m_pDirCurrentSyncDeleteA;
358 QPointer<QAction> m_pDirCurrentSyncDeleteB;
359 QPointer<QAction> m_pDirCurrentSyncDeleteAAndB;
360 QPointer<QAction> m_pDirCurrentSyncMergeToA;
361 QPointer<QAction> m_pDirCurrentSyncMergeToB;
362 QPointer<QAction> m_pDirCurrentSyncMergeToAAndB;
363
364 QPointer<QAction> m_pDirSaveMergeState;
365 QPointer<QAction> m_pDirLoadMergeState;
366 };
367
data(const QModelIndex & index,int role) const368 QVariant DirectoryMergeWindow::DirectoryMergeWindowPrivate::data(const QModelIndex& index, int role) const
369 {
370 MergeFileInfos* pMFI = getMFI(index);
371 if(pMFI)
372 {
373 if(role == Qt::DisplayRole)
374 {
375 switch(index.column())
376 {
377 case s_NameCol:
378 return QFileInfo(pMFI->subPath()).fileName();
379 case s_ACol:
380 return i18n("A");
381 case s_BCol:
382 return i18n("B");
383 case s_CCol:
384 return i18n("C");
385 //case s_OpCol: return i18n("Operation");
386 //case s_OpStatusCol: return i18n("Status");
387 case s_UnsolvedCol:
388 return pMFI->diffStatus().getUnsolvedConflicts();
389 case s_SolvedCol:
390 return pMFI->diffStatus().getSolvedConflicts();
391 case s_NonWhiteCol:
392 return pMFI->diffStatus().getNonWhitespaceConflicts();
393 case s_WhiteCol:
394 return pMFI->diffStatus().getWhitespaceConflicts();
395 //default : return QVariant();
396 }
397
398 if(s_OpCol == index.column())
399 {
400 bool bDir = pMFI->hasDir();
401 switch(pMFI->getOperation())
402 {
403 case eNoOperation:
404 return "";
405 break;
406 case eCopyAToB:
407 return i18n("Copy A to B");
408 break;
409 case eCopyBToA:
410 return i18n("Copy B to A");
411 break;
412 case eDeleteA:
413 return i18n("Delete A");
414 break;
415 case eDeleteB:
416 return i18n("Delete B");
417 break;
418 case eDeleteAB:
419 return i18n("Delete A & B");
420 break;
421 case eMergeToA:
422 return i18n("Merge to A");
423 break;
424 case eMergeToB:
425 return i18n("Merge to B");
426 break;
427 case eMergeToAB:
428 return i18n("Merge to A & B");
429 break;
430 case eCopyAToDest:
431 return i18n("A");
432 break;
433 case eCopyBToDest:
434 return i18n("B");
435 break;
436 case eCopyCToDest:
437 return i18n("C");
438 break;
439 case eDeleteFromDest:
440 return i18n("Delete (if exists)");
441 break;
442 case eMergeABCToDest:
443 case eMergeABToDest:
444 return bDir ? i18n("Merge") : i18n("Merge (manual)");
445 break;
446 case eConflictingFileTypes:
447 return i18n("Error: Conflicting File Types");
448 break;
449 case eChangedAndDeleted:
450 return i18n("Error: Changed and Deleted");
451 break;
452 case eConflictingAges:
453 return i18n("Error: Dates are equal but files are not.");
454 break;
455 default:
456 Q_ASSERT(true);
457 break;
458 }
459 }
460 if(s_OpStatusCol == index.column())
461 {
462 switch(pMFI->getOpStatus())
463 {
464 case eOpStatusNone:
465 return "";
466 case eOpStatusDone:
467 return i18n("Done");
468 case eOpStatusError:
469 return i18n("Error");
470 case eOpStatusSkipped:
471 return i18n("Skipped.");
472 case eOpStatusNotSaved:
473 return i18n("Not saved.");
474 case eOpStatusInProgress:
475 return i18n("In progress...");
476 case eOpStatusToDo:
477 return i18n("To do.");
478 }
479 }
480 }
481 else if(role == Qt::DecorationRole)
482 {
483 if(s_NameCol == index.column())
484 {
485 return PixMapUtils::getOnePixmap(eAgeEnd, pMFI->hasLink(), pMFI->hasDir());
486 }
487
488 if(s_ACol == index.column())
489 {
490 return PixMapUtils::getOnePixmap(pMFI->getAgeA(), pMFI->isLinkA(), pMFI->isDirA());
491 }
492 if(s_BCol == index.column())
493 {
494 return PixMapUtils::getOnePixmap(pMFI->getAgeB(), pMFI->isLinkB(), pMFI->isDirB());
495 }
496 if(s_CCol == index.column())
497 {
498 return PixMapUtils::getOnePixmap(pMFI->getAgeC(), pMFI->isLinkC(), pMFI->isDirC());
499 }
500 }
501 else if(role == Qt::TextAlignmentRole)
502 {
503 if(s_UnsolvedCol == index.column() || s_SolvedCol == index.column() || s_NonWhiteCol == index.column() || s_WhiteCol == index.column())
504 return Qt::AlignRight;
505 }
506 }
507 return QVariant();
508 }
509
headerData(int section,Qt::Orientation orientation,int role) const510 QVariant DirectoryMergeWindow::DirectoryMergeWindowPrivate::headerData(int section, Qt::Orientation orientation, int role) const
511 {
512 if(orientation == Qt::Horizontal && section >= 0 && section < columnCount(QModelIndex()) && role == Qt::DisplayRole)
513 {
514 switch(section)
515 {
516 case s_NameCol:
517 return i18n("Name");
518 case s_ACol:
519 return i18n("A");
520 case s_BCol:
521 return i18n("B");
522 case s_CCol:
523 return i18n("C");
524 case s_OpCol:
525 return i18n("Operation");
526 case s_OpStatusCol:
527 return i18n("Status");
528 case s_UnsolvedCol:
529 return i18n("Unsolved");
530 case s_SolvedCol:
531 return i18n("Solved");
532 case s_NonWhiteCol:
533 return i18n("Nonwhite");
534 case s_WhiteCol:
535 return i18n("White");
536 default:
537 return QVariant();
538 }
539 }
540 return QVariant();
541 }
542
getIntFromIndex(const QModelIndex & index) const543 int DirectoryMergeWindow::getIntFromIndex(const QModelIndex& index) const
544 {
545 return index == d->m_selection1Index ? 1 : index == d->m_selection2Index ? 2 : index == d->m_selection3Index ? 3 : 0;
546 }
547
getOptions() const548 const QSharedPointer<Options>& DirectoryMergeWindow::getOptions() const
549 {
550 return d->m_pOptions;
551 }
552
553 // Previously Q3ListViewItem::paintCell(p,cg,column,width,align);
554 class DirectoryMergeWindow::DirMergeItemDelegate : public QStyledItemDelegate
555 {
556 private:
557 DirectoryMergeWindow* m_pDMW;
getOptions() const558 const QSharedPointer<Options>& getOptions() const { return m_pDMW->getOptions(); }
559
560 public:
DirMergeItemDelegate(DirectoryMergeWindow * pParent)561 explicit DirMergeItemDelegate(DirectoryMergeWindow* pParent)
562 : QStyledItemDelegate(pParent), m_pDMW(pParent)
563 {
564 }
paint(QPainter * thePainter,const QStyleOptionViewItem & option,const QModelIndex & index) const565 void paint(QPainter* thePainter, const QStyleOptionViewItem& option, const QModelIndex& index) const override
566 {
567 QtNumberType column = index.column();
568 if(column == s_ACol || column == s_BCol || column == s_CCol)
569 {
570 QVariant value = index.data(Qt::DecorationRole);
571 QPixmap icon;
572 if(value.isValid())
573 {
574 if(value.type() == QVariant::Icon)
575 {
576 icon = qvariant_cast<QIcon>(value).pixmap(16, 16);
577 //icon = qvariant_cast<QIcon>(value);
578 //decorationRect = QRect(QPoint(0, 0), icon.actualSize(option.decorationSize, iconMode, iconState));
579 }
580 else
581 {
582 icon = qvariant_cast<QPixmap>(value);
583 //decorationRect = QRect(QPoint(0, 0), option.decorationSize).intersected(pixmap.rect());
584 }
585 }
586
587 int x = option.rect.left();
588 int y = option.rect.top();
589 //QPixmap icon = value.value<QPixmap>(); //pixmap(column);
590 if(!icon.isNull())
591 {
592 const auto dpr = thePainter->device()->devicePixelRatioF();
593 const int w = qRound(icon.width() / dpr);
594 const int h = qRound(icon.height() / dpr);
595 int yOffset = (sizeHint(option, index).height() - h) / 2;
596 thePainter->drawPixmap(x + 2, y + yOffset, icon);
597
598 int i = m_pDMW->getIntFromIndex(index);
599 if(i != 0)
600 {
601 QColor c(i == 1 ? getOptions()->m_colorA : i == 2 ? getOptions()->m_colorB : getOptions()->m_colorC);
602 thePainter->setPen(c); // highlight() );
603 thePainter->drawRect(x + 2, y + yOffset, w, h);
604 thePainter->setPen(QPen(c, 0, Qt::DotLine));
605 thePainter->drawRect(x + 1, y + yOffset - 1, w + 2, h + 2);
606 thePainter->setPen(Qt::white);
607 QString s(QChar('A' + i - 1));
608
609 thePainter->drawText(x + 2 + (w - Utils::getHorizontalAdvance(thePainter->fontMetrics(), s)) / 2,
610 y + yOffset + (h + thePainter->fontMetrics().ascent()) / 2 - 1,
611 s);
612 }
613 else
614 {
615 thePainter->setPen(m_pDMW->palette().window().color());
616 thePainter->drawRect(x + 1, y + yOffset - 1, w + 2, h + 2);
617 }
618 return;
619 }
620 }
621
622 QStyleOptionViewItem option2 = option;
623 if(column >= s_UnsolvedCol)
624 {
625 option2.displayAlignment = Qt::AlignRight;
626 }
627 QStyledItemDelegate::paint(thePainter, option2, index);
628 }
sizeHint(const QStyleOptionViewItem & option,const QModelIndex & index) const629 QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override
630 {
631 QSize sz = QStyledItemDelegate::sizeHint(option, index);
632 return sz.expandedTo(QSize(0, 18));
633 }
634 };
635
DirectoryMergeWindow(QWidget * pParent,const QSharedPointer<Options> & pOptions)636 DirectoryMergeWindow::DirectoryMergeWindow(QWidget* pParent, const QSharedPointer<Options>& pOptions)
637 : QTreeView(pParent)
638 {
639 d = new DirectoryMergeWindowPrivate(this);
640 setModel(d);
641 setItemDelegate(new DirMergeItemDelegate(this));
642 chk_connect(this, &DirectoryMergeWindow::doubleClicked, this, &DirectoryMergeWindow::onDoubleClick);
643 chk_connect(this, &DirectoryMergeWindow::expanded, this, &DirectoryMergeWindow::onExpanded);
644
645 d->m_pOptions = pOptions;
646
647 setSortingEnabled(true);
648 }
649
~DirectoryMergeWindow()650 DirectoryMergeWindow::~DirectoryMergeWindow()
651 {
652 delete d;
653 }
654
setDirectoryMergeInfo(DirectoryMergeInfo * p)655 void DirectoryMergeWindow::setDirectoryMergeInfo(DirectoryMergeInfo* p)
656 {
657 d->m_pDirectoryMergeInfo = p;
658 }
isDirectoryMergeInProgress()659 bool DirectoryMergeWindow::isDirectoryMergeInProgress()
660 {
661 return d->m_bRealMergeStarted;
662 }
isSyncMode()663 bool DirectoryMergeWindow::isSyncMode()
664 {
665 return d->m_bSyncMode;
666 }
isScanning()667 bool DirectoryMergeWindow::isScanning()
668 {
669 return d->m_bScanning;
670 }
671
totalColumnWidth()672 int DirectoryMergeWindow::totalColumnWidth()
673 {
674 int w = 0;
675 for(int i = 0; i < s_OpStatusCol; ++i)
676 {
677 w += columnWidth(i);
678 }
679 return w;
680 }
681
reload()682 void DirectoryMergeWindow::reload()
683 {
684 if(isDirectoryMergeInProgress())
685 {
686 int result = KMessageBox::warningYesNo(this,
687 i18n("You are currently doing a folder merge. Are you sure, you want to abort the merge and rescan the folder?"),
688 i18n("Warning"),
689 KGuiItem(i18n("Rescan")),
690 KGuiItem(i18n("Continue Merging")));
691 if(result != KMessageBox::Yes)
692 return;
693 }
694
695 init(gDirInfo, true);
696 //fix file visibilities after reload or menu will be out of sync with display if changed from defaults.
697 updateFileVisibilities();
698 }
699
calcDirStatus(bool bThreeDirs,const QModelIndex & mi,int & nofFiles,int & nofDirs,int & nofEqualFiles,int & nofManualMerges)700 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::calcDirStatus(bool bThreeDirs, const QModelIndex& mi,
701 int& nofFiles, int& nofDirs, int& nofEqualFiles, int& nofManualMerges)
702 {
703 const MergeFileInfos* pMFI = getMFI(mi);
704 if(pMFI->hasDir())
705 {
706 ++nofDirs;
707 }
708 else
709 {
710 ++nofFiles;
711 if(pMFI->isEqualAB() && (!bThreeDirs || pMFI->isEqualAC()))
712 {
713 ++nofEqualFiles;
714 }
715 else
716 {
717 if(pMFI->getOperation() == eMergeABCToDest || pMFI->getOperation() == eMergeABToDest)
718 ++nofManualMerges;
719 }
720 }
721 for(int childIdx = 0; childIdx < rowCount(mi); ++childIdx)
722 calcDirStatus(bThreeDirs, index(childIdx, 0, mi), nofFiles, nofDirs, nofEqualFiles, nofManualMerges);
723 }
724
init(const QSharedPointer<DirectoryInfo> & dirInfo,bool bDirectoryMerge,bool bReload)725 bool DirectoryMergeWindow::init(
726 const QSharedPointer<DirectoryInfo>& dirInfo,
727 bool bDirectoryMerge,
728 bool bReload)
729 {
730 return d->init(dirInfo, bDirectoryMerge, bReload);
731 }
732
buildMergeMap(const QSharedPointer<DirectoryInfo> & dirInfo)733 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::buildMergeMap(const QSharedPointer<DirectoryInfo>& dirInfo)
734 {
735 t_DirectoryList::iterator dirIterator;
736
737 if(dirInfo->dirA().isValid())
738 {
739 for(dirIterator = dirInfo->getDirListA().begin(); dirIterator != dirInfo->getDirListA().end(); ++dirIterator)
740 {
741 MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)];
742
743 mfi.setFileInfoA(&(*dirIterator));
744 }
745 }
746
747 if(dirInfo->dirB().isValid())
748 {
749 for(dirIterator = dirInfo->getDirListB().begin(); dirIterator != dirInfo->getDirListB().end(); ++dirIterator)
750 {
751 MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)];
752
753 mfi.setFileInfoB(&(*dirIterator));
754 }
755 }
756
757 if(dirInfo->dirC().isValid())
758 {
759 for(dirIterator = dirInfo->getDirListC().begin(); dirIterator != dirInfo->getDirListC().end(); ++dirIterator)
760 {
761 MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)];
762
763 mfi.setFileInfoC(&(*dirIterator));
764 }
765 }
766 }
767
init(const QSharedPointer<DirectoryInfo> & dirInfo,bool bDirectoryMerge,bool bReload)768 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::init(
769 const QSharedPointer<DirectoryInfo>& dirInfo,
770 bool bDirectoryMerge,
771 bool bReload)
772 {
773 if(m_pOptions->m_bDmFullAnalysis)
774 {
775 QStringList errors;
776 // A full analysis uses the same resources that a normal text-diff/merge uses.
777 // So make sure that the user saves his data first.
778 if(!KDiff3App::shouldContinue())
779 return false;
780 Q_EMIT mWindow->startDiffMerge(errors, "", "", "", "", "", "", "", nullptr); // hide main window
781 }
782
783 mWindow->show();
784 mWindow->setUpdatesEnabled(true);
785
786 std::map<QString, t_ItemInfo> expandedDirsMap;
787
788 if(bReload)
789 {
790 // Remember expanded items TODO
791 //QTreeWidgetItemIterator it( this );
792 //while ( *it )
793 //{
794 // DirMergeItem* pDMI = static_cast<DirMergeItem*>( *it );
795 // t_ItemInfo& ii = expandedDirsMap[ pDMI->m_pMFI->subPath() ];
796 // ii.bExpanded = pDMI->isExpanded();
797 // ii.bOperationComplete = pDMI->m_pMFI->m_bOperationComplete;
798 // ii.status = pDMI->text( s_OpStatusCol );
799 // ii.eMergeOperation = pDMI->m_pMFI->getOperation();
800 // ++it;
801 //}
802 }
803
804 ProgressProxy pp;
805 m_bFollowDirLinks = m_pOptions->m_bDmFollowDirLinks;
806 m_bFollowFileLinks = m_pOptions->m_bDmFollowFileLinks;
807 m_bSimulatedMergeStarted = false;
808 m_bRealMergeStarted = false;
809 m_bError = false;
810 m_bDirectoryMerge = bDirectoryMerge;
811 m_selection1Index = QModelIndex();
812 m_selection2Index = QModelIndex();
813 m_selection3Index = QModelIndex();
814 m_bCaseSensitive = m_pOptions->m_bDmCaseSensitiveFilenameComparison;
815 m_bUnfoldSubdirs = m_pOptions->m_bDmUnfoldSubdirs;
816 m_bSkipDirStatus = m_pOptions->m_bDmSkipDirStatus;
817
818 beginResetModel();
819 m_pRoot->clear();
820 m_mergeItemList.clear();
821 endResetModel();
822
823 m_currentIndexForOperation = m_mergeItemList.end();
824
825 if(!bReload)
826 {
827 m_pDirShowIdenticalFiles->setChecked(true);
828 m_pDirShowDifferentFiles->setChecked(true);
829 m_pDirShowFilesOnlyInA->setChecked(true);
830 m_pDirShowFilesOnlyInB->setChecked(true);
831 m_pDirShowFilesOnlyInC->setChecked(true);
832 }
833 Q_ASSERT(dirInfo != nullptr);
834 const FileAccess& dirA = dirInfo->dirA();
835 const FileAccess& dirB = dirInfo->dirB();
836 const FileAccess& dirC = dirInfo->dirC();
837 const FileAccess& dirDest = dirInfo->destDir();
838 // Check if all input directories exist and are valid. The dest dir is not tested now.
839 // The test will happen only when we are going to write to it.
840 if(!dirA.isDir() || !dirB.isDir() ||
841 (dirC.isValid() && !dirC.isDir()))
842 {
843 QString text(i18n("Opening of folders failed:"));
844 text += "\n\n";
845 if(!dirA.isDir())
846 {
847 text += i18n("Folder A \"%1\" does not exist or is not a folder.\n", dirA.prettyAbsPath());
848 }
849
850 if(!dirB.isDir())
851 {
852 text += i18n("Folder B \"%1\" does not exist or is not a folder.\n", dirB.prettyAbsPath());
853 }
854
855 if(dirC.isValid() && !dirC.isDir())
856 {
857 text += i18n("Folder C \"%1\" does not exist or is not a folder.\n", dirC.prettyAbsPath());
858 }
859
860 KMessageBox::sorry(mWindow, text, i18n("Folder Opening Error"));
861 return false;
862 }
863
864 if(dirC.isValid() &&
865 (dirDest.prettyAbsPath() == dirA.prettyAbsPath() || dirDest.prettyAbsPath() == dirB.prettyAbsPath()))
866 {
867 KMessageBox::error(mWindow,
868 i18n("The destination folder must not be the same as A or B when "
869 "three folders are merged.\nCheck again before continuing."),
870 i18n("Parameter Warning"));
871 return false;
872 }
873
874 m_bScanning = true;
875 Q_EMIT mWindow->statusBarMessage(i18n("Scanning folders..."));
876
877 m_bSyncMode = m_pOptions->m_bDmSyncMode && dirInfo->allowSyncMode();
878
879 m_fileMergeMap.clear();
880 s_eCaseSensitivity = m_bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
881 // calc how many directories will be read:
882 double nofScans = (dirA.isValid() ? 1 : 0) + (dirB.isValid() ? 1 : 0) + (dirC.isValid() ? 1 : 0);
883 int currentScan = 0;
884
885 //TODO setColumnWidthMode(s_UnsolvedCol, Q3ListView::Manual);
886 // setColumnWidthMode(s_SolvedCol, Q3ListView::Manual);
887 // setColumnWidthMode(s_WhiteCol, Q3ListView::Manual);
888 // setColumnWidthMode(s_NonWhiteCol, Q3ListView::Manual);
889 mWindow->setColumnHidden(s_CCol, !dirC.isValid());
890 mWindow->setColumnHidden(s_WhiteCol, !m_pOptions->m_bDmFullAnalysis);
891 mWindow->setColumnHidden(s_NonWhiteCol, !m_pOptions->m_bDmFullAnalysis);
892 mWindow->setColumnHidden(s_UnsolvedCol, !m_pOptions->m_bDmFullAnalysis);
893 mWindow->setColumnHidden(s_SolvedCol, !(m_pOptions->m_bDmFullAnalysis && dirC.isValid()));
894
895 bool bListDirSuccessA = true;
896 bool bListDirSuccessB = true;
897 bool bListDirSuccessC = true;
898
899 if(dirA.isValid())
900 {
901 pp.setInformation(i18n("Reading Folder A"));
902 pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans);
903 ++currentScan;
904
905 bListDirSuccessA = dirInfo->listDirA(*m_pOptions);
906 }
907
908 if(dirB.isValid())
909 {
910 pp.setInformation(i18n("Reading Folder B"));
911 pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans);
912 ++currentScan;
913
914 bListDirSuccessB = dirInfo->listDirB(*m_pOptions);
915 }
916
917 e_MergeOperation eDefaultMergeOp;
918 if(dirC.isValid())
919 {
920 pp.setInformation(i18n("Reading Folder C"));
921 pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans);
922 ++currentScan;
923
924 bListDirSuccessC = dirInfo->listDirC(*m_pOptions);
925
926 eDefaultMergeOp = eMergeABCToDest;
927 }
928 else
929 eDefaultMergeOp = m_bSyncMode ? eMergeToAB : eMergeABToDest;
930
931 buildMergeMap(dirInfo);
932
933 bool bContinue = true;
934 if(!bListDirSuccessA || !bListDirSuccessB || !bListDirSuccessC)
935 {
936 QString s = i18n("Some subfolders were not readable in");
937 if(!bListDirSuccessA) s += "\nA: " + dirA.prettyAbsPath();
938 if(!bListDirSuccessB) s += "\nB: " + dirB.prettyAbsPath();
939 if(!bListDirSuccessC) s += "\nC: " + dirC.prettyAbsPath();
940 s += '\n';
941 s += i18n("Check the permissions of the subfolders.");
942 bContinue = KMessageBox::Continue == KMessageBox::warningContinueCancel(mWindow, s);
943 }
944
945 if(bContinue)
946 {
947 prepareListView(pp);
948
949 mWindow->updateFileVisibilities();
950
951 for(int childIdx = 0; childIdx < rowCount(); ++childIdx)
952 {
953 QModelIndex mi = index(childIdx, 0, QModelIndex());
954 calcSuggestedOperation(mi, eDefaultMergeOp);
955 }
956 }
957
958 mWindow->sortByColumn(0, Qt::AscendingOrder);
959
960 for(int column = 0; column < columnCount(QModelIndex()); ++column)
961 mWindow->resizeColumnToContents(column);
962
963 // Try to improve the view a little bit.
964 QWidget* pParent = mWindow->parentWidget();
965 QSplitter* pSplitter = static_cast<QSplitter*>(pParent);
966 if(pSplitter != nullptr)
967 {
968 QList<int> sizes = pSplitter->sizes();
969 int total = sizes[0] + sizes[1];
970 if(total < 10)
971 total = 100;
972 sizes[0] = total * 6 / 10;
973 sizes[1] = total - sizes[0];
974 pSplitter->setSizes(sizes);
975 }
976
977 m_bScanning = false;
978 Q_EMIT mWindow->statusBarMessage(i18n("Ready."));
979
980 if(bContinue && !m_bSkipDirStatus)
981 {
982 // Generate a status report
983 int nofFiles = 0;
984 int nofDirs = 0;
985 int nofEqualFiles = 0;
986 int nofManualMerges = 0;
987 //TODO
988 for(int childIdx = 0; childIdx < rowCount(); ++childIdx)
989 calcDirStatus(dirC.isValid(), index(childIdx, 0, QModelIndex()),
990 nofFiles, nofDirs, nofEqualFiles, nofManualMerges);
991
992 QString s;
993 s = i18n("Folder Comparison Status\n\n"
994 "Number of subfolders: %1\n"
995 "Number of equal files: %2\n"
996 "Number of different files: %3",
997 nofDirs, nofEqualFiles, nofFiles - nofEqualFiles);
998
999 if(dirC.isValid())
1000 s += '\n' + i18n("Number of manual merges: %1", nofManualMerges);
1001 KMessageBox::information(mWindow, s);
1002 //
1003 //TODO
1004 //if ( topLevelItemCount()>0 )
1005 //{
1006 // topLevelItem(0)->setSelected(true);
1007 // setCurrentItem( topLevelItem(0) );
1008 //}
1009 }
1010
1011 if(bReload)
1012 {
1013 // Remember expanded items
1014 //TODO
1015 //QTreeWidgetItemIterator it( this );
1016 //while ( *it )
1017 //{
1018 // DirMergeItem* pDMI = static_cast<DirMergeItem*>( *it );
1019 // std::map<QString,t_ItemInfo>::iterator i = expandedDirsMap.find( pDMI->m_pMFI->subPath() );
1020 // if ( i!=expandedDirsMap.end() )
1021 // {
1022 // t_ItemInfo& ii = i->second;
1023 // pDMI->setExpanded( ii.bExpanded );
1024 // //pDMI->m_pMFI->setMergeOperation( ii.eMergeOperation, false ); unsafe, might have changed
1025 // pDMI->m_pMFI->m_bOperationComplete = ii.bOperationComplete;
1026 // pDMI->setText( s_OpStatusCol, ii.status );
1027 // }
1028 // ++it;
1029 //}
1030 }
1031 else if(m_bUnfoldSubdirs)
1032 {
1033 m_pDirUnfoldAll->trigger();
1034 }
1035
1036 return true;
1037 }
1038
getDirNameA() const1039 inline QString DirectoryMergeWindow::getDirNameA() const
1040 {
1041 return gDirInfo->dirA().prettyAbsPath();
1042 }
getDirNameB() const1043 inline QString DirectoryMergeWindow::getDirNameB() const
1044 {
1045 return gDirInfo->dirB().prettyAbsPath();
1046 }
getDirNameC() const1047 inline QString DirectoryMergeWindow::getDirNameC() const
1048 {
1049 return gDirInfo->dirC().prettyAbsPath();
1050 }
getDirNameDest() const1051 inline QString DirectoryMergeWindow::getDirNameDest() const
1052 {
1053 return gDirInfo->destDir().prettyAbsPath();
1054 }
1055
onExpanded()1056 void DirectoryMergeWindow::onExpanded()
1057 {
1058 resizeColumnToContents(s_NameCol);
1059 }
1060
slotChooseAEverywhere()1061 void DirectoryMergeWindow::slotChooseAEverywhere()
1062 {
1063 d->setAllMergeOperations(eCopyAToDest);
1064 }
1065
slotChooseBEverywhere()1066 void DirectoryMergeWindow::slotChooseBEverywhere()
1067 {
1068 d->setAllMergeOperations(eCopyBToDest);
1069 }
1070
slotChooseCEverywhere()1071 void DirectoryMergeWindow::slotChooseCEverywhere()
1072 {
1073 d->setAllMergeOperations(eCopyCToDest);
1074 }
1075
slotAutoChooseEverywhere()1076 void DirectoryMergeWindow::slotAutoChooseEverywhere()
1077 {
1078 e_MergeOperation eDefaultMergeOp = d->isThreeWay() ? eMergeABCToDest : d->m_bSyncMode ? eMergeToAB : eMergeABToDest;
1079 d->setAllMergeOperations(eDefaultMergeOp);
1080 }
1081
slotNoOpEverywhere()1082 void DirectoryMergeWindow::slotNoOpEverywhere()
1083 {
1084 d->setAllMergeOperations(eNoOperation);
1085 }
1086
slotFoldAllSubdirs()1087 void DirectoryMergeWindow::slotFoldAllSubdirs()
1088 {
1089 collapseAll();
1090 }
1091
slotUnfoldAllSubdirs()1092 void DirectoryMergeWindow::slotUnfoldAllSubdirs()
1093 {
1094 expandAll();
1095 }
1096
1097 // Merge current item (merge mode)
slotCurrentDoNothing()1098 void DirectoryMergeWindow::slotCurrentDoNothing()
1099 {
1100 d->setMergeOperation(currentIndex(), eNoOperation);
1101 }
slotCurrentChooseA()1102 void DirectoryMergeWindow::slotCurrentChooseA()
1103 {
1104 d->setMergeOperation(currentIndex(), d->m_bSyncMode ? eCopyAToB : eCopyAToDest);
1105 }
slotCurrentChooseB()1106 void DirectoryMergeWindow::slotCurrentChooseB()
1107 {
1108 d->setMergeOperation(currentIndex(), d->m_bSyncMode ? eCopyBToA : eCopyBToDest);
1109 }
slotCurrentChooseC()1110 void DirectoryMergeWindow::slotCurrentChooseC()
1111 {
1112 d->setMergeOperation(currentIndex(), eCopyCToDest);
1113 }
slotCurrentMerge()1114 void DirectoryMergeWindow::slotCurrentMerge()
1115 {
1116 bool bThreeDirs = d->isThreeWay();
1117 d->setMergeOperation(currentIndex(), bThreeDirs ? eMergeABCToDest : eMergeABToDest);
1118 }
slotCurrentDelete()1119 void DirectoryMergeWindow::slotCurrentDelete()
1120 {
1121 d->setMergeOperation(currentIndex(), eDeleteFromDest);
1122 }
1123 // Sync current item
slotCurrentCopyAToB()1124 void DirectoryMergeWindow::slotCurrentCopyAToB()
1125 {
1126 d->setMergeOperation(currentIndex(), eCopyAToB);
1127 }
slotCurrentCopyBToA()1128 void DirectoryMergeWindow::slotCurrentCopyBToA()
1129 {
1130 d->setMergeOperation(currentIndex(), eCopyBToA);
1131 }
slotCurrentDeleteA()1132 void DirectoryMergeWindow::slotCurrentDeleteA()
1133 {
1134 d->setMergeOperation(currentIndex(), eDeleteA);
1135 }
slotCurrentDeleteB()1136 void DirectoryMergeWindow::slotCurrentDeleteB()
1137 {
1138 d->setMergeOperation(currentIndex(), eDeleteB);
1139 }
slotCurrentDeleteAAndB()1140 void DirectoryMergeWindow::slotCurrentDeleteAAndB()
1141 {
1142 d->setMergeOperation(currentIndex(), eDeleteAB);
1143 }
slotCurrentMergeToA()1144 void DirectoryMergeWindow::slotCurrentMergeToA()
1145 {
1146 d->setMergeOperation(currentIndex(), eMergeToA);
1147 }
slotCurrentMergeToB()1148 void DirectoryMergeWindow::slotCurrentMergeToB()
1149 {
1150 d->setMergeOperation(currentIndex(), eMergeToB);
1151 }
slotCurrentMergeToAAndB()1152 void DirectoryMergeWindow::slotCurrentMergeToAAndB()
1153 {
1154 d->setMergeOperation(currentIndex(), eMergeToAB);
1155 }
1156
keyPressEvent(QKeyEvent * e)1157 void DirectoryMergeWindow::keyPressEvent(QKeyEvent* e)
1158 {
1159 if((e->QInputEvent::modifiers() & Qt::ControlModifier) != 0)
1160 {
1161 MergeFileInfos* pMFI = d->getMFI(currentIndex());
1162 if(pMFI == nullptr)
1163 return;
1164
1165 bool bThreeDirs = pMFI->isThreeWay();
1166 bool bMergeMode = bThreeDirs || !d->m_bSyncMode;
1167 bool bFTConflict = pMFI->conflictingFileTypes();
1168
1169 switch(e->key())
1170 {
1171 case Qt::Key_1:
1172 if(pMFI->existsInA())
1173 {
1174 slotCurrentChooseA();
1175 }
1176 return;
1177 case Qt::Key_2:
1178 if(pMFI->existsInB())
1179 {
1180 slotCurrentChooseB();
1181 }
1182 return;
1183 case Qt::Key_Space:
1184 slotCurrentDoNothing();
1185 return;
1186 }
1187
1188 if(bMergeMode)
1189 {
1190 switch(e->key())
1191 {
1192 case Qt::Key_3:
1193 if(pMFI->existsInC())
1194 {
1195 slotCurrentChooseC();
1196 }
1197 return;
1198
1199 case Qt::Key_4:
1200 if(!bFTConflict)
1201 {
1202 slotCurrentMerge();
1203 }
1204 return;
1205 case Qt::Key_Delete:
1206 slotCurrentDelete();
1207 return;
1208 default:
1209 break;
1210 }
1211 }
1212 else
1213 {
1214 switch(e->key())
1215 {
1216 case Qt::Key_4:
1217 if(!bFTConflict)
1218 {
1219 slotCurrentMergeToAAndB();
1220 }
1221 return;
1222 case Qt::Key_Delete:
1223 if(pMFI->existsInA() && pMFI->existsInB())
1224 slotCurrentDeleteAAndB();
1225 else if(pMFI->existsInA())
1226 slotCurrentDeleteA();
1227 else if(pMFI->existsInB())
1228 slotCurrentDeleteB();
1229 return;
1230 default:
1231 break;
1232 }
1233 }
1234 }
1235 //Override Qt's default behavior for this key.
1236 else if(e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter)
1237 {
1238 onDoubleClick(currentIndex());
1239 return;
1240 }
1241
1242 QTreeView::keyPressEvent(e);
1243 }
1244
focusInEvent(QFocusEvent *)1245 void DirectoryMergeWindow::focusInEvent(QFocusEvent*)
1246 {
1247 Q_EMIT updateAvailabilities();
1248 }
focusOutEvent(QFocusEvent *)1249 void DirectoryMergeWindow::focusOutEvent(QFocusEvent*)
1250 {
1251 Q_EMIT updateAvailabilities();
1252 }
1253
setAllMergeOperations(e_MergeOperation eDefaultOperation)1254 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::setAllMergeOperations(e_MergeOperation eDefaultOperation)
1255 {
1256 if(KMessageBox::Yes == KMessageBox::warningYesNo(mWindow,
1257 i18n("This affects all merge operations."),
1258 i18n("Changing All Merge Operations"),
1259 KStandardGuiItem::cont(),
1260 KStandardGuiItem::cancel()))
1261 {
1262 for(int i = 0; i < rowCount(); ++i)
1263 {
1264 calcSuggestedOperation(index(i, 0, QModelIndex()), eDefaultOperation);
1265 }
1266 }
1267 }
1268
nextSibling(const QModelIndex & mi)1269 QModelIndex DirectoryMergeWindow::DirectoryMergeWindowPrivate::nextSibling(const QModelIndex& mi)
1270 {
1271 QModelIndex miParent = mi.parent();
1272 int currentIdx = mi.row();
1273 if(currentIdx + 1 < mi.model()->rowCount(miParent))
1274 return mi.model()->index(mi.row() + 1, 0, miParent); // next child of parent
1275 return QModelIndex();
1276 }
1277
1278 // Iterate through the complete tree. Start by specifying QListView::firstChild().
treeIterator(QModelIndex mi,bool bVisitChildren,bool bFindInvisible)1279 QModelIndex DirectoryMergeWindow::DirectoryMergeWindowPrivate::treeIterator(QModelIndex mi, bool bVisitChildren, bool bFindInvisible)
1280 {
1281 if(mi.isValid())
1282 {
1283 do
1284 {
1285 if(bVisitChildren && mi.model()->rowCount(mi) != 0)
1286 mi = mi.model()->index(0, 0, mi);
1287 else
1288 {
1289 QModelIndex miNextSibling = nextSibling(mi);
1290 if(miNextSibling.isValid())
1291 mi = miNextSibling;
1292 else
1293 {
1294 mi = mi.parent();
1295 while(mi.isValid())
1296 {
1297 miNextSibling = nextSibling(mi);
1298 if(miNextSibling.isValid())
1299 {
1300 mi = miNextSibling;
1301 break;
1302 }
1303 else
1304 {
1305 mi = mi.parent();
1306 }
1307 }
1308 }
1309 }
1310 } while(mi.isValid() && mWindow->isRowHidden(mi.row(), mi.parent()) && !bFindInvisible);
1311 }
1312 return mi;
1313 }
1314
prepareListView(ProgressProxy & pp)1315 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::prepareListView(ProgressProxy& pp)
1316 {
1317 QStringList errors;
1318 //TODO clear();
1319 PixMapUtils::initPixmaps(m_pOptions->m_newestFileColor, m_pOptions->m_oldestFileColor,
1320 m_pOptions->m_midAgeFileColor, m_pOptions->m_missingFileColor);
1321
1322 mWindow->setRootIsDecorated(true);
1323
1324 int nrOfFiles = m_fileMergeMap.size();
1325 int currentIdx = 1;
1326 QElapsedTimer t;
1327 t.start();
1328 pp.setMaxNofSteps(nrOfFiles);
1329
1330 for(MergeFileInfos &mfi: m_fileMergeMap)
1331 {
1332 const QString& fileName = mfi.subPath();
1333
1334 pp.setInformation(
1335 i18n("Processing %1 / %2\n%3", currentIdx, nrOfFiles, fileName), currentIdx, false);
1336 if(pp.wasCancelled()) break;
1337 ++currentIdx;
1338
1339 // The comparisons and calculations for each file take place here.
1340 if(!mfi.compareFilesAndCalcAges(errors, m_pOptions, mWindow) && errors.size() >= 30)
1341 break;
1342
1343 // Get dirname from fileName: Search for "/" from end:
1344 int pos = fileName.lastIndexOf('/');
1345 QString dirPart;
1346 QString filePart;
1347 if(pos == -1)
1348 {
1349 // Top dir
1350 filePart = fileName;
1351 }
1352 else
1353 {
1354 dirPart = fileName.left(pos);
1355 filePart = fileName.mid(pos + 1);
1356 }
1357
1358 if(dirPart.isEmpty()) // Top level
1359 {
1360 m_pRoot->addChild(&mfi); //new DirMergeItem( this, filePart, &mfi );
1361 mfi.setParent(m_pRoot);
1362 }
1363 else
1364 {
1365 const FileAccess* pFA = mfi.getFileInfoA() ? mfi.getFileInfoA() : mfi.getFileInfoB() ? mfi.getFileInfoB() : mfi.getFileInfoC();
1366 MergeFileInfos& dirMfi = pFA->parent() ? m_fileMergeMap[FileKey(*pFA->parent())] : *m_pRoot; // parent
1367
1368 dirMfi.addChild(&mfi); //new DirMergeItem( dirMfi.m_pDMI, filePart, &mfi );
1369 mfi.setParent(&dirMfi);
1370 // // Equality for parent dirs is set in updateFileVisibilities()
1371 }
1372
1373 mfi.updateAge();
1374 }
1375
1376 if(errors.size() > 0)
1377 {
1378 if(errors.size() < 15)
1379 {
1380 KMessageBox::errorList(mWindow, i18n("Some files could not be processed."), errors);
1381 }
1382 else if(errors.size() < 30)
1383 {
1384 KMessageBox::error(mWindow, i18n("Some files could not be processed."));
1385 }
1386 else
1387 KMessageBox::error(mWindow, i18n("Aborting due to too many errors."));
1388 }
1389
1390 beginResetModel();
1391 endResetModel();
1392 }
1393
calcSuggestedOperation(const QModelIndex & mi,e_MergeOperation eDefaultMergeOp)1394 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::calcSuggestedOperation(const QModelIndex& mi, e_MergeOperation eDefaultMergeOp)
1395 {
1396 const MergeFileInfos* pMFI = getMFI(mi);
1397 if(pMFI == nullptr)
1398 return;
1399
1400 bool bCheckC = pMFI->isThreeWay();
1401 bool bCopyNewer = m_pOptions->m_bDmCopyNewer;
1402 bool bOtherDest = !((gDirInfo->destDir().absoluteFilePath() == gDirInfo->dirA().absoluteFilePath()) ||
1403 (gDirInfo->destDir().absoluteFilePath() == gDirInfo->dirB().absoluteFilePath()) ||
1404 (bCheckC && gDirInfo->destDir().absoluteFilePath() == gDirInfo->dirC().absoluteFilePath()));
1405
1406 //Crash and burn in debug mode these states are never valid.
1407 //The checks are duplicated here so they show in the assert text.
1408 Q_ASSERT(!(eDefaultMergeOp == eMergeABCToDest && !bCheckC));
1409 Q_ASSERT(!(eDefaultMergeOp == eMergeToAB && bCheckC));
1410
1411 //Check for two bugged states that are recoverable. This should never happen!
1412 if(Q_UNLIKELY(eDefaultMergeOp == eMergeABCToDest && !bCheckC))
1413 {
1414 qCWarning(kdiffMain) << "Invalid State detected in DirectoryMergeWindow::DirectoryMergeWindowPrivate::calcSuggestedOperation";
1415 eDefaultMergeOp = eMergeABToDest;
1416 }
1417 if(Q_UNLIKELY(eDefaultMergeOp == eMergeToAB && bCheckC))
1418 {
1419 qCWarning(kdiffMain) << "Invalid State detected in DirectoryMergeWindow::DirectoryMergeWindowPrivate::calcSuggestedOperation";
1420 eDefaultMergeOp = eMergeABCToDest;
1421 }
1422
1423 if(eDefaultMergeOp == eMergeToA || eDefaultMergeOp == eMergeToB ||
1424 eDefaultMergeOp == eMergeABCToDest || eDefaultMergeOp == eMergeABToDest || eDefaultMergeOp == eMergeToAB)
1425 {
1426 if(!bCheckC)
1427 {
1428 if(pMFI->isEqualAB())
1429 {
1430 setMergeOperation(mi, bOtherDest ? eCopyBToDest : eNoOperation);
1431 }
1432 else if(pMFI->existsInA() && pMFI->existsInB())
1433 {
1434 if(!bCopyNewer || pMFI->isDirA())
1435 setMergeOperation(mi, eDefaultMergeOp);
1436 else if(bCopyNewer && pMFI->conflictingAges())
1437 {
1438 setMergeOperation(mi, eConflictingAges);
1439 }
1440 else
1441 {
1442 if(pMFI->getAgeA() == eNew)
1443 setMergeOperation(mi, eDefaultMergeOp == eMergeToAB ? eCopyAToB : eCopyAToDest);
1444 else
1445 setMergeOperation(mi, eDefaultMergeOp == eMergeToAB ? eCopyBToA : eCopyBToDest);
1446 }
1447 }
1448 else if(!pMFI->existsInA() && pMFI->existsInB())
1449 {
1450 if(eDefaultMergeOp == eMergeABToDest)
1451 setMergeOperation(mi, eCopyBToDest);
1452 else if(eDefaultMergeOp == eMergeToB)
1453 setMergeOperation(mi, eNoOperation);
1454 else
1455 setMergeOperation(mi, eCopyBToA);
1456 }
1457 else if(pMFI->existsInA() && !pMFI->existsInB())
1458 {
1459 if(eDefaultMergeOp == eMergeABToDest)
1460 setMergeOperation(mi, eCopyAToDest);
1461 else if(eDefaultMergeOp == eMergeToA)
1462 setMergeOperation(mi, eNoOperation);
1463 else
1464 setMergeOperation(mi, eCopyAToB);
1465 }
1466 else //if ( !pMFI->existsInA() && !pMFI->existsInB() )
1467 {
1468 setMergeOperation(mi, eNoOperation);
1469 }
1470 }
1471 else
1472 {
1473 if(pMFI->isEqualAB() && pMFI->isEqualAC())
1474 {
1475 setMergeOperation(mi, bOtherDest ? eCopyCToDest : eNoOperation);
1476 }
1477 else if(pMFI->existsInA() && pMFI->existsInB() && pMFI->existsInC())
1478 {
1479 if(pMFI->isEqualAB() || pMFI->isEqualBC())
1480 setMergeOperation(mi, eCopyCToDest);
1481 else if(pMFI->isEqualAC())
1482 setMergeOperation(mi, eCopyBToDest);
1483 else
1484 setMergeOperation(mi, eMergeABCToDest);
1485 }
1486 else if(pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC())
1487 {
1488 if(pMFI->isEqualAB())
1489 setMergeOperation(mi, eDeleteFromDest);
1490 else
1491 setMergeOperation(mi, eChangedAndDeleted);
1492 }
1493 else if(pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC())
1494 {
1495 if(pMFI->isEqualAC())
1496 setMergeOperation(mi, eDeleteFromDest);
1497 else
1498 setMergeOperation(mi, eChangedAndDeleted);
1499 }
1500 else if(!pMFI->existsInA() && pMFI->existsInB() && pMFI->existsInC())
1501 {
1502 if(pMFI->isEqualBC())
1503 setMergeOperation(mi, eCopyCToDest);
1504 else
1505 setMergeOperation(mi, eMergeABCToDest);
1506 }
1507 else if(!pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC())
1508 {
1509 setMergeOperation(mi, eCopyCToDest);
1510 }
1511 else if(!pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC())
1512 {
1513 setMergeOperation(mi, eCopyBToDest);
1514 }
1515 else if(pMFI->existsInA() && !pMFI->existsInB() && !pMFI->existsInC())
1516 {
1517 setMergeOperation(mi, eDeleteFromDest);
1518 }
1519 else //if ( !pMFI->existsInA() && !pMFI->existsInB() && !pMFI->existsInC() )
1520 {
1521 setMergeOperation(mi, eNoOperation);
1522 }
1523 }
1524
1525 // Now check if file/dir-types fit.
1526 if(pMFI->conflictingFileTypes())
1527 {
1528 setMergeOperation(mi, eConflictingFileTypes);
1529 }
1530 }
1531 else
1532 {
1533 e_MergeOperation eMO = eDefaultMergeOp;
1534 switch(eDefaultMergeOp)
1535 {
1536 case eConflictingFileTypes:
1537 case eChangedAndDeleted:
1538 case eConflictingAges:
1539 case eDeleteA:
1540 case eDeleteB:
1541 case eDeleteAB:
1542 case eDeleteFromDest:
1543 case eNoOperation:
1544 break;
1545 case eCopyAToB:
1546 if(!pMFI->existsInA())
1547 {
1548 eMO = eDeleteB;
1549 }
1550 break;
1551 case eCopyBToA:
1552 if(!pMFI->existsInB())
1553 {
1554 eMO = eDeleteA;
1555 }
1556 break;
1557 case eCopyAToDest:
1558 if(!pMFI->existsInA())
1559 {
1560 eMO = eDeleteFromDest;
1561 }
1562 break;
1563 case eCopyBToDest:
1564 if(!pMFI->existsInB())
1565 {
1566 eMO = eDeleteFromDest;
1567 }
1568 break;
1569 case eCopyCToDest:
1570 if(!pMFI->existsInC())
1571 {
1572 eMO = eDeleteFromDest;
1573 }
1574 break;
1575
1576 case eMergeToA:
1577 case eMergeToB:
1578 case eMergeToAB:
1579 case eMergeABCToDest:
1580 case eMergeABToDest:
1581 break;
1582 default:
1583 Q_ASSERT(true);
1584 break;
1585 }
1586 setMergeOperation(mi, eMO);
1587 }
1588 }
1589
onDoubleClick(const QModelIndex & mi)1590 void DirectoryMergeWindow::onDoubleClick(const QModelIndex& mi)
1591 {
1592 if(!mi.isValid())
1593 return;
1594
1595 d->m_bSimulatedMergeStarted = false;
1596 if(d->m_bDirectoryMerge)
1597 mergeCurrentFile();
1598 else
1599 compareCurrentFile();
1600 }
1601
currentChanged(const QModelIndex & current,const QModelIndex & previous)1602 void DirectoryMergeWindow::currentChanged(const QModelIndex& current, const QModelIndex& previous)
1603 {
1604 QTreeView::currentChanged(current, previous);
1605 MergeFileInfos* pMFI = d->getMFI(current);
1606 if(pMFI == nullptr)
1607 return;
1608
1609 d->m_pDirectoryMergeInfo->setInfo(gDirInfo->dirA(), gDirInfo->dirB(), gDirInfo->dirC(), gDirInfo->destDir(), *pMFI);
1610 }
1611
mousePressEvent(QMouseEvent * e)1612 void DirectoryMergeWindow::mousePressEvent(QMouseEvent* e)
1613 {
1614 QTreeView::mousePressEvent(e);
1615 QModelIndex mi = indexAt(e->pos());
1616 int c = mi.column();
1617 QPoint p = e->globalPos();
1618 MergeFileInfos* pMFI = d->getMFI(mi);
1619 if(pMFI == nullptr)
1620 return;
1621
1622 if(c == s_OpCol)
1623 {
1624 bool bThreeDirs = d->isThreeWay();
1625
1626 QMenu m(this);
1627 if(bThreeDirs)
1628 {
1629 m.addAction(d->m_pDirCurrentDoNothing);
1630 int count = 0;
1631 if(pMFI->existsInA())
1632 {
1633 m.addAction(d->m_pDirCurrentChooseA);
1634 ++count;
1635 }
1636 if(pMFI->existsInB())
1637 {
1638 m.addAction(d->m_pDirCurrentChooseB);
1639 ++count;
1640 }
1641 if(pMFI->existsInC())
1642 {
1643 m.addAction(d->m_pDirCurrentChooseC);
1644 ++count;
1645 }
1646 if(!pMFI->conflictingFileTypes() && count > 1) m.addAction(d->m_pDirCurrentMerge);
1647 m.addAction(d->m_pDirCurrentDelete);
1648 }
1649 else if(d->m_bSyncMode)
1650 {
1651 m.addAction(d->m_pDirCurrentSyncDoNothing);
1652 if(pMFI->existsInA()) m.addAction(d->m_pDirCurrentSyncCopyAToB);
1653 if(pMFI->existsInB()) m.addAction(d->m_pDirCurrentSyncCopyBToA);
1654 if(pMFI->existsInA()) m.addAction(d->m_pDirCurrentSyncDeleteA);
1655 if(pMFI->existsInB()) m.addAction(d->m_pDirCurrentSyncDeleteB);
1656 if(pMFI->existsInA() && pMFI->existsInB())
1657 {
1658 m.addAction(d->m_pDirCurrentSyncDeleteAAndB);
1659 if(!pMFI->conflictingFileTypes())
1660 {
1661 m.addAction(d->m_pDirCurrentSyncMergeToA);
1662 m.addAction(d->m_pDirCurrentSyncMergeToB);
1663 m.addAction(d->m_pDirCurrentSyncMergeToAAndB);
1664 }
1665 }
1666 }
1667 else
1668 {
1669 m.addAction(d->m_pDirCurrentDoNothing);
1670 if(pMFI->existsInA())
1671 {
1672 m.addAction(d->m_pDirCurrentChooseA);
1673 }
1674 if(pMFI->existsInB())
1675 {
1676 m.addAction(d->m_pDirCurrentChooseB);
1677 }
1678 if(!pMFI->conflictingFileTypes() && pMFI->existsInA() && pMFI->existsInB()) m.addAction(d->m_pDirCurrentMerge);
1679 m.addAction(d->m_pDirCurrentDelete);
1680 }
1681
1682 m.exec(p);
1683 }
1684 else if(c == s_ACol || c == s_BCol || c == s_CCol)
1685 {
1686 QString itemPath;
1687 if(c == s_ACol && pMFI->existsInA())
1688 {
1689 itemPath = pMFI->fullNameA();
1690 }
1691 else if(c == s_BCol && pMFI->existsInB())
1692 {
1693 itemPath = pMFI->fullNameB();
1694 }
1695 else if(c == s_CCol && pMFI->existsInC())
1696 {
1697 itemPath = pMFI->fullNameC();
1698 }
1699
1700 if(!itemPath.isEmpty())
1701 {
1702 d->selectItemAndColumn(mi, e->button() == Qt::RightButton);
1703 }
1704 }
1705 }
1706
1707 #ifndef QT_NO_CONTEXTMENU
contextMenuEvent(QContextMenuEvent * e)1708 void DirectoryMergeWindow::contextMenuEvent(QContextMenuEvent* e)
1709 {
1710 QModelIndex mi = indexAt(e->pos());
1711 int c = mi.column();
1712
1713 MergeFileInfos* pMFI = d->getMFI(mi);
1714 if(pMFI == nullptr)
1715 return;
1716 if(c == s_ACol || c == s_BCol || c == s_CCol)
1717 {
1718 QString itemPath;
1719 if(c == s_ACol && pMFI->existsInA())
1720 {
1721 itemPath = pMFI->fullNameA();
1722 }
1723 else if(c == s_BCol && pMFI->existsInB())
1724 {
1725 itemPath = pMFI->fullNameB();
1726 }
1727 else if(c == s_CCol && pMFI->existsInC())
1728 {
1729 itemPath = pMFI->fullNameC();
1730 }
1731
1732 if(!itemPath.isEmpty())
1733 {
1734 d->selectItemAndColumn(mi, true);
1735 QMenu m(this);
1736 m.addAction(d->m_pDirCompareExplicit);
1737 m.addAction(d->m_pDirMergeExplicit);
1738
1739 m.popup(e->globalPos());
1740 }
1741 }
1742 }
1743 #endif
1744
getFileName(const QModelIndex & mi) const1745 QString DirectoryMergeWindow::DirectoryMergeWindowPrivate::getFileName(const QModelIndex& mi) const
1746 {
1747 MergeFileInfos* pMFI = getMFI(mi);
1748 if(pMFI != nullptr)
1749 {
1750 return mi.column() == s_ACol ? pMFI->getFileInfoA()->absoluteFilePath() : mi.column() == s_BCol ? pMFI->getFileInfoB()->absoluteFilePath() : mi.column() == s_CCol ? pMFI->getFileInfoC()->absoluteFilePath() : QString("");
1751 }
1752 return QString();
1753 }
1754
isDir(const QModelIndex & mi) const1755 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::isDir(const QModelIndex& mi) const
1756 {
1757 MergeFileInfos* pMFI = getMFI(mi);
1758 if(pMFI != nullptr)
1759 {
1760 return mi.column() == s_ACol ? pMFI->isDirA() : mi.column() == s_BCol ? pMFI->isDirB() : pMFI->isDirC();
1761 }
1762 return false;
1763 }
1764
selectItemAndColumn(const QModelIndex & mi,bool bContextMenu)1765 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::selectItemAndColumn(const QModelIndex& mi, bool bContextMenu)
1766 {
1767 if(bContextMenu && (mi == m_selection1Index || mi == m_selection2Index || mi == m_selection3Index))
1768 return;
1769
1770 QModelIndex old1 = m_selection1Index;
1771 QModelIndex old2 = m_selection2Index;
1772 QModelIndex old3 = m_selection3Index;
1773
1774 bool bReset = false;
1775
1776 if(m_selection1Index.isValid())
1777 {
1778 if(isDir(m_selection1Index) != isDir(mi))
1779 bReset = true;
1780 }
1781
1782 if(bReset || m_selection3Index.isValid() || mi == m_selection1Index || mi == m_selection2Index || mi == m_selection3Index)
1783 {
1784 // restart
1785 m_selection1Index = QModelIndex();
1786 m_selection2Index = QModelIndex();
1787 m_selection3Index = QModelIndex();
1788 }
1789 else if(!m_selection1Index.isValid())
1790 {
1791 m_selection1Index = mi;
1792 m_selection2Index = QModelIndex();
1793 m_selection3Index = QModelIndex();
1794 }
1795 else if(!m_selection2Index.isValid())
1796 {
1797 m_selection2Index = mi;
1798 m_selection3Index = QModelIndex();
1799 }
1800 else if(!m_selection3Index.isValid())
1801 {
1802 m_selection3Index = mi;
1803 }
1804 if(old1.isValid()) Q_EMIT dataChanged(old1, old1);
1805 if(old2.isValid()) Q_EMIT dataChanged(old2, old2);
1806 if(old3.isValid()) Q_EMIT dataChanged(old3, old3);
1807 if(m_selection1Index.isValid()) Q_EMIT dataChanged(m_selection1Index, m_selection1Index);
1808 if(m_selection2Index.isValid()) Q_EMIT dataChanged(m_selection2Index, m_selection2Index);
1809 if(m_selection3Index.isValid()) Q_EMIT dataChanged(m_selection3Index, m_selection3Index);
1810 Q_EMIT mWindow->updateAvailabilities();
1811 }
1812
1813 //TODO
1814 //void DirMergeItem::init(MergeFileInfos* pMFI)
1815 //{
1816 // pMFI->m_pDMI = this; //no not here
1817 // m_pMFI = pMFI;
1818 // TotalDiffStatus& tds = pMFI->m_totalDiffStatus;
1819 // if ( m_pMFI->dirA() || m_pMFI->dirB() || m_pMFI->isDirC() )
1820 // {
1821 // }
1822 // else
1823 // {
1824 // setText( s_UnsolvedCol, QString::number( tds.getUnsolvedConflicts() ) );
1825 // setText( s_SolvedCol, QString::number( tds.getSolvedConflicts() ) );
1826 // setText( s_NonWhiteCol, QString::number( tds.getUnsolvedConflicts() + tds.getSolvedConflicts() - tds.getWhitespaceConflicts() ) );
1827 // setText( s_WhiteCol, QString::number( tds.getWhitespaceConflicts() ) );
1828 // }
1829 // setSizeHint( s_ACol, QSize(17,17) ); // Iconsize
1830 // setSizeHint( s_BCol, QSize(17,17) ); // Iconsize
1831 // setSizeHint( s_CCol, QSize(17,17) ); // Iconsize
1832 //}
1833
sort(int column,Qt::SortOrder order)1834 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::sort(int column, Qt::SortOrder order)
1835 {
1836 Q_UNUSED(column);
1837 beginResetModel();
1838 m_pRoot->sort(order);
1839 endResetModel();
1840 }
1841
setMergeOperation(const QModelIndex & mi,e_MergeOperation eMergeOp,bool bRecursive)1842 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::setMergeOperation(const QModelIndex& mi, e_MergeOperation eMergeOp, bool bRecursive)
1843 {
1844 MergeFileInfos* pMFI = getMFI(mi);
1845 if(pMFI == nullptr)
1846 return;
1847
1848 if(eMergeOp != pMFI->getOperation())
1849 {
1850 pMFI->startOperation();
1851 setOpStatus(mi, eOpStatusNone);
1852 }
1853
1854 pMFI->setOperation(eMergeOp);
1855 if(bRecursive)
1856 {
1857 e_MergeOperation eChildrenMergeOp = pMFI->getOperation();
1858 if(eChildrenMergeOp == eConflictingFileTypes) eChildrenMergeOp = eMergeABCToDest;
1859
1860 for(int childIdx = 0; childIdx < pMFI->children().count(); ++childIdx)
1861 {
1862 calcSuggestedOperation(index(childIdx, 0, mi), eChildrenMergeOp);
1863 }
1864 }
1865 }
1866
compareCurrentFile()1867 void DirectoryMergeWindow::compareCurrentFile()
1868 {
1869 if(!d->canContinue()) return;
1870
1871 if(d->m_bRealMergeStarted)
1872 {
1873 KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible"));
1874 return;
1875 }
1876 QStringList errors;
1877 if(MergeFileInfos* pMFI = d->getMFI(currentIndex()))
1878 {
1879 if(!(pMFI->hasDir()))
1880 {
1881 Q_EMIT startDiffMerge(errors,
1882 pMFI->existsInA() ? pMFI->getFileInfoA()->absoluteFilePath() : QString(""),
1883 pMFI->existsInB() ? pMFI->getFileInfoB()->absoluteFilePath() : QString(""),
1884 pMFI->existsInC() ? pMFI->getFileInfoC()->absoluteFilePath() : QString(""),
1885 "",
1886 "", "", "", nullptr);
1887 }
1888 }
1889 Q_EMIT updateAvailabilities();
1890 }
1891
slotCompareExplicitlySelectedFiles()1892 void DirectoryMergeWindow::slotCompareExplicitlySelectedFiles()
1893 {
1894 if(!d->isDir(d->m_selection1Index) && !d->canContinue()) return;
1895
1896 if(d->m_bRealMergeStarted)
1897 {
1898 KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible"));
1899 return;
1900 }
1901
1902 QStringList errors;
1903 Q_EMIT startDiffMerge(errors,
1904 d->getFileName(d->m_selection1Index),
1905 d->getFileName(d->m_selection2Index),
1906 d->getFileName(d->m_selection3Index),
1907 "",
1908 "", "", "", nullptr);
1909 d->m_selection1Index = QModelIndex();
1910 d->m_selection2Index = QModelIndex();
1911 d->m_selection3Index = QModelIndex();
1912
1913 Q_EMIT updateAvailabilities();
1914 update();
1915 }
1916
slotMergeExplicitlySelectedFiles()1917 void DirectoryMergeWindow::slotMergeExplicitlySelectedFiles()
1918 {
1919 if(!d->isDir(d->m_selection1Index) && !d->canContinue()) return;
1920
1921 if(d->m_bRealMergeStarted)
1922 {
1923 KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible"));
1924 return;
1925 }
1926 QStringList errors;
1927 QString fn1 = d->getFileName(d->m_selection1Index);
1928 QString fn2 = d->getFileName(d->m_selection2Index);
1929 QString fn3 = d->getFileName(d->m_selection3Index);
1930
1931 Q_EMIT startDiffMerge(errors, fn1, fn2, fn3,
1932 fn3.isEmpty() ? fn2 : fn3,
1933 "", "", "", nullptr);
1934 d->m_selection1Index = QModelIndex();
1935 d->m_selection2Index = QModelIndex();
1936 d->m_selection3Index = QModelIndex();
1937
1938 Q_EMIT updateAvailabilities();
1939 update();
1940 }
1941
isFileSelected()1942 bool DirectoryMergeWindow::isFileSelected()
1943 {
1944 if(MergeFileInfos* pMFI = d->getMFI(currentIndex()))
1945 {
1946 return !(pMFI->hasDir() || pMFI->conflictingFileTypes());
1947 }
1948 return false;
1949 }
1950
mergeResultSaved(const QString & fileName)1951 void DirectoryMergeWindow::mergeResultSaved(const QString& fileName)
1952 {
1953 QModelIndex mi = (d->m_mergeItemList.empty() || d->m_currentIndexForOperation == d->m_mergeItemList.end())
1954 ? QModelIndex()
1955 : *d->m_currentIndexForOperation;
1956
1957 MergeFileInfos* pMFI = d->getMFI(mi);
1958 if(pMFI == nullptr)
1959 {
1960 // This can happen if the same file is saved and modified and saved again. Nothing to do then.
1961 return;
1962 }
1963 if(fileName == pMFI->fullNameDest())
1964 {
1965 if(pMFI->getOperation() == eMergeToAB)
1966 {
1967 bool bSuccess = d->copyFLD(pMFI->fullNameB(), pMFI->fullNameA());
1968 if(!bSuccess)
1969 {
1970 KMessageBox::error(this, i18n("An error occurred while copying."));
1971 d->m_pStatusInfo->setWindowTitle(i18n("Merge Error"));
1972 d->m_pStatusInfo->exec();
1973 //if ( m_pStatusInfo->firstChild()!=0 )
1974 // m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() );
1975 d->m_bError = true;
1976 d->setOpStatus(mi, eOpStatusError);
1977 pMFI->setOperation(eCopyBToA);
1978 return;
1979 }
1980 }
1981 d->setOpStatus(mi, eOpStatusDone);
1982 pMFI->endOperation();
1983 if(d->m_mergeItemList.size() == 1)
1984 {
1985 d->m_mergeItemList.clear();
1986 d->m_bRealMergeStarted = false;
1987 }
1988 }
1989
1990 Q_EMIT updateAvailabilities();
1991 }
1992
canContinue()1993 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::canContinue()
1994 {
1995 if(KDiff3App::shouldContinue() && !m_bError)
1996 {
1997 QModelIndex mi = (m_mergeItemList.empty() || m_currentIndexForOperation == m_mergeItemList.end()) ? QModelIndex() : *m_currentIndexForOperation;
1998 MergeFileInfos* pMFI = getMFI(mi);
1999 if(pMFI && pMFI->isOperationRunning())
2000 {
2001 setOpStatus(mi, eOpStatusNotSaved);
2002 pMFI->endOperation();
2003 if(m_mergeItemList.size() == 1)
2004 {
2005 m_mergeItemList.clear();
2006 m_bRealMergeStarted = false;
2007 }
2008 }
2009
2010 return true;
2011 }
2012 return false;
2013 }
2014
executeMergeOperation(MergeFileInfos & mfi,bool & bSingleFileMerge)2015 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::executeMergeOperation(MergeFileInfos& mfi, bool& bSingleFileMerge)
2016 {
2017 bool bCreateBackups = m_pOptions->m_bDmCreateBakFiles;
2018 // First decide destname
2019 QString destName;
2020 switch(mfi.getOperation())
2021 {
2022 case eNoOperation:
2023 case eDeleteAB:
2024 break;
2025 case eMergeToAB: // let the user save in B. In mergeResultSaved() the file will be copied to A.
2026 case eMergeToB:
2027 case eDeleteB:
2028 case eCopyAToB:
2029 destName = mfi.fullNameB();
2030 break;
2031 case eMergeToA:
2032 case eDeleteA:
2033 case eCopyBToA:
2034 destName = mfi.fullNameA();
2035 break;
2036 case eMergeABToDest:
2037 case eMergeABCToDest:
2038 case eCopyAToDest:
2039 case eCopyBToDest:
2040 case eCopyCToDest:
2041 case eDeleteFromDest:
2042 destName = mfi.fullNameDest();
2043 break;
2044 default:
2045 KMessageBox::error(mWindow, i18n("Unknown merge operation. (This must never happen!)"));
2046 }
2047
2048 bool bSuccess = false;
2049 bSingleFileMerge = false;
2050 switch(mfi.getOperation())
2051 {
2052 case eNoOperation:
2053 bSuccess = true;
2054 break;
2055 case eCopyAToDest:
2056 case eCopyAToB:
2057 bSuccess = copyFLD(mfi.fullNameA(), destName);
2058 break;
2059 case eCopyBToDest:
2060 case eCopyBToA:
2061 bSuccess = copyFLD(mfi.fullNameB(), destName);
2062 break;
2063 case eCopyCToDest:
2064 bSuccess = copyFLD(mfi.fullNameC(), destName);
2065 break;
2066 case eDeleteFromDest:
2067 case eDeleteA:
2068 case eDeleteB:
2069 bSuccess = deleteFLD(destName, bCreateBackups);
2070 break;
2071 case eDeleteAB:
2072 bSuccess = deleteFLD(mfi.fullNameA(), bCreateBackups) &&
2073 deleteFLD(mfi.fullNameB(), bCreateBackups);
2074 break;
2075 case eMergeABToDest:
2076 case eMergeToA:
2077 case eMergeToAB:
2078 case eMergeToB:
2079 bSuccess = mergeFLD(mfi.fullNameA(), mfi.fullNameB(), "",
2080 destName, bSingleFileMerge);
2081 break;
2082 case eMergeABCToDest:
2083 bSuccess = mergeFLD(
2084 mfi.existsInA() ? mfi.fullNameA() : QString(""),
2085 mfi.existsInB() ? mfi.fullNameB() : QString(""),
2086 mfi.existsInC() ? mfi.fullNameC() : QString(""),
2087 destName, bSingleFileMerge);
2088 break;
2089 default:
2090 KMessageBox::error(mWindow, i18n("Unknown merge operation."));
2091 }
2092
2093 return bSuccess;
2094 }
2095
2096 // Check if the merge can start, and prepare the m_mergeItemList which then contains all
2097 // items that must be merged.
prepareMergeStart(const QModelIndex & miBegin,const QModelIndex & miEnd,bool bVerbose)2098 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::prepareMergeStart(const QModelIndex& miBegin, const QModelIndex& miEnd, bool bVerbose)
2099 {
2100 if(bVerbose)
2101 {
2102 int status = KMessageBox::warningYesNoCancel(mWindow,
2103 i18n("The merge is about to begin.\n\n"
2104 "Choose \"Do it\" if you have read the instructions and know what you are doing.\n"
2105 "Choosing \"Simulate it\" will tell you what would happen.\n\n"
2106 "Be aware that this program still has beta status "
2107 "and there is NO WARRANTY whatsoever! Make backups of your vital data!"),
2108 i18n("Starting Merge"),
2109 KGuiItem(i18n("Do It")),
2110 KGuiItem(i18n("Simulate It")));
2111 if(status == KMessageBox::Yes)
2112 m_bRealMergeStarted = true;
2113 else if(status == KMessageBox::No)
2114 m_bSimulatedMergeStarted = true;
2115 else
2116 return;
2117 }
2118 else
2119 {
2120 m_bRealMergeStarted = true;
2121 }
2122
2123 m_mergeItemList.clear();
2124 if(!miBegin.isValid())
2125 return;
2126
2127 for(QModelIndex mi = miBegin; mi != miEnd; mi = treeIterator(mi))
2128 {
2129 MergeFileInfos* pMFI = getMFI(mi);
2130 if(pMFI && pMFI->isOperationRunning())
2131 {
2132 m_mergeItemList.push_back(mi);
2133 QString errorText;
2134 if(pMFI->getOperation() == eConflictingFileTypes)
2135 {
2136 errorText = i18n("The highlighted item has a different type in the different folders. Select what to do.");
2137 }
2138 if(pMFI->getOperation() == eConflictingAges)
2139 {
2140 errorText = i18n("The modification dates of the file are equal but the files are not. Select what to do.");
2141 }
2142 if(pMFI->getOperation() == eChangedAndDeleted)
2143 {
2144 errorText = i18n("The highlighted item was changed in one folder and deleted in the other. Select what to do.");
2145 }
2146 if(!errorText.isEmpty())
2147 {
2148 mWindow->scrollTo(mi, QAbstractItemView::EnsureVisible);
2149 mWindow->setCurrentIndex(mi);
2150 KMessageBox::error(mWindow, errorText);
2151 m_mergeItemList.clear();
2152 m_bRealMergeStarted = false;
2153 return;
2154 }
2155 }
2156 }
2157
2158 m_currentIndexForOperation = m_mergeItemList.begin();
2159 }
2160
slotRunOperationForCurrentItem()2161 void DirectoryMergeWindow::slotRunOperationForCurrentItem()
2162 {
2163 if(!d->canContinue()) return;
2164
2165 bool bVerbose = false;
2166 if(d->m_mergeItemList.empty())
2167 {
2168 QModelIndex miBegin = currentIndex();
2169 QModelIndex miEnd = d->treeIterator(miBegin, false, false); // find next visible sibling (no children)
2170
2171 d->prepareMergeStart(miBegin, miEnd, bVerbose);
2172 d->mergeContinue(true, bVerbose);
2173 }
2174 else
2175 d->mergeContinue(false, bVerbose);
2176 }
2177
slotRunOperationForAllItems()2178 void DirectoryMergeWindow::slotRunOperationForAllItems()
2179 {
2180 if(!d->canContinue()) return;
2181
2182 bool bVerbose = true;
2183 if(d->m_mergeItemList.empty())
2184 {
2185 QModelIndex miBegin = d->rowCount() > 0 ? d->index(0, 0, QModelIndex()) : QModelIndex();
2186
2187 d->prepareMergeStart(miBegin, QModelIndex(), bVerbose);
2188 d->mergeContinue(true, bVerbose);
2189 }
2190 else
2191 d->mergeContinue(false, bVerbose);
2192 }
2193
mergeCurrentFile()2194 void DirectoryMergeWindow::mergeCurrentFile()
2195 {
2196 if(!d->canContinue()) return;
2197
2198 if(d->m_bRealMergeStarted)
2199 {
2200 KMessageBox::sorry(this, i18n("This operation is currently not possible because folder merge is currently running."), i18n("Operation Not Possible"));
2201 return;
2202 }
2203
2204 if(isFileSelected())
2205 {
2206 MergeFileInfos* pMFI = d->getMFI(currentIndex());
2207 if(pMFI != nullptr)
2208 {
2209 d->m_mergeItemList.clear();
2210 d->m_mergeItemList.push_back(currentIndex());
2211 d->m_currentIndexForOperation = d->m_mergeItemList.begin();
2212 bool bDummy = false;
2213 d->mergeFLD(
2214 pMFI->existsInA() ? pMFI->getFileInfoA()->absoluteFilePath() : QString(""),
2215 pMFI->existsInB() ? pMFI->getFileInfoB()->absoluteFilePath() : QString(""),
2216 pMFI->existsInC() ? pMFI->getFileInfoC()->absoluteFilePath() : QString(""),
2217 pMFI->fullNameDest(),
2218 bDummy);
2219 }
2220 }
2221 Q_EMIT updateAvailabilities();
2222 }
2223
2224 // When bStart is true then m_currentIndexForOperation must still be processed.
2225 // When bVerbose is true then a messagebox will tell when the merge is complete.
mergeContinue(bool bStart,bool bVerbose)2226 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::mergeContinue(bool bStart, bool bVerbose)
2227 {
2228 ProgressProxy pp;
2229 if(m_mergeItemList.empty())
2230 return;
2231
2232 int nrOfItems = 0;
2233 int nrOfCompletedItems = 0;
2234 int nrOfCompletedSimItems = 0;
2235
2236 // Count the number of completed items (for the progress bar).
2237 for(const QModelIndex& i : m_mergeItemList)
2238 {
2239 MergeFileInfos* pMFI = getMFI(i);
2240 ++nrOfItems;
2241 if(!pMFI->isOperationRunning())
2242 ++nrOfCompletedItems;
2243 if(!pMFI->isSimOpRunning())
2244 ++nrOfCompletedSimItems;
2245 }
2246
2247 m_pStatusInfo->hide();
2248 m_pStatusInfo->clear();
2249
2250 QModelIndex miCurrent = m_currentIndexForOperation == m_mergeItemList.end() ? QModelIndex() : *m_currentIndexForOperation;
2251
2252 bool bContinueWithCurrentItem = bStart; // true for first item, else false
2253 bool bSkipItem = false;
2254 if(!bStart && m_bError && miCurrent.isValid())
2255 {
2256 int status = KMessageBox::warningYesNoCancel(mWindow,
2257 i18n("There was an error in the last step.\n"
2258 "Do you want to continue with the item that caused the error or do you want to skip this item?"),
2259 i18n("Continue merge after an error"),
2260 KGuiItem(i18n("Continue With Last Item")),
2261 KGuiItem(i18n("Skip Item")));
2262 if(status == KMessageBox::Yes)
2263 bContinueWithCurrentItem = true;
2264 else if(status == KMessageBox::No)
2265 bSkipItem = true;
2266 else
2267 return;
2268 m_bError = false;
2269 }
2270
2271 pp.setMaxNofSteps(nrOfItems);
2272
2273 bool bSuccess = true;
2274 bool bSingleFileMerge = false;
2275 bool bSim = m_bSimulatedMergeStarted;
2276 while(bSuccess)
2277 {
2278 MergeFileInfos* pMFI = getMFI(miCurrent);
2279 if(pMFI == nullptr)
2280 {
2281 m_mergeItemList.clear();
2282 m_bRealMergeStarted = false;
2283 break;
2284 }
2285
2286 if(pMFI != nullptr && !bContinueWithCurrentItem)
2287 {
2288 if(bSim)
2289 {
2290 if(rowCount(miCurrent) == 0)
2291 {
2292 pMFI->endSimOp();
2293 }
2294 }
2295 else
2296 {
2297 if(rowCount(miCurrent) == 0)
2298 {
2299 if(pMFI->isOperationRunning())
2300 {
2301 setOpStatus(miCurrent, bSkipItem ? eOpStatusSkipped : eOpStatusDone);
2302 pMFI->endOperation();
2303 bSkipItem = false;
2304 }
2305 }
2306 else
2307 {
2308 setOpStatus(miCurrent, eOpStatusInProgress);
2309 }
2310 }
2311 }
2312
2313 if(!bContinueWithCurrentItem)
2314 {
2315 // Depth first
2316 QModelIndex miPrev = miCurrent;
2317 ++m_currentIndexForOperation;
2318 miCurrent = m_currentIndexForOperation == m_mergeItemList.end() ? QModelIndex() : *m_currentIndexForOperation;
2319 if((!miCurrent.isValid() || miCurrent.parent() != miPrev.parent()) && miPrev.parent().isValid())
2320 {
2321 // Check if the parent may be set to "Done"
2322 QModelIndex miParent = miPrev.parent();
2323 bool bDone = true;
2324 while(bDone && miParent.isValid())
2325 {
2326 for(int childIdx = 0; childIdx < rowCount(miParent); ++childIdx)
2327 {
2328 pMFI = getMFI(index(childIdx, 0, miParent));
2329 if((!bSim && pMFI->isOperationRunning()) || (bSim && !pMFI->isSimOpRunning()))
2330 {
2331 bDone = false;
2332 break;
2333 }
2334 }
2335 if(bDone)
2336 {
2337 pMFI = getMFI(miParent);
2338 if(bSim)
2339 pMFI->endSimOp();
2340 else
2341 {
2342 setOpStatus(miParent, eOpStatusDone);
2343 pMFI->endOperation();
2344 }
2345 }
2346 miParent = miParent.parent();
2347 }
2348 }
2349 }
2350
2351 if(!miCurrent.isValid()) // end?
2352 {
2353 if(m_bRealMergeStarted)
2354 {
2355 if(bVerbose)
2356 {
2357 KMessageBox::information(mWindow, i18n("Merge operation complete."), i18n("Merge Complete"));
2358 }
2359 m_bRealMergeStarted = false;
2360 m_pStatusInfo->setWindowTitle(i18n("Merge Complete"));
2361 }
2362 if(m_bSimulatedMergeStarted)
2363 {
2364 m_bSimulatedMergeStarted = false;
2365 QModelIndex mi = rowCount() > 0 ? index(0, 0, QModelIndex()) : QModelIndex();
2366 for(; mi.isValid(); mi = treeIterator(mi))
2367 {
2368 getMFI(mi)->startSimOp();
2369 }
2370 m_pStatusInfo->setWindowTitle(i18n("Simulated merge complete: Check if you agree with the proposed operations."));
2371 m_pStatusInfo->exec();
2372 }
2373 m_mergeItemList.clear();
2374 m_bRealMergeStarted = false;
2375 return;
2376 }
2377
2378 pMFI = getMFI(miCurrent);
2379
2380 pp.setInformation(pMFI->subPath(),
2381 bSim ? nrOfCompletedSimItems : nrOfCompletedItems,
2382 false // bRedrawUpdate
2383 );
2384
2385 bSuccess = executeMergeOperation(*pMFI, bSingleFileMerge); // Here the real operation happens.
2386
2387 if(bSuccess)
2388 {
2389 if(bSim)
2390 ++nrOfCompletedSimItems;
2391 else
2392 ++nrOfCompletedItems;
2393 bContinueWithCurrentItem = false;
2394 }
2395
2396 if(pp.wasCancelled())
2397 break;
2398 } // end while
2399
2400 //g_pProgressDialog->hide();
2401
2402 mWindow->setCurrentIndex(miCurrent);
2403 mWindow->scrollTo(miCurrent, EnsureVisible);
2404 if(!bSuccess && !bSingleFileMerge)
2405 {
2406 KMessageBox::error(mWindow, i18n("An error occurred. Press OK to see detailed information."));
2407 m_pStatusInfo->setWindowTitle(i18n("Merge Error"));
2408 m_pStatusInfo->exec();
2409 //if ( m_pStatusInfo->firstChild()!=0 )
2410 // m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() );
2411 m_bError = true;
2412
2413 setOpStatus(miCurrent, eOpStatusError);
2414 }
2415 else
2416 {
2417 m_bError = false;
2418 }
2419 Q_EMIT mWindow->updateAvailabilities();
2420
2421 if(m_currentIndexForOperation == m_mergeItemList.end())
2422 {
2423 m_mergeItemList.clear();
2424 m_bRealMergeStarted = false;
2425 }
2426 }
2427
deleteFLD(const QString & name,bool bCreateBackup)2428 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::deleteFLD(const QString& name, bool bCreateBackup)
2429 {
2430 FileAccess fi(name, true);
2431 if(!fi.exists())
2432 return true;
2433
2434 if(bCreateBackup)
2435 {
2436 bool bSuccess = renameFLD(name, name + ".orig");
2437 if(!bSuccess)
2438 {
2439 m_pStatusInfo->addText(i18n("Error: While deleting %1: Creating backup failed.", name));
2440 return false;
2441 }
2442 }
2443 else
2444 {
2445 if(fi.isDir() && !fi.isSymLink())
2446 m_pStatusInfo->addText(i18n("delete folder recursively( %1 )", name));
2447 else
2448 m_pStatusInfo->addText(i18n("delete( %1 )", name));
2449
2450 if(m_bSimulatedMergeStarted)
2451 {
2452 return true;
2453 }
2454
2455 if(fi.isDir() && !fi.isSymLink()) // recursive directory delete only for real dirs, not symlinks
2456 {
2457 t_DirectoryList dirList;
2458 bool bSuccess = fi.listDir(&dirList, false, true, "*", "", "", false, false); // not recursive, find hidden files
2459
2460 if(!bSuccess)
2461 {
2462 // No Permission to read directory or other error.
2463 m_pStatusInfo->addText(i18n("Error: delete folder operation failed while trying to read the folder."));
2464 return false;
2465 }
2466
2467 for(const FileAccess& fi2: dirList) // for each file...
2468 {
2469 Q_ASSERT(fi2.fileName() != "." && fi2.fileName() != "..");
2470
2471 bSuccess = deleteFLD(fi2.absoluteFilePath(), false);
2472 if(!bSuccess) break;
2473 }
2474 if(bSuccess)
2475 {
2476 bSuccess = FileAccess::removeDir(name);
2477 if(!bSuccess)
2478 {
2479 m_pStatusInfo->addText(i18n("Error: rmdir( %1 ) operation failed.", name)); // krazy:exclude=syscalls
2480 return false;
2481 }
2482 }
2483 }
2484 else
2485 {
2486 bool bSuccess = fi.removeFile();
2487 if(!bSuccess)
2488 {
2489 m_pStatusInfo->addText(i18n("Error: delete operation failed."));
2490 return false;
2491 }
2492 }
2493 }
2494 return true;
2495 }
2496
mergeFLD(const QString & nameA,const QString & nameB,const QString & nameC,const QString & nameDest,bool & bSingleFileMerge)2497 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::mergeFLD(const QString& nameA, const QString& nameB, const QString& nameC, const QString& nameDest, bool& bSingleFileMerge)
2498 {
2499 FileAccess fi(nameA);
2500 if(fi.isDir())
2501 {
2502 return makeDir(nameDest);
2503 }
2504
2505 QStringList errors;
2506 // Make sure that the dir exists, into which we will save the file later.
2507 int pos = nameDest.lastIndexOf('/');
2508 if(pos > 0)
2509 {
2510 QString parentName = nameDest.left(pos);
2511 bool bSuccess = makeDir(parentName, true /*quiet*/);
2512 if(!bSuccess)
2513 return false;
2514 }
2515
2516 m_pStatusInfo->addText(i18n("manual merge( %1, %2, %3 -> %4)", nameA, nameB, nameC, nameDest));
2517 if(m_bSimulatedMergeStarted)
2518 {
2519 m_pStatusInfo->addText(i18n(" Note: After a manual merge the user should continue by pressing F7."));
2520 return true;
2521 }
2522
2523 bSingleFileMerge = true;
2524 setOpStatus(*m_currentIndexForOperation, eOpStatusInProgress);
2525 mWindow->scrollTo(*m_currentIndexForOperation, EnsureVisible);
2526
2527 Q_EMIT mWindow->startDiffMerge(errors, nameA, nameB, nameC, nameDest, "", "", "", nullptr);
2528
2529 return false;
2530 }
2531
copyFLD(const QString & srcName,const QString & destName)2532 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::copyFLD(const QString& srcName, const QString& destName)
2533 {
2534 bool bSuccess = false;
2535
2536 if(srcName == destName)
2537 return true;
2538
2539 FileAccess fi(srcName);
2540 FileAccess faDest(destName, true);
2541 if(faDest.exists() && !(fi.isDir() && faDest.isDir() && (fi.isSymLink() == faDest.isSymLink())))
2542 {
2543 bSuccess = deleteFLD(destName, m_pOptions->m_bDmCreateBakFiles);
2544 if(!bSuccess)
2545 {
2546 m_pStatusInfo->addText(i18n("Error: copy( %1 -> %2 ) failed."
2547 "Deleting existing destination failed.",
2548 srcName, destName));
2549 return bSuccess;
2550 }
2551 }
2552
2553 if(fi.isSymLink() && ((fi.isDir() && !m_bFollowDirLinks) || (!fi.isDir() && !m_bFollowFileLinks)))
2554 {
2555 m_pStatusInfo->addText(i18n("copyLink( %1 -> %2 )", srcName, destName));
2556
2557 if(m_bSimulatedMergeStarted)
2558 {
2559 return true;
2560 }
2561 FileAccess destFi(destName);
2562 if(!destFi.isLocal() || !fi.isLocal())
2563 {
2564 m_pStatusInfo->addText(i18n("Error: copyLink failed: Remote links are not yet supported."));
2565 return false;
2566 }
2567
2568 bSuccess = false;
2569 QString linkTarget = fi.readLink();
2570 if(!linkTarget.isEmpty())
2571 {
2572 bSuccess = FileAccess::symLink(linkTarget, destName);
2573 if(!bSuccess)
2574 m_pStatusInfo->addText(i18n("Error: copyLink failed."));
2575 }
2576 return bSuccess;
2577 }
2578
2579 if(fi.isDir())
2580 {
2581 if(faDest.exists())
2582 return true;
2583
2584 bSuccess = makeDir(destName);
2585 return bSuccess;
2586 }
2587
2588 int pos = destName.lastIndexOf('/');
2589 if(pos > 0)
2590 {
2591 QString parentName = destName.left(pos);
2592 bSuccess = makeDir(parentName, true /*quiet*/);
2593 if(!bSuccess)
2594 return false;
2595 }
2596
2597 m_pStatusInfo->addText(i18n("copy( %1 -> %2 )", srcName, destName));
2598
2599 if(m_bSimulatedMergeStarted)
2600 {
2601 return true;
2602 }
2603
2604 FileAccess faSrc(srcName);
2605 bSuccess = faSrc.copyFile(destName);
2606 if(!bSuccess) m_pStatusInfo->addText(faSrc.getStatusText());
2607 return bSuccess;
2608 }
2609
2610 // Rename is not an operation that can be selected by the user.
2611 // It will only be used to create backups.
2612 // Hence it will delete an existing destination without making a backup (of the old backup.)
renameFLD(const QString & srcName,const QString & destName)2613 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::renameFLD(const QString& srcName, const QString& destName)
2614 {
2615 if(srcName == destName)
2616 return true;
2617 FileAccess destFile = FileAccess(destName, true);
2618 if(destFile.exists())
2619 {
2620 bool bSuccess = deleteFLD(destName, false /*no backup*/);
2621 if(!bSuccess)
2622 {
2623 m_pStatusInfo->addText(i18n("Error during rename( %1 -> %2 ): "
2624 "Cannot delete existing destination.",
2625 srcName, destName));
2626 return false;
2627 }
2628 }
2629
2630 m_pStatusInfo->addText(i18n("rename( %1 -> %2 )", srcName, destName));
2631 if(m_bSimulatedMergeStarted)
2632 {
2633 return true;
2634 }
2635
2636 bool bSuccess = FileAccess(srcName).rename(destFile);
2637 if(!bSuccess)
2638 {
2639 m_pStatusInfo->addText(i18n("Error: Rename failed."));
2640 return false;
2641 }
2642
2643 return true;
2644 }
2645
makeDir(const QString & name,bool bQuiet)2646 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::makeDir(const QString& name, bool bQuiet)
2647 {
2648 FileAccess fi(name, true);
2649 if(fi.exists() && fi.isDir())
2650 return true;
2651
2652 if(fi.exists() && !fi.isDir())
2653 {
2654 bool bSuccess = deleteFLD(name, true);
2655 if(!bSuccess)
2656 {
2657 m_pStatusInfo->addText(i18n("Error during makeDir of %1. "
2658 "Cannot delete existing file.",
2659 name));
2660 return false;
2661 }
2662 }
2663
2664 int pos = name.lastIndexOf('/');
2665 if(pos > 0)
2666 {
2667 QString parentName = name.left(pos);
2668 bool bSuccess = makeDir(parentName, true);
2669 if(!bSuccess)
2670 return false;
2671 }
2672
2673 if(!bQuiet)
2674 m_pStatusInfo->addText(i18n("makeDir( %1 )", name));
2675
2676 if(m_bSimulatedMergeStarted)
2677 {
2678 return true;
2679 }
2680
2681 bool bSuccess = FileAccess::makeDir(name);
2682 if(!bSuccess)
2683 {
2684 m_pStatusInfo->addText(i18n("Error while creating folder."));
2685 return false;
2686 }
2687 return true;
2688 }
2689
DirectoryMergeInfo(QWidget * pParent)2690 DirectoryMergeInfo::DirectoryMergeInfo(QWidget* pParent)
2691 : QFrame(pParent)
2692 {
2693 QVBoxLayout* topLayout = new QVBoxLayout(this);
2694 topLayout->setContentsMargins(0, 0, 0, 0);
2695
2696 QGridLayout* grid = new QGridLayout();
2697 topLayout->addLayout(grid);
2698 grid->setColumnStretch(1, 10);
2699
2700 int line = 0;
2701
2702 m_pA = new QLabel(i18n("A"), this);
2703 grid->addWidget(m_pA, line, 0);
2704 m_pInfoA = new QLabel(this);
2705 grid->addWidget(m_pInfoA, line, 1);
2706 ++line;
2707
2708 m_pB = new QLabel(i18n("B"), this);
2709 grid->addWidget(m_pB, line, 0);
2710 m_pInfoB = new QLabel(this);
2711 grid->addWidget(m_pInfoB, line, 1);
2712 ++line;
2713
2714 m_pC = new QLabel(i18n("C"), this);
2715 grid->addWidget(m_pC, line, 0);
2716 m_pInfoC = new QLabel(this);
2717 grid->addWidget(m_pInfoC, line, 1);
2718 ++line;
2719
2720 m_pDest = new QLabel(i18n("Dest"), this);
2721 grid->addWidget(m_pDest, line, 0);
2722 m_pInfoDest = new QLabel(this);
2723 grid->addWidget(m_pInfoDest, line, 1);
2724 ++line;
2725
2726 m_pInfoList = new QTreeWidget(this);
2727 topLayout->addWidget(m_pInfoList);
2728 m_pInfoList->setHeaderLabels({i18n("Folder"), i18n("Type"), i18n("Size"),
2729 i18n("Attr"), i18n("Last Modification"), i18n("Link-Destination")});
2730 setMinimumSize(100, 100);
2731
2732 m_pInfoList->installEventFilter(this);
2733 m_pInfoList->setRootIsDecorated(false);
2734 }
2735
eventFilter(QObject * o,QEvent * e)2736 bool DirectoryMergeInfo::eventFilter(QObject* o, QEvent* e)
2737 {
2738 if(e->type() == QEvent::FocusIn && o == m_pInfoList)
2739 Q_EMIT gotFocus();
2740 return false;
2741 }
2742
addListViewItem(const QString & dir,const QString & basePath,FileAccess * fi)2743 void DirectoryMergeInfo::addListViewItem(const QString& dir, const QString& basePath, FileAccess* fi)
2744 {
2745 if(basePath.isEmpty())
2746 {
2747 return;
2748 }
2749
2750 if(fi != nullptr && fi->exists())
2751 {
2752 QString dateString = fi->lastModified().toString(QLocale::system().dateTimeFormat());
2753
2754 m_pInfoList->addTopLevelItem(new QTreeWidgetItem(
2755 m_pInfoList,
2756 {dir, QString(fi->isDir() ? i18n("Folder") : i18n("File")) + (fi->isSymLink() ? i18n("-Link") : ""), QString::number(fi->size()), QLatin1String(fi->isReadable() ? "r" : " ") + QLatin1String(fi->isWritable() ? "w" : " ") + QLatin1String((fi->isExecutable() ? "x" : " ")), dateString, QString(fi->isSymLink() ? (" -> " + fi->readLink()) : QString(""))}));
2757 }
2758 else
2759 {
2760 m_pInfoList->addTopLevelItem(new QTreeWidgetItem(
2761 m_pInfoList,
2762 {dir, i18n("not available"), "", "", "", ""}));
2763 }
2764 }
2765
setInfo(const FileAccess & dirA,const FileAccess & dirB,const FileAccess & dirC,const FileAccess & dirDest,MergeFileInfos & mfi)2766 void DirectoryMergeInfo::setInfo(
2767 const FileAccess& dirA,
2768 const FileAccess& dirB,
2769 const FileAccess& dirC,
2770 const FileAccess& dirDest,
2771 MergeFileInfos& mfi)
2772 {
2773 bool bHideDest = false;
2774 if(dirA.absoluteFilePath() == dirDest.absoluteFilePath())
2775 {
2776 m_pA->setText(i18n("A (Dest): "));
2777 bHideDest = true;
2778 }
2779 else
2780 m_pA->setText(!dirC.isValid() ? i18n("A: ") : i18n("A (Base): "));
2781
2782 m_pInfoA->setText(dirA.prettyAbsPath());
2783
2784 if(dirB.absoluteFilePath() == dirDest.absoluteFilePath())
2785 {
2786 m_pB->setText(i18n("B (Dest): "));
2787 bHideDest = true;
2788 }
2789 else
2790 m_pB->setText(i18n("B: "));
2791 m_pInfoB->setText(dirB.prettyAbsPath());
2792
2793 if(dirC.absoluteFilePath() == dirDest.absoluteFilePath())
2794 {
2795 m_pC->setText(i18n("C (Dest): "));
2796 bHideDest = true;
2797 }
2798 else
2799 m_pC->setText(i18n("C: "));
2800 m_pInfoC->setText(dirC.prettyAbsPath());
2801
2802 m_pDest->setText(i18n("Dest: "));
2803 m_pInfoDest->setText(dirDest.prettyAbsPath());
2804
2805 if(!dirC.isValid())
2806 {
2807 m_pC->hide();
2808 m_pInfoC->hide();
2809 }
2810 else
2811 {
2812 m_pC->show();
2813 m_pInfoC->show();
2814 }
2815
2816 if(!dirDest.isValid() || bHideDest)
2817 {
2818 m_pDest->hide();
2819 m_pInfoDest->hide();
2820 }
2821 else
2822 {
2823 m_pDest->show();
2824 m_pInfoDest->show();
2825 }
2826
2827 m_pInfoList->clear();
2828 addListViewItem(i18n("A"), dirA.prettyAbsPath(), mfi.getFileInfoA());
2829 addListViewItem(i18n("B"), dirB.prettyAbsPath(), mfi.getFileInfoB());
2830 addListViewItem(i18n("C"), dirC.prettyAbsPath(), mfi.getFileInfoC());
2831 if(!bHideDest)
2832 {
2833 FileAccess fiDest(dirDest.prettyAbsPath() + '/' + mfi.subPath(), true);
2834 addListViewItem(i18n("Dest"), dirDest.prettyAbsPath(), &fiDest);
2835 }
2836 for(int i = 0; i < m_pInfoList->columnCount(); ++i)
2837 m_pInfoList->resizeColumnToContents(i);
2838 }
2839
slotSaveMergeState()2840 void DirectoryMergeWindow::slotSaveMergeState()
2841 {
2842 //slotStatusMsg(i18n("Saving Directory Merge State ..."));
2843
2844 QString dirMergeStateFilename = QFileDialog::getSaveFileName(this, i18n("Save Folder Merge State As..."), QDir::currentPath());
2845 if(!dirMergeStateFilename.isEmpty())
2846 {
2847 QFile file(dirMergeStateFilename);
2848 bool bSuccess = file.open(QIODevice::WriteOnly);
2849 if(bSuccess)
2850 {
2851 QTextStream ts(&file);
2852
2853 QModelIndex mi(d->index(0, 0, QModelIndex()));
2854 while(mi.isValid())
2855 {
2856 MergeFileInfos* pMFI = d->getMFI(mi);
2857 ts << *pMFI;
2858 mi = d->treeIterator(mi, true, true);
2859 }
2860 }
2861 }
2862
2863 //slotStatusMsg(i18n("Ready."));
2864 }
2865
slotLoadMergeState()2866 void DirectoryMergeWindow::slotLoadMergeState()
2867 {
2868 }
2869
updateFileVisibilities()2870 void DirectoryMergeWindow::updateFileVisibilities()
2871 {
2872 bool bShowIdentical = d->m_pDirShowIdenticalFiles->isChecked();
2873 bool bShowDifferent = d->m_pDirShowDifferentFiles->isChecked();
2874 bool bShowOnlyInA = d->m_pDirShowFilesOnlyInA->isChecked();
2875 bool bShowOnlyInB = d->m_pDirShowFilesOnlyInB->isChecked();
2876 bool bShowOnlyInC = d->m_pDirShowFilesOnlyInC->isChecked();
2877 bool bThreeDirs = d->isThreeWay();
2878 d->m_selection1Index = QModelIndex();
2879 d->m_selection2Index = QModelIndex();
2880 d->m_selection3Index = QModelIndex();
2881
2882 // in first run set all dirs to equal and determine if they are not equal.
2883 // on second run don't change the equal-status anymore; it is needed to
2884 // set the visibility (when bShowIdentical is false).
2885 for(int loop = 0; loop < 2; ++loop)
2886 {
2887 QModelIndex mi = d->rowCount() > 0 ? d->index(0, 0, QModelIndex()) : QModelIndex();
2888 while(mi.isValid())
2889 {
2890 MergeFileInfos* pMFI = d->getMFI(mi);
2891 bool bDir = pMFI->hasDir();
2892 if(loop == 0 && bDir)
2893 { //Treat all links and directories to equal by default.
2894 pMFI->updateDirectoryOrLink();
2895 }
2896
2897 bool bVisible =
2898 (bShowIdentical && pMFI->existsEveryWhere() && pMFI->isEqualAB() && (pMFI->isEqualAC() || !bThreeDirs)) ||
2899 ((bShowDifferent || bDir) && pMFI->existsCount() >= 2 && (!pMFI->isEqualAB() || !(pMFI->isEqualAC() || !bThreeDirs))) ||
2900 (bShowOnlyInA && pMFI->onlyInA()) || (bShowOnlyInB && pMFI->onlyInB()) || (bShowOnlyInC && pMFI->onlyInC());
2901
2902 QString fileName = pMFI->fileName();
2903 bVisible = bVisible && ((bDir && !Utils::wildcardMultiMatch(d->m_pOptions->m_DmDirAntiPattern, fileName, d->m_bCaseSensitive)) || (Utils::wildcardMultiMatch(d->m_pOptions->m_DmFilePattern, fileName, d->m_bCaseSensitive) && !Utils::wildcardMultiMatch(d->m_pOptions->m_DmFileAntiPattern, fileName, d->m_bCaseSensitive)));
2904
2905 if(loop != 0)
2906 setRowHidden(mi.row(), mi.parent(), !bVisible);
2907
2908 bool bEqual = bThreeDirs ? pMFI->isEqualAB() && pMFI->isEqualAC() : pMFI->isEqualAB();
2909 if(!bEqual && bVisible && loop == 0) // Set all parents to "not equal"
2910 {
2911 pMFI->updateParents();
2912 }
2913 mi = d->treeIterator(mi, true, true);
2914 }
2915 }
2916 }
2917
slotShowIdenticalFiles()2918 void DirectoryMergeWindow::slotShowIdenticalFiles()
2919 {
2920 d->m_pOptions->m_bDmShowIdenticalFiles = d->m_pDirShowIdenticalFiles->isChecked();
2921 updateFileVisibilities();
2922 }
slotShowDifferentFiles()2923 void DirectoryMergeWindow::slotShowDifferentFiles()
2924 {
2925 updateFileVisibilities();
2926 }
slotShowFilesOnlyInA()2927 void DirectoryMergeWindow::slotShowFilesOnlyInA()
2928 {
2929 updateFileVisibilities();
2930 }
slotShowFilesOnlyInB()2931 void DirectoryMergeWindow::slotShowFilesOnlyInB()
2932 {
2933 updateFileVisibilities();
2934 }
slotShowFilesOnlyInC()2935 void DirectoryMergeWindow::slotShowFilesOnlyInC()
2936 {
2937 updateFileVisibilities();
2938 }
2939
slotSynchronizeDirectories()2940 void DirectoryMergeWindow::slotSynchronizeDirectories() {}
slotChooseNewerFiles()2941 void DirectoryMergeWindow::slotChooseNewerFiles() {}
2942
initDirectoryMergeActions(KDiff3App * pKDiff3App,KActionCollection * ac)2943 void DirectoryMergeWindow::initDirectoryMergeActions(KDiff3App* pKDiff3App, KActionCollection* ac)
2944 {
2945 #include "xpm/showequalfiles.xpm"
2946 #include "xpm/showfilesonlyina.xpm"
2947 #include "xpm/showfilesonlyinb.xpm"
2948 #include "xpm/showfilesonlyinc.xpm"
2949 #include "xpm/startmerge.xpm"
2950
2951 d->m_pDirStartOperation = GuiUtils::createAction<QAction>(i18n("Start/Continue Folder Merge"), QKeySequence(Qt::Key_F7), this, &DirectoryMergeWindow::slotRunOperationForAllItems, ac, "dir_start_operation");
2952 d->m_pDirRunOperationForCurrentItem = GuiUtils::createAction<QAction>(i18n("Run Operation for Current Item"), QKeySequence(Qt::Key_F6), this, &DirectoryMergeWindow::slotRunOperationForCurrentItem, ac, "dir_run_operation_for_current_item");
2953 d->m_pDirCompareCurrent = GuiUtils::createAction<QAction>(i18n("Compare Selected File"), this, &DirectoryMergeWindow::compareCurrentFile, ac, "dir_compare_current");
2954 d->m_pDirMergeCurrent = GuiUtils::createAction<QAction>(i18n("Merge Current File"), QIcon(QPixmap(startmerge)), i18n("Merge\nFile"), pKDiff3App, &KDiff3App::slotMergeCurrentFile, ac, "merge_current");
2955 d->m_pDirFoldAll = GuiUtils::createAction<QAction>(i18n("Fold All Subfolders"), this, &DirectoryMergeWindow::collapseAll, ac, "dir_fold_all");
2956 d->m_pDirUnfoldAll = GuiUtils::createAction<QAction>(i18n("Unfold All Subfolders"), this, &DirectoryMergeWindow::expandAll, ac, "dir_unfold_all");
2957 d->m_pDirRescan = GuiUtils::createAction<QAction>(i18n("Rescan"), QKeySequence(Qt::SHIFT + Qt::Key_F5), this, &DirectoryMergeWindow::reload, ac, "dir_rescan");
2958 d->m_pDirSaveMergeState = nullptr; //GuiUtils::createAction< QAction >(i18n("Save Directory Merge State ..."), 0, this, &DirectoryMergeWindow::slotSaveMergeState, ac, "dir_save_merge_state");
2959 d->m_pDirLoadMergeState = nullptr; //GuiUtils::createAction< QAction >(i18n("Load Directory Merge State ..."), 0, this, &DirectoryMergeWindow::slotLoadMergeState, ac, "dir_load_merge_state");
2960 d->m_pDirChooseAEverywhere = GuiUtils::createAction<QAction>(i18n("Choose A for All Items"), this, &DirectoryMergeWindow::slotChooseAEverywhere, ac, "dir_choose_a_everywhere");
2961 d->m_pDirChooseBEverywhere = GuiUtils::createAction<QAction>(i18n("Choose B for All Items"), this, &DirectoryMergeWindow::slotChooseBEverywhere, ac, "dir_choose_b_everywhere");
2962 d->m_pDirChooseCEverywhere = GuiUtils::createAction<QAction>(i18n("Choose C for All Items"), this, &DirectoryMergeWindow::slotChooseCEverywhere, ac, "dir_choose_c_everywhere");
2963 d->m_pDirAutoChoiceEverywhere = GuiUtils::createAction<QAction>(i18n("Auto-Choose Operation for All Items"), this, &DirectoryMergeWindow::slotAutoChooseEverywhere, ac, "dir_autochoose_everywhere");
2964 d->m_pDirDoNothingEverywhere = GuiUtils::createAction<QAction>(i18n("No Operation for All Items"), this, &DirectoryMergeWindow::slotNoOpEverywhere, ac, "dir_nothing_everywhere");
2965
2966 // d->m_pDirSynchronizeDirectories = GuiUtils::createAction< KToggleAction >(i18n("Synchronize Directories"), 0, this, &DirectoryMergeWindow::slotSynchronizeDirectories, ac, "dir_synchronize_directories");
2967 // d->m_pDirChooseNewerFiles = GuiUtils::createAction< KToggleAction >(i18n("Copy Newer Files Instead of Merging"), 0, this, &DirectoryMergeWindow::slotChooseNewerFiles, ac, "dir_choose_newer_files");
2968
2969 d->m_pDirShowIdenticalFiles = GuiUtils::createAction<KToggleAction>(i18n("Show Identical Files"), QIcon(QPixmap(showequalfiles)), i18n("Identical\nFiles"), this, &DirectoryMergeWindow::slotShowIdenticalFiles, ac, "dir_show_identical_files");
2970 d->m_pDirShowDifferentFiles = GuiUtils::createAction<KToggleAction>(i18n("Show Different Files"), this, &DirectoryMergeWindow::slotShowDifferentFiles, ac, "dir_show_different_files");
2971 d->m_pDirShowFilesOnlyInA = GuiUtils::createAction<KToggleAction>(i18n("Show Files only in A"), QIcon(QPixmap(showfilesonlyina)), i18n("Files\nonly in A"), this, &DirectoryMergeWindow::slotShowFilesOnlyInA, ac, "dir_show_files_only_in_a");
2972 d->m_pDirShowFilesOnlyInB = GuiUtils::createAction<KToggleAction>(i18n("Show Files only in B"), QIcon(QPixmap(showfilesonlyinb)), i18n("Files\nonly in B"), this, &DirectoryMergeWindow::slotShowFilesOnlyInB, ac, "dir_show_files_only_in_b");
2973 d->m_pDirShowFilesOnlyInC = GuiUtils::createAction<KToggleAction>(i18n("Show Files only in C"), QIcon(QPixmap(showfilesonlyinc)), i18n("Files\nonly in C"), this, &DirectoryMergeWindow::slotShowFilesOnlyInC, ac, "dir_show_files_only_in_c");
2974
2975 d->m_pDirShowIdenticalFiles->setChecked(d->m_pOptions->m_bDmShowIdenticalFiles);
2976
2977 d->m_pDirCompareExplicit = GuiUtils::createAction<QAction>(i18n("Compare Explicitly Selected Files"), this, &DirectoryMergeWindow::slotCompareExplicitlySelectedFiles, ac, "dir_compare_explicitly_selected_files");
2978 d->m_pDirMergeExplicit = GuiUtils::createAction<QAction>(i18n("Merge Explicitly Selected Files"), this, &DirectoryMergeWindow::slotMergeExplicitlySelectedFiles, ac, "dir_merge_explicitly_selected_files");
2979
2980 d->m_pDirCurrentDoNothing = GuiUtils::createAction<QAction>(i18n("Do Nothing"), this, &DirectoryMergeWindow::slotCurrentDoNothing, ac, "dir_current_do_nothing");
2981 d->m_pDirCurrentChooseA = GuiUtils::createAction<QAction>(i18n("A"), this, &DirectoryMergeWindow::slotCurrentChooseA, ac, "dir_current_choose_a");
2982 d->m_pDirCurrentChooseB = GuiUtils::createAction<QAction>(i18n("B"), this, &DirectoryMergeWindow::slotCurrentChooseB, ac, "dir_current_choose_b");
2983 d->m_pDirCurrentChooseC = GuiUtils::createAction<QAction>(i18n("C"), this, &DirectoryMergeWindow::slotCurrentChooseC, ac, "dir_current_choose_c");
2984 d->m_pDirCurrentMerge = GuiUtils::createAction<QAction>(i18n("Merge"), this, &DirectoryMergeWindow::slotCurrentMerge, ac, "dir_current_merge");
2985 d->m_pDirCurrentDelete = GuiUtils::createAction<QAction>(i18n("Delete (if exists)"), this, &DirectoryMergeWindow::slotCurrentDelete, ac, "dir_current_delete");
2986
2987 d->m_pDirCurrentSyncDoNothing = GuiUtils::createAction<QAction>(i18n("Do Nothing"), this, &DirectoryMergeWindow::slotCurrentDoNothing, ac, "dir_current_sync_do_nothing");
2988 d->m_pDirCurrentSyncCopyAToB = GuiUtils::createAction<QAction>(i18n("Copy A to B"), this, &DirectoryMergeWindow::slotCurrentCopyAToB, ac, "dir_current_sync_copy_a_to_b");
2989 d->m_pDirCurrentSyncCopyBToA = GuiUtils::createAction<QAction>(i18n("Copy B to A"), this, &DirectoryMergeWindow::slotCurrentCopyBToA, ac, "dir_current_sync_copy_b_to_a");
2990 d->m_pDirCurrentSyncDeleteA = GuiUtils::createAction<QAction>(i18n("Delete A"), this, &DirectoryMergeWindow::slotCurrentDeleteA, ac, "dir_current_sync_delete_a");
2991 d->m_pDirCurrentSyncDeleteB = GuiUtils::createAction<QAction>(i18n("Delete B"), this, &DirectoryMergeWindow::slotCurrentDeleteB, ac, "dir_current_sync_delete_b");
2992 d->m_pDirCurrentSyncDeleteAAndB = GuiUtils::createAction<QAction>(i18n("Delete A && B"), this, &DirectoryMergeWindow::slotCurrentDeleteAAndB, ac, "dir_current_sync_delete_a_and_b");
2993 d->m_pDirCurrentSyncMergeToA = GuiUtils::createAction<QAction>(i18n("Merge to A"), this, &DirectoryMergeWindow::slotCurrentMergeToA, ac, "dir_current_sync_merge_to_a");
2994 d->m_pDirCurrentSyncMergeToB = GuiUtils::createAction<QAction>(i18n("Merge to B"), this, &DirectoryMergeWindow::slotCurrentMergeToB, ac, "dir_current_sync_merge_to_b");
2995 d->m_pDirCurrentSyncMergeToAAndB = GuiUtils::createAction<QAction>(i18n("Merge to A && B"), this, &DirectoryMergeWindow::slotCurrentMergeToAAndB, ac, "dir_current_sync_merge_to_a_and_b");
2996 }
2997
setupConnections(const KDiff3App * app)2998 void DirectoryMergeWindow::setupConnections(const KDiff3App* app)
2999 {
3000 chk_connect(this, &DirectoryMergeWindow::startDiffMerge, app, &KDiff3App::slotFileOpen2);
3001 chk_connect(selectionModel(), &QItemSelectionModel::selectionChanged, app, &KDiff3App::slotUpdateAvailabilities);
3002 chk_connect(selectionModel(), &QItemSelectionModel::currentChanged, app, &KDiff3App::slotUpdateAvailabilities);
3003 chk_connect(this, static_cast<void (DirectoryMergeWindow::*) (void)>(&DirectoryMergeWindow::updateAvailabilities), app, &KDiff3App::slotUpdateAvailabilities);
3004 chk_connect(this, &DirectoryMergeWindow::statusBarMessage, app, &KDiff3App::slotStatusMsg);
3005 chk_connect(app, &KDiff3App::doRefresh, this, &DirectoryMergeWindow::slotRefresh);
3006 }
3007
updateAvailabilities(bool bMergeEditorVisible,bool bDirCompare,bool bDiffWindowVisible,KToggleAction * chooseA,KToggleAction * chooseB,KToggleAction * chooseC)3008 void DirectoryMergeWindow::updateAvailabilities(bool bMergeEditorVisible, bool bDirCompare, bool bDiffWindowVisible,
3009 KToggleAction* chooseA, KToggleAction* chooseB, KToggleAction* chooseC)
3010 {
3011 d->m_pDirStartOperation->setEnabled(bDirCompare);
3012 d->m_pDirRunOperationForCurrentItem->setEnabled(bDirCompare);
3013 d->m_pDirFoldAll->setEnabled(bDirCompare);
3014 d->m_pDirUnfoldAll->setEnabled(bDirCompare);
3015
3016 d->m_pDirCompareCurrent->setEnabled(bDirCompare && isVisible() && isFileSelected());
3017
3018 d->m_pDirMergeCurrent->setEnabled((bDirCompare && isVisible() && isFileSelected()) || bDiffWindowVisible);
3019
3020 d->m_pDirRescan->setEnabled(bDirCompare);
3021
3022 bool bThreeDirs = d->isThreeWay();
3023 d->m_pDirAutoChoiceEverywhere->setEnabled(bDirCompare && isVisible());
3024 d->m_pDirDoNothingEverywhere->setEnabled(bDirCompare && isVisible());
3025 d->m_pDirChooseAEverywhere->setEnabled(bDirCompare && isVisible());
3026 d->m_pDirChooseBEverywhere->setEnabled(bDirCompare && isVisible());
3027 d->m_pDirChooseCEverywhere->setEnabled(bDirCompare && isVisible() && bThreeDirs);
3028
3029 MergeFileInfos* pMFI = d->getMFI(currentIndex());
3030
3031 bool bItemActive = bDirCompare && isVisible() && pMFI != nullptr; // && hasFocus();
3032 bool bMergeMode = bThreeDirs || !d->m_bSyncMode;
3033 bool bFTConflict = pMFI == nullptr ? false : pMFI->conflictingFileTypes();
3034
3035 bool bDirWindowHasFocus = isVisible() && hasFocus();
3036
3037 d->m_pDirShowIdenticalFiles->setEnabled(bDirCompare && isVisible());
3038 d->m_pDirShowDifferentFiles->setEnabled(bDirCompare && isVisible());
3039 d->m_pDirShowFilesOnlyInA->setEnabled(bDirCompare && isVisible());
3040 d->m_pDirShowFilesOnlyInB->setEnabled(bDirCompare && isVisible());
3041 d->m_pDirShowFilesOnlyInC->setEnabled(bDirCompare && isVisible() && bThreeDirs);
3042
3043 d->m_pDirCompareExplicit->setEnabled(bDirCompare && isVisible() && d->m_selection2Index.isValid());
3044 d->m_pDirMergeExplicit->setEnabled(bDirCompare && isVisible() && d->m_selection2Index.isValid());
3045
3046 d->m_pDirCurrentDoNothing->setEnabled(bItemActive && bMergeMode);
3047 d->m_pDirCurrentChooseA->setEnabled(bItemActive && bMergeMode && pMFI->existsInA());
3048 d->m_pDirCurrentChooseB->setEnabled(bItemActive && bMergeMode && pMFI->existsInB());
3049 d->m_pDirCurrentChooseC->setEnabled(bItemActive && bMergeMode && pMFI->existsInC());
3050 d->m_pDirCurrentMerge->setEnabled(bItemActive && bMergeMode && !bFTConflict);
3051 d->m_pDirCurrentDelete->setEnabled(bItemActive && bMergeMode);
3052 if(bDirWindowHasFocus)
3053 {
3054 chooseA->setEnabled(bItemActive && bDirCompare ? pMFI->existsInA() : true);
3055 chooseB->setEnabled(bItemActive && bDirCompare ? pMFI->existsInB() : true);
3056 chooseC->setEnabled(bItemActive && bDirCompare ? pMFI->existsInC() : KDiff3App::isTripleDiff() );
3057 chooseA->setChecked(false);
3058 chooseB->setChecked(false);
3059 chooseC->setChecked(false);
3060 }
3061 else
3062 {
3063 chooseA->setEnabled(bMergeEditorVisible);
3064 chooseB->setEnabled(bMergeEditorVisible);
3065 chooseC->setEnabled(bMergeEditorVisible && KDiff3App::isTripleDiff());
3066 }
3067
3068 d->m_pDirCurrentSyncDoNothing->setEnabled(bItemActive && !bMergeMode);
3069 d->m_pDirCurrentSyncCopyAToB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA());
3070 d->m_pDirCurrentSyncCopyBToA->setEnabled(bItemActive && !bMergeMode && pMFI->existsInB());
3071 d->m_pDirCurrentSyncDeleteA->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA());
3072 d->m_pDirCurrentSyncDeleteB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInB());
3073 d->m_pDirCurrentSyncDeleteAAndB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA() && pMFI->existsInB());
3074 d->m_pDirCurrentSyncMergeToA->setEnabled(bItemActive && !bMergeMode && !bFTConflict);
3075 d->m_pDirCurrentSyncMergeToB->setEnabled(bItemActive && !bMergeMode && !bFTConflict);
3076 d->m_pDirCurrentSyncMergeToAAndB->setEnabled(bItemActive && !bMergeMode && !bFTConflict);
3077 }
3078
3079 //#include "directorymergewindow.moc"
3080