File indexing completed on 2024-05-12 05:54:50
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 Files\"</code> searches out those files/folders that name is " 0215 "<code>Program 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(©ToLeftNr, ©ToLeftSize, ©ToRightNr, ©ToRightSize, &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 }