Warning, file /utilities/krusader/app/Synchronizer/synchronizergui.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2003 Csaba Karai <krusader@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "synchronizergui.h"
0009 #include "../Dialogs/krspwidgets.h"
0010 #include "../FileSystem/krpermhandler.h"
0011 #include "../FileSystem/krquery.h"
0012 #include "../KViewer/krviewer.h"
0013 #include "../Panel/listpanel.h"
0014 #include "../Panel/panelfunc.h"
0015 #include "../defaults.h"
0016 #include "../filelisticon.h"
0017 #include "../krglobal.h"
0018 #include "../krservices.h"
0019 #include "../krslots.h"
0020 #include "../krusaderview.h"
0021 #include "feedtolistboxdialog.h"
0022 #include "synchronizedialog.h"
0023 #include "synchronizercolors.h"
0024 
0025 // QtCore
0026 #include <QEventLoop>
0027 #include <QHash>
0028 #include <QMimeData>
0029 #include <QRegExp>
0030 // QtGui
0031 #include <QClipboard>
0032 #include <QCursor>
0033 #include <QDrag>
0034 #include <QKeyEvent>
0035 #include <QMouseEvent>
0036 #include <QPixmap>
0037 #include <QResizeEvent>
0038 // QtWidgets
0039 #include <QApplication>
0040 #include <QFrame>
0041 #include <QGridLayout>
0042 #include <QGroupBox>
0043 #include <QHBoxLayout>
0044 #include <QHeaderView>
0045 #include <QLabel>
0046 #include <QLayout>
0047 #include <QMenu>
0048 #include <QSpinBox>
0049 
0050 #include <KConfigCore/KSharedConfig>
0051 #include <KGuiAddons/KColorUtils>
0052 #include <KI18n/KLocalizedString>
0053 #include <KIOWidgets/KUrlRequester>
0054 #include <KWidgetsAddons/KMessageBox>
0055 #include <utility>
0056 
0057 class SynchronizerListView : public KrTreeWidget
0058 {
0059 private:
0060     Synchronizer *synchronizer;
0061     bool isLeft;
0062 
0063 public:
0064     SynchronizerListView(Synchronizer *sync, QWidget *parent)
0065         : KrTreeWidget(parent)
0066         , synchronizer(sync)
0067     {
0068     }
0069 
0070     void mouseMoveEvent(QMouseEvent *e) override
0071     {
0072         isLeft = ((e->modifiers() & Qt::ShiftModifier) == 0);
0073         KrTreeWidget::mouseMoveEvent(e);
0074     }
0075 
0076     void startDrag(Qt::DropActions /* supportedActs */) override
0077     {
0078         QList<QUrl> urls;
0079 
0080         unsigned ndx = 0;
0081         SynchronizerFileItem *currentItem;
0082 
0083         while ((currentItem = synchronizer->getItemAt(ndx++)) != nullptr) {
0084             auto *viewItem = (SynchronizerGUI::SyncViewItem *)currentItem->userData();
0085 
0086             if (!viewItem || !viewItem->isSelected() || viewItem->isHidden())
0087                 continue;
0088 
0089             SynchronizerFileItem *item = viewItem->synchronizerItemRef();
0090             if (item) {
0091                 if (isLeft && item->existsInLeft()) {
0092                     QString leftDirName = item->leftDirectory().isEmpty() ? "" : item->leftDirectory() + '/';
0093                     QUrl leftURL = Synchronizer::fsUrl(synchronizer->leftBaseDirectory() + leftDirName + item->leftName());
0094                     urls.push_back(leftURL);
0095                 } else if (!isLeft && item->existsInRight()) {
0096                     QString rightDirName = item->rightDirectory().isEmpty() ? "" : item->rightDirectory() + '/';
0097                     QUrl rightURL = Synchronizer::fsUrl(synchronizer->rightBaseDirectory() + rightDirName + item->rightName());
0098                     urls.push_back(rightURL);
0099                 }
0100             }
0101         }
0102 
0103         if (urls.count() == 0)
0104             return;
0105 
0106         auto *drag = new QDrag(this);
0107         auto *mimeData = new QMimeData;
0108         mimeData->setImageData(FileListIcon(isLeft ? "arrow-left-double" : "arrow-right-double").pixmap());
0109         mimeData->setUrls(urls);
0110         drag->setMimeData(mimeData);
0111         drag->exec();
0112     }
0113 };
0114 
0115 SynchronizerGUI::SynchronizerGUI(QWidget *parent, QUrl leftURL, QUrl rightURL, QStringList selList)
0116     : QDialog(parent)
0117 {
0118     initGUI(QString(), std::move(leftURL), std::move(rightURL), std::move(selList));
0119 }
0120 
0121 SynchronizerGUI::SynchronizerGUI(QWidget *parent, QString profile)
0122     : QDialog(parent)
0123 {
0124     initGUI(std::move(profile), QUrl(), QUrl(), QStringList());
0125 }
0126 
0127 void SynchronizerGUI::initGUI(const QString &profileName, QUrl leftURL, QUrl rightURL, QStringList selList)
0128 {
0129     setAttribute(Qt::WA_DeleteOnClose);
0130 
0131     selectedFiles = std::move(selList);
0132     isComparing = wasClosed = wasSync = false;
0133 
0134     hasSelectedFiles = (selectedFiles.count() != 0);
0135 
0136     if (leftURL.isEmpty())
0137         leftURL = QUrl::fromLocalFile(ROOT_DIR);
0138     if (rightURL.isEmpty())
0139         rightURL = QUrl::fromLocalFile(ROOT_DIR);
0140 
0141     setWindowTitle(i18n("Krusader::Synchronize Folders"));
0142     auto *synchGrid = new QGridLayout(this);
0143     synchGrid->setSpacing(6);
0144     synchGrid->setContentsMargins(11, 11, 11, 11);
0145 
0146     synchronizerTabs = new QTabWidget(this);
0147 
0148     /* ============================== Compare groupbox ============================== */
0149 
0150     QWidget *synchronizerTab = new QWidget(this);
0151     auto *synchronizerGrid = new QGridLayout(synchronizerTab);
0152     synchronizerGrid->setSpacing(6);
0153     synchronizerGrid->setContentsMargins(11, 11, 11, 11);
0154 
0155     auto *compareDirs = new QGroupBox(synchronizerTab);
0156     compareDirs->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
0157     compareDirs->setTitle(i18n("Folder Comparison"));
0158 
0159     auto *grid = new QGridLayout(compareDirs);
0160     grid->setSpacing(6);
0161     grid->setContentsMargins(11, 11, 11, 11);
0162 
0163     leftDirLabel = new QLabel(compareDirs);
0164     leftDirLabel->setAlignment(Qt::AlignHCenter);
0165     grid->addWidget(leftDirLabel, 0, 0);
0166 
0167     QLabel *filterLabel = new QLabel(compareDirs);
0168     filterLabel->setText(i18n("File &Filter:"));
0169     filterLabel->setAlignment(Qt::AlignHCenter);
0170     grid->addWidget(filterLabel, 0, 1);
0171 
0172     rightDirLabel = new QLabel(compareDirs);
0173     rightDirLabel->setAlignment(Qt::AlignHCenter);
0174     grid->addWidget(rightDirLabel, 0, 2);
0175 
0176     KConfigGroup group(krConfig, "Synchronize");
0177 
0178     leftLocation = new KrHistoryComboBox(false, compareDirs);
0179     leftLocation->setMaxCount(25); // remember 25 items
0180     leftLocation->setDuplicatesEnabled(false);
0181     leftLocation->setEditable(true);
0182     leftLocation->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
0183     QStringList list = group.readEntry("Left Folder History", QStringList());
0184     leftLocation->setHistoryItems(list);
0185     auto *leftUrlReq = new KUrlRequester(leftLocation, compareDirs);
0186     leftUrlReq->setUrl(leftURL);
0187     leftUrlReq->setMode(KFile::Directory);
0188     leftUrlReq->setMinimumWidth(250);
0189     grid->addWidget(leftUrlReq, 1, 0);
0190     leftLocation->setWhatsThis(i18n("The left base folder used during the synchronization process."));
0191     leftUrlReq->setEnabled(!hasSelectedFiles);
0192     leftLocation->setEnabled(!hasSelectedFiles);
0193     leftDirLabel->setBuddy(leftLocation);
0194 
0195     fileFilter = new KrHistoryComboBox(false, compareDirs);
0196     fileFilter->setMaxCount(25); // remember 25 items
0197     fileFilter->setDuplicatesEnabled(false);
0198     fileFilter->setMinimumWidth(100);
0199     fileFilter->setMaximumWidth(100);
0200     fileFilter->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0201     list = group.readEntry("File Filter", QStringList());
0202     fileFilter->setHistoryItems(list);
0203     fileFilter->setEditText("*");
0204     grid->addWidget(fileFilter, 1, 1);
0205     filterLabel->setBuddy(fileFilter);
0206 
0207     QString wtFilter = "<p><img src='toolbar|find'></p>"
0208         + i18n("<p>The filename filtering criteria is defined here.</p><p>You can make use of wildcards. Multiple patterns are separated by space (means "
0209                "logical OR) and patterns are excluded from the search using the pipe symbol.</p><p>If the pattern is ended with a slash "
0210                "(<code>*pattern*/</code>), that means that pattern relates to recursive search of folders.<ul><li><code>pattern</code> - means to search those "
0211                "files/folders that name is <code>pattern</code>, recursive search goes through all subfolders independently of the value of "
0212                "<code>pattern</code></li><li><code>pattern/</code> - means to search all files/folders, but recursive search goes through/excludes the folders "
0213                "that name is <code>pattern</code></li></ul></p><p>It is allowed to use quotation marks for names that contain space. Filter "
0214                "<code>\"Program&nbsp;Files\"</code> searches out those files/folders that name is "
0215                "<code>Program&nbsp;Files</code>.</p><p>Examples:</p><ul><li><code>*.o</code></li><li><code>*.h *.c\?\?</code></li><li><code>*.cpp *.h | "
0216                "*.moc.cpp</code></li><li><code>* | .svn/ .git/</code></li></ul><p><b>Note</b>: the search term '<code>text</code>' is equivalent to "
0217                "'<code>*text*</code>'.</p>");
0218     fileFilter->setWhatsThis(wtFilter);
0219     filterLabel->setWhatsThis(wtFilter);
0220 
0221     rightLocation = new KrHistoryComboBox(compareDirs);
0222     rightLocation->setMaxCount(25); // remember 25 items
0223     rightLocation->setDuplicatesEnabled(false);
0224     rightLocation->setEditable(true);
0225     rightLocation->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
0226     list = group.readEntry("Right Folder History", QStringList());
0227     rightLocation->setHistoryItems(list);
0228     auto *rightUrlReq = new KUrlRequester(rightLocation, compareDirs);
0229     rightUrlReq->setUrl(rightURL);
0230     rightUrlReq->setMode(KFile::Directory);
0231     rightUrlReq->setMinimumWidth(250);
0232     grid->addWidget(rightUrlReq, 1, 2);
0233     rightLocation->setWhatsThis(i18n("The right base folder used during the synchronization process."));
0234     rightUrlReq->setEnabled(!hasSelectedFiles);
0235     rightLocation->setEnabled(!hasSelectedFiles);
0236     rightDirLabel->setBuddy(rightLocation);
0237 
0238     QWidget *optionWidget = new QWidget(compareDirs);
0239     auto *optionBox = new QHBoxLayout(optionWidget);
0240     optionBox->setContentsMargins(0, 0, 0, 0);
0241 
0242     QWidget *optionGridWidget = new QWidget(optionWidget);
0243     auto *optionGrid = new QGridLayout(optionGridWidget);
0244 
0245     optionBox->addWidget(optionGridWidget);
0246 
0247     cbSubdirs = new QCheckBox(i18n("Recurse subfolders"), optionGridWidget);
0248     cbSubdirs->setChecked(group.readEntry("Recurse Subdirectories", _RecurseSubdirs));
0249     optionGrid->addWidget(cbSubdirs, 0, 0);
0250     cbSubdirs->setWhatsThis(i18n("Compare not only the base folders but their subfolders as well."));
0251     cbSymlinks = new QCheckBox(i18n("Follow symlinks"), optionGridWidget);
0252     cbSymlinks->setChecked(group.readEntry("Follow Symlinks", _FollowSymlinks));
0253     cbSymlinks->setEnabled(cbSubdirs->isChecked());
0254     optionGrid->addWidget(cbSymlinks, 0, 1);
0255     cbSymlinks->setWhatsThis(i18n("Follow symbolic links during the compare process."));
0256     cbByContent = new QCheckBox(i18n("Compare by content"), optionGridWidget);
0257     cbByContent->setChecked(group.readEntry("Compare By Content", _CompareByContent));
0258     optionGrid->addWidget(cbByContent, 0, 2);
0259     cbByContent->setWhatsThis(i18n("Compare duplicated files with same size by content."));
0260     cbIgnoreDate = new QCheckBox(i18n("Ignore Date"), optionGridWidget);
0261     cbIgnoreDate->setChecked(group.readEntry("Ignore Date", _IgnoreDate));
0262     optionGrid->addWidget(cbIgnoreDate, 1, 0);
0263     cbIgnoreDate->setWhatsThis(
0264         i18n("<p>Ignore date information during the compare process.</p><p><b>Note</b>: useful if the files are located on network filesystems or in "
0265              "archives.</p>"));
0266     cbAsymmetric = new QCheckBox(i18n("Asymmetric"), optionGridWidget);
0267     cbAsymmetric->setChecked(group.readEntry("Asymmetric", _Asymmetric));
0268     optionGrid->addWidget(cbAsymmetric, 1, 1);
0269     cbAsymmetric->setWhatsThis(
0270         i18n("<p><b>Asymmetric mode</b></p><p>The left side is the destination, the right is the source folder. Files existing only in the left folder will be "
0271              "deleted, the other differing ones will be copied from right to left.</p><p><b>Note</b>: useful when updating a folder from a file server.</p>"));
0272     cbIgnoreCase = new QCheckBox(i18n("Ignore Case"), optionGridWidget);
0273     cbIgnoreCase->setChecked(group.readEntry("Ignore Case", _IgnoreCase));
0274     optionGrid->addWidget(cbIgnoreCase, 1, 2);
0275     cbIgnoreCase->setWhatsThis(i18n("<p>Case insensitive filename compare.</p><p><b>Note</b>: useful when synchronizing Windows filesystems.</p>"));
0276 
0277     /* =========================== Show options groupbox ============================= */
0278 
0279     auto *showOptions = new QGroupBox(optionWidget);
0280     optionBox->addWidget(showOptions);
0281 
0282     showOptions->setTitle(i18n("S&how options"));
0283     showOptions->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0284 
0285     auto *showOptionsLayout = new QGridLayout(showOptions);
0286     showOptionsLayout->setSpacing(4);
0287     showOptionsLayout->setContentsMargins(11, 11, 11, 11);
0288 
0289     bool checked;
0290     QString description;
0291 
0292     checked = group.readEntry("LeftToRight Button", _BtnLeftToRight);
0293     description = i18n("Show files marked to <i>Copy from left to right</i>.");
0294     btnLeftToRight = createButton(showOptions, "arrow-right", checked, Qt::CTRL + Qt::Key_L, description, ">");
0295     showOptionsLayout->addWidget(btnLeftToRight, 0, 0);
0296 
0297     checked = group.readEntry("Equals Button", _BtnEquals);
0298     description = i18n("Show files considered to be identical.");
0299     btnEquals = createButton(showOptions, "equals", checked, Qt::CTRL + Qt::Key_E, description, "=");
0300     showOptionsLayout->addWidget(btnEquals, 0, 1);
0301 
0302     checked = group.readEntry("Differents Button", _BtnDifferents);
0303     description = i18n("Show excluded files.");
0304     btnDifferents = createButton(showOptions, "unequals", checked, Qt::CTRL + Qt::Key_D, description, "!=");
0305     showOptionsLayout->addWidget(btnDifferents, 0, 2);
0306 
0307     checked = group.readEntry("RightToLeft Button", _BtnRightToLeft);
0308     description = i18n("Show files marked to <i>Copy from right to left</i>.");
0309     btnRightToLeft = createButton(showOptions, "arrow-left", checked, Qt::CTRL + Qt::Key_R, description, "<");
0310     showOptionsLayout->addWidget(btnRightToLeft, 0, 3);
0311 
0312     checked = group.readEntry("Deletable Button", _BtnDeletable);
0313     description = i18n("Show files marked to delete.");
0314     btnDeletable = createButton(showOptions, "user-trash", checked, Qt::CTRL + Qt::Key_T, description);
0315     showOptionsLayout->addWidget(btnDeletable, 0, 4);
0316 
0317     checked = group.readEntry("Duplicates Button", _BtnDuplicates);
0318     description = i18n("Show files that exist on both sides.");
0319     btnDuplicates = createButton(showOptions, "arrow-up", checked, Qt::CTRL + Qt::Key_I, description, i18n("Duplicates"), true);
0320     showOptionsLayout->addWidget(btnDuplicates, 0, 5);
0321 
0322     checked = group.readEntry("Singles Button", _BtnSingles);
0323     description = i18n("Show files that exist on one side only.");
0324     btnSingles = createButton(showOptions, "arrow-down", checked, Qt::CTRL + Qt::Key_N, description, i18n("Singles"), true);
0325     showOptionsLayout->addWidget(btnSingles, 0, 6);
0326 
0327     grid->addWidget(optionWidget, 2, 0, 1, 3);
0328 
0329     synchronizerGrid->addWidget(compareDirs, 0, 0);
0330 
0331     /* ========================= Synchronization list view ========================== */
0332     syncList = new SynchronizerListView(&synchronizer, synchronizerTab); // create the main container
0333     syncList->setWhatsThis(i18n("The compare results of the synchronizer (Ctrl+M)."));
0334     syncList->setAutoFillBackground(true);
0335     syncList->installEventFilter(this);
0336 
0337     KConfigGroup gl(krConfig, "Look&Feel");
0338     syncList->setFont(gl.readEntry("Filelist Font", _FilelistFont));
0339 
0340     syncList->setBackgroundRole(QPalette::Window);
0341     syncList->setAutoFillBackground(true);
0342 
0343     QStringList labels;
0344     labels << i18nc("@title:column file name", "Name"); // 0
0345     labels << i18nc("@title:column", "Size"); // 1
0346     labels << i18nc("@title:column", "Date"); // 2
0347     labels << i18n("<=>"); // 3
0348     labels << i18nc("@title:column", "Date"); // 4
0349     labels << i18nc("@title:column", "Size"); // 5
0350     labels << i18nc("@title:column file name", "Name"); // 6
0351 
0352     syncList->setHeaderLabels(labels);
0353 
0354     QHeaderView *header = syncList->header();
0355 
0356     if (group.hasKey("State")) {
0357         header->restoreState(group.readEntry("State", QByteArray()));
0358     }
0359 
0360     header->setSectionResizeMode(0, QHeaderView::Stretch);
0361     header->setSectionResizeMode(1, QHeaderView::ResizeToContents);
0362     header->setSectionResizeMode(2, QHeaderView::ResizeToContents);
0363     header->setSectionResizeMode(3, QHeaderView::ResizeToContents);
0364     header->setSectionResizeMode(4, QHeaderView::ResizeToContents);
0365     header->setSectionResizeMode(5, QHeaderView::ResizeToContents);
0366     header->setSectionResizeMode(6, QHeaderView::Stretch);
0367     header->setStretchLastSection(false);
0368 
0369     syncList->setAllColumnsShowFocus(true);
0370     syncList->setSelectionMode(QAbstractItemView::ExtendedSelection);
0371     syncList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0372     syncList->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0373     header->setSortIndicatorShown(false);
0374     syncList->setSortingEnabled(false);
0375     syncList->setRootIsDecorated(true);
0376     syncList->setIndentation(10);
0377     syncList->setDragEnabled(true);
0378     syncList->setAutoFillBackground(true);
0379 
0380     synchronizerGrid->addWidget(syncList, 1, 0);
0381 
0382     synchronizerTabs->addTab(synchronizerTab, i18n("&Synchronizer"));
0383     synchGrid->addWidget(synchronizerTabs, 0, 0);
0384 
0385     filterTabs = FilterTabs::addTo(synchronizerTabs, FilterTabs::HasDontSearchIn);
0386     generalFilter = dynamic_cast<GeneralFilter *>(filterTabs->get("GeneralFilter"));
0387     generalFilter->searchFor->setEditText(fileFilter->currentText());
0388     generalFilter->searchForCase->setChecked(true);
0389 
0390     // creating the time shift, equality threshold, hidden files options
0391 
0392     auto *optionsGroup = new QGroupBox(generalFilter);
0393     optionsGroup->setTitle(i18n("&Options"));
0394 
0395     auto *optionsLayout = new QGridLayout(optionsGroup);
0396     optionsLayout->setAlignment(Qt::AlignTop);
0397     optionsLayout->setSpacing(6);
0398     optionsLayout->setContentsMargins(11, 11, 11, 11);
0399 
0400     QLabel *parallelThreadsLabel = new QLabel(i18n("Parallel threads:"), optionsGroup);
0401     optionsLayout->addWidget(parallelThreadsLabel, 0, 0);
0402     parallelThreadsSpinBox = new QSpinBox(optionsGroup);
0403     parallelThreadsSpinBox->setMinimum(1);
0404     parallelThreadsSpinBox->setMaximum(15);
0405     int parThreads = group.readEntry("Parallel Threads", 1);
0406     parallelThreadsSpinBox->setValue(parThreads);
0407 
0408     optionsLayout->addWidget(parallelThreadsSpinBox, 0, 1);
0409 
0410     QLabel *equalityLabel = new QLabel(i18n("Equality threshold:"), optionsGroup);
0411     optionsLayout->addWidget(equalityLabel, 1, 0);
0412 
0413     equalitySpinBox = new QSpinBox(optionsGroup);
0414     equalitySpinBox->setMaximum(9999);
0415     optionsLayout->addWidget(equalitySpinBox, 1, 1);
0416 
0417     equalityUnitCombo = new QComboBox(optionsGroup);
0418     equalityUnitCombo->addItem(i18n("sec"));
0419     equalityUnitCombo->addItem(i18n("min"));
0420     equalityUnitCombo->addItem(i18n("hour"));
0421     equalityUnitCombo->addItem(i18n("day"));
0422     optionsLayout->addWidget(equalityUnitCombo, 1, 2);
0423 
0424     QLabel *timeShiftLabel = new QLabel(i18n("Time shift (right-left):"), optionsGroup);
0425     optionsLayout->addWidget(timeShiftLabel, 2, 0);
0426 
0427     timeShiftSpinBox = new QSpinBox(optionsGroup);
0428     timeShiftSpinBox->setMinimum(-9999);
0429     timeShiftSpinBox->setMaximum(9999);
0430     optionsLayout->addWidget(timeShiftSpinBox, 2, 1);
0431 
0432     timeShiftUnitCombo = new QComboBox(optionsGroup);
0433     timeShiftUnitCombo->addItem(i18n("sec"));
0434     timeShiftUnitCombo->addItem(i18n("min"));
0435     timeShiftUnitCombo->addItem(i18n("hour"));
0436     timeShiftUnitCombo->addItem(i18n("day"));
0437     optionsLayout->addWidget(timeShiftUnitCombo, 2, 2);
0438 
0439     QFrame *line = new QFrame(optionsGroup);
0440     line->setFrameStyle(QFrame::HLine | QFrame::Sunken);
0441     optionsLayout->addWidget(line, 3, 0, 1, 3);
0442 
0443     ignoreHiddenFilesCB = new QCheckBox(i18n("Ignore hidden files"), optionsGroup);
0444     optionsLayout->addWidget(ignoreHiddenFilesCB, 4, 0, 1, 3);
0445 
0446     generalFilter->middleLayout->addWidget(optionsGroup);
0447 
0448     /* ================================== Buttons =================================== */
0449 
0450     auto *buttons = new QHBoxLayout;
0451     buttons->setSpacing(6);
0452     buttons->setContentsMargins(0, 0, 0, 0);
0453 
0454     profileManager = new ProfileManager("SynchronizerProfile", this);
0455     profileManager->setShortcut(Qt::CTRL + Qt::Key_P);
0456     profileManager->setWhatsThis(i18n("Profile manager (Ctrl+P)."));
0457     buttons->addWidget(profileManager);
0458 
0459     btnSwapSides = new QPushButton(this);
0460     btnSwapSides->setIcon(Icon("document-swap"));
0461     btnSwapSides->setShortcut(Qt::CTRL + Qt::Key_S);
0462     btnSwapSides->setWhatsThis(i18n("Swap sides (Ctrl+S)."));
0463     buttons->addWidget(btnSwapSides);
0464 
0465     statusLabel = new QLabel(this);
0466     buttons->addWidget(statusLabel);
0467 
0468     auto *spacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
0469     buttons->addItem(spacer);
0470 
0471     btnCompareDirs = new QPushButton(this);
0472     btnCompareDirs->setText(i18n("Compare"));
0473     btnCompareDirs->setIcon(Icon("kr_comparedirs"));
0474     btnCompareDirs->setDefault(true);
0475     buttons->addWidget(btnCompareDirs);
0476 
0477     btnScrollResults = new QPushButton(this);
0478     btnScrollResults->setCheckable(true);
0479     btnScrollResults->setChecked(group.readEntry("Scroll Results", _ScrollResults));
0480     btnScrollResults->hide();
0481     if (btnScrollResults->isChecked())
0482         btnScrollResults->setText(i18n("Quiet"));
0483     else
0484         btnScrollResults->setText(i18n("Scroll Results"));
0485     buttons->addWidget(btnScrollResults);
0486 
0487     btnStopComparing = new QPushButton(this);
0488     btnStopComparing->setText(i18n("Stop"));
0489     btnStopComparing->setIcon(Icon("process-stop"));
0490     btnStopComparing->setEnabled(false);
0491     buttons->addWidget(btnStopComparing);
0492 
0493     btnFeedToListBox = new QPushButton(this);
0494     btnFeedToListBox->setText(i18n("Feed to listbox"));
0495     btnFeedToListBox->setIcon(Icon("list-add"));
0496     btnFeedToListBox->setEnabled(false);
0497     btnFeedToListBox->hide();
0498     buttons->addWidget(btnFeedToListBox);
0499 
0500     btnSynchronize = new QPushButton(this);
0501     btnSynchronize->setText(i18n("Synchronize"));
0502     btnSynchronize->setIcon(Icon("folder-sync"));
0503     btnSynchronize->setEnabled(false);
0504     buttons->addWidget(btnSynchronize);
0505 
0506     auto *btnCloseSync = new QPushButton(this);
0507     btnCloseSync->setText(i18n("Close"));
0508     btnCloseSync->setIcon(Icon("dialog-close"));
0509     buttons->addWidget(btnCloseSync);
0510 
0511     synchGrid->addLayout(buttons, 1, 0);
0512 
0513     /* =============================== Connect table ================================ */
0514 
0515     connect(syncList, &KrTreeWidget::itemRightClicked, this, &SynchronizerGUI::rightMouseClicked);
0516     connect(syncList, &KrTreeWidget::itemActivated, this, &SynchronizerGUI::doubleClicked);
0517 
0518     connect(profileManager, &ProfileManager::loadFromProfile, this, &SynchronizerGUI::loadFromProfile);
0519     connect(profileManager, &ProfileManager::saveToProfile, this, &SynchronizerGUI::saveToProfile);
0520 
0521     connect(btnSwapSides, &QPushButton::clicked, this, &SynchronizerGUI::swapSides);
0522     connect(btnCompareDirs, &QPushButton::clicked, this, &SynchronizerGUI::compare);
0523     connect(btnStopComparing, &QPushButton::clicked, this, &SynchronizerGUI::stop);
0524     connect(btnFeedToListBox, &QPushButton::clicked, this, &SynchronizerGUI::feedToListBox);
0525     connect(btnSynchronize, &QPushButton::clicked, this, &SynchronizerGUI::synchronize);
0526     connect(btnScrollResults, &QPushButton::toggled, this, &SynchronizerGUI::setScrolling);
0527     connect(btnCloseSync, &QPushButton::clicked, this, &SynchronizerGUI::closeDialog);
0528 
0529     connect(cbSubdirs, &QCheckBox::toggled, this, &SynchronizerGUI::subdirsChecked);
0530     connect(cbAsymmetric, &QCheckBox::toggled, this, &SynchronizerGUI::setPanelLabels);
0531 
0532     connect(&synchronizer, &Synchronizer::comparedFileData, this, &SynchronizerGUI::addFile);
0533     connect(&synchronizer, &Synchronizer::markChanged, this, &SynchronizerGUI::markChanged);
0534     connect(&synchronizer, &Synchronizer::statusInfo, this, &SynchronizerGUI::statusInfo);
0535 
0536     connect(btnLeftToRight, &QPushButton::toggled, this, &SynchronizerGUI::refresh);
0537     connect(btnEquals, &QPushButton::toggled, this, &SynchronizerGUI::refresh);
0538     connect(btnDifferents, &QPushButton::toggled, this, &SynchronizerGUI::refresh);
0539     connect(btnRightToLeft, &QPushButton::toggled, this, &SynchronizerGUI::refresh);
0540     connect(btnDeletable, &QPushButton::toggled, this, &SynchronizerGUI::refresh);
0541     connect(btnDuplicates, &QPushButton::toggled, this, &SynchronizerGUI::refresh);
0542     connect(btnSingles, &QPushButton::toggled, this, &SynchronizerGUI::refresh);
0543 
0544     connect(fileFilter, &KrHistoryComboBox::currentTextChanged, this, &SynchronizerGUI::connectFilters);
0545     connect(generalFilter->searchFor, &KrHistoryComboBox::currentTextChanged, this, &SynchronizerGUI::connectFilters);
0546     connect(generalFilter->searchFor, &KrHistoryComboBox::currentTextChanged, this, &SynchronizerGUI::setCompletion);
0547     connect(generalFilter->dontSearchIn, &KURLListRequester::checkValidity, this, &SynchronizerGUI::checkExcludeURLValidity);
0548 
0549     connect(profileManager, &ProfileManager::loadFromProfile, filterTabs, &FilterTabs::loadFromProfile);
0550     connect(profileManager, &ProfileManager::saveToProfile, filterTabs, &FilterTabs::saveToProfile);
0551 
0552     setPanelLabels();
0553     setCompletion();
0554 
0555     /* =============================== Loading the colors ================================ */
0556 
0557     KConfigGroup gc(krConfig, "Colors");
0558 
0559     QString COLOR_NAMES[] = {"Equals", "Differs", "LeftCopy", "RightCopy", "Delete"};
0560     QPalette defaultPalette = QGuiApplication::palette();
0561 
0562     DECLARE_SYNCHRONIZER_BACKGROUND_DEFAULTS;
0563     DECLARE_SYNCHRONIZER_FOREGROUND_DEFAULTS;
0564 
0565     for (int clr = 0; clr != TT_MAX; clr++) {
0566         QString colorName = clr > 4 ? "Equals" : COLOR_NAMES[clr];
0567         QColor backgroundDefault = clr > 4 ? defaultPalette.color(QPalette::Active, QPalette::Base) : SYNCHRONIZER_BACKGROUND_DEFAULTS[clr];
0568         QColor foregroundDefault = clr > 4 ? defaultPalette.color(QPalette::Active, QPalette::Text) : SYNCHRONIZER_FOREGROUND_DEFAULTS[clr];
0569 
0570         QString foreEntry = QString("Synchronizer ") + colorName + QString(" Foreground");
0571         QString bckgEntry = QString("Synchronizer ") + colorName + QString(" Background");
0572 
0573         if (gc.readEntry(foreEntry, QString()) == "KDE default")
0574             foreGrounds[clr] = QColor();
0575         else if (gc.readEntry(foreEntry, QString()).isEmpty()) // KDE4 workaround, default color doesn't work
0576             foreGrounds[clr] = foregroundDefault;
0577         else
0578             foreGrounds[clr] = gc.readEntry(foreEntry, foregroundDefault);
0579 
0580         if (gc.readEntry(bckgEntry, QString()) == "KDE default")
0581             backGrounds[clr] = QColor();
0582         else if (gc.readEntry(foreEntry, QString()).isEmpty()) // KDE4 workaround, default color doesn't work
0583             backGrounds[clr] = backgroundDefault;
0584         else
0585             backGrounds[clr] = gc.readEntry(bckgEntry, backgroundDefault);
0586     }
0587     if (backGrounds[TT_EQUALS].isValid()) {
0588         QPalette pal = syncList->palette();
0589         pal.setColor(QPalette::Base, backGrounds[TT_EQUALS]);
0590         syncList->setPalette(pal);
0591     }
0592 
0593     if (group.readEntry("Window Maximized", false)) {
0594         setWindowState(windowState() | Qt::WindowMaximized);
0595     } else {
0596         int sx = group.readEntry("Window Width", -1);
0597         int sy = group.readEntry("Window Height", -1);
0598         if (sx != -1 && sy != -1) {
0599             resize(sx, sy);
0600         }
0601     }
0602 
0603     if (!profileName.isNull())
0604         profileManager->loadProfile(profileName);
0605 
0606     synchronizer.setParentWidget(this);
0607 }
0608 
0609 SynchronizerGUI::~SynchronizerGUI()
0610 {
0611     syncList->clear(); // for sanity: deletes the references to the synchronizer list
0612 }
0613 
0614 void SynchronizerGUI::setPanelLabels()
0615 {
0616     if (hasSelectedFiles && cbAsymmetric->isChecked()) {
0617         leftDirLabel->setText(i18n("Selected files from targ&et folder:"));
0618         rightDirLabel->setText(i18n("Selected files from sou&rce folder:"));
0619     } else if (hasSelectedFiles && !cbAsymmetric->isChecked()) {
0620         leftDirLabel->setText(i18n("Selected files from &left folder:"));
0621         rightDirLabel->setText(i18n("Selected files from &right folder:"));
0622     } else if (cbAsymmetric->isChecked()) {
0623         leftDirLabel->setText(i18n("Targ&et folder:"));
0624         rightDirLabel->setText(i18n("Sou&rce folder:"));
0625     } else {
0626         leftDirLabel->setText(i18n("&Left folder:"));
0627         rightDirLabel->setText(i18n("&Right folder:"));
0628     }
0629 }
0630 
0631 void SynchronizerGUI::setCompletion()
0632 {
0633     generalFilter->dontSearchIn->setCompletionDir(Synchronizer::fsUrl(rightLocation->currentText()));
0634 }
0635 
0636 void SynchronizerGUI::checkExcludeURLValidity(QString &text, QString &error)
0637 {
0638     QUrl url = Synchronizer::fsUrl(text);
0639     if (url.isRelative())
0640         return;
0641 
0642     QString leftBase = leftLocation->currentText();
0643     if (!leftBase.endsWith('/'))
0644         leftBase += '/';
0645     QUrl leftBaseURL = Synchronizer::fsUrl(leftBase);
0646     if (leftBaseURL.isParentOf(url) && !url.isParentOf(leftBaseURL)) {
0647         text = QDir(leftBaseURL.path()).relativeFilePath(url.path());
0648         return;
0649     }
0650 
0651     QString rightBase = rightLocation->currentText();
0652     if (!rightBase.endsWith('/'))
0653         rightBase += '/';
0654     QUrl rightBaseURL = Synchronizer::fsUrl(rightBase);
0655     if (rightBaseURL.isParentOf(url) && !url.isParentOf(rightBaseURL)) {
0656         text = QDir(rightBaseURL.path()).relativeFilePath(url.path());
0657         return;
0658     }
0659 
0660     error = i18n("URL must be the descendant of either the left or the right base URL.");
0661 }
0662 
0663 void SynchronizerGUI::doubleClicked(QTreeWidgetItem *itemIn)
0664 {
0665     if (!itemIn)
0666         return;
0667 
0668     auto *syncItem = dynamic_cast<SyncViewItem *>(itemIn);
0669     SynchronizerFileItem *item = syncItem->synchronizerItemRef();
0670     if (item && item->existsInLeft() && item->existsInRight() && !item->isDir()) {
0671         QString leftDirName = item->leftDirectory().isEmpty() ? "" : item->leftDirectory() + '/';
0672         QString rightDirName = item->rightDirectory().isEmpty() ? "" : item->rightDirectory() + '/';
0673         QUrl leftURL = Synchronizer::fsUrl(synchronizer.leftBaseDirectory() + leftDirName + item->leftName());
0674         QUrl rightURL = Synchronizer::fsUrl(synchronizer.rightBaseDirectory() + rightDirName + item->rightName());
0675 
0676         SLOTS->compareContent(leftURL, rightURL);
0677     } else if (item && item->isDir()) {
0678         itemIn->setExpanded(!itemIn->isExpanded());
0679     }
0680 }
0681 
0682 void SynchronizerGUI::rightMouseClicked(QTreeWidgetItem *itemIn, const QPoint &pos)
0683 {
0684     // these are the values that will exist in the menu
0685 #define EXCLUDE_ID 90
0686 #define RESTORE_ID 91
0687 #define COPY_TO_LEFT_ID 92
0688 #define COPY_TO_RIGHT_ID 93
0689 #define REVERSE_DIR_ID 94
0690 #define DELETE_ID 95
0691 #define VIEW_LEFT_FILE_ID 96
0692 #define VIEW_RIGHT_FILE_ID 97
0693 #define COMPARE_FILES_ID 98
0694 #define SELECT_ITEMS_ID 99
0695 #define DESELECT_ITEMS_ID 100
0696 #define INVERT_SELECTION_ID 101
0697 #define SYNCH_WITH_KGET_ID 102
0698 #define COPY_CLPBD_LEFT_ID 103
0699 #define COPY_CLPBD_RIGHT_ID 104
0700     //////////////////////////////////////////////////////////
0701     if (!itemIn)
0702         return;
0703 
0704     auto *syncItem = dynamic_cast<SyncViewItem *>(itemIn);
0705     if (syncItem == nullptr)
0706         return;
0707 
0708     SynchronizerFileItem *item = syncItem->synchronizerItemRef();
0709 
0710     bool isDuplicate = item->existsInLeft() && item->existsInRight();
0711     bool isDir = item->isDir();
0712 
0713     // create the menu
0714     QMenu popup;
0715     QAction *myact;
0716     QHash<QAction *, int> actHash;
0717 
0718     popup.setTitle(i18n("Synchronize Folders"));
0719 
0720     myact = popup.addAction(i18n("E&xclude"));
0721     actHash[myact] = EXCLUDE_ID;
0722     myact = popup.addAction(i18n("Restore ori&ginal operation"));
0723     actHash[myact] = RESTORE_ID;
0724     myact = popup.addAction(i18n("Re&verse direction"));
0725     actHash[myact] = REVERSE_DIR_ID;
0726     myact = popup.addAction(i18n("Copy from &right to left"));
0727     actHash[myact] = COPY_TO_LEFT_ID;
0728     myact = popup.addAction(i18n("Copy from &left to right"));
0729     actHash[myact] = COPY_TO_RIGHT_ID;
0730     myact = popup.addAction(i18n("&Delete (left single)"));
0731     actHash[myact] = DELETE_ID;
0732 
0733     popup.addSeparator();
0734 
0735     myact = popup.addAction(i18n("V&iew left file"));
0736     myact->setEnabled(!isDir && item->existsInLeft());
0737     actHash[myact] = VIEW_LEFT_FILE_ID;
0738     myact = popup.addAction(i18n("Vi&ew right file"));
0739     myact->setEnabled(!isDir && item->existsInRight());
0740     actHash[myact] = VIEW_RIGHT_FILE_ID;
0741     myact = popup.addAction(i18n("&Compare Files"));
0742     myact->setEnabled(!isDir && isDuplicate);
0743     actHash[myact] = COMPARE_FILES_ID;
0744 
0745     popup.addSeparator();
0746 
0747     myact = popup.addAction(i18n("C&opy selected to clipboard (left)"));
0748     actHash[myact] = COPY_CLPBD_LEFT_ID;
0749     myact = popup.addAction(i18n("Co&py selected to clipboard (right)"));
0750     actHash[myact] = COPY_CLPBD_RIGHT_ID;
0751 
0752     popup.addSeparator();
0753 
0754     myact = popup.addAction(i18n("&Select items"));
0755     actHash[myact] = SELECT_ITEMS_ID;
0756     myact = popup.addAction(i18n("Deselec&t items"));
0757     actHash[myact] = DESELECT_ITEMS_ID;
0758     myact = popup.addAction(i18n("I&nvert selection"));
0759     actHash[myact] = INVERT_SELECTION_ID;
0760 
0761     QUrl leftBDir = Synchronizer::fsUrl(synchronizer.leftBaseDirectory());
0762     QUrl rightBDir = Synchronizer::fsUrl(synchronizer.rightBaseDirectory());
0763 
0764     if (KrServices::cmdExist("kget")
0765         && ((!leftBDir.isLocalFile() && rightBDir.isLocalFile() && btnLeftToRight->isChecked())
0766             || (leftBDir.isLocalFile() && !rightBDir.isLocalFile() && btnRightToLeft->isChecked()))) {
0767         popup.addSeparator();
0768         myact = popup.addAction(i18n("Synchronize with &KGet"));
0769         actHash[myact] = SYNCH_WITH_KGET_ID;
0770     }
0771 
0772     QAction *res = popup.exec(pos);
0773 
0774     int result = -1;
0775     if (actHash.contains(res))
0776         result = actHash[res];
0777 
0778     if (result != -1)
0779         executeOperation(item, result);
0780 }
0781 
0782 void SynchronizerGUI::executeOperation(SynchronizerFileItem *item, int op)
0783 {
0784     // check out the user's option
0785     QString leftDirName = item->leftDirectory().isEmpty() ? "" : item->leftDirectory() + '/';
0786     QString rightDirName = item->rightDirectory().isEmpty() ? "" : item->rightDirectory() + '/';
0787 
0788     QUrl leftURL = Synchronizer::fsUrl(synchronizer.leftBaseDirectory() + leftDirName + item->leftName());
0789     QUrl rightURL = Synchronizer::fsUrl(synchronizer.rightBaseDirectory() + rightDirName + item->rightName());
0790 
0791     switch (op) {
0792     case EXCLUDE_ID:
0793     case RESTORE_ID:
0794     case COPY_TO_LEFT_ID:
0795     case COPY_TO_RIGHT_ID:
0796     case REVERSE_DIR_ID:
0797     case DELETE_ID: {
0798         unsigned ndx = 0;
0799         SynchronizerFileItem *currentItem;
0800 
0801         while ((currentItem = synchronizer.getItemAt(ndx++)) != nullptr) {
0802             auto *viewItem = (SyncViewItem *)currentItem->userData();
0803 
0804             if (!viewItem || !viewItem->isSelected() || viewItem->isHidden())
0805                 continue;
0806 
0807             switch (op) {
0808             case EXCLUDE_ID:
0809                 synchronizer.exclude(viewItem->synchronizerItemRef());
0810                 break;
0811             case RESTORE_ID:
0812                 synchronizer.restore(viewItem->synchronizerItemRef());
0813                 break;
0814             case REVERSE_DIR_ID:
0815                 synchronizer.reverseDirection(viewItem->synchronizerItemRef());
0816                 break;
0817             case COPY_TO_LEFT_ID:
0818                 synchronizer.copyToLeft(viewItem->synchronizerItemRef());
0819                 break;
0820             case COPY_TO_RIGHT_ID:
0821                 synchronizer.copyToRight(viewItem->synchronizerItemRef());
0822                 break;
0823             case DELETE_ID:
0824                 synchronizer.deleteLeft(viewItem->synchronizerItemRef());
0825                 break;
0826             }
0827         }
0828 
0829         refresh();
0830     } break;
0831     case VIEW_LEFT_FILE_ID:
0832         KrViewer::view(leftURL, this); // view the file
0833         break;
0834     case VIEW_RIGHT_FILE_ID:
0835         KrViewer::view(rightURL, this); // view the file
0836         break;
0837     case COMPARE_FILES_ID:
0838         SLOTS->compareContent(leftURL, rightURL);
0839         break;
0840     case SELECT_ITEMS_ID:
0841     case DESELECT_ITEMS_ID: {
0842         KrQuery query = KrSpWidgets::getMask((op == SELECT_ITEMS_ID ? i18n("Select items") : i18n("Deselect items")), true, this);
0843         if (query.isNull())
0844             break;
0845 
0846         unsigned ndx = 0;
0847         SynchronizerFileItem *currentItem;
0848 
0849         while ((currentItem = synchronizer.getItemAt(ndx++)) != nullptr) {
0850             auto *viewItem = (SyncViewItem *)currentItem->userData();
0851 
0852             if (!viewItem || viewItem->isHidden())
0853                 continue;
0854 
0855             if (query.match(currentItem->leftName()) || query.match(currentItem->rightName()))
0856                 viewItem->setSelected(op == SELECT_ITEMS_ID);
0857         }
0858     } break;
0859     case INVERT_SELECTION_ID: {
0860         unsigned ndx = 0;
0861         SynchronizerFileItem *currentItem;
0862 
0863         while ((currentItem = synchronizer.getItemAt(ndx++)) != nullptr) {
0864             auto *viewItem = (SyncViewItem *)currentItem->userData();
0865 
0866             if (!viewItem || viewItem->isHidden())
0867                 continue;
0868 
0869             viewItem->setSelected(!viewItem->isSelected());
0870         }
0871     } break;
0872     case SYNCH_WITH_KGET_ID:
0873         synchronizer.synchronizeWithKGet();
0874         closeDialog();
0875         break;
0876     case COPY_CLPBD_LEFT_ID:
0877         copyToClipboard(true);
0878         break;
0879     case COPY_CLPBD_RIGHT_ID:
0880         copyToClipboard(false);
0881         break;
0882     case -1:
0883         return; // the user clicked outside of the menu
0884     }
0885 }
0886 
0887 void SynchronizerGUI::closeDialog()
0888 {
0889     if (isComparing) {
0890         stop();
0891         wasClosed = true;
0892         return;
0893     }
0894 
0895     KConfigGroup group(krConfig, "Synchronize");
0896 
0897     QStringList list;
0898 
0899     foreach (const QString &item, leftLocation->historyItems()) {
0900         QUrl url(item);
0901         // make sure no passwords are saved in config
0902         url.setPassword(QString());
0903         list << url.toDisplayString(QUrl::PreferLocalFile);
0904     }
0905     group.writeEntry("Left Folder History", list);
0906     list.clear();
0907     foreach (const QString &item, rightLocation->historyItems()) {
0908         QUrl url(item);
0909         // make sure no passwords are saved in config
0910         url.setPassword(QString());
0911         list << url.toDisplayString(QUrl::PreferLocalFile);
0912     }
0913     group.writeEntry("Right Folder History", list);
0914 
0915     list = fileFilter->historyItems();
0916     group.writeEntry("File Filter", list);
0917 
0918     group.writeEntry("Recurse Subdirectories", cbSubdirs->isChecked());
0919     group.writeEntry("Follow Symlinks", cbSymlinks->isChecked());
0920     group.writeEntry("Compare By Content", cbByContent->isChecked());
0921     group.writeEntry("Ignore Date", cbIgnoreDate->isChecked());
0922     group.writeEntry("Asymmetric", cbAsymmetric->isChecked());
0923     group.writeEntry("Ignore Case", cbIgnoreCase->isChecked());
0924     group.writeEntry("LeftToRight Button", btnLeftToRight->isChecked());
0925     group.writeEntry("Equals Button", btnEquals->isChecked());
0926     group.writeEntry("Differents Button", btnDifferents->isChecked());
0927     group.writeEntry("RightToLeft Button", btnRightToLeft->isChecked());
0928     group.writeEntry("Deletable Button", btnDeletable->isChecked());
0929     group.writeEntry("Duplicates Button", btnDuplicates->isChecked());
0930     group.writeEntry("Singles Button", btnSingles->isChecked());
0931 
0932     group.writeEntry("Scroll Results", btnScrollResults->isChecked());
0933 
0934     group.writeEntry("Parallel Threads", parallelThreadsSpinBox->value());
0935 
0936     group.writeEntry("Window Width", size().width());
0937     group.writeEntry("Window Height", size().height());
0938     group.writeEntry("Window Maximized", isMaximized());
0939 
0940     group.writeEntry("State", syncList->header()->saveState());
0941 
0942     QDialog::reject();
0943 
0944     this->deleteLater();
0945 
0946     if (wasSync) {
0947         LEFT_PANEL->func->refresh();
0948         RIGHT_PANEL->func->refresh();
0949         ACTIVE_PANEL->gui->slotFocusOnMe();
0950     }
0951 }
0952 
0953 void SynchronizerGUI::compare()
0954 {
0955     KrQuery query;
0956 
0957     if (!filterTabs->fillQuery(&query))
0958         return;
0959 
0960     // perform some previous tests
0961     QString leftLocationTrimmed = leftLocation->currentText().trimmed();
0962     QString rightLocationTrimmed = rightLocation->currentText().trimmed();
0963     if (leftLocationTrimmed.isEmpty()) {
0964         KMessageBox::error(this, i18n("The target folder must not be empty."));
0965         leftLocation->setFocus();
0966         return;
0967     }
0968     if (rightLocationTrimmed.isEmpty()) {
0969         KMessageBox::error(this, i18n("The source folder must not be empty."));
0970         rightLocation->setFocus();
0971         return;
0972     }
0973 
0974     if (leftLocationTrimmed == rightLocationTrimmed) {
0975         if (KMessageBox::warningContinueCancel(this, i18n("Warning: The left and the right side are showing the same folder.")) != KMessageBox::Continue) {
0976             return;
0977         }
0978     }
0979 
0980     query.setNameFilter(fileFilter->currentText(), query.isCaseSensitive());
0981     synchronizerTabs->setCurrentIndex(0);
0982 
0983     syncList->clear();
0984     lastItem = nullptr;
0985 
0986     leftLocation->addToHistory(leftLocation->currentText());
0987     rightLocation->addToHistory(rightLocation->currentText());
0988     fileFilter->addToHistory(fileFilter->currentText());
0989 
0990     setMarkFlags();
0991 
0992     btnCompareDirs->setEnabled(false);
0993     profileManager->setEnabled(false);
0994     btnSwapSides->setEnabled(false);
0995     btnStopComparing->setEnabled(isComparing = true);
0996     btnStopComparing->show();
0997     btnFeedToListBox->setEnabled(false);
0998     btnFeedToListBox->hide();
0999     btnSynchronize->setEnabled(false);
1000     btnCompareDirs->hide();
1001     btnScrollResults->show();
1002     disableMarkButtons();
1003 
1004     int fileCount = synchronizer.compare(leftLocation->currentText(),
1005                                          rightLocation->currentText(),
1006                                          &query,
1007                                          cbSubdirs->isChecked(),
1008                                          cbSymlinks->isChecked(),
1009                                          cbIgnoreDate->isChecked(),
1010                                          cbAsymmetric->isChecked(),
1011                                          cbByContent->isChecked(),
1012                                          cbIgnoreCase->isChecked(),
1013                                          btnScrollResults->isChecked(),
1014                                          selectedFiles,
1015                                          convertToSeconds(equalitySpinBox->value(), equalityUnitCombo->currentIndex()),
1016                                          convertToSeconds(timeShiftSpinBox->value(), timeShiftUnitCombo->currentIndex()),
1017                                          parallelThreadsSpinBox->value(),
1018                                          ignoreHiddenFilesCB->isChecked());
1019     enableMarkButtons();
1020     btnStopComparing->setEnabled(isComparing = false);
1021     btnStopComparing->hide();
1022     btnFeedToListBox->show();
1023     btnCompareDirs->setEnabled(true);
1024     profileManager->setEnabled(true);
1025     btnSwapSides->setEnabled(true);
1026     btnCompareDirs->show();
1027     btnScrollResults->hide();
1028     if (fileCount) {
1029         btnSynchronize->setEnabled(true);
1030         btnFeedToListBox->setEnabled(true);
1031     }
1032 
1033     syncList->setFocus();
1034 
1035     if (wasClosed)
1036         closeDialog();
1037 }
1038 
1039 void SynchronizerGUI::stop()
1040 {
1041     synchronizer.stop();
1042 }
1043 
1044 void SynchronizerGUI::feedToListBox()
1045 {
1046     FeedToListBoxDialog listBox(this, &synchronizer, syncList, btnEquals->isChecked());
1047     if (listBox.isAccepted())
1048         closeDialog();
1049 }
1050 
1051 void SynchronizerGUI::reject()
1052 {
1053     closeDialog();
1054 }
1055 
1056 void SynchronizerGUI::addFile(SynchronizerFileItem *item)
1057 {
1058     QString leftName = "", rightName = "", leftDate = "", rightDate = "", leftSize = "", rightSize = "";
1059     bool isDir = item->isDir();
1060 
1061     QColor textColor = foreGrounds[item->task()];
1062     QColor baseColor = backGrounds[item->task()];
1063 
1064     if (item->existsInLeft()) {
1065         leftName = item->leftName();
1066         leftSize = isDir ? dirLabel() + ' ' : KrPermHandler::parseSize(item->leftSize());
1067         leftDate = SynchronizerGUI::convertTime(item->leftDate());
1068     }
1069 
1070     if (item->existsInRight()) {
1071         rightName = item->rightName();
1072         rightSize = isDir ? dirLabel() + ' ' : KrPermHandler::parseSize(item->rightSize());
1073         rightDate = SynchronizerGUI::convertTime(item->rightDate());
1074     }
1075 
1076     SyncViewItem *listItem = nullptr;
1077     SyncViewItem *dirItem;
1078 
1079     if (item->parent() == nullptr) {
1080         listItem = new SyncViewItem(item,
1081                                     textColor,
1082                                     baseColor,
1083                                     syncList,
1084                                     lastItem,
1085                                     leftName,
1086                                     leftSize,
1087                                     leftDate,
1088                                     Synchronizer::getTaskTypeName(item->task()),
1089                                     rightDate,
1090                                     rightSize,
1091                                     rightName);
1092         lastItem = listItem;
1093     } else {
1094         dirItem = (SyncViewItem *)item->parent()->userData();
1095         if (dirItem) {
1096             dirItem->setExpanded(true);
1097             listItem = new SyncViewItem(item,
1098                                         textColor,
1099                                         baseColor,
1100                                         dirItem,
1101                                         dirItem->lastItem(),
1102                                         leftName,
1103                                         leftSize,
1104                                         leftDate,
1105                                         Synchronizer::getTaskTypeName(item->task()),
1106                                         rightDate,
1107                                         rightSize,
1108                                         rightName);
1109 
1110             dirItem->setLastItem(listItem);
1111         }
1112     }
1113 
1114     if (listItem) {
1115         listItem->setIcon(0, Icon(isDir ? "folder" : "document-new"));
1116         if (!item->isMarked())
1117             listItem->setHidden(true);
1118         else
1119             syncList->scrollTo(syncList->indexOf(listItem));
1120     }
1121 }
1122 
1123 void SynchronizerGUI::markChanged(SynchronizerFileItem *item, bool ensureVisible)
1124 {
1125     auto *listItem = (SyncViewItem *)item->userData();
1126     if (listItem) {
1127         if (!item->isMarked()) {
1128             listItem->setHidden(true);
1129         } else {
1130             QString leftName = "", rightName = "", leftDate = "", rightDate = "", leftSize = "", rightSize = "";
1131             bool isDir = item->isDir();
1132 
1133             if (item->existsInLeft()) {
1134                 leftName = item->leftName();
1135                 leftSize = isDir ? dirLabel() + ' ' : KrPermHandler::parseSize(item->leftSize());
1136                 leftDate = SynchronizerGUI::convertTime(item->leftDate());
1137             }
1138 
1139             if (item->existsInRight()) {
1140                 rightName = item->rightName();
1141                 rightSize = isDir ? dirLabel() + ' ' : KrPermHandler::parseSize(item->rightSize());
1142                 rightDate = SynchronizerGUI::convertTime(item->rightDate());
1143             }
1144 
1145             listItem->setHidden(false);
1146             listItem->setText(0, leftName);
1147             listItem->setText(1, leftSize);
1148             listItem->setText(2, leftDate);
1149             listItem->setText(3, Synchronizer::getTaskTypeName(item->task()));
1150             listItem->setText(4, rightDate);
1151             listItem->setText(5, rightSize);
1152             listItem->setText(6, rightName);
1153             listItem->setColors(foreGrounds[item->task()], backGrounds[item->task()]);
1154 
1155             if (ensureVisible)
1156                 syncList->scrollTo(syncList->indexOf(listItem));
1157         }
1158     }
1159 }
1160 
1161 void SynchronizerGUI::subdirsChecked(bool isOn)
1162 {
1163     cbSymlinks->setEnabled(isOn);
1164 }
1165 
1166 void SynchronizerGUI::disableMarkButtons()
1167 {
1168     btnLeftToRight->setEnabled(false);
1169     btnEquals->setEnabled(false);
1170     btnDifferents->setEnabled(false);
1171     btnRightToLeft->setEnabled(false);
1172     btnDeletable->setEnabled(false);
1173     btnDuplicates->setEnabled(false);
1174     btnSingles->setEnabled(false);
1175 }
1176 
1177 void SynchronizerGUI::enableMarkButtons()
1178 {
1179     btnLeftToRight->setEnabled(true);
1180     btnEquals->setEnabled(true);
1181     btnDifferents->setEnabled(true);
1182     btnRightToLeft->setEnabled(true);
1183     btnDeletable->setEnabled(true);
1184     btnDuplicates->setEnabled(true);
1185     btnSingles->setEnabled(true);
1186 }
1187 
1188 QString SynchronizerGUI::convertTime(time_t time) const
1189 {
1190     // convert the time_t to struct tm
1191     struct tm *t = localtime((time_t *)&time);
1192 
1193     QDateTime tmp(QDate(t->tm_year + 1900, t->tm_mon + 1, t->tm_mday), QTime(t->tm_hour, t->tm_min));
1194     return QLocale().toString(tmp, QLocale::ShortFormat);
1195 }
1196 
1197 void SynchronizerGUI::setMarkFlags()
1198 {
1199     synchronizer.setMarkFlags(btnRightToLeft->isChecked(),
1200                               btnEquals->isChecked(),
1201                               btnDifferents->isChecked(),
1202                               btnLeftToRight->isChecked(),
1203                               btnDuplicates->isChecked(),
1204                               btnSingles->isChecked(),
1205                               btnDeletable->isChecked());
1206 }
1207 
1208 void SynchronizerGUI::refresh()
1209 {
1210     if (!isComparing) {
1211         syncList->clearSelection();
1212         setMarkFlags();
1213         btnCompareDirs->setEnabled(false);
1214         profileManager->setEnabled(false);
1215         btnSwapSides->setEnabled(false);
1216         btnSynchronize->setEnabled(false);
1217         btnFeedToListBox->setEnabled(false);
1218         disableMarkButtons();
1219         int fileCount = synchronizer.refresh();
1220         enableMarkButtons();
1221         btnCompareDirs->setEnabled(true);
1222         profileManager->setEnabled(true);
1223         btnSwapSides->setEnabled(true);
1224         if (fileCount) {
1225             btnFeedToListBox->setEnabled(true);
1226             btnSynchronize->setEnabled(true);
1227         }
1228     }
1229 }
1230 
1231 void SynchronizerGUI::synchronize()
1232 {
1233     int copyToLeftNr, copyToRightNr, deleteNr;
1234     KIO::filesize_t copyToLeftSize, copyToRightSize, deleteSize;
1235 
1236     if (!synchronizer.totalSizes(&copyToLeftNr, &copyToLeftSize, &copyToRightNr, &copyToRightSize, &deleteNr, &deleteSize)) {
1237         KMessageBox::information(parentWidget(), i18n("Synchronizer has nothing to do."));
1238         return;
1239     }
1240 
1241     auto *sd = new SynchronizeDialog(this,
1242                                      &synchronizer,
1243                                      copyToLeftNr,
1244                                      copyToLeftSize,
1245                                      copyToRightNr,
1246                                      copyToRightSize,
1247                                      deleteNr,
1248                                      deleteSize,
1249                                      parallelThreadsSpinBox->value());
1250 
1251     wasSync = sd->wasSyncronizationStarted();
1252     delete sd;
1253 
1254     if (wasSync)
1255         closeDialog();
1256 }
1257 
1258 void SynchronizerGUI::statusInfo(const QString &info)
1259 {
1260     statusLabel->setText(info);
1261     qApp->processEvents();
1262 }
1263 
1264 void SynchronizerGUI::swapSides()
1265 {
1266     if (btnCompareDirs->isEnabled()) {
1267         QString leftCurrent = leftLocation->currentText();
1268         leftLocation->lineEdit()->setText(rightLocation->currentText());
1269         rightLocation->lineEdit()->setText(leftCurrent);
1270         synchronizer.swapSides();
1271         refresh();
1272     }
1273 }
1274 
1275 void SynchronizerGUI::keyPressEvent(QKeyEvent *e)
1276 {
1277     switch (e->key()) {
1278     case Qt::Key_M: {
1279         if (e->modifiers() == Qt::ControlModifier) {
1280             syncList->setFocus();
1281             e->accept();
1282         }
1283         break;
1284     }
1285     case Qt::Key_F3:
1286     case Qt::Key_F4: {
1287         e->accept();
1288         syncList->setFocus();
1289         QTreeWidgetItem *listItem = syncList->currentItem();
1290         if (listItem == nullptr)
1291             break;
1292 
1293         bool isedit = e->key() == Qt::Key_F4;
1294 
1295         SynchronizerFileItem *item = (dynamic_cast<SyncViewItem *>(listItem))->synchronizerItemRef();
1296         QString leftDirName = item->leftDirectory().isEmpty() ? "" : item->leftDirectory() + '/';
1297         QString rightDirName = item->rightDirectory().isEmpty() ? "" : item->rightDirectory() + '/';
1298 
1299         if (item->isDir())
1300             return;
1301 
1302         if (e->modifiers() == Qt::ShiftModifier && item->existsInRight()) {
1303             QUrl rightURL = Synchronizer::fsUrl(synchronizer.rightBaseDirectory() + rightDirName + item->rightName());
1304             if (isedit)
1305                 KrViewer::edit(rightURL, this); // view the file
1306             else
1307                 KrViewer::view(rightURL, this); // view the file
1308             return;
1309         } else if (e->modifiers() == 0 && item->existsInLeft()) {
1310             QUrl leftURL = Synchronizer::fsUrl(synchronizer.leftBaseDirectory() + leftDirName + item->leftName());
1311             if (isedit)
1312                 KrViewer::edit(leftURL, this); // view the file
1313             else
1314                 KrViewer::view(leftURL, this); // view the file
1315             return;
1316         }
1317     } break;
1318     case Qt::Key_U:
1319         if (e->modifiers() != Qt::ControlModifier)
1320             break;
1321         e->accept();
1322         swapSides();
1323         return;
1324     case Qt::Key_Escape:
1325         if (!btnStopComparing->isHidden() && btnStopComparing->isEnabled()) { // is it comparing?
1326             e->accept();
1327             btnStopComparing->animateClick(); // just click the stop button
1328         } else {
1329             e->accept();
1330             if (syncList->topLevelItemCount() != 0) {
1331                 int result = KMessageBox::warningYesNo(
1332                     this,
1333                     i18n("The synchronizer window contains data from a previous compare. If you exit, this data will be lost. Do you really want to exit?"),
1334                     i18n("Krusader::Synchronize Folders"),
1335                     KStandardGuiItem::yes(),
1336                     KStandardGuiItem::no(),
1337                     "syncGUIexit");
1338                 if (result != KMessageBox::Yes)
1339                     return;
1340             }
1341             QDialog::reject();
1342         }
1343         return;
1344     }
1345 
1346     QDialog::keyPressEvent(e);
1347 }
1348 
1349 bool SynchronizerGUI::eventFilter(QObject * /* watched */, QEvent *e)
1350 {
1351     if (e->type() == QEvent::KeyPress) {
1352         auto *ke = dynamic_cast<QKeyEvent *>(e);
1353         switch (ke->key()) {
1354         case Qt::Key_Down:
1355         case Qt::Key_Left:
1356         case Qt::Key_Right:
1357         case Qt::Key_Up:
1358         case Qt::Key_Delete:
1359         case Qt::Key_W: {
1360             if (ke->key() == Qt::Key_W) {
1361                 if (ke->modifiers() != Qt::ControlModifier)
1362                     return false;
1363             } else if (ke->modifiers() != Qt::AltModifier)
1364                 return false;
1365 
1366             int op = -1;
1367             switch (ke->key()) {
1368             case Qt::Key_Down:
1369                 op = EXCLUDE_ID;
1370                 break;
1371             case Qt::Key_Left:
1372                 op = COPY_TO_LEFT_ID;
1373                 break;
1374             case Qt::Key_Right:
1375                 op = COPY_TO_RIGHT_ID;
1376                 break;
1377             case Qt::Key_Up:
1378                 op = RESTORE_ID;
1379                 break;
1380             case Qt::Key_Delete:
1381                 op = DELETE_ID;
1382                 break;
1383             case Qt::Key_W:
1384                 op = REVERSE_DIR_ID;
1385                 break;
1386             }
1387 
1388             ke->accept();
1389 
1390             QTreeWidgetItem *listItem = syncList->currentItem();
1391             if (listItem == nullptr)
1392                 return true;
1393 
1394             SynchronizerFileItem *item = (dynamic_cast<SyncViewItem *>(listItem))->synchronizerItemRef();
1395 
1396             bool hasSelected = false;
1397             QList<QTreeWidgetItem *> selected = syncList->selectedItems();
1398             for (int i = 0; i != selected.count(); i++)
1399                 if (selected[i]->isSelected() && !selected[i]->isHidden())
1400                     hasSelected = true;
1401             if (!hasSelected)
1402                 listItem->setSelected(true);
1403 
1404             executeOperation(item, op);
1405             return true;
1406         }
1407         }
1408     }
1409     return false;
1410 }
1411 
1412 void SynchronizerGUI::loadFromProfile(const QString &profile)
1413 {
1414     syncList->clear();
1415     synchronizer.reset();
1416     isComparing = wasClosed = false;
1417     btnSynchronize->setEnabled(false);
1418     btnFeedToListBox->setEnabled(false);
1419 
1420     KConfigGroup pg(krConfig, profile);
1421 
1422     if (!hasSelectedFiles) {
1423         leftLocation->lineEdit()->setText(pg.readEntry("Left Location", QString()));
1424         rightLocation->lineEdit()->setText(pg.readEntry("Right Location", QString()));
1425     }
1426     fileFilter->lineEdit()->setText(pg.readEntry("Search For", QString()));
1427 
1428     cbSubdirs->setChecked(pg.readEntry("Recurse Subdirectories", true));
1429     cbSymlinks->setChecked(pg.readEntry("Follow Symlinks", false));
1430     cbByContent->setChecked(pg.readEntry("Compare By Content", false));
1431     cbIgnoreDate->setChecked(pg.readEntry("Ignore Date", false));
1432     cbAsymmetric->setChecked(pg.readEntry("Asymmetric", false));
1433     cbIgnoreCase->setChecked(pg.readEntry("Ignore Case", false));
1434 
1435     btnScrollResults->setChecked(pg.readEntry("Scroll Results", false));
1436 
1437     btnLeftToRight->setChecked(pg.readEntry("Show Left To Right", true));
1438     btnEquals->setChecked(pg.readEntry("Show Equals", true));
1439     btnDifferents->setChecked(pg.readEntry("Show Differents", true));
1440     btnRightToLeft->setChecked(pg.readEntry("Show Right To Left", true));
1441     btnDeletable->setChecked(pg.readEntry("Show Deletable", true));
1442     btnDuplicates->setChecked(pg.readEntry("Show Duplicates", true));
1443     btnSingles->setChecked(pg.readEntry("Show Singles", true));
1444 
1445     int equalityThreshold = pg.readEntry("Equality Threshold", 0);
1446     int equalityCombo = 0;
1447     convertFromSeconds(equalityThreshold, equalityCombo, equalityThreshold);
1448     equalitySpinBox->setValue(equalityThreshold);
1449     equalityUnitCombo->setCurrentIndex(equalityCombo);
1450 
1451     int timeShift = pg.readEntry("Time Shift", 0);
1452     int timeShiftCombo = 0;
1453     convertFromSeconds(timeShift, timeShiftCombo, timeShift);
1454     timeShiftSpinBox->setValue(timeShift);
1455     timeShiftUnitCombo->setCurrentIndex(timeShiftCombo);
1456 
1457     int parallelThreads = pg.readEntry("Parallel Threads", 1);
1458     parallelThreadsSpinBox->setValue(parallelThreads);
1459 
1460     bool ignoreHidden = pg.readEntry("Ignore Hidden Files", false);
1461     ignoreHiddenFilesCB->setChecked(ignoreHidden);
1462 
1463     refresh();
1464     btnCompareDirs->setFocus();
1465 }
1466 
1467 void SynchronizerGUI::saveToProfile(const QString &profile)
1468 {
1469     KConfigGroup group(krConfig, profile);
1470 
1471     group.writeEntry("Left Location", leftLocation->currentText());
1472     group.writeEntry("Search For", fileFilter->currentText());
1473     group.writeEntry("Right Location", rightLocation->currentText());
1474 
1475     group.writeEntry("Recurse Subdirectories", cbSubdirs->isChecked());
1476     group.writeEntry("Follow Symlinks", cbSymlinks->isChecked());
1477     group.writeEntry("Compare By Content", cbByContent->isChecked());
1478     group.writeEntry("Ignore Date", cbIgnoreDate->isChecked());
1479     group.writeEntry("Asymmetric", cbAsymmetric->isChecked());
1480     group.writeEntry("Ignore Case", cbIgnoreCase->isChecked());
1481 
1482     group.writeEntry("Scroll Results", btnScrollResults->isChecked());
1483 
1484     group.writeEntry("Show Left To Right", btnLeftToRight->isChecked());
1485     group.writeEntry("Show Equals", btnEquals->isChecked());
1486     group.writeEntry("Show Differents", btnDifferents->isChecked());
1487     group.writeEntry("Show Right To Left", btnRightToLeft->isChecked());
1488     group.writeEntry("Show Deletable", btnDeletable->isChecked());
1489     group.writeEntry("Show Duplicates", btnDuplicates->isChecked());
1490     group.writeEntry("Show Singles", btnSingles->isChecked());
1491 
1492     group.writeEntry("Equality Threshold", convertToSeconds(equalitySpinBox->value(), equalityUnitCombo->currentIndex()));
1493     group.writeEntry("Time Shift", convertToSeconds(timeShiftSpinBox->value(), timeShiftUnitCombo->currentIndex()));
1494     group.writeEntry("Parallel Threads", parallelThreadsSpinBox->value());
1495 
1496     group.writeEntry("Ignore Hidden Files", ignoreHiddenFilesCB->isChecked());
1497 }
1498 
1499 void SynchronizerGUI::connectFilters(const QString &newString)
1500 {
1501     if (synchronizerTabs->currentIndex())
1502         fileFilter->setEditText(newString);
1503     else
1504         generalFilter->searchFor->setEditText(newString);
1505 }
1506 
1507 void SynchronizerGUI::setScrolling(bool isOn)
1508 {
1509     if (isOn)
1510         btnScrollResults->setText(i18n("Quiet"));
1511     else
1512         btnScrollResults->setText(i18n("Scroll Results"));
1513 
1514     synchronizer.setScrolling(isOn);
1515 }
1516 
1517 int SynchronizerGUI::convertToSeconds(int time, int unit)
1518 {
1519     switch (unit) {
1520     case 1:
1521         return time * 60;
1522     case 2:
1523         return time * 3600;
1524     case 3:
1525         return time * 86400;
1526     default:
1527         return time;
1528     }
1529 }
1530 
1531 void SynchronizerGUI::convertFromSeconds(int &time, int &unit, int second)
1532 {
1533     unit = 0;
1534     time = second;
1535     int absTime = (time < 0) ? -time : time;
1536 
1537     if (absTime >= 86400 && (absTime % 86400) == 0) {
1538         time /= 86400;
1539         unit = 3;
1540     } else if (absTime >= 3600 && (absTime % 3600) == 0) {
1541         time /= 3600;
1542         unit = 2;
1543     } else if (absTime >= 60 && (absTime % 60) == 0) {
1544         time /= 60;
1545         unit = 1;
1546     }
1547 }
1548 
1549 QPushButton *SynchronizerGUI::createButton(QWidget *parent,
1550                                            const QString &iconName,
1551                                            bool checked,
1552                                            const QKeySequence &shortCut,
1553                                            const QString &description,
1554                                            const QString &text,
1555                                            bool textAndIcon)
1556 {
1557     auto *button = new QPushButton(parent);
1558     bool iconExists = Icon::exists(iconName);
1559     if (!text.isEmpty() && (textAndIcon || !iconExists)) {
1560         button->setText(text);
1561     }
1562     if (iconExists) {
1563         button->setIcon(Icon(iconName));
1564     }
1565     button->setCheckable(true);
1566     button->setChecked(checked);
1567     button->setShortcut(shortCut);
1568     const QString infoText = QString("%1 (%2)").arg(description, shortCut.toString(QKeySequence::NativeText));
1569     button->setWhatsThis(infoText);
1570     button->setToolTip(infoText);
1571     return button;
1572 }
1573 
1574 void SynchronizerGUI::copyToClipboard(bool isLeft)
1575 {
1576     QList<QUrl> urls;
1577 
1578     unsigned ndx = 0;
1579     SynchronizerFileItem *currentItem;
1580 
1581     while ((currentItem = synchronizer.getItemAt(ndx++)) != nullptr) {
1582         auto *viewItem = (SynchronizerGUI::SyncViewItem *)currentItem->userData();
1583 
1584         if (!viewItem || !viewItem->isSelected() || viewItem->isHidden())
1585             continue;
1586 
1587         SynchronizerFileItem *item = viewItem->synchronizerItemRef();
1588         if (item) {
1589             if (isLeft && item->existsInLeft()) {
1590                 QString leftDirName = item->leftDirectory().isEmpty() ? "" : item->leftDirectory() + '/';
1591                 QUrl leftURL = Synchronizer::fsUrl(synchronizer.leftBaseDirectory() + leftDirName + item->leftName());
1592                 urls.push_back(leftURL);
1593             } else if (!isLeft && item->existsInRight()) {
1594                 QString rightDirName = item->rightDirectory().isEmpty() ? "" : item->rightDirectory() + '/';
1595                 QUrl rightURL = Synchronizer::fsUrl(synchronizer.rightBaseDirectory() + rightDirName + item->rightName());
1596                 urls.push_back(rightURL);
1597             }
1598         }
1599     }
1600 
1601     if (urls.count() == 0)
1602         return;
1603 
1604     auto *mimeData = new QMimeData;
1605     mimeData->setImageData(FileListIcon(isLeft ? "arrow-left-double" : "arrow-right-double").pixmap());
1606     mimeData->setUrls(urls);
1607 
1608     QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
1609 }
1610 
1611 QString SynchronizerGUI::dirLabel()
1612 {
1613     // HACK add <> brackets AFTER translating - otherwise KUIT thinks it's a tag
1614     static QString label = QString("<") + i18nc("Show the string 'DIR' instead of file size in detailed view (for folders)", "DIR") + '>';
1615     return label;
1616 }