diff --git a/krusader/Panel/listpanel.cpp b/krusader/Panel/listpanel.cpp index 235b25c1..5b4be362 100644 --- a/krusader/Panel/listpanel.cpp +++ b/krusader/Panel/listpanel.cpp @@ -1,1344 +1,1302 @@ /*************************************************************************** listpanel.cpp ------------------- copyright : (C) 2000 by Shie Erlich & Rafi Yanai e-mail : krusader@users.sourceforge.net web site : http://krusader.sourceforge.net --------------------------------------------------------------------------- Description *************************************************************************** A db dD d8888b. db db .d8888. .d8b. d8888b. d88888b d8888b. 88 ,8P' 88 `8D 88 88 88' YP d8' `8b 88 `8D 88' 88 `8D 88,8P 88oobY' 88 88 `8bo. 88ooo88 88 88 88ooooo 88oobY' 88`8b 88`8b 88 88 `Y8b. 88~~~88 88 88 88~~~~~ 88`8b 88 `88. 88 `88. 88b d88 db 8D 88 88 88 .8D 88. 88 `88. YP YD 88 YD ~Y8888P' `8888Y' YP YP Y8888D' Y88888P 88 YD S o u r c e F i l e *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "listpanel.h" // QtCore #include #include #include #include #include #include #include // QtGui #include #include #include #include #include #include #include #include // QtWidgets #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include "../defaults.h" #include "../krusader.h" #include "../krslots.h" #include "../kicons.h" #include "../krusaderview.h" #include "../krservices.h" #include "../VFS/krpermhandler.h" #include "../Archive/krarchandler.h" #include "../MountMan/kmountman.h" #include "../BookMan/krbookmarkbutton.h" #include "../Dialogs/krdialogs.h" #include "../Dialogs/krspwidgets.h" #include "../Dialogs/percentalsplitter.h" #include "../Dialogs/popularurls.h" #include "../GUI/kcmdline.h" #include "../GUI/dirhistorybutton.h" #include "../GUI/mediabutton.h" #include "../GUI/syncbrowsebutton.h" #include "../UserAction/useractionpopupmenu.h" #include "listpanelactions.h" #include "viewactions.h" #include "krpreviewpopup.h" #include "panelpopup.h" #include "panelfunc.h" #include "krpopupmenu.h" #include "krviewfactory.h" #include "krcolorcache.h" #include "krerrordisplay.h" #include "krlayoutfactory.h" #include "krsearchbar.h" #include "dirhistoryqueue.h" class ActionButton : public QToolButton { public: ActionButton(QWidget *parent, ListPanel *panel, QAction *action, QString text = QString()) : QToolButton(parent), panel(panel), action(action) { setText(text); setAutoRaise(true); if(KConfigGroup(krConfig, "ListPanelButtons").readEntry("Icons", false) || text.isEmpty()) setIcon(action->icon()); setToolTip(action->toolTip()); } protected: virtual void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE { panel->slotFocusOnMe(); action->trigger(); } ListPanel *panel; QAction *action; }; ///////////////////////////////////////////////////// // The list panel constructor // ///////////////////////////////////////////////////// ListPanel::ListPanel(QWidget *parent, AbstractPanelManager *manager, KConfigGroup cfg) : QWidget(parent), KrPanel(manager), panelType(-1), colorMask(255), compareMode(false), previewJob(0), inlineRefreshJob(0), searchBar(0), cdRootButton(0), cdUpButton(0), popupBtn(0), popup(0), vfsError(0), _locked(false) { if(cfg.isValid()) panelType = cfg.readEntry("Type", -1); if (panelType == -1) panelType = defaultPanelType(); gui = this; func = new ListPanelFunc(this); _actions = krApp->listPanelActions(); setAcceptDrops(true); QHash widgets; #define ADD_WIDGET(widget) widgets.insert(#widget, widget); // media button mediaButton = new MediaButton(this); connect(mediaButton, SIGNAL(aboutToShow()), this, SLOT(slotFocusOnMe())); connect(mediaButton, SIGNAL(openUrl(const QUrl&)), func, SLOT(openUrl(const QUrl&))); connect(mediaButton, SIGNAL(newTab(const QUrl&)), SLOT(newTab(const QUrl&))); ADD_WIDGET(mediaButton); // status bar status = new KrSqueezedTextLabel(this); KConfigGroup group(krConfig, "Look&Feel"); status->setFont(group.readEntry("Filelist Font", _FilelistFont)); status->setAutoFillBackground(false); status->setText(""); // needed for initialization code! status->setWhatsThis(i18n("The statusbar displays information about the filesystem " "which holds your current folder: total size, free space, " "type of filesystem, etc.")); ADD_WIDGET(status); // back button backButton = new ActionButton(this, this, _actions->actHistoryBackward); ADD_WIDGET(backButton); // forward button forwardButton = new ActionButton(this, this, _actions->actHistoryForward); ADD_WIDGET(forwardButton); // ... create the history button historyButton = new DirHistoryButton(func->history, this); connect(historyButton, SIGNAL(aboutToShow()), this, SLOT(slotFocusOnMe())); connect(historyButton, SIGNAL(gotoPos(int)), func, SLOT(historyGotoPos(int))); ADD_WIDGET(historyButton); // bookmarks button bookmarksButton = new KrBookmarkButton(this); connect(bookmarksButton, SIGNAL(aboutToShow()), this, SLOT(slotFocusOnMe())); connect(bookmarksButton, SIGNAL(openUrl(const QUrl&)), func, SLOT(openUrl(const QUrl&))); bookmarksButton->setWhatsThis(i18n("Open menu with bookmarks. You can also add " "current location to the list, edit bookmarks " "or add subfolder to the list.")); ADD_WIDGET(bookmarksButton); // url input field urlNavigator = new KUrlNavigator(new KFilePlacesModel(this), QUrl(), this); urlNavigator->setWhatsThis(i18n("Name of folder where you are. You can also " "enter name of desired location to move there. " "Use of Net protocols like ftp or fish is possible.")); // handle certain key events here in event filter urlNavigator->editor()->installEventFilter(this); urlNavigator->setUrlEditable(isNavigatorEditModeSet()); urlNavigator->setShowFullPath(group.readEntry("Navigator Full Path", false)); connect(urlNavigator, SIGNAL(returnPressed()), this, SLOT(slotFocusOnMe())); connect(urlNavigator, SIGNAL(urlChanged(QUrl)), func, SLOT(navigatorUrlChanged(QUrl))); connect(urlNavigator->editor()->lineEdit(), SIGNAL(editingFinished()), this, SLOT(resetNavigatorMode())); connect(urlNavigator, SIGNAL(tabRequested(QUrl)), this, SLOT(newTab(QUrl))); connect(urlNavigator, SIGNAL(urlsDropped(QUrl, QDropEvent*)), this, SLOT(handleDrop(QUrl, QDropEvent*))); ADD_WIDGET(urlNavigator); // toolbar QWidget * toolbar = new QWidget(this); QHBoxLayout * toolbarLayout = new QHBoxLayout(toolbar); toolbarLayout->setContentsMargins(0, 0, 0, 0); toolbarLayout->setSpacing(0); ADD_WIDGET(toolbar); vfsError = new KrErrorDisplay(this); vfsError->setWordWrap(true); vfsError->hide(); ADD_WIDGET(vfsError); // client area clientArea = new QWidget(this); QVBoxLayout *clientLayout = new QVBoxLayout(clientArea); clientLayout->setSpacing(0); clientLayout->setContentsMargins(0, 0, 0, 0); ADD_WIDGET(clientArea); // totals label totals = new KrSqueezedTextLabel(this); totals->setFont(group.readEntry("Filelist Font", _FilelistFont)); totals->setAutoFillBackground(false); totals->setWhatsThis(i18n("The totals bar shows how many files exist, " "how many selected and the bytes math")); ADD_WIDGET(totals); // free space label freeSpace = new KrSqueezedTextLabel(this); freeSpace->setFont(group.readEntry("Filelist Font", _FilelistFont)); freeSpace->setAutoFillBackground(false); freeSpace->setText(""); freeSpace->setAlignment(Qt::AlignRight | Qt::AlignVCenter); ADD_WIDGET(freeSpace); // progress indicator for the preview job previewProgress = new QProgressBar(this); previewProgress->hide(); ADD_WIDGET(previewProgress); // a cancel button for the inplace refresh mechanism inlineRefreshCancelButton = new QToolButton(this); inlineRefreshCancelButton->hide(); inlineRefreshCancelButton->setIcon(krLoader->loadIcon("dialog-cancel", KIconLoader::Toolbar, 16)); connect(inlineRefreshCancelButton, SIGNAL(clicked()), this, SLOT(inlineRefreshCancel())); ADD_WIDGET(inlineRefreshCancelButton); // button for changing the panel popup position in the panel popupPositionBtn = new QToolButton(this); popupPositionBtn->hide(); popupPositionBtn->setAutoRaise(true); popupPositionBtn->setIcon(krLoader->loadIcon("exchange-positions", KIconLoader::Toolbar, 16)); popupPositionBtn->setToolTip(i18n("Move popup panel clockwise")); connect(popupPositionBtn, &QToolButton::clicked, [this]() { // moving position clockwise setPopupPosition((popupPosition() + 1) % 4); }); ADD_WIDGET(popupPositionBtn); // a quick button to open the popup panel popupBtn = new QToolButton(this); popupBtn->setAutoRaise(true); popupBtn->setIcon(krLoader->loadIcon("arrow-up", KIconLoader::Toolbar, 16)); connect(popupBtn, SIGNAL(clicked()), this, SLOT(togglePanelPopup())); popupBtn->setToolTip(i18n("Open the popup panel")); ADD_WIDGET(popupBtn); #undef ADD_WIDGET // toolbar buttons cdOtherButton = new ActionButton(toolbar, this, _actions->actCdToOther, "="); toolbarLayout->addWidget(cdOtherButton); cdUpButton = new ActionButton(toolbar, this, _actions->actDirUp, ".."); toolbarLayout->addWidget(cdUpButton); cdHomeButton = new ActionButton(toolbar, this, _actions->actHome, "~"); toolbarLayout->addWidget(cdHomeButton); cdRootButton = new ActionButton(toolbar, this, _actions->actRoot, "/"); toolbarLayout->addWidget(cdRootButton); // ... creates the button for sync-browsing syncBrowseButton = new SyncBrowseButton(toolbar); syncBrowseButton->setAutoRaise(true); toolbarLayout->addWidget(syncBrowseButton); setButtons(); // create a splitter to hold the view and the popup splt = new PercentalSplitter(clientArea); splt->setChildrenCollapsible(true); splt->setOrientation(Qt::Vertical); // expand vertical if splitter orientation is horizontal QSizePolicy sizePolicy = splt->sizePolicy(); sizePolicy.setVerticalPolicy(QSizePolicy::Expanding); splt->setSizePolicy(sizePolicy); clientLayout->addWidget(splt); // view createView(); // search (in folder) bar searchBar = new KrSearchBar(view, clientArea); searchBar->hide(); bool top = group.readEntry("Quicksearch Position", "bottom") == "top"; clientLayout->insertWidget(top ? 0 : -1, searchBar); // create the layout KrLayoutFactory fact(this, widgets); QLayout *layout = fact.createLayout(); if(!layout) { // fallback: create a layout by ourself QVBoxLayout *v = new QVBoxLayout; v->setContentsMargins(0, 0, 0, 0); v->setSpacing(0); QHBoxLayout *h = new QHBoxLayout; h->setContentsMargins(0, 0, 0, 0); h->setSpacing(0); h->addWidget(urlNavigator); h->addWidget(toolbar); h->addStretch(); v->addLayout(h); h = new QHBoxLayout; h->setContentsMargins(0, 0, 0, 0); h->setSpacing(0); h->addWidget(mediaButton); h->addWidget(status); h->addWidget(backButton); h->addWidget(forwardButton); h->addWidget(historyButton); h->addWidget(bookmarksButton); v->addLayout(h); v->addWidget(vfsError); v->addWidget(clientArea); h = new QHBoxLayout; h->setContentsMargins(0, 0, 0, 0); h->setSpacing(0); h->addWidget(totals); h->addWidget(freeSpace); h->addWidget(previewProgress); h->addWidget(inlineRefreshCancelButton); h->addWidget(popupBtn); v->addLayout(h); layout = v; } setLayout(layout); connect(&KrColorCache::getColorCache(), SIGNAL(colorsRefreshed()), this, SLOT(refreshColors())); connect(krApp, SIGNAL(shutdown()), SLOT(inlineRefreshCancel())); } ListPanel::~ListPanel() { inlineRefreshCancel(); delete view; view = 0; delete func; delete status; delete bookmarksButton; delete totals; delete urlNavigator; delete cdRootButton; delete cdHomeButton; delete cdUpButton; delete cdOtherButton; delete syncBrowseButton; // delete layout; } void ListPanel::reparent(QWidget *parent, AbstractPanelManager *manager) { setParent(parent); _manager = manager; } int ListPanel::defaultPanelType() { KConfigGroup group(krConfig, "Look&Feel"); return group.readEntry("Default Panel Type", KrViewFactory::defaultViewId()); } bool ListPanel::isNavigatorEditModeSet() { KConfigGroup group(krConfig, "Look&Feel"); return group.readEntry("Navigator Edit Mode", false); } void ListPanel::createView() { view = KrViewFactory::createView(panelType, splt, krConfig); view->init(); view->setMainWindow(krApp); // KrViewFactory may create a different view type than requested panelType = view->instance()->id(); if(this == ACTIVE_PANEL) view->prepareForActive(); else view->prepareForPassive(); view->refreshColors(); splt->insertWidget(popupPosition() < 2 ? 1 : 0, view->widget()); view->widget()->installEventFilter(this); connect(view->op(), SIGNAL(calcSpace(KrViewItem*)), func, SLOT(calcSpace(KrViewItem*))); connect(view->op(), SIGNAL(goHome()), func, SLOT(home())); connect(view->op(), SIGNAL(dirUp()), func, SLOT(dirUp())); connect(view->op(), SIGNAL(deleteFiles(bool)), func, SLOT(deleteFiles(bool))); connect(view->op(), SIGNAL(middleButtonClicked(KrViewItem *)), SLOT(newTab(KrViewItem *))); connect(view->op(), SIGNAL(currentChanged(KrViewItem *)), SLOT(slotCurrentChanged(KrViewItem*))); connect(view->op(), SIGNAL(renameItem(const QString &, const QString &)), func, SLOT(rename(const QString &, const QString &))); connect(view->op(), SIGNAL(executed(const QString&)), func, SLOT(execute(const QString&))); connect(view->op(), SIGNAL(goInside(const QString&)), func, SLOT(goInside(const QString&))); connect(view->op(), SIGNAL(needFocus()), this, SLOT(slotFocusOnMe())); connect(view->op(), SIGNAL(selectionChanged()), this, SLOT(slotUpdateTotals())); connect(view->op(), SIGNAL(itemDescription(const QString&)), krApp, SLOT(statusBarUpdate(const QString&))); connect(view->op(), SIGNAL(contextMenu(const QPoint &)), this, SLOT(popRightClickMenu(const QPoint &))); connect(view->op(), SIGNAL(emptyContextMenu(const QPoint &)), this, SLOT(popEmptyRightClickMenu(const QPoint &))); connect(view->op(), SIGNAL(letsDrag(QStringList, QPixmap)), this, SLOT(startDragging(QStringList, QPixmap))); connect(view->op(), &KrViewOperator::gotDrop, this, [this](QDropEvent *event) {handleDrop(event, true); }); connect(view->op(), SIGNAL(previewJobStarted(KJob*)), this, SLOT(slotPreviewJobStarted(KJob*))); connect(view->op(), SIGNAL(refreshActions()), krApp->viewActions(), SLOT(refreshActions())); connect(view->op(), SIGNAL(currentChanged(KrViewItem*)), func->history, SLOT(saveCurrentItem())); connect(view->op(), &KrViewOperator::goBack, func, &ListPanelFunc::historyBackward); connect(view->op(), &KrViewOperator::goForward, func, &ListPanelFunc::historyForward); view->setFiles(func->files()); func->refreshActions(); } void ListPanel::changeType(int type) { if (panelType != type) { QString current = view->getCurrentItem(); QList selection = view->selectedUrls(); bool filterApplysToDirs = view->properties()->filterApplysToDirs; KrViewProperties::FilterSpec filter = view->filter(); FilterSettings filterSettings = view->properties()->filterSettings; panelType = type; KrView *oldView = view; createView(); searchBar->setView(view); delete oldView; view->setFilter(filter, filterSettings, filterApplysToDirs); view->setSelectionUrls(selection); view->setCurrentItem(current); view->makeItemVisible(view->getCurrentKrViewItem()); } } int ListPanel::getProperties() { int props = 0; if (syncBrowseButton->state() == SYNCBROWSE_CD) props |= PROP_SYNC_BUTTON_ON; if (_locked) props |= PROP_LOCKED; return props; } void ListPanel::setProperties(int prop) { syncBrowseButton->setChecked(prop & PROP_SYNC_BUTTON_ON); _locked = (prop & PROP_LOCKED); } bool ListPanel::eventFilter(QObject * watched, QEvent * e) { if(view && watched == view->widget()) { if(e->type() == QEvent::FocusIn && this != ACTIVE_PANEL && !isHidden()) slotFocusOnMe(); else if(e->type() == QEvent::ShortcutOverride) { QKeyEvent *ke = static_cast(e); if(ke->key() == Qt::Key_Escape && ke->modifiers() == Qt::NoModifier) { // if the cancel refresh action has no shortcut assigned, // we need this event ourselves to cancel refresh if(_actions->actCancelRefresh->shortcut().isEmpty()) { e->accept(); return true; } } } } // handle URL navigator key events else if(watched == urlNavigator->editor()) { // override default shortcut for panel focus if(e->type() == QEvent::ShortcutOverride) { QKeyEvent *ke = static_cast(e); if ((ke->key() == Qt::Key_Escape) && (ke->modifiers() == Qt::NoModifier)) { e->accept(); // we will get the key press event now return true; } } else if(e->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(e); if ((ke->key() == Qt::Key_Down) && (ke->modifiers() == Qt::ControlModifier)) { slotFocusOnMe(); return true; } else if ((ke->key() == Qt::Key_Escape) && (ke->modifiers() == Qt::NoModifier)) { // reset navigator urlNavigator->editor()->setUrl(urlNavigator->locationUrl()); slotFocusOnMe(); return true; } } } return false; } void ListPanel::togglePanelPopup() { if(!popup) { popup = new PanelPopup(splt, isLeft(), krApp); // fix vertical grow of splitter (and entire window) if its content // demands more space QSizePolicy sizePolicy = popup->sizePolicy(); sizePolicy.setVerticalPolicy(QSizePolicy::Ignored); popup->setSizePolicy(sizePolicy); connect(this, SIGNAL(pathChanged(const QUrl&)), popup, SLOT(onPanelPathChange(const QUrl&))); connect(popup, SIGNAL(selection(const QUrl&)), SLOTS, SLOT(refresh(const QUrl&))); connect(popup, SIGNAL(hideMe()), this, SLOT(togglePanelPopup())); } if (popup->isHidden()) { if (popupSizes.count() > 0) { splt->setSizes(popupSizes); } else { // on the first time, resize to 50% QList lst; lst << height() / 2 << height() / 2; splt->setSizes(lst); } popup->show(); popupBtn->setIcon(krLoader->loadIcon("arrow-down", KIconLoader::Toolbar, 16)); popupBtn->setToolTip(i18n("Close the popup panel")); popupPositionBtn->show(); } else { popupSizes.clear(); popupSizes = splt->sizes(); popup->hide(); popupBtn->setIcon(krLoader->loadIcon("arrow-up", KIconLoader::Toolbar, 16)); popupBtn->setToolTip(i18n("Open the popup panel")); popupPositionBtn->hide(); QList lst; lst << height() << 0; splt->setSizes(lst); if (ACTIVE_PANEL) ACTIVE_PANEL->gui->slotFocusOnMe(); } } QString ListPanel::realPath() const { return _realPath.path(); } void ListPanel::setButtons() { KConfigGroup group(krConfig, "Look&Feel"); mediaButton->setVisible(group.readEntry("Media Button Visible", true)); backButton->setVisible(group.readEntry("Back Button Visible", false)); forwardButton->setVisible(group.readEntry("Forward Button Visible", false)); historyButton->setVisible(group.readEntry("History Button Visible", true)); bookmarksButton->setVisible(group.readEntry("Bookmarks Button Visible", true)); if (group.readEntry("Panel Toolbar visible", _PanelToolBar)) { cdRootButton->setVisible(group.readEntry("Root Button Visible", _cdRoot)); cdHomeButton->setVisible(group.readEntry("Home Button Visible", _cdHome)); cdUpButton->setVisible(group.readEntry("Up Button Visible", _cdUp)); cdOtherButton->setVisible(group.readEntry("Equal Button Visible", _cdOther)); syncBrowseButton->setVisible(group.readEntry("SyncBrowse Button Visible", _syncBrowseButton)); } else { cdRootButton->hide(); cdHomeButton->hide(); cdUpButton->hide(); cdOtherButton->hide(); syncBrowseButton->hide(); } } void ListPanel::slotUpdateTotals() { totals->setText(view->statistics()); } void ListPanel::compareDirs(bool otherPanelToo) { // Performs a check in order to avoid that the next code is executed twice if (otherPanelToo == true) { // If both panels are showing the same directory if (_manager->currentPanel()->virtualPath() == otherPanel()->virtualPath()) { if (KMessageBox::warningContinueCancel(this, i18n("Warning: The left and the right side are showing the same folder.")) != KMessageBox::Continue) { return; } } } KConfigGroup pg(krConfig, "Private"); int compareMode = pg.readEntry("Compare Mode", 0); KConfigGroup group(krConfig, "Look&Feel"); bool selectDirs = group.readEntry("Mark Dirs", false); KrViewItem *item, *otherItem; for (item = view->getFirst(); item != 0; item = view->getNext(item)) { if (item->name() == "..") continue; for (otherItem = otherPanel()->view->getFirst(); otherItem != 0 && otherItem->name() != item->name() ; otherItem = otherPanel()->view->getNext(otherItem)); bool isSingle = (otherItem == 0), isDifferent = false, isNewer = false; if (func->getVFile(item)->vfile_isDir() && !selectDirs) { item->setSelected(false); continue; } if (otherItem) { if (!func->getVFile(item)->vfile_isDir()) isDifferent = ITEM2VFILE(otherPanel(), otherItem)->vfile_getSize() != func->getVFile(item)->vfile_getSize(); isNewer = func->getVFile(item)->vfile_getTime_t() > ITEM2VFILE(otherPanel(), otherItem)->vfile_getTime_t(); } switch (compareMode) { case 0: item->setSelected(isNewer || isSingle); break; case 1: item->setSelected(isNewer); break; case 2: item->setSelected(isSingle); break; case 3: item->setSelected(isDifferent || isSingle); break; case 4: item->setSelected(isDifferent); break; } } view->updateView(); if (otherPanelToo) otherPanel()->gui->compareDirs(false); } void ListPanel::refreshColors() { view->refreshColors(); emit refreshColors(this == ACTIVE_PANEL); } void ListPanel::slotFocusOnMe(bool focus) { if (focus && _manager->currentPanel() != this) { // ignore focus request if this panel is not shown return; } krApp->setUpdatesEnabled(false); if(focus) { emit activate(); _actions->activePanelChanged(); func->refreshActions(); slotCurrentChanged(view->getCurrentKrViewItem()); view->prepareForActive(); otherPanel()->gui->slotFocusOnMe(false); } else { // in case a new url was entered but not refreshed to, // reset url navigator to the current url urlNavigator->setLocationUrl(virtualPath()); view->prepareForPassive(); } urlNavigator->setActive(focus); refreshColors(); emit refreshPathLabel(); krApp->setUpdatesEnabled(true); } // this is used to start the panel ////////////////////////////////////////////////////////////////// void ListPanel::start(QUrl url, bool immediate) { QUrl virt(url); if (!virt.isValid()) virt = QUrl::fromLocalFile(ROOT_DIR); if (virt.isLocalFile()) _realPath = virt; else _realPath = QUrl::fromLocalFile(ROOT_DIR); if (immediate) func->immediateOpenUrl(virt, true); else func->openUrl(virt); setJumpBack(virt); } void ListPanel::slotStartUpdate(bool directoryChange) { if (inlineRefreshJob) inlineRefreshListResult(0); setCursor(Qt::BusyCursor); const QUrl currentUrl = virtualPath(); if (directoryChange) { if (this == ACTIVE_PANEL) { slotFocusOnMe(); } if (func->files()->isLocal()) _realPath = currentUrl; urlNavigator->setLocationUrl(currentUrl); emit pathChanged(currentUrl); krApp->popularUrls()->addUrl(currentUrl); searchBar->hideBar(); } - updateFilesystemStats(currentUrl); - if (compareMode) otherPanel()->view->refresh(); // return cursor to normal arrow setCursor(Qt::ArrowCursor); slotUpdateTotals(); } -void ListPanel::updateFilesystemStats(const QUrl &url) +void ListPanel::updateFilesystemStats(const QString &metaInfo, + KIO::filesize_t total, KIO::filesize_t free) { - mediaButton->mountPointChanged(QString()); - freeSpace->setText(QString()); - - if (!KConfigGroup(krConfig, "Look&Feel").readEntry("ShowSpaceInformation", true)) { - status->setText(func->files()->metaInformation().isEmpty() - ? i18n("Space information disabled") - : func->files()->metaInformation()); - return; - } - - if (!url.isLocalFile()) { - status->setText(func->files()->metaInformation().isEmpty() - ? i18n("No space information on non-local filesystems") - : func->files()->metaInformation()); - return; - } - - // check for special filesystems; - QString path = url.path(); // must be local url - if (path.left(4) == "/dev") { - status->setText(i18n("No space information on [dev]")); - return; - } -#ifdef BSD - if (path.left(5) == "/procfs") { // /procfs is a special case - no volume information - status->setText(i18n("No space information on [procfs]")); - return; - } -#else - if (path.left(5) == "/proc") { // /proc is a special case - no volume information - status->setText(i18n("No space information on [proc]")); - return; - } -#endif + QString statusText, mountPointText, freeSpaceText; - KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo(path); - if(!info.isValid()) { - status->setText(i18n("Space information unavailable")); - return; - } - int perc = 0; - if (info.size() != 0) { // make sure that if totalsize==0, then perc=0 - perc = (int)(((float)info.available() / (float)info.size()) * 100.0); - } - // mount point information - find it in the list first - KMountPoint::List lst = KMountPoint::currentMountPoints(); - QString fstype = i18nc("Unknown file system type", "unknown"); - for (KMountPoint::List::iterator it = lst.begin(); it != lst.end(); ++it) { - if ((*it)->mountPoint() == info.mountPoint()) { - fstype = (*it)->mountType(); - break; - } + if (!metaInfo.isEmpty()) { + statusText = metaInfo; + mountPointText = freeSpaceText = ""; + } else { + const int perc = total == 0 ? 0 : (int)(((float)free / (float)total) * 100.0); + // get mount point + const QString path = func->files()->currentDirectory().path(); + const KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(path); + mountPointText = mountPoint ? mountPoint->mountPoint() : ""; + const QString fstype = + mountPoint ? mountPoint->mountType() : i18nc("Unknown file system type", "unknown"); + + statusText = i18nc("%1=free space,%2=total space,%3=percentage of usage, " + "%4=mountpoint,%5=filesystem type", + "%1 free out of %2 (%3%) on %4 [(%5)]", KIO::convertSize(free), + KIO::convertSize(total), perc, mountPointText, fstype); + + freeSpaceText = " " + i18n("%1 free", KIO::convertSize(free)); } - QString stats = i18nc("%1=free space,%2=total space,%3=percentage of usage, " - "%4=mountpoint,%5=filesystem type", - "%1 free out of %2 (%3%) on %4 [(%5)]", - KIO::convertSize(info.available()), - KIO::convertSize(info.size()), perc, - info.mountPoint(), fstype); - - status->setText(stats); - - freeSpace->setText(" " + i18n("%1 free", KIO::convertSize(info.available()))); - mediaButton->mountPointChanged(info.mountPoint()); + status->setText(statusText); + freeSpace->setText(freeSpaceText); + mediaButton->mountPointChanged(mountPointText); } void ListPanel::handleDrop(QDropEvent *event, bool onView) { // check what was dropped const QList urls = KUrlMimeData::urlsFromMimeData(event->mimeData()); if (urls.isEmpty()) { event->ignore(); // not for us to handle! return; } // find dropping destination QString destinationDir = ""; const bool dragFromThisPanel = event->source() == this; const KrViewItem *item = onView ? view->getKrViewItemAt(event->pos()) : 0; if (item) { const vfile *file = item->getVfile(); if (file && !file->vfile_isDir() && dragFromThisPanel) { event->ignore(); // dragging on files in same panel, ignore return ; } else if (!file || file->vfile_isDir()) { // item is ".." dummy or a directory destinationDir = item->name(); } } else if (dragFromThisPanel) { event->ignore(); // dragged from this panel onto an empty spot in this panel, ignore return ; } QUrl destination = QUrl(virtualPath()); destination.setPath(destination.path() + '/' + destinationDir); func->files()->dropFiles(destination, event); if(KConfigGroup(krConfig, "Look&Feel").readEntry("UnselectBeforeOperation", _UnselectBeforeOperation)) { KrPanel *p = dragFromThisPanel ? this : otherPanel(); p->view->saveSelection(); p->view->unselectAll(); } } void ListPanel::handleDrop(const QUrl &destination, QDropEvent *event) { func->files()->dropFiles(destination, event); } void ListPanel::startDragging(QStringList names, QPixmap px) { if (names.isEmpty()) { // avoid dragging empty urls return; } QList urls = func->files()->getUrls(names); QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; drag->setPixmap(px); mimeData->setUrls(urls); drag->setMimeData(mimeData); drag->start(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction); } // pops a right-click menu for items void ListPanel::popRightClickMenu(const QPoint &loc) { // run it, on the mouse location int j = QFontMetrics(font()).height() * 2; KrPopupMenu::run(QPoint(loc.x() + 5, loc.y() + j), this); } void ListPanel::popEmptyRightClickMenu(const QPoint &loc) { KrPopupMenu::run(loc, this); } QString ListPanel::getCurrentName() { QString name = view->getCurrentItem(); if (name != "..") return name; else return QString(); } void ListPanel::prepareToDelete() { view->setNameToMakeCurrent(view->firstUnmarkedBelowCurrent()); } void ListPanel::keyPressEvent(QKeyEvent *e) { switch (e->key()) { case Qt::Key_Enter : case Qt::Key_Return : if (e->modifiers() & Qt::ControlModifier) { if (e->modifiers() & Qt::AltModifier) { vfile *vf = func->files()->getVfile(view->getCurrentKrViewItem()->name()); if (vf && vf->vfile_isDir()) newTab(vf->vfile_getUrl(), true); } else { SLOTS->insertFileName((e->modifiers() & Qt::ShiftModifier) != 0); } } else e->ignore(); break; case Qt::Key_Right : case Qt::Key_Left : if (e->modifiers() == Qt::ControlModifier) { // user pressed CTRL+Right/Left - refresh other panel to the selected path if it's a // directory otherwise as this one if ((isLeft() && e->key() == Qt::Key_Right) || (!isLeft() && e->key() == Qt::Key_Left)) { QUrl newPath; KrViewItem *it = view->getCurrentKrViewItem(); if (it->name() == "..") { newPath = KIO::upUrl(func->files()->currentDirectory()); } else { vfile *v = func->getVFile(it); // If it's a directory different from ".." if (v && v->vfile_isDir() && v->vfile_getName() != "..") { newPath = v->vfile_getUrl(); } else { // If it's a supported compressed file if (v && KRarcHandler::arcSupported(v->vfile_getMime())) { newPath = func->browsableArchivePath(v->vfile_getUrl().fileName()); } else { newPath = func->files()->currentDirectory(); } } } otherPanel()->func->openUrl(newPath); } else { func->openUrl(otherPanel()->func->files()->currentDirectory()); } return ; } else e->ignore(); break; case Qt::Key_Down : if (e->modifiers() == Qt::ControlModifier) { // give the keyboard focus to the command line if (MAIN_VIEW->cmdLine()->isVisible()) MAIN_VIEW->cmdLineFocus(); else MAIN_VIEW->focusTerminalEmulator(); return ; } else if (e->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) { // give the keyboard focus to TE MAIN_VIEW->focusTerminalEmulator(); } else e->ignore(); break; case Qt::Key_Up : if (e->modifiers() == Qt::ControlModifier) { // give the keyboard focus to the url navigator editLocation(); return ; } else e->ignore(); break; case Qt::Key_Escape: inlineRefreshCancel(); break; default: // if we got this, it means that the view is not doing // the quick search thing, so send the characters to the commandline, if normal key if (e->modifiers() == Qt::NoModifier) MAIN_VIEW->cmdLine()->addText(e->text()); //e->ignore(); } } void ListPanel::showEvent(QShowEvent *e) { panelActive(); QWidget::showEvent(e); } void ListPanel::hideEvent(QHideEvent *e) { panelInactive(); QWidget::hideEvent(e); } void ListPanel::panelActive() { //func->files()->vfs_enableRefresh(true) } void ListPanel::panelInactive() { // don't refresh when not active (ie: hidden, application isn't focused ...) // TODO disabled so that the user sees changes in non-focused window; if the performance impact // is too high we need another solution here //func->files()->vfs_enableRefresh(false); } void ListPanel::slotPreviewJobStarted(KJob *job) { previewJob = job; previewProgress->setValue(0); previewProgress->setFormat(i18n("loading previews: %p%")); previewProgress->show(); inlineRefreshCancelButton->show(); previewProgress->setMaximumHeight(inlineRefreshCancelButton->height()); connect(job, SIGNAL(percent(KJob*, unsigned long)), SLOT(slotPreviewJobPercent(KJob*, unsigned long))); connect(job, SIGNAL(result(KJob*)), SLOT(slotPreviewJobResult(KJob*))); } void ListPanel::slotPreviewJobPercent(KJob* /*job*/, unsigned long percent) { previewProgress->setValue(percent); } void ListPanel::slotPreviewJobResult(KJob* /*job*/) { previewJob = 0; previewProgress->hide(); if(!inlineRefreshJob) inlineRefreshCancelButton->hide(); } void ListPanel::slotJobStarted(KIO::Job* job) { // disable the parts of the panel we don't want touched status->setEnabled(false); urlNavigator->setEnabled(false); cdRootButton->setEnabled(false); cdHomeButton->setEnabled(false); cdUpButton->setEnabled(false); cdOtherButton->setEnabled(false); popupBtn->setEnabled(false); if(popup) popup->setEnabled(false); bookmarksButton->setEnabled(false); historyButton->setEnabled(false); syncBrowseButton->setEnabled(false); // connect to the job interface to provide in-panel refresh notification connect(job, SIGNAL(infoMessage(KJob*, const QString &)), SLOT(inlineRefreshInfoMessage(KJob*, const QString &))); connect(job, SIGNAL(percent(KJob*, unsigned long)), SLOT(inlineRefreshPercent(KJob*, unsigned long))); connect(job, SIGNAL(result(KJob*)), this, SLOT(inlineRefreshListResult(KJob*))); inlineRefreshJob = job; totals->setText(i18n(">> Reading...")); inlineRefreshCancelButton->show(); } void ListPanel::inlineRefreshCancel() { if (inlineRefreshJob) { disconnect(inlineRefreshJob, 0, this, 0); inlineRefreshJob->kill(KJob::EmitResult); inlineRefreshListResult(0); } if(previewJob) { disconnect(previewJob, 0, this, 0); previewJob->kill(KJob::EmitResult); slotPreviewJobResult(0); } } void ListPanel::inlineRefreshPercent(KJob*, unsigned long perc) { QString msg = i18n(">> Reading: %1 % complete...", perc); totals->setText(msg); } void ListPanel::inlineRefreshInfoMessage(KJob*, const QString &msg) { totals->setText(i18n(">> Reading: %1", msg)); } void ListPanel::inlineRefreshListResult(KJob*) { if(inlineRefreshJob) disconnect(inlineRefreshJob, 0, this, 0); inlineRefreshJob = 0; // reenable everything status->setEnabled(true); urlNavigator->setEnabled(true); cdRootButton->setEnabled(true); cdHomeButton->setEnabled(true); cdUpButton->setEnabled(true); cdOtherButton->setEnabled(true); popupBtn->setEnabled(true); if(popup) popup->setEnabled(true); bookmarksButton->setEnabled(true); historyButton->setEnabled(true); syncBrowseButton->setEnabled(true); if(!previewJob) inlineRefreshCancelButton->hide(); } void ListPanel::jumpBack() { func->openUrl(_jumpBackURL); } void ListPanel::setJumpBack(QUrl url) { _jumpBackURL = url; } void ListPanel::slotVfsError(QString msg) { if (func->ignoreVFSErrors()) return; refreshColors(); vfsError->setText(i18n("Error: %1", msg)); vfsError->show(); } void ListPanel::showButtonMenu(QToolButton *b) { if(this != ACTIVE_PANEL) slotFocusOnMe(); if(b->isHidden()) b->menu()->exec(mapToGlobal(clientArea->pos())); else b->click(); } void ListPanel::openBookmarks() { showButtonMenu(bookmarksButton); } void ListPanel::openHistory() { showButtonMenu(historyButton); } void ListPanel::openMedia() { showButtonMenu(mediaButton); } void ListPanel::rightclickMenu() { if (view->getCurrentKrViewItem()) popRightClickMenu(mapToGlobal(view->getCurrentKrViewItem()->itemRect().topLeft())); } void ListPanel::toggleSyncBrowse() { syncBrowseButton->toggle(); } void ListPanel::editLocation() { urlNavigator->setUrlEditable(true); urlNavigator->setFocus(); urlNavigator->editor()->lineEdit()->selectAll(); } void ListPanel::showSearchBar() { searchBar->showBar(); } void ListPanel::showSearchFilter() { searchBar->showBar(KrSearchBar::MODE_FILTER); } void ListPanel::saveSettings(KConfigGroup cfg, bool saveHistory) { QUrl url = virtualPath(); url.setPassword(QString()); // make sure no password is saved cfg.writeEntry("Url", url.toString()); cfg.writeEntry("Type", getType()); cfg.writeEntry("Properties", getProperties()); if(saveHistory) func->history->save(KConfigGroup(&cfg, "History")); view->saveSettings(KConfigGroup(&cfg, "View")); // splitter/popup state if (popup && !popup->isHidden()) { popup->saveSettings(KConfigGroup(&cfg, "PanelPopup")); cfg.writeEntry("PopupPosition", popupPosition()); cfg.writeEntry("SplitterSizes", splt->saveState()); cfg.writeEntry("PopupPage", popup->currentPage()); } else { cfg.deleteEntry("PopupPosition"); cfg.deleteEntry("SplitterSizes"); cfg.deleteEntry("PopupPage"); } } void ListPanel::restoreSettings(KConfigGroup cfg) { changeType(cfg.readEntry("Type", defaultPanelType())); setProperties(cfg.readEntry("Properties", 0)); view->restoreSettings(KConfigGroup(&cfg, "View")); _realPath = QUrl::fromLocalFile(ROOT_DIR); if(func->history->restore(KConfigGroup(&cfg, "History"))) func->refresh(); else { QUrl url(cfg.readEntry("Url", ROOT_DIR)); if (!url.isValid()) url = QUrl::fromLocalFile(ROOT_DIR); func->openUrl(url); } setJumpBack(func->history->currentUrl()); if (cfg.hasKey("PopupPosition")) { // popup was visible, restore togglePanelPopup(); // create and show popup->restoreSettings(KConfigGroup(&cfg, "PanelPopup")); setPopupPosition(cfg.readEntry("PopupPosition", 42 /* dummy */)); splt->restoreState(cfg.readEntry("SplitterSizes", QByteArray())); popup->setCurrentPage(cfg.readEntry("PopupPage", 0)); } } void ListPanel::slotCurrentChanged(KrViewItem *item) { // update status bar if (item) krApp->statusBarUpdate(item->description()); // update popup panel; which panel to display on? PanelPopup *p; if(popup && !popup->isHidden()) p = popup; else if(otherPanel()->gui->popup && !otherPanel()->gui->popup->isHidden()) p = otherPanel()->gui->popup; else return; p->update(item ? func->files()->getVfile(item->name()) : 0); } void ListPanel::otherPanelChanged() { func->syncURL = QUrl(); } void ListPanel::getFocusCandidates(QVector &widgets) { if(urlNavigator->editor()->isVisible()) widgets << urlNavigator->editor(); if(view->widget()->isVisible()) widgets << view->widget(); if(popup && popup->isVisible()) widgets << popup; } void ListPanel::updateButtons() { backButton->setEnabled(func->history->canGoBack()); forwardButton->setEnabled(func->history->canGoForward()); historyButton->setEnabled(func->history->count() > 1); cdRootButton->setEnabled(!virtualPath().matches(QUrl::fromLocalFile(ROOT_DIR), QUrl::StripTrailingSlash)); cdUpButton->setEnabled(!func->files()->isRoot()); cdHomeButton->setEnabled(!func->atHome()); } void ListPanel::newTab(KrViewItem *it) { if (!it) return; else if (it->name() == "..") { newTab(KIO::upUrl(virtualPath()), true); } else if (ITEM2VFILE(this, it)->vfile_isDir()) { QUrl url = virtualPath(); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + '/' + (it->name())); newTab(url, true); } } void ListPanel::resetNavigatorMode() { if (isNavigatorEditModeSet()) return; // set to "navigate" mode if url wasn't changed if (urlNavigator->uncommittedUrl().matches(virtualPath(), QUrl::StripTrailingSlash)) { // NOTE: this also sets focus to the navigator urlNavigator->setUrlEditable(false); slotFocusOnMe(); } } int ListPanel::popupPosition() const { int pos = splt->orientation() == Qt::Vertical ? 1 : 0; return pos + (qobject_cast(splt->widget(0)) == NULL ? 2 : 0); } void ListPanel::setPopupPosition(int pos) { splt->setOrientation(pos % 2 == 0 ? Qt::Horizontal : Qt::Vertical); if ((pos < 2) != (qobject_cast(splt->widget(0)) != NULL)) { splt->insertWidget(0, splt->widget(1)); // swapping widgets in splitter } } diff --git a/krusader/Panel/listpanel.h b/krusader/Panel/listpanel.h index c3821803..7a3bf4ac 100644 --- a/krusader/Panel/listpanel.h +++ b/krusader/Panel/listpanel.h @@ -1,250 +1,251 @@ /*************************************************************************** listpanel.h ------------------- begin : Thu May 4 2000 copyright : (C) 2000 by Shie Erlich & Rafi Yanai e-mail : krusader@users.sourceforge.net web site : http://krusader.sourceforge.net --------------------------------------------------------------------------- Description *************************************************************************** A db dD d8888b. db db .d8888. .d8b. d8888b. d88888b d8888b. 88 ,8P' 88 `8D 88 88 88' YP d8' `8b 88 `8D 88' 88 `8D 88,8P 88oobY' 88 88 `8bo. 88ooo88 88 88 88ooooo 88oobY' 88`8b 88`8b 88 88 `Y8b. 88~~~88 88 88 88~~~~~ 88`8b 88 `88. 88 `88. 88b d88 db 8D 88 88 88 .8D 88. 88 `88. YP YD 88 YD ~Y8888P' `8888Y' YP YP Y8888D' Y88888P 88 YD H e a d e r F i l e *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef LISTPANEL_H #define LISTPANEL_H // QtCore #include #include #include #include #include #include // QtGui #include #include #include #include #include #include #include // QtWidgets #include #include #include #include #include #include #include #include #include #include "krpanel.h" #include "krviewitem.h" #include "../Dialogs/krsqueezedtextlabel.h" #define PROP_SYNC_BUTTON_ON 1 #define PROP_LOCKED 2 class KrView; class KrSearchBar; class DirHistoryButton; class MediaButton; class PanelPopup; class SyncBrowseButton; class KrBookmarkButton; class ListPanelFunc; class QSplitter; class KrErrorDisplay; class ListPanelActions; class ListPanel : public QWidget, public KrPanel { friend class ListPanelFunc; Q_OBJECT public: #define ITEM2VFILE(PANEL_PTR, KRVIEWITEM) PANEL_PTR->func->files()->getVfile(KRVIEWITEM->name()) #define NAME2VFILE(PANEL_PTR, STRING_NAME) PANEL_PTR->func->files()->getVfile(STRING_NAME) // constructor create the panel, but DOESN'T fill it with data, use start() ListPanel(QWidget *parent, AbstractPanelManager *manager, KConfigGroup cfg = KConfigGroup()); ~ListPanel(); virtual void otherPanelChanged() Q_DECL_OVERRIDE; void start(QUrl url = QUrl(), bool immediate = false); void reparent(QWidget *parent, AbstractPanelManager *manager); int getType() { return panelType; } void changeType(int); bool isLocked() { return _locked; } void setLocked(bool lck) { _locked = lck; } ListPanelActions *actions() { return _actions; } /// The last shown local path. QString realPath() const; QString getCurrentName(); void getSelectedNames(QStringList* fileNames) { view->getSelectedItems(fileNames); } void setButtons(); void setJumpBack(QUrl url); int getProperties(); void setProperties(int); void getFocusCandidates(QVector &widgets); void saveSettings(KConfigGroup cfg, bool saveHistory); void restoreSettings(KConfigGroup cfg); public slots: void popRightClickMenu(const QPoint&); void popEmptyRightClickMenu(const QPoint &); void compareDirs(bool otherPanelToo = true); void slotFocusOnMe(bool focus = true); void slotUpdateTotals(); // react to file changes in vfs (path change or refresh) void slotStartUpdate(bool directoryChange); void togglePanelPopup(); void panelActive(); // called when the panel becomes active void panelInactive(); // called when panel becomes inactive void refreshColors(); void inlineRefreshCancel(); void openMedia(); void openHistory(); void openBookmarks(); void rightclickMenu(); void toggleSyncBrowse(); void editLocation(); void showSearchBar(); void showSearchFilter(); void jumpBack(); void setJumpBack() { setJumpBack(virtualPath()); } ///////////////////////// service functions - called internally //////////////////////// void prepareToDelete(); // internal use only protected: virtual void keyPressEvent(QKeyEvent *e) Q_DECL_OVERRIDE; virtual void mousePressEvent(QMouseEvent*) Q_DECL_OVERRIDE { slotFocusOnMe(); } virtual void showEvent(QShowEvent *) Q_DECL_OVERRIDE; virtual void hideEvent(QHideEvent *) Q_DECL_OVERRIDE; virtual bool eventFilter(QObject * watched, QEvent * e) Q_DECL_OVERRIDE; void showButtonMenu(QToolButton *b); void createView(); void updateButtons(); static int defaultPanelType(); static bool isNavigatorEditModeSet(); // return the navigator edit mode setting protected slots: void slotCurrentChanged(KrViewItem *item); void handleDrop(QDropEvent *event, bool onView = false); // handle drops on frame or view void handleDrop(const QUrl &destination, QDropEvent *event); // handle drops with destination void startDragging(QStringList, QPixmap); void slotPreviewJobStarted(KJob *job); void slotPreviewJobPercent(KJob *job, unsigned long percent); void slotPreviewJobResult(KJob *job); // those handle the in-panel refresh notifications void slotJobStarted(KIO::Job* job); void inlineRefreshInfoMessage(KJob* job, const QString &msg); void inlineRefreshListResult(KJob* job); void inlineRefreshPercent(KJob*, unsigned long); void slotVfsError(QString msg); void newTab(KrViewItem *item); void newTab(const QUrl &url, bool nextToThis = false) { _manager->newTab(url, nextToThis ? this : 0); } void resetNavigatorMode(); // set navigator mode after focus was lost + // update filesystem meta info, disk-free and mount status + void updateFilesystemStats(const QString &metaInfo, KIO::filesize_t total, KIO::filesize_t free); signals: void signalStatus(QString msg); // emmited when we need to update the status bar void pathChanged(const QUrl &url); // directory changed or refreshed void activate(); // emitted when the user changes panels void finishedDragging(); // NOTE: currently not used void refreshColors(bool active); // emitted when we have to update the path label width void refreshPathLabel(); protected: int panelType; QUrl _realPath; // named with _ to keep realPath() compatibility QUrl _jumpBackURL; int colorMask; bool compareMode; //FilterSpec filter; KJob *previewJob; KIO::Job *inlineRefreshJob; ListPanelActions *_actions; QPixmap currDragPix; QWidget *clientArea; QSplitter *splt; KUrlNavigator* urlNavigator; KrSearchBar* searchBar; QToolButton *backButton, *forwardButton; QToolButton *cdRootButton; QToolButton *cdHomeButton; QToolButton *cdUpButton; QToolButton *cdOtherButton; QToolButton *popupPositionBtn; QToolButton *popupBtn; PanelPopup *popup; // lazy initialized KrBookmarkButton *bookmarksButton; KrSqueezedTextLabel *status, *totals, *freeSpace; QProgressBar *previewProgress; DirHistoryButton* historyButton; MediaButton *mediaButton; SyncBrowseButton *syncBrowseButton; QToolButton *inlineRefreshCancelButton; KrErrorDisplay *vfsError; private: - void updateFilesystemStats(const QUrl &url); // update disk-free and mount status bool handleDropInternal(QDropEvent *event, const QString &dir); int popupPosition() const; // 0: West, 1: North, 2: East, 3: South void setPopupPosition(int); private: bool _locked; QList popupSizes; }; #endif diff --git a/krusader/Panel/panelfunc.cpp b/krusader/Panel/panelfunc.cpp index ebcc5e2e..b0548788 100644 --- a/krusader/Panel/panelfunc.cpp +++ b/krusader/Panel/panelfunc.cpp @@ -1,1218 +1,1219 @@ /*************************************************************************** panelfunc.cpp ------------------- copyright : (C) 2000 by Shie Erlich & Rafi Yanai e-mail : krusader@users.sourceforge.net web site : http://krusader.sourceforge.net --------------------------------------------------------------------------- Description *************************************************************************** A db dD d8888b. db db .d8888. .d8b. d8888b. d88888b d8888b. 88 ,8P' 88 `8D 88 88 88' YP d8' `8b 88 `8D 88' 88 `8D 88,8P 88oobY' 88 88 `8bo. 88ooo88 88 88 88ooooo 88oobY' 88`8b 88`8b 88 88 `Y8b. 88~~~88 88 88 88~~~~~ 88`8b 88 `88. 88 `88. 88b d88 db 8D 88 88 88 .8D 88. 88 `88. YP YD 88 YD ~Y8888P' `8888Y' YP YP Y8888D' Y88888P 88 YD S o u r c e F i l e *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "panelfunc.h" // QtCore #include #include #include #include #include #include // QtGui #include #include // QtWidgets #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dirhistoryqueue.h" #include "krcalcspacedialog.h" #include "listpanel.h" #include "krerrordisplay.h" #include "listpanelactions.h" #include "../krglobal.h" #include "../krslots.h" #include "../kractions.h" #include "../defaults.h" #include "../abstractpanelmanager.h" #include "../krservices.h" #include "../Archive/krarchandler.h" #include "../Archive/packjob.h" #include "../VFS/vfile.h" #include "../VFS/virt_vfs.h" #include "../VFS/krpermhandler.h" #include "../VFS/krvfshandler.h" #include "../Dialogs/packgui.h" #include "../Dialogs/krdialogs.h" #include "../Dialogs/krpleasewait.h" #include "../Dialogs/krspwidgets.h" #include "../Dialogs/checksumdlg.h" #include "../KViewer/krviewer.h" #include "../GUI/syncbrowsebutton.h" #include "../MountMan/kmountman.h" #include "../JobMan/jobman.h" QPointer ListPanelFunc::copyToClipboardOrigin; ListPanelFunc::ListPanelFunc(ListPanel *parent) : QObject(parent), panel(parent), vfsP(0), urlManuallyEntered(false), _refreshing(false), _ignoreVFSErrors(false) { history = new DirHistoryQueue(panel); delayTimer.setSingleShot(true); connect(&delayTimer, SIGNAL(timeout()), this, SLOT(doRefresh())); } ListPanelFunc::~ListPanelFunc() { if (vfsP) { vfsP->deleteLater(); } delete history; } void ListPanelFunc::navigatorUrlChanged(const QUrl &url) { if (_refreshing) return; if (!ListPanel::isNavigatorEditModeSet()) { panel->urlNavigator->setUrlEditable(false); } openUrl(KrServices::escapeFileUrl(url), QString(), true); } bool ListPanelFunc::isSyncing(const QUrl &url) { if(otherFunc()->otherFunc() == this && panel->otherPanel()->gui->syncBrowseButton->state() == SYNCBROWSE_CD && !otherFunc()->syncURL.isEmpty() && otherFunc()->syncURL == url) return true; return false; } void ListPanelFunc::openFileNameInternal(const QString &name, bool externallyExecutable) { if (name == "..") { dirUp(); return ; } vfile *vf = files()->getVfile(name); if (vf == 0) return ; QUrl url = files()->getUrl(name); if (vf->vfile_isDir()) { panel->view->setNameToMakeCurrent(QString()); openUrl(url); return; } QString mime = vf->vfile_getMime(); QUrl arcPath = browsableArchivePath(name); if (!arcPath.isEmpty()) { bool browseAsDirectory = !externallyExecutable || (KConfigGroup(krConfig, "Archives").readEntry("ArchivesAsDirectories", _ArchivesAsDirectories) && (KRarcHandler::arcSupported(mime) || KrServices::isoSupported(mime))); if (browseAsDirectory) { openUrl(arcPath); return; } } if (externallyExecutable) { if (KRun::isExecutableFile(url, mime)) { runCommand(KShell::quoteArg(url.path())); return; } KService::Ptr service = KMimeTypeTrader::self()->preferredService(mime); if(service) { runService(*service, QList() << url); return; } displayOpenWithDialog(QList() << url); } } #if 0 //FIXME: see if this is still needed void ListPanelFunc::popErronousUrl() { QUrl current = urlStack.last(); while (urlStack.count() != 0) { QUrl url = urlStack.takeLast(); if (!current.equals(url)) { immediateOpenUrl(url, true); return; } } immediateOpenUrl(QUrl::fromLocalFile(ROOT_DIR), true); } #endif QUrl ListPanelFunc::cleanPath(const QUrl &urlIn) { QUrl url = urlIn; url.setPath(QDir::cleanPath(url.path())); if (!url.isValid() || url.isRelative()) { if (url.url() == "~") url = QUrl::fromLocalFile(QDir::homePath()); else if (!url.url().startsWith('/')) { // possible relative URL - translate to full URL url = files()->currentDirectory(); url.setPath(url.path() + '/' + urlIn.path()); } } url.setPath(QDir::cleanPath(url.path())); return url; } void ListPanelFunc::openUrl(const QUrl &url, const QString& nameToMakeCurrent, bool manuallyEntered) { if (panel->syncBrowseButton->state() == SYNCBROWSE_CD) { //do sync-browse stuff.... if(syncURL.isEmpty()) syncURL = panel->otherPanel()->virtualPath(); QString relative = QDir(panel->virtualPath().path() + '/').relativeFilePath(url.path()); syncURL.setPath(QDir::cleanPath(syncURL.path() + '/' + relative)); panel->otherPanel()->gui->setLocked(false); otherFunc()->openUrlInternal(syncURL, nameToMakeCurrent, false, false, false); } openUrlInternal(url, nameToMakeCurrent, false, false, manuallyEntered); } void ListPanelFunc::immediateOpenUrl(const QUrl &url, bool disableLock) { openUrlInternal(url, QString(), true, disableLock, false); } void ListPanelFunc::openUrlInternal(const QUrl &url, const QString& nameToMakeCurrent, bool immediately, bool disableLock, bool manuallyEntered) { QUrl cleanUrl = cleanPath(url); if (!disableLock && panel->isLocked() && !files()->currentDirectory().matches(cleanUrl, QUrl::StripTrailingSlash)) { panel->_manager->newTab(url); urlManuallyEntered = false; return; } urlManuallyEntered = manuallyEntered; history->add(cleanUrl, nameToMakeCurrent); if(immediately) doRefresh(); else refresh(); } void ListPanelFunc::refresh() { panel->inlineRefreshCancel(); delayTimer.start(0); // to avoid qApp->processEvents() deadlock situaltion } void ListPanelFunc::doRefresh() { _refreshing = true; delayTimer.stop(); QUrl url = history->currentUrl(); if(!url.isValid()) { //FIXME go back in history here ? panel->slotStartUpdate(true); // refresh the panel urlManuallyEntered = false; return ; } panel->inlineRefreshCancel(); // if we are not refreshing to current URL bool isEqualUrl = files()->currentDirectory().matches(url, QUrl::StripTrailingSlash); if (!isEqualUrl) { panel->setCursor(Qt::WaitCursor); panel->view->clearSavedSelection(); } if(panel->vfsError) panel->vfsError->hide(); bool refreshFailed = false; while (true) { QUrl url = history->currentUrl(); isEqualUrl = files()->currentDirectory().matches(url, QUrl::StripTrailingSlash); // may get a new vfs for this url vfs* vfs = KrVfsHandler::instance().getVfs(url, files()); vfs->setParentWindow(krMainWindow); connect(vfs, &vfs::aboutToOpenDir, &krMtMan, &KMountMan::autoMount, Qt::DirectConnection); if (vfs != vfsP) { panel->view->setFiles(0); // disconnect older signals disconnect(vfsP, 0, panel, 0); vfsP->deleteLater(); vfsP = vfs; // v != 0 so this is safe } else { if (vfsP->isRefreshing()) { delayTimer.start(100); /* if vfs is busy try refreshing later */ return; } } // (re)connect vfs signals disconnect(files(), 0, panel, 0); connect(files(), SIGNAL(refreshDone(bool)), panel, SLOT(slotStartUpdate(bool))); + connect(files(), &vfs::filesystemInfoChanged, panel, &ListPanel::updateFilesystemStats); connect(files(), SIGNAL(refreshJobStarted(KIO::Job*)), panel, SLOT(slotJobStarted(KIO::Job*))); connect(files(), SIGNAL(error(QString)), panel, SLOT(slotVfsError(QString))); panel->view->setFiles(files()); if(!history->currentItem().isEmpty() && isEqualUrl) { // if the url we're refreshing into is the current one, then the // partial refresh will not generate the needed signals to actually allow the // view to use nameToMakeCurrent. do it here instead (patch by Thomas Jarosch) panel->view->setCurrentItem(history->currentItem()); panel->view->makeItemVisible(panel->view->getCurrentKrViewItem()); } panel->view->setNameToMakeCurrent(history->currentItem()); int savedHistoryState = history->state(); // NOTE: this is blocking. Returns false on error or interruption (cancel requested or panel // was deleted) const bool refreshed = vfsP->refresh(url); if (refreshed) { // update the history and address bar, as the actual url might differ from the one requested history->setCurrentUrl(vfsP->currentDirectory()); panel->urlNavigator->setLocationUrl(vfsP->currentDirectory()); break; // we have a valid refreshed URL now } if (!panel || !panel->view) // this panel was deleted while refreshing return; refreshFailed = true; panel->view->setNameToMakeCurrent(QString()); if(history->state() != savedHistoryState) // don't go back if the history was touched break; if(!history->goBack()) { // put the root dir to the beginning of history, if it's not there yet if (!url.matches(QUrl::fromLocalFile(ROOT_DIR), QUrl::StripTrailingSlash)) history->pushBackRoot(); else break; } _ignoreVFSErrors = true; } _ignoreVFSErrors = false; panel->view->setNameToMakeCurrent(QString()); panel->setCursor(Qt::ArrowCursor); // on local file system change the working directory if (files()->isLocal()) QDir::setCurrent(KrServices::urlToLocalPath(files()->currentDirectory())); // see if the open url operation failed, and if so, // put the attempted url in the navigator bar and let the user change it if (refreshFailed) { if(isSyncing(url)) panel->otherPanel()->gui->syncBrowseButton->setChecked(false); else if(urlManuallyEntered) { panel->urlNavigator->setLocationUrl(url); if(panel == ACTIVE_PANEL) panel->editLocation(); } } if(otherFunc()->otherFunc() == this) // not true if our tab is not active otherFunc()->syncURL = QUrl(); urlManuallyEntered = false; refreshActions(); _refreshing = false; } void ListPanelFunc::redirectLink() { if (!files()->isLocal()) { KMessageBox::sorry(krMainWindow, i18n("You can edit links only on local file systems")); return ; } vfile *vf = files()->getVfile(panel->getCurrentName()); if (!vf) return ; QString file = vf->vfile_getUrl().path(); QString currentLink = vf->vfile_getSymDest(); if (currentLink.isEmpty()) { KMessageBox::sorry(krMainWindow, i18n("The current file is not a link, so it cannot be redirected.")); return ; } // ask the user for a new destination bool ok = false; QString newLink = QInputDialog::getText(krMainWindow, i18n("Link Redirection"), i18n("Please enter the new link destination:"), QLineEdit::Normal, currentLink, &ok); // if the user canceled - quit if (!ok || newLink == currentLink) return ; // delete the current link if (unlink(file.toLocal8Bit()) == -1) { KMessageBox::sorry(krMainWindow, i18n("Cannot remove old link: %1", file)); return ; } // try to create a new symlink if (symlink(newLink.toLocal8Bit(), file.toLocal8Bit()) == -1) { KMessageBox:: /* --=={ Patch by Heiner }==-- */sorry(krMainWindow, i18n("Failed to create a new link: %1", file)); return ; } } void ListPanelFunc::krlink(bool sym) { if (!files()->isLocal()) { KMessageBox::sorry(krMainWindow, i18n("You can create links only on local file systems")); return; } QString name = panel->getCurrentName(); // ask the new link name.. bool ok = false; QString linkName = QInputDialog::getText(krMainWindow, i18n("New Link"), i18n("Create a new link to: %1", name), QLineEdit::Normal, name, &ok); // if the user canceled - quit if (!ok || linkName == name) return; // if the name is already taken - quit if (files()->getVfile(linkName) != 0) { KMessageBox::sorry(krMainWindow, i18n("A folder or a file with this name already exists.")); return; } // make link name and target absolute path if (linkName.left(1) != "/") linkName = files()->currentDirectory().path() + '/' + linkName; name = files()->getUrl(name).path(); if (sym) { if (symlink(name.toLocal8Bit(), linkName.toLocal8Bit()) == -1) KMessageBox::sorry(krMainWindow, i18n("Failed to create a new symlink '%1' to: '%2'", linkName, name)); } else { if (link(name.toLocal8Bit(), linkName.toLocal8Bit()) == -1) KMessageBox::sorry(krMainWindow, i18n("Failed to create a new link '%1' to '%2'", linkName, name)); } } void ListPanelFunc::view() { QString fileName = panel->getCurrentName(); if (fileName.isNull()) return ; // if we're trying to view a directory, just exit vfile * vf = files()->getVfile(fileName); if (!vf || vf->vfile_isDir()) return ; if (!vf->vfile_isReadable()) { KMessageBox::sorry(0, i18n("No permissions to view this file.")); return ; } // call KViewer. KrViewer::view(files()->getUrl(fileName)); // nothing more to it! } void ListPanelFunc::viewDlg() { // ask the user for a url to view QUrl dest = KChooseDir::getFile(i18n("Enter a URL to view:"), panel->virtualPath(), panel->virtualPath()); if (dest.isEmpty()) return ; // the user canceled KrViewer::view(dest); // view the file } void ListPanelFunc::terminal() { SLOTS->runTerminal(panel->realPath()); } void ListPanelFunc::edit() { KFileItem tmp; if (fileToCreate.isEmpty()) { QString name = panel->getCurrentName(); if (name.isNull()) return; fileToCreate = files()->getUrl(name); } tmp = KFileItem(fileToCreate); if (tmp.isDir()) { KMessageBox::sorry(krMainWindow, i18n("You cannot edit a folder")); fileToCreate = QUrl(); return ; } if (!tmp.isReadable()) { KMessageBox::sorry(0, i18n("No permissions to edit this file.")); fileToCreate = QUrl(); return; } KrViewer::edit(fileToCreate); fileToCreate = QUrl(); } void ListPanelFunc::editNew() { if(!fileToCreate.isEmpty()) return; // ask the user for the filename to edit fileToCreate = KChooseDir::getFile(i18n("Enter the filename to edit:"), panel->virtualPath(), panel->virtualPath()); if(fileToCreate.isEmpty()) return ; // the user canceled // if the file exists, edit it instead of creating a new one QFile f(fileToCreate.toLocalFile()); if(f.exists()) { edit(); } else { QTemporaryFile *tempFile = new QTemporaryFile; tempFile->open(); KIO::CopyJob *job = KIO::copy(QUrl::fromLocalFile(tempFile->fileName()), fileToCreate); job->setUiDelegate(0); job->setDefaultPermissions(true); connect(job, SIGNAL(result(KJob*)), SLOT(slotFileCreated(KJob*))); connect(job, SIGNAL(result(KJob*)), tempFile, SLOT(deleteLater())); } } void ListPanelFunc::slotFileCreated(KJob *job) { if(!job->error() || job->error() == KIO::ERR_FILE_ALREADY_EXIST) { KrViewer::edit(fileToCreate); if(KIO::upUrl(fileToCreate).matches(panel->virtualPath(), QUrl::StripTrailingSlash)) refresh(); else if(KIO::upUrl(fileToCreate).matches(panel->otherPanel()->virtualPath(), QUrl::StripTrailingSlash)) otherFunc()->refresh(); } else KMessageBox::sorry(krMainWindow, job->errorString()); fileToCreate = QUrl(); } void ListPanelFunc::moveFilesByQueue() { moveFiles(!krJobMan->isQueueModeEnabled()); } void ListPanelFunc::copyFilesByQueue() { copyFiles(!krJobMan->isQueueModeEnabled()); } void ListPanelFunc::copyFiles(bool reverseQueueMode, bool move) { QStringList fileNames; panel->getSelectedNames(&fileNames); if (fileNames.isEmpty()) return ; // safety QUrl destination = panel->otherPanel()->virtualPath(); bool startPaused = false; KConfigGroup group(krConfig, "Advanced"); bool showDialog = move ? group.readEntry("Confirm Move", _ConfirmMove) : group.readEntry("Confirm Copy", _ConfirmCopy); if (showDialog) { QString operationText; if (move) { operationText = fileNames.count() == 1 ? i18n("Move %1 to:", fileNames.first()) : i18np("Move %1 file to:", "Move %1 files to:", fileNames.count()); } else { operationText = fileNames.count() == 1 ? i18n("Copy %1 to:", fileNames.first()) : i18np("Copy %1 file to:", "Copy %1 files to:", fileNames.count()); } // ask the user for the copy/move dest KChooseDir::ChooseResult result = KChooseDir::getCopyDir(operationText, destination, panel->virtualPath()); destination = result.url; if (destination.isEmpty()) return ; // the user canceled reverseQueueMode = result.reverseQueueMode; startPaused = result.startPaused; } const QList fileUrls = files()->getUrls(fileNames); if (move) { // after the delete return the cursor to the first unmarked file above the current item panel->prepareToDelete(); } // make sure the user does not overwrite multiple files by mistake if (fileNames.count() > 1) { destination = vfs::ensureTrailingSlash(destination); } KIO::CopyJob::CopyMode mode = move ? KIO::CopyJob::Move : KIO::CopyJob::Copy; KrVfsHandler::instance().startCopyFiles(fileUrls, destination, mode, true, reverseQueueMode, startPaused); if(KConfigGroup(krConfig, "Look&Feel").readEntry("UnselectBeforeOperation", _UnselectBeforeOperation)) { panel->view->saveSelection(); panel->view->unselectAll(); } } // called from SLOTS to begin the renaming process void ListPanelFunc::rename() { panel->view->renameCurrentItem(); } // called by signal itemRenamed() from the view to complete the renaming process void ListPanelFunc::rename(const QString &oldname, const QString &newname) { if (oldname == newname) return ; // do nothing // set current after rename panel->view->setNameToMakeCurrent(newname); // as always - the vfs do the job files()->rename(oldname, newname); } void ListPanelFunc::mkdir() { // ask the new dir name.. // suggested name is the complete name for the directories // while filenames are suggested without their extension QString suggestedName = panel->getCurrentName(); if (!suggestedName.isEmpty() && !files()->getVfile(suggestedName)->vfile_isDir()) suggestedName = QFileInfo(suggestedName).completeBaseName(); QString dirName = QInputDialog::getText(krMainWindow, i18n("New folder"), i18n("Folder's name:"), QLineEdit::Normal, suggestedName); // if the user canceled - quit if (dirName.isEmpty()) return ; QStringList dirTree = dirName.split('/'); for (QStringList::Iterator it = dirTree.begin(); it != dirTree.end(); ++it) { if (*it == ".") continue; if (*it == "..") { immediateOpenUrl(QUrl::fromUserInput(*it, QString(), QUrl::AssumeLocalFile)); continue; } // check if the name is already taken if (files()->getVfile(*it)) { // if it is the last dir to be created - quit if (*it == dirTree.last()) { KMessageBox::sorry(krMainWindow, i18n("A folder or a file with this name already exists.")); return ; } // else go into this dir else { immediateOpenUrl(QUrl::fromUserInput(*it, QString(), QUrl::AssumeLocalFile)); continue; } } panel->view->setNameToMakeCurrent(*it); // as always - the vfs does the job files()->mkDir(*it); if (dirTree.count() > 1) immediateOpenUrl(QUrl::fromUserInput(*it, QString(), QUrl::AssumeLocalFile)); } // for } // TODO it is not possible to move virtual local files to trash void ListPanelFunc::deleteFiles(bool reallyDelete) { // first get the selected file names list QStringList fileNames; panel->getSelectedNames(&fileNames); if (fileNames.isEmpty()) return ; KConfigGroup gg(krConfig, "General"); bool trash = gg.readEntry("Move To Trash", _MoveToTrash); // now ask the user if he want to delete: KConfigGroup group(krConfig, "Advanced"); if (group.readEntry("Confirm Delete", _ConfirmDelete)) { QString s; KGuiItem b; if (!reallyDelete && trash && files()->isLocal()) { s = i18np("Do you really want to move this item to the trash?", "Do you really want to move these %1 items to the trash?", fileNames.count()); b = KGuiItem(i18n("&Trash")); } else if (files()->type() == vfs::VFS_VIRT && files()->isRoot()) { s = i18np( "Do you really want to delete this virtual item (physical files stay untouched)?", "Do you really want to delete these %1 virtual items (physical files stay " "untouched)?", fileNames.count()); b = KStandardGuiItem::del(); } else if (files()->type() == vfs::VFS_VIRT) { s = i18np("Do you really want to delete this item physically (not just " "removing it from the virtual items)?", "Do you really want to delete these %1 items physically (not just " "removing them from the virtual items)?", fileNames.count()); b = KStandardGuiItem::del(); } else { s = i18np("Do you really want to delete this item?", "Do you really want to delete these %1 items?", fileNames.count()); b = KStandardGuiItem::del(); } // show message // note: i'm using continue and not yes/no because the yes/no has cancel as default button if (KMessageBox::warningContinueCancelList(krMainWindow, s, fileNames, i18n("Warning"), b) != KMessageBox::Continue) return ; } // we want to warn the user about non empty dir // and files he don't have permission to delete bool emptyDirVerify = group.readEntry("Confirm Unempty Dir", _ConfirmUnemptyDir); emptyDirVerify = (emptyDirVerify && files()->isLocal()); QDir dir; for (QStringList::Iterator name = fileNames.begin(); name != fileNames.end();) { vfile * vf = files()->getVfile(*name); // verify non-empty dirs delete... (only for local vfs) if (vf && emptyDirVerify && vf->vfile_isDir() && !vf->vfile_isSymLink()) { dir.setPath(panel->virtualPath().path() + '/' + (*name)); if (dir.entryList(QDir::TypeMask | QDir::System | QDir::Hidden).count() > 2) { switch (KMessageBox::warningYesNoCancel(krMainWindow, i18n("

Folder %1 is not empty.

Skip this one or delete all?

", *name), QString(), KGuiItem(i18n("&Skip")), KGuiItem(i18n("&Delete All")))) { case KMessageBox::No : emptyDirVerify = false; break; case KMessageBox::Yes : name = fileNames.erase(name); continue; default : return ; } } } ++name; } if (fileNames.count() == 0) return ; // nothing to delete // after the delete return the cursor to the first unmarked // file above the current item; panel->prepareToDelete(); // let the vfs do the job... files()->deleteFiles(fileNames, reallyDelete); } void ListPanelFunc::goInside(const QString& name) { openFileNameInternal(name, false); } void ListPanelFunc::runCommand(QString cmd) { krOut << "Run command: " << cmd; QString workdir = panel->virtualPath().isLocalFile() ? panel->virtualPath().path() : QDir::homePath(); if(!KRun::runCommand(cmd, krMainWindow, workdir)) KMessageBox::error(0, i18n("Could not start %1", cmd)); } void ListPanelFunc::runService(const KService &service, QList urls) { krOut << "Run service: " << service.name(); KIO::DesktopExecParser parser(service, urls); QStringList args = parser.resultingArguments(); if (!args.isEmpty()) runCommand(KShell::joinArgs(args)); else KMessageBox::error(0, i18n("%1 cannot open %2", service.name(), KrServices::toStringList(urls).join(", "))); } void ListPanelFunc::displayOpenWithDialog(QList urls) { KRun::displayOpenWithDialog(urls, krMainWindow); } QUrl ListPanelFunc::browsableArchivePath(const QString &filename) { vfile *vf = files()->getVfile(filename); QUrl url = files()->getUrl(filename); QString mime = vf->vfile_getMime(); if(url.isLocalFile()) { QString protocol = KrServices::registeredProtocol(mime); if(!protocol.isEmpty()) { url.setScheme(protocol); return url; } } return QUrl(); } // this is done when you double click on a file void ListPanelFunc::execute(const QString& name) { openFileNameInternal(name, true); } void ListPanelFunc::pack() { QStringList fileNames; panel->getSelectedNames(&fileNames); if (fileNames.isEmpty()) return ; // safety if (fileNames.count() == 0) return ; // nothing to pack // choose the default name QString defaultName = panel->virtualPath().fileName(); if (defaultName.isEmpty()) defaultName = "pack"; if (fileNames.count() == 1) defaultName = fileNames.first(); // ask the user for archive name and packer new PackGUI(defaultName, panel->otherPanel()->virtualPath().toDisplayString(QUrl::PreferLocalFile | QUrl::StripTrailingSlash), fileNames.count(), fileNames.first()); if (PackGUI::type.isEmpty()) { return ; // the user canceled } // check for partial URLs if (!PackGUI::destination.contains(":/") && !PackGUI::destination.startsWith('/')) { PackGUI::destination = panel->virtualPath().toDisplayString() + '/' + PackGUI::destination; } QString destDir = PackGUI::destination; if (!destDir.endsWith('/')) destDir += '/'; bool packToOtherPanel = (destDir == vfs::ensureTrailingSlash(panel->otherPanel()->virtualPath()).toDisplayString(QUrl::PreferLocalFile)); QUrl destURL = QUrl::fromUserInput(destDir + PackGUI::filename + '.' + PackGUI::type, QString(), QUrl::AssumeLocalFile); if (destURL.isLocalFile() && QFile::exists(destURL.path())) { QString msg = i18n("

The archive %1.%2 already exists. Do you want to overwrite it?

All data in the previous archive will be lost.

", PackGUI::filename, PackGUI::type); if (PackGUI::type == "zip") { msg = i18n("

The archive %1.%2 already exists. Do you want to overwrite it?

Zip will replace identically named entries in the zip archive or add entries for new names.

", PackGUI::filename, PackGUI::type); } if (KMessageBox::warningContinueCancel(krMainWindow, msg, QString(), KStandardGuiItem::overwrite()) == KMessageBox::Cancel) return ; // stop operation } else if (destURL.scheme() == QStringLiteral("virt")) { KMessageBox::error(krMainWindow, i18n("Cannot pack files onto a virtual destination.")); return; } PackJob * job = PackJob::createPacker(files()->currentDirectory(), destURL, fileNames, PackGUI::type, PackGUI::extraProps); job->setUiDelegate(new KIO::JobUiDelegate()); KIO::getJobTracker()->registerJob(job); job->ui()->setAutoErrorHandlingEnabled(true); if (packToOtherPanel) connect(job, SIGNAL(result(KJob*)), panel->otherPanel()->func, SLOT(refresh())); } void ListPanelFunc::testArchive() { QStringList fileNames; panel->getSelectedNames(&fileNames); if (fileNames.isEmpty()) return ; // safety TestArchiveJob * job = TestArchiveJob::testArchives(files()->currentDirectory(), fileNames); job->setUiDelegate(new KIO::JobUiDelegate()); KIO::getJobTracker()->registerJob(job); job->ui()->setAutoErrorHandlingEnabled(true); } void ListPanelFunc::unpack() { QStringList fileNames; panel->getSelectedNames(&fileNames); if (fileNames.isEmpty()) return ; // safety QString s; if (fileNames.count() == 1) s = i18n("Unpack %1 to:", fileNames[0]); else s = i18np("Unpack %1 file to:", "Unpack %1 files to:", fileNames.count()); // ask the user for the copy dest QUrl dest = KChooseDir::getDir(s, panel->otherPanel()->virtualPath(), panel->virtualPath()); if (dest.isEmpty()) return ; // the user canceled bool packToOtherPanel = (dest.matches(panel->otherPanel()->virtualPath(), QUrl::StripTrailingSlash)); UnpackJob * job = UnpackJob::createUnpacker(files()->currentDirectory(), dest, fileNames); job->setUiDelegate(new KIO::JobUiDelegate()); KIO::getJobTracker()->registerJob(job); job->ui()->setAutoErrorHandlingEnabled(true); if (packToOtherPanel) connect(job, SIGNAL(result(KJob*)), panel->otherPanel()->func, SLOT(refresh())); } // a small ugly function, used to prevent duplication of EVERY line of // code (maybe except 3) from createChecksum and matchChecksum static void checksum_wrapper(ListPanel *panel, QStringList& args, bool &folders) { KrViewItemList items; panel->view->getSelectedKrViewItems(&items); if (items.isEmpty()) return ; // nothing to do // determine if we need recursive mode (md5deep) folders = false; for (KrViewItemList::Iterator it = items.begin(); it != items.end(); ++it) { if (panel->func->getVFile(*it)->vfile_isDir()) { folders = true; args << (*it)->name(); } else args << (*it)->name(); } } void ListPanelFunc::createChecksum() { QStringList args; bool folders; checksum_wrapper(panel, args, folders); CreateChecksumDlg dlg(args, folders, panel->realPath()); } void ListPanelFunc::matchChecksum() { QStringList args; bool folders; checksum_wrapper(panel, args, folders); QList checksumFiles = files()->searchVfiles(KRQuery(MatchChecksumDlg::checksumTypesFilter)); MatchChecksumDlg dlg(args, folders, panel->realPath(), (checksumFiles.size() == 1 ? checksumFiles[0]->vfile_getUrl().toDisplayString(QUrl::PreferLocalFile) : QString())); } void ListPanelFunc::calcSpace(KrViewItem *item) { QStringList items; if (item) { items << item->name(); } else { panel->view->getSelectedItems(&items); if (items.isEmpty()) { panel->view->selectAllIncludingDirs(); panel->view->getSelectedItems(&items); if (items.isEmpty()) return ; // nothing to do } } QPointer calc = new KrCalcSpaceDialog(krMainWindow, panel, items, item != 0); calc->exec(); panel->slotUpdateTotals(); delete calc; } void ListPanelFunc::FTPDisconnect() { // you can disconnect only if connected! if (files()->isRemote()) { panel->_actions->actFTPDisconnect->setEnabled(false); panel->view->setNameToMakeCurrent(QString()); openUrl(QUrl::fromLocalFile(panel->realPath())); // open the last local URL } } void ListPanelFunc::newFTPconnection() { QUrl url = KRSpWidgets::newFTP(); // if the user canceled - quit if (url.isEmpty()) return ; panel->_actions->actFTPDisconnect->setEnabled(true); openUrl(url); } void ListPanelFunc::properties() { QStringList names; panel->getSelectedNames(&names); if (names.isEmpty()) return ; // no names... KFileItemList fi; for (int i = 0 ; i < names.count() ; ++i) { vfile* vf = files()->getVfile(names[i]); if (!vf) continue; QUrl url = files()->getUrl(names[i]); fi.push_back(KFileItem(vf->vfile_getEntry(), url)); } if (fi.isEmpty()) return ; // Show the properties dialog KPropertiesDialog *dlg = new KPropertiesDialog(fi, krMainWindow); connect(dlg, SIGNAL(applied()), SLOT(refresh())); dlg->show(); } void ListPanelFunc::refreshActions() { panel->updateButtons(); if(ACTIVE_PANEL != panel) return; QString protocol = files()->currentDirectory().scheme(); krRemoteEncoding->setEnabled(protocol == "ftp" || protocol == "sftp" || protocol == "fish" || protocol == "krarc"); //krMultiRename->setEnabled( vfsType == vfs::VFS_NORMAL ); // batch rename //krProperties ->setEnabled( vfsType == vfs::VFS_NORMAL || vfsType == vfs::VFS_FTP ); // file properties /* krUnpack->setEnabled(true); // unpack archive krTest->setEnabled(true); // test archive krSelect->setEnabled(true); // select a group by filter krSelectAll->setEnabled(true); // select all files krUnselect->setEnabled(true); // unselect by filter krUnselectAll->setEnabled( true); // remove all selections krInvert->setEnabled(true); // invert the selection krFTPConnect->setEnabled(true); // connect to an ftp krFTPNew->setEnabled(true); // create a new connection krAllFiles->setEnabled(true); // show all files in list krCustomFiles->setEnabled(true); // show a custom set of files krRoot->setEnabled(true); // go all the way up krExecFiles->setEnabled(true); // show only executables */ panel->_actions->setViewActions[panel->panelType]->setChecked(true); panel->_actions->actFTPDisconnect->setEnabled(files()->isRemote()); // allow disconnecting a network session panel->_actions->actCreateChecksum->setEnabled(files()->isLocal()); panel->_actions->actDirUp->setEnabled(!files()->isRoot()); panel->_actions->actRoot->setEnabled(!panel->virtualPath().matches(QUrl::fromLocalFile(ROOT_DIR), QUrl::StripTrailingSlash)); panel->_actions->actHome->setEnabled(!atHome()); panel->_actions->actHistoryBackward->setEnabled(history->canGoBack()); panel->_actions->actHistoryForward->setEnabled(history->canGoForward()); panel->view->op()->emitRefreshActions(); } vfs* ListPanelFunc::files() { if (!vfsP) vfsP = KrVfsHandler::instance().getVfs(QUrl::fromLocalFile("/")); return vfsP; } void ListPanelFunc::clipboardChanged(QClipboard::Mode mode) { if (mode == QClipboard::Clipboard && this == copyToClipboardOrigin) { disconnect(QApplication::clipboard(), 0, this, 0); copyToClipboardOrigin = 0; } } void ListPanelFunc::copyToClipboard(bool move) { QStringList fileNames; panel->getSelectedNames(&fileNames); if (fileNames.isEmpty()) return ; // safety QList fileUrls = files()->getUrls(fileNames); QMimeData *mimeData = new QMimeData; mimeData->setData("application/x-kde-cutselection", move ? "1" : "0"); mimeData->setUrls(fileUrls); if (copyToClipboardOrigin) disconnect(QApplication::clipboard(), 0, copyToClipboardOrigin, 0); copyToClipboardOrigin = this; QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); connect(QApplication::clipboard(), SIGNAL(changed(QClipboard::Mode)), this, SLOT(clipboardChanged(QClipboard::Mode))); } void ListPanelFunc::pasteFromClipboard() { QClipboard * cb = QApplication::clipboard(); ListPanelFunc *origin = 0; if (copyToClipboardOrigin) { disconnect(QApplication::clipboard(), 0, copyToClipboardOrigin, 0); origin = copyToClipboardOrigin; copyToClipboardOrigin = 0; } bool move = false; const QMimeData *data = cb->mimeData(); if (data->hasFormat("application/x-kde-cutselection")) { QByteArray a = data->data("application/x-kde-cutselection"); if (!a.isEmpty()) move = (a.at(0) == '1'); // true if 1 } QList urls = data->urls(); if (urls.isEmpty()) return ; if(origin && KConfigGroup(krConfig, "Look&Feel").readEntry("UnselectBeforeOperation", _UnselectBeforeOperation)) { origin->panel->view->saveSelection(); for(KrViewItem *item = origin->panel->view->getFirst(); item != 0; item = origin->panel->view->getNext(item)) { if (urls.contains(item->getVfile()->vfile_getUrl())) item->setSelected(false); } } files()->addFiles(urls, move ? KIO::CopyJob::Move : KIO::CopyJob::Copy); } ListPanelFunc* ListPanelFunc::otherFunc() { return panel->otherPanel()->func; } void ListPanelFunc::historyGotoPos(int pos) { if(history->gotoPos(pos)) refresh(); } void ListPanelFunc::historyBackward() { if(history->goBack()) refresh(); } void ListPanelFunc::historyForward() { if(history->goForward()) refresh(); } void ListPanelFunc::dirUp() { openUrl(KIO::upUrl(files()->currentDirectory()), files()->currentDirectory().fileName()); } void ListPanelFunc::home() { openUrl(QUrl::fromLocalFile(QDir::homePath())); } void ListPanelFunc::root() { openUrl(QUrl::fromLocalFile(ROOT_DIR)); } void ListPanelFunc::cdToOtherPanel() { openUrl(panel->otherPanel()->virtualPath()); } void ListPanelFunc::syncOtherPanel() { otherFunc()->openUrl(panel->virtualPath()); } bool ListPanelFunc::atHome() { return QUrl::fromLocalFile(QDir::homePath()).matches(panel->virtualPath(), QUrl::StripTrailingSlash); } diff --git a/krusader/VFS/default_vfs.cpp b/krusader/VFS/default_vfs.cpp index d553b056..920c03d2 100644 --- a/krusader/VFS/default_vfs.cpp +++ b/krusader/VFS/default_vfs.cpp @@ -1,382 +1,403 @@ /*************************************************************************** default_vfs.cpp ------------------- copyright : (C) 2000 by Rafi Yanai e-mail : krusader@users.sourceforge.net web site : http://krusader.sourceforge.net --------------------------------------------------------------------------- *************************************************************************** A db dD d8888b. db db .d8888. .d8b. d8888b. d88888b d8888b. 88 ,8P' 88 `8D 88 88 88' YP d8' `8b 88 `8D 88' 88 `8D 88,8P 88oobY' 88 88 `8bo. 88ooo88 88 88 88ooooo 88oobY' 88`8b 88`8b 88 88 `Y8b. 88~~~88 88 88 88~~~~~ 88`8b 88 `88. 88 `88. 88b d88 db 8D 88 88 88 .8D 88. 88 `88. YP YD 88 YD ~Y8888P' `8888Y' YP YP Y8888D' Y88888P 88 YD S o u r c e F i l e *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "default_vfs.h" // QtCore #include #include #include #include #include #include #include #include #include +#include #include #include #include "../defaults.h" #include "../krglobal.h" #include "../krservices.h" #include "../JobMan/jobman.h" #include "../JobMan/krjob.h" default_vfs::default_vfs(): vfs(), _watcher() { _type = VFS_DEFAULT; } void default_vfs::copyFiles(const QList &urls, const QUrl &destination, KIO::CopyJob::CopyMode mode, bool showProgressInfo, bool reverseQueueMode, bool startPaused) { // resolve relative path before resolving symlinks const QUrl dest = resolveRelativePath(destination); KIO::JobFlags flags = showProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo; // allow job to be started only manually when startPaused=true AND queueMode=false bool queueMode = krJobMan->isQueueModeEnabled() != reverseQueueMode; KrJob *krJob = KrJob::createCopyJob(mode, urls, destination, flags, startPaused && !queueMode); connect(krJob, &KrJob::started, [=](KIO::Job *job) { connectJob(job, dest); }); if (mode == KIO::CopyJob::Move) { // notify source about removed files connect(krJob, &KrJob::started, [=](KIO::Job *job) { connectSourceVFS(job, urls); }); } krJobMan->manageJob(krJob, reverseQueueMode, startPaused); } void default_vfs::dropFiles(const QUrl &destination, QDropEvent *event) { // resolve relative path before resolving symlinks const QUrl dest = resolveRelativePath(destination); KIO::DropJob *job = KIO::drop(event, dest); // NOTE: DropJob does not provide information about the actual user choice // (move/copy/link/abort). We have to assume the worst (move) connectJob(job, dest); connectSourceVFS(job, KUrlMimeData::urlsFromMimeData(event->mimeData())); // NOTE: DrobJobs are internally recorded //recordJobUndo(job, type, dst, src); } void default_vfs::connectSourceVFS(KJob *job, const QList urls) { if (!urls.isEmpty()) { // NOTE: we assume that all files were in the same directory and only emit one signal for // the directory of the first file URL // their current directory was deleted const QUrl url = urls.first().adjusted(QUrl::RemoveFilename); connect(job, &KIO::Job::result, [=]() { emit filesystemChanged(url); }); } } void default_vfs::addFiles(const QList &fileUrls, KIO::CopyJob::CopyMode mode, QString dir) { QUrl destination(_currentDirectory); if (!dir.isEmpty()) { destination.setPath(QDir::cleanPath(destination.path() + '/' + dir)); const QString scheme = destination.scheme(); if (scheme == "tar" || scheme == "zip" || scheme == "krarc") { if (QDir(cleanUrl(destination).path()).exists()) // if we get out from the archive change the protocol destination.setScheme("file"); } } copyFiles(fileUrls, destination, mode); } void default_vfs::deleteFiles(const QStringList &fileNames, bool forceDeletion) { // get absolute URLs for file names const QList fileUrls = getUrls(fileNames); // delete or move to trash? const KConfigGroup group(krConfig, "General"); const bool moveToTrash = !forceDeletion && isLocal() && group.readEntry("Move To Trash", _MoveToTrash); KrJob *krJob = KrJob::createDeleteJob(fileUrls, moveToTrash); connect(krJob, &KrJob::started, [=](KIO::Job *job) { connectJob(job, currentDirectory()); }); krJobMan->manageJob(krJob); } void default_vfs::mkDir(const QString &name) { KIO::SimpleJob* job = KIO::mkdir(getUrl(name)); connectJob(job, currentDirectory()); } void default_vfs::rename(const QString &oldName, const QString &newName) { const QUrl oldUrl = getUrl(oldName); const QUrl newUrl = getUrl(newName); KIO::Job *job = KIO::moveAs(oldUrl, newUrl, KIO::HideProgressInfo); connectJob(job, currentDirectory()); KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, {oldUrl}, newUrl, job); } void default_vfs::connectJob(KJob *job, const QUrl &destination) { // (additional) direct refresh if on local fs because watcher is too slow const bool refresh = cleanUrl(destination) == _currentDirectory && isLocal(); connect(job, &KIO::Job::result, this, [=](KJob* job) { slotJobResult(job, refresh); }); connect(job, &KIO::Job::result, [=]() { emit filesystemChanged(destination); }); } QUrl default_vfs::getUrl(const QString& name) { // NOTE: on non-local fs file URL does not have to be path + name! vfile *vf = getVfile(name); if (vf) return vf->vfile_getUrl(); QUrl absoluteUrl(_currentDirectory); absoluteUrl.setPath(absoluteUrl.path() + "/" + name); return absoluteUrl; } +void default_vfs::updateFilesystemInfo() +{ + if (!KConfigGroup(krConfig, "Look&Feel").readEntry("ShowSpaceInformation", true)) { + _mountPoint = ""; + emit filesystemInfoChanged(i18n("Space information disabled"), 0, 0); + return; + } + + if (!_currentDirectory.isLocalFile()) { + _mountPoint = ""; + emit filesystemInfoChanged(i18n("No space information on non-local filesystems"), 0, 0); + return; + } + + const KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo(_currentDirectory.path()); + if (!info.isValid()) { + _mountPoint = ""; + emit filesystemInfoChanged(i18n("Space information unavailable"), 0, 0); + return; + } + + _mountPoint = info.mountPoint(); + emit filesystemInfoChanged("", info.size(), info.available()); +} + // ==== protected ==== bool default_vfs::refreshInternal(const QUrl &directory, bool showHidden) { if (!KProtocolManager::supportsListing(directory)) { emit error(i18n("Protocol not supported by Krusader:\n%1", directory.url())); return false; } delete _watcher; // stop watching the old dir if (directory.isLocalFile()) { // we could read local directories with KIO but using Qt is a lot faster! return refreshLocal(directory); } _currentDirectory = cleanUrl(directory); // start the listing job KIO::ListJob *job = KIO::listDir(_currentDirectory, KIO::HideProgressInfo, showHidden); connect(job, &KIO::ListJob::entries, this, &default_vfs::slotAddFiles); connect(job, &KIO::ListJob::redirection, this, &default_vfs::slotRedirection); connect(job, &KIO::ListJob::permanentRedirection, this, &default_vfs::slotRedirection); connect(job, &KIO::Job::result, this, &default_vfs::slotListResult); // ensure connection credentials are asked only once if(!parentWindow.isNull()) { KIO::JobUiDelegate *ui = static_cast(job->uiDelegate()); ui->setWindow(parentWindow); } emit refreshJobStarted(job); _listError = false; // ugly: we have to wait here until the list job is finished QEventLoop eventLoop; connect(job, &KJob::finished, &eventLoop, &QEventLoop::quit); eventLoop.exec(); // blocking until quit() return !_listError; } -bool default_vfs::ignoreRefresh() -{ - return !_watcher.isNull(); -} - // ==== protected slots ==== void default_vfs::slotListResult(KJob *job) { if (job && job->error()) { // we failed to refresh _listError = true; emit error(job->errorString()); // display error message (in panel) } } void default_vfs::slotAddFiles(KIO::Job *, const KIO::UDSEntryList& entries) { for (const KIO::UDSEntry entry : entries) { vfile *vfile = vfs::createVFileFromKIO(entry, _currentDirectory); if (vfile) { addVfile(vfile); } } } void default_vfs::slotRedirection(KIO::Job *job, const QUrl &url) { krOut << "default_vfs; redirection to " << url; // some protocols (zip, tar) send redirect to local URL without scheme const QUrl newUrl = preferLocalUrl(url); if (newUrl.scheme() != _currentDirectory.scheme()) { // abort and start over again, // some protocols (iso, zip, tar) do this on transition to local fs job->kill(); _isRefreshing = false; refresh(newUrl); return; } _currentDirectory = cleanUrl(newUrl); } void default_vfs::slotWatcherDirty(const QString& path) { if (path == realPath()) { // this happens // 1. if a directory was created/deleted/renamed inside this directory. No deleted // 2. during and after a file operation (create/delete/rename/touch) inside this directory // KDirWatcher doesn't reveal the name of changed directories and we have to refresh. // (QFileSystemWatcher in Qt5.7 can't help here either) refresh(); return; } const QString name = QUrl::fromLocalFile(path).fileName(); vfile *vf = getVfile(name); if (!vf) { krOut << "dirty watcher file not found (unexpected): " << path; return; } // we have an updated file.. vfile *newVf = createLocalVFile(name); *vf = *newVf; delete newVf; emit updatedVfile(vf); } void default_vfs::slotWatcherDeleted(const QString& path) { if (path != realPath()) { // ignore deletion of files here, a 'dirty' signal will be send anyway return; } // the current directory was deleted, try a refresh, which will fail. An error message will // be emitted and the empty (non-existing) directory remains. refresh(); } bool default_vfs::refreshLocal(const QUrl &directory) { const QString path = KrServices::urlToLocalPath(directory); #ifdef Q_WS_WIN if (!path.contains("/")) { // change C: to C:/ path = path + QString("/"); } #endif // check if the new directory exists if (!QDir(path).exists()) { emit error(i18n("The folder %1 does not exist.", path)); return false; } // mount if needed emit aboutToOpenDir(path); // set the current directory... _currentDirectory = directory; _currentDirectory.setPath(QDir::cleanPath(_currentDirectory.path())); // Note: we are using low-level Qt functions here. // It's around twice as fast as using the QDir class. QT_DIR* dir = QT_OPENDIR(path.toLocal8Bit()); if (!dir) { emit error(i18n("Cannot open the folder %1.", path)); return false; } // change directory to the new directory const QString savedDir = QDir::currentPath(); if (!QDir::setCurrent(path)) { emit error(i18nc("%1=folder path", "Access to %1 denied", path)); QT_CLOSEDIR(dir); return false; } QT_DIRENT* dirEnt; QString name; const bool showHidden = showHiddenFiles(); while ((dirEnt = QT_READDIR(dir)) != NULL) { name = QString::fromLocal8Bit(dirEnt->d_name); // show hidden files? if (!showHidden && name.left(1) == ".") continue ; // we don't need the "." and ".." entries if (name == "." || name == "..") continue; vfile* temp = createLocalVFile(name); addVfile(temp); } // clean up QT_CLOSEDIR(dir); QDir::setCurrent(savedDir); // start watching the new dir for file changes _watcher = new KDirWatch(this); // if the current dir is a link path the watcher needs to watch the real path - and signal // parameters will be the real path _watcher->addDir(realPath(), KDirWatch::WatchFiles); connect(_watcher.data(), &KDirWatch::dirty, this, &default_vfs::slotWatcherDirty); // NOTE: not connecting 'created' signal. A 'dirty' is send after that anyway //connect(_watcher, SIGNAL(created(const QString&)), this, SLOT(slotWatcherCreated(const QString&))); connect(_watcher.data(), &KDirWatch::deleted, this, &default_vfs::slotWatcherDeleted); _watcher->startScan(false); return true; } vfile *default_vfs::createLocalVFile(const QString &name) { return vfs::createLocalVFile(name, _currentDirectory.path()); } QString default_vfs::default_vfs::realPath() { return QDir(_currentDirectory.toLocalFile()).canonicalPath(); } QUrl default_vfs::resolveRelativePath(const QUrl &url) { // if e.g. "/tmp/bin" is a link to "/bin", // resolve "/tmp/bin/.." to "/tmp" and not "/" return url.adjusted(QUrl::NormalizePathSegments); } diff --git a/krusader/VFS/default_vfs.h b/krusader/VFS/default_vfs.h index 764d22fd..b7b7072c 100644 --- a/krusader/VFS/default_vfs.h +++ b/krusader/VFS/default_vfs.h @@ -1,104 +1,108 @@ /*************************************************************************** default_vfs.h ------------------- begin : Thu May 4 2000 copyright : (C) 2000 by Shie Erlich & Rafi Yanai e-mail : krusader@users.sourceforge.net web site : http://krusader.sourceforge.net *************************************************************************** A db dD d8888b. db db .d8888. .d8b. d8888b. d88888b d8888b. 88 ,8P' 88 `8D 88 88 88' YP d8' `8b 88 `8D 88' 88 `8D 88,8P 88oobY' 88 88 `8bo. 88ooo88 88 88 88ooooo 88oobY' 88`8b 88`8b 88 88 `Y8b. 88~~~88 88 88 88~~~~~ 88`8b 88 `88. 88 `88. 88b d88 db 8D 88 88 88 .8D 88. 88 `88. YP YD 88 YD ~Y8888P' `8888Y' YP YP Y8888D' Y88888P 88 YD H e a d e r F i l e *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef DEFAULT_VFS_H #define DEFAULT_VFS_H #include "vfs.h" #include #include /** * @brief Default filesystem implementation supporting all KIO protocols * * This vfs implementation allows file operations and listing for all supported KIO protocols (local * and remote/network). * * Refreshing local directories is optimized for performance. * * NOTE: For detecting local file changes a filesystem watcher is used. It cannot be used for * refreshing the view after own file operations are performed because the detection is to slow * (~500ms delay between operation finished and watcher emits signals). * */ class default_vfs : public vfs { Q_OBJECT public: default_vfs(); ~default_vfs() {} virtual void copyFiles(const QList &urls, const QUrl &destination, KIO::CopyJob::CopyMode mode = KIO::CopyJob::Copy, bool showProgressInfo = true, bool reverseQueueMode = false, bool startPaused = false) Q_DECL_OVERRIDE; virtual void dropFiles(const QUrl &destination, QDropEvent *event) Q_DECL_OVERRIDE; virtual void addFiles(const QList &fileUrls, KIO::CopyJob::CopyMode mode, QString dir = "") Q_DECL_OVERRIDE; virtual void deleteFiles(const QStringList &fileNames, bool forceDeletion = false) Q_DECL_OVERRIDE; virtual void mkDir(const QString &name) Q_DECL_OVERRIDE; virtual void rename(const QString &fileName, const QString &newName) Q_DECL_OVERRIDE; /// Return URL for file name - even if file does not exist. virtual QUrl getUrl(const QString &name) Q_DECL_OVERRIDE; + QString mountPoint() { return _mountPoint; } + bool hasAutoUpdate() Q_DECL_OVERRIDE { return !_watcher.isNull(); } + void updateFilesystemInfo() Q_DECL_OVERRIDE; + protected: virtual bool refreshInternal(const QUrl &origin, bool showHidden) Q_DECL_OVERRIDE; - virtual bool ignoreRefresh() Q_DECL_OVERRIDE; protected slots: /// Handle result after dir listing job is finished void slotListResult(KJob *job); /// Fill directory file list with new files from the dir lister void slotAddFiles(KIO::Job *job, const KIO::UDSEntryList &entries); /// URL redirection signal from dir lister void slotRedirection(KIO::Job *job, const QUrl &url); // React to filesystem changes nofified by watcher // NOTE: the path parameter can be the directory itself or files in this directory void slotWatcherDirty(const QString &path); void slotWatcherDeleted(const QString &path); private: void connectSourceVFS(KJob *job, const QList urls); void connectJob(KJob *job, const QUrl &destination); bool refreshLocal(const QUrl &directory); // NOTE: this is very fast vfile *createLocalVFile(const QString &name); /// Returns the current path with symbolic links resolved QString realPath(); static QUrl resolveRelativePath(const QUrl &url); QPointer _watcher; // dir watcher used to detect changes in the current dir bool _listError; // for async operation, return list job result + QString _mountPoint; // the mount point of the current dir }; #endif diff --git a/krusader/VFS/krvfshandler.cpp b/krusader/VFS/krvfshandler.cpp index 8cd47526..eaa3e42f 100644 --- a/krusader/VFS/krvfshandler.cpp +++ b/krusader/VFS/krvfshandler.cpp @@ -1,178 +1,191 @@ /***************************************************************************** * Copyright (C) 2003 Shie Erlich * * Copyright (C) 2003 Rafi Yanai * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This package is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this package; if not, write to the Free Software * * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ #include "krvfshandler.h" #ifdef HAVE_POSIX_ACL #include #ifdef HAVE_NON_POSIX_ACL_EXTENSIONS #include #endif #endif // QtCore #include +#include + #include "default_vfs.h" #include "virt_vfs.h" #include "../krservices.h" KrVfsHandler::KrVfsHandler() : _defaultVFS(0), _virtVFS(0) {} vfs *KrVfsHandler::getVfs(const QUrl &url, vfs *oldVfs) { const vfs::VFS_TYPE type = getVfsType(url); return oldVfs && oldVfs->type() == type ? oldVfs : createVfs(type); } void KrVfsHandler::startCopyFiles(const QList &urls, const QUrl &destination, KIO::CopyJob::CopyMode mode, bool showProgressInfo, bool reverseQueueMode, bool startPaused) { const vfs::VFS_TYPE type = getVfsType(destination); vfs *vfs; switch (type) { case vfs::VFS_VIRT: if (!_virtVFS) _virtVFS = createVfs(type); vfs = _virtVFS; break; default: if (!_defaultVFS) _defaultVFS = createVfs(type); vfs = _defaultVFS; } vfs->copyFiles(urls, destination, mode, showProgressInfo, reverseQueueMode, startPaused); } void KrVfsHandler::refreshVfs(const QUrl &directory) { QMutableListIterator> it(_vfs_list); while (it.hasNext()) { if (it.next().isNull()) { it.remove(); } } - // refresh all vfs currently showing this directory + QString mountPoint = ""; + if (directory.isLocalFile()) { + KMountPoint::Ptr kMountPoint = KMountPoint::currentMountPoints().findByPath(directory.path()); + if (kMountPoint) + mountPoint = kMountPoint->mountPoint(); + } + for(QPointer vfsPointer: _vfs_list) { // always refresh virtual vfs showing a virtual directory; it can contain files from various // places, we don't know if they were (re)moved, refreshing is also fast enough vfs *vfs = vfsPointer.data(); const QUrl vfsDir = vfs->currentDirectory(); - if (vfsDir == vfs::cleanUrl(directory) || (vfsDir.scheme() == "virt" && !vfs->isRoot())) { - vfs->mayRefresh(); + if ((vfsDir == vfs::cleanUrl(directory) || (vfsDir.scheme() == "virt" && !vfs->isRoot())) + && !vfs->hasAutoUpdate()) { + // refresh all vfs currently showing this directory... + vfs->refresh(); + } else if (!mountPoint.isEmpty() && mountPoint == vfs->mountPoint()) { + // ..or refresh filesystem info if mount point is the same (for free space update) + vfs->updateFilesystemInfo(); } } } vfs *KrVfsHandler::createVfs(vfs::VFS_TYPE type) { vfs *newVfs; switch (type) { case (vfs::VFS_VIRT): newVfs = new virt_vfs(); break; default: newVfs = new default_vfs(); } QPointer vfsPointer(newVfs); _vfs_list.append(vfsPointer); connect(newVfs, &vfs::filesystemChanged, this, &KrVfsHandler::refreshVfs); return newVfs; } // ==== static ==== KrVfsHandler &KrVfsHandler::instance() { static KrVfsHandler instance; return instance; } vfs::VFS_TYPE KrVfsHandler::getVfsType(const QUrl &url) { return url.scheme() == QStringLiteral("virt") ? vfs::VFS_VIRT : vfs::VFS_DEFAULT; } void KrVfsHandler::getACL(vfile *file, QString &acl, QString &defAcl) { Q_UNUSED(file); acl.clear(); defAcl.clear(); #ifdef HAVE_POSIX_ACL QString fileName = vfs::cleanUrl(file->vfile_getUrl()).path(); #ifdef HAVE_NON_POSIX_ACL_EXTENSIONS if (acl_extended_file(fileName)) { #endif acl = getACL(fileName, ACL_TYPE_ACCESS); if (file->vfile_isDir()) defAcl = getACL(fileName, ACL_TYPE_DEFAULT); #ifdef HAVE_NON_POSIX_ACL_EXTENSIONS } #endif #endif } QString KrVfsHandler::getACL(const QString & path, int type) { Q_UNUSED(path); Q_UNUSED(type); #ifdef HAVE_POSIX_ACL acl_t acl = 0; // do we have an acl for the file, and/or a default acl for the dir, if it is one? if ((acl = acl_get_file(path.toLocal8Bit(), type)) != 0) { bool aclExtended = false; #ifdef HAVE_NON_POSIX_ACL_EXTENSIONS aclExtended = acl_equiv_mode(acl, 0); #else acl_entry_t entry; int ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); while (ret == 1) { acl_tag_t currentTag; acl_get_tag_type(entry, ¤tTag); if (currentTag != ACL_USER_OBJ && currentTag != ACL_GROUP_OBJ && currentTag != ACL_OTHER) { aclExtended = true; break; } ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry); } #endif if (!aclExtended) { acl_free(acl); acl = 0; } } if (acl == 0) return QString(); char *aclString = acl_to_text(acl, 0); QString ret = QString::fromLatin1(aclString); acl_free((void*)aclString); acl_free(acl); return ret; #else return QString(); #endif } diff --git a/krusader/VFS/vfs.cpp b/krusader/VFS/vfs.cpp index 35baaa6e..74f98896 100644 --- a/krusader/VFS/vfs.cpp +++ b/krusader/VFS/vfs.cpp @@ -1,443 +1,437 @@ /*************************************************************************** vfs.cpp ------------------- copyright : (C) 2000 by Shie Erlich & Rafi Yanai e-mail : krusader@users.sourceforge.net web site : http://krusader.sourceforge.net ------------------------------------------------------------------------ the vfs class is an extendable class which by itself does (almost) nothing. other VFSs like the normal_vfs inherits from this class and make it possible to use a consistent API for all types of VFSs. *************************************************************************** A db dD d8888b. db db .d8888. .d8b. d8888b. d88888b d8888b. 88 ,8P' 88 `8D 88 88 88' YP d8' `8b 88 `8D 88' 88 `8D 88,8P 88oobY' 88 88 `8bo. 88ooo88 88 88 88ooooo 88oobY' 88`8b 88`8b 88 88 `Y8b. 88~~~88 88 88 88~~~~~ 88`8b 88 `88. 88 `88. 88b d88 db 8D 88 88 88 .8D 88. 88 `88. YP YD 88 YD ~Y8888P' `8888Y' YP YP Y8888D' Y88888P 88 YD S o u r c e F i l e *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "vfs.h" // QtCore #include #include #include // QtWidgets #include #include #include #include #include #include #include "../defaults.h" #include "../krglobal.h" #include "krpermhandler.h" vfs::vfs() : VfileContainer(0), _isRefreshing(false) {} vfs::~vfs() { clear(_vfiles); emit cleared(); // please don't remove this line. This informs the view about deleting the references } QList vfs::getUrls(const QStringList &names) { QList urls; for (const QString name : names) { urls.append(getUrl(name)); } return urls; } QList vfs::searchVfiles(const KRQuery &filter) { QList result; for (vfile *vf : _vfiles.values()) { if (filter.match(vf)) { result.append(vf); } } return result; } KIO::filesize_t vfs::vfs_totalSize() { KIO::filesize_t temp = 0; for (vfile *vf : _vfiles.values()) { if (!vf->vfile_isDir() && vf->vfile_getName() != "." && vf->vfile_getName() != "..") { temp += vf->vfile_getSize(); } } return temp; } void vfs::calcSpace(const QString &name, KIO::filesize_t *totalSize, unsigned long *totalFiles, unsigned long *totalDirs, bool *stop) { const QUrl url = getUrl(name); if (url.isEmpty()) { krOut << "item for calculating space not found: " << name; return; } calcSpace(url, totalSize, totalFiles, totalDirs, stop); } QUrl vfs::ensureTrailingSlash(const QUrl &url) { if (url.path().endsWith('/')) { return url; } QUrl adjustedUrl(url); adjustedUrl.setPath(adjustedUrl.path() + '/'); return adjustedUrl; } QUrl vfs::preferLocalUrl(const QUrl &url){ if (url.isEmpty() || !url.scheme().isEmpty()) return url; QUrl adjustedUrl = url; adjustedUrl.setScheme("file"); return adjustedUrl; } bool vfs::refresh(const QUrl &directory) { if (_isRefreshing) { // NOTE: this does not happen (unless async)"; return false; } // workaround for krarc: find out if transition to local fs is wanted and adjust URL manually QUrl url = directory; if (_currentDirectory.scheme() == "krarc" && url.scheme() == "krarc" && QDir(url.path()).exists()) { url.setScheme("file"); } const bool dirChange = !url.isEmpty() && cleanUrl(url) != _currentDirectory; const QUrl toRefresh = dirChange ? url.adjusted(QUrl::NormalizePathSegments) : _currentDirectory; if (!toRefresh.isValid()) { emit error(i18n("Malformed URL:\n%1", toRefresh.toDisplayString())); return false; } _isRefreshing = true; vfileDict tempVfiles(_vfiles); // old vfiles are still used during refresh _vfiles.clear(); if (dirChange) // show an empty directory while loading the new one and clear selection emit cleared(); const bool res = refreshInternal(toRefresh, showHiddenFiles()); _isRefreshing = false; if (!res) { // cleanup and abort if (!dirChange) emit cleared(); clear(tempVfiles); return false; } emit refreshDone(dirChange); clear(tempVfiles); - return true; -} + updateFilesystemInfo(); -void vfs::mayRefresh() -{ - if (ignoreRefresh()) { - return; - } - refresh(); + return true; } // ==== protected ==== bool vfs::showHiddenFiles() { const KConfigGroup gl(krConfig, "Look&Feel"); return gl.readEntry("Show Hidden", _ShowHidden); } void vfs::calcSpace(const QUrl &url, KIO::filesize_t *totalSize, unsigned long *totalFiles, unsigned long *totalDirs, bool *stop) { if (url.isLocalFile()) { calcSpaceLocal(cleanUrl(url).path(), totalSize, totalFiles, totalDirs, stop); } else { calcSpaceKIO(url, totalSize, totalFiles, totalDirs, stop); } } void vfs::calcSpaceLocal(const QString &path, KIO::filesize_t *totalSize, unsigned long *totalFiles, unsigned long *totalDirs, bool *stop) { if (stop && *stop) return; if (path == "/proc") return; QT_STATBUF stat_p; // KDE lstat is necessary as QFileInfo and KFileItem // if the name is wrongly encoded, then we zero the size out stat_p.st_size = 0; stat_p.st_mode = 0; QT_LSTAT(path.toLocal8Bit(), &stat_p); // reports wrong size for a symbolic link if (S_ISLNK(stat_p.st_mode) || !S_ISDIR(stat_p.st_mode)) { // single files are easy : ) ++(*totalFiles); (*totalSize) += stat_p.st_size; } else { // handle directories avoid a nasty crash on un-readable dirs bool readable = ::access(path.toLocal8Bit(), R_OK | X_OK) == 0; if (!readable) return; QDir dir(path); if (!dir.exists()) return; ++(*totalDirs); dir.setFilter(QDir::TypeMask | QDir::System | QDir::Hidden); dir.setSorting(QDir::Name | QDir::DirsFirst); // recurse on all the files in the directory QFileInfoList fileList = dir.entryInfoList(); for (int k = 0; k != fileList.size(); k++) { if (*stop) return; QFileInfo qfiP = fileList[k]; if (qfiP.fileName() != "." && qfiP.fileName() != "..") calcSpaceLocal(path + '/' + qfiP.fileName(), totalSize, totalFiles, totalDirs, stop); } } } // TODO called from another thread, creating KIO jobs does not work here void vfs::calcSpaceKIO(const QUrl &url, KIO::filesize_t *totalSize, unsigned long *totalFiles, unsigned long *totalDirs, bool *stop) { return; if (stop && *stop) return; _calcKdsBusy = stop; _calcKdsTotalSize = totalSize; _calcKdsTotalFiles = totalFiles; _calcKdsTotalDirs = totalDirs; _calcStatBusy = true; KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); // thread problem here connect(statJob, &KIO::Job::result, this, &vfs::slotCalcStatResult); while (!(*stop) && _calcStatBusy) { usleep(1000); } if (_calcEntry.count() == 0) return; // statJob failed const KFileItem kfi(_calcEntry, url, true); if (kfi.isFile() || kfi.isLink()) { (*totalFiles)++; *totalSize += kfi.size(); return; } KIO::DirectorySizeJob *directorySizeJob = KIO::directorySize(url); connect(directorySizeJob, &KIO::Job::result, this, &vfs::slotCalcKdsResult); while (!(*stop)) { // we are in a separate thread - so sleeping is OK usleep(1000); } } vfile *vfs::createLocalVFile(const QString &name, const QString &directory, bool virt) { const QString path = QDir(directory).filePath(name); const QByteArray fileName = path.toLocal8Bit(); QT_STATBUF stat_p; stat_p.st_size = 0; stat_p.st_mode = 0; stat_p.st_mtime = 0; stat_p.st_uid = 0; stat_p.st_gid = 0; QT_LSTAT(fileName.data(), &stat_p); const KIO::filesize_t size = stat_p.st_size; QString perm = KRpermHandler::mode2QString(stat_p.st_mode); const bool symLink = S_ISLNK(stat_p.st_mode); if (S_ISDIR(stat_p.st_mode)) perm[0] = 'd'; const QString mime; QString symDest; bool brokenLink = false; if (S_ISLNK(stat_p.st_mode)) { // find where the link is pointing to // the path of the symlink target cannot be longer than the file size of the symlink char buffer[stat_p.st_size]; memset(buffer, 0, sizeof(buffer)); int bytesRead = readlink(fileName.data(), buffer, sizeof(buffer)); if (bytesRead != -1) { symDest = QString::fromLocal8Bit(buffer, bytesRead); if (QDir(symDest).exists()) perm[0] = 'd'; if (!QDir(directory).exists(symDest)) brokenLink = true; } else { krOut << "Failed to read link: " << path << endl; } } int rwx = 0; if (::access(fileName.data(), R_OK) == 0) rwx |= R_OK; if (::access(fileName.data(), W_OK) == 0) rwx |= W_OK; #ifndef Q_CC_MSVC if (::access(fileName.data(), X_OK) == 0) rwx |= X_OK; #endif // create a new virtual file object return new vfile(virt ? path : name, size, perm, stat_p.st_mtime, symLink, brokenLink, stat_p.st_uid, stat_p.st_gid, mime, symDest, stat_p.st_mode, rwx, QUrl::fromLocalFile(path)); } vfile *vfs::createVFileFromKIO(const KIO::UDSEntry &entry, const QUrl &directory, bool virt) { const KFileItem kfi(entry, directory, true, true); const QString name = kfi.text(); // ignore un-needed entries if (name.isEmpty() || name == "." || name == "..") { return 0; } // get file statistics... const KIO::filesize_t size = kfi.size(); const time_t mtime = kfi.time(KFileItem::ModificationTime).toTime_t(); const bool symLink = kfi.isLink(); const mode_t mode = kfi.mode() | kfi.permissions(); // NOTE: we could get the mimetype (and file icon) from the kfileitem here but this is very // slow. Instead, the vfile class has it's own (faster) way to determine the file type. const QString mime; QString perm = KRpermHandler::mode2QString(mode); QString symDest = ""; if (symLink) { symDest = kfi.linkDest(); if (kfi.isDir()) perm[0] = 'd'; } int rwx = -1; const QString prot = directory.scheme(); if (prot == "krarc" || prot == "tar" || prot == "zip") rwx = PERM_ALL; const QUrl url = !kfi.localPath().isEmpty() ? QUrl::fromLocalFile(kfi.localPath()) : kfi.url(); const QString fname = virt ? url.toDisplayString() : name; // create a new virtual file object vfile *vf; if (kfi.user().isEmpty()) { vf = new vfile(fname, size, perm, mtime, symLink, false, getuid(), getgid(), mime, symDest, mode, rwx, url); } else { QString currentUser = directory.userName(); if (currentUser.contains("@")) // remove the FTP proxy tags from the username currentUser.truncate(currentUser.indexOf('@')); if (currentUser.isEmpty()) { if (directory.host().isEmpty()) { currentUser = KRpermHandler::uid2user(getuid()); } else { currentUser = ""; // empty, but not QString() } } // NOTE: "broken link" flag is always false, checking link destination existence is // considered to be too expensive vf = new vfile(fname, size, perm, mtime, symLink, false, kfi.user(), kfi.group(), currentUser, mime, symDest, mode, rwx, kfi.ACL().asString(), kfi.defaultACL().asString(), url); } return vf; } // ==== protected slots ==== void vfs::slotJobResult(KJob *job, bool refresh) { if (job->error() && job->uiDelegate()) { // show errors for modifying operations as popup (works always) job->uiDelegate()->showErrorMessage(); } if (refresh) { vfs::refresh(); } } /// to be implemented void vfs::slotCalcKdsResult(KJob *job) { if (!job->error()) { KIO::DirectorySizeJob *kds = static_cast(job); *_calcKdsTotalSize += kds->totalSize(); *_calcKdsTotalFiles += kds->totalFiles(); *_calcKdsTotalDirs += kds->totalSubdirs(); } *_calcKdsBusy = true; } void vfs::slotCalcStatResult(KJob *job) { _calcEntry = job->error() ? KIO::UDSEntry() : static_cast(job)->statResult(); _calcStatBusy = false; } // ==== private ==== void vfs::clear(vfileDict &vfiles) { QHashIterator lit(vfiles); while (lit.hasNext()) { delete lit.next().value(); } vfiles.clear(); } diff --git a/krusader/VFS/vfs.h b/krusader/VFS/vfs.h index 4307089c..fce1f6f6 100644 --- a/krusader/VFS/vfs.h +++ b/krusader/VFS/vfs.h @@ -1,231 +1,236 @@ /*************************************************************************** vfs.h ------------------- begin : Thu May 4 2000 copyright : (C) 2000 by Shie Erlich & Rafi Yanai e-mail : krusader@users.sourceforge.net web site : http://krusader.sourceforge.net *************************************************************************** A db dD d8888b. db db .d8888. .d8b. d8888b. d88888b d8888b. 88 ,8P' 88 `8D 88 88 88' YP d8' `8b 88 `8D 88' 88 `8D 88,8P 88oobY' 88 88 `8bo. 88ooo88 88 88 88ooooo 88oobY' 88`8b 88`8b 88 88 `Y8b. 88~~~88 88 88 88~~~~~ 88`8b 88 `88. 88 `88. 88b d88 db 8D 88 88 88 .8D 88. 88 `88. YP YD 88 YD ~Y8888P' `8888Y' YP YP Y8888D' Y88888P 88 YD H e a d e r F i l e *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef VFS_H #define VFS_H #include "vfilecontainer.h" // QtCore #include #include #include #include #include // QtGui #include // QtWidgets #include #include #include "vfile.h" #include "krquery.h" /** * An abstract virtual filesystem. Use the implementations of this class for all file operations. * * It represents a directory and gives access to its files. All common file operations * are supported. Methods with absolute URL as argument can be used independently from the current * directory. Otherwise - if the methods argument is a file name - the operation is performed inside * the current directory. * * Notification signals are emitted if the directory content may have been changed. */ class vfs : public VfileContainer { Q_OBJECT public: typedef QHash vfileDict; enum VFS_TYPE { /// Virtual filesystem. Krusaders custom virt:/ protocol VFS_VIRT, /// Filesystem supporting all KIO protocols (file:/, ftp:/, smb:/, etc.) VFS_DEFAULT }; vfs(); virtual ~vfs(); // VfileContainer implementation virtual inline QList vfiles() { return _vfiles.values(); } virtual inline unsigned long numVfiles() { return _vfiles.count(); } virtual inline bool isRoot() { const QString path = _currentDirectory.path(); return path.isEmpty() || path == "/"; } /// Copy (copy, move or link) files in this VFS. /// Destination is absolute URL. May implemented async. virtual void copyFiles(const QList &urls, const QUrl &destination, KIO::CopyJob::CopyMode mode = KIO::CopyJob::Copy, bool showProgressInfo = true, bool reverseQueueMode = false, bool startPaused = false) = 0; /// Handle file dropping in this VFS. Destination is absolute URL. May implemented async. virtual void dropFiles(const QUrl &destination, QDropEvent *event) = 0; /// Copy (copy, move or link) files to the current VFS directory or to "dir", the /// directory name relative to the current dir. May implemented async. virtual void addFiles(const QList &fileUrls, KIO::CopyJob::CopyMode mode, QString dir = "") = 0; /// Delete or move a file in the current directory to trash. May implemented async. virtual void deleteFiles(const QStringList &fileNames, bool reallyDelete = false) = 0; /// Create a new directory in the current directory. May implemented async. virtual void mkDir(const QString &name) = 0; /// Rename file/directory in the current directory. May implemented async. virtual void rename(const QString &fileName, const QString &newName) = 0; /// Return an absolute URL for a single file/directory name in the current directory - with no /// trailing slash. virtual QUrl getUrl(const QString &name) = 0; /// Return a list of URLs for multiple files/directories in the current directory. virtual QList getUrls(const QStringList &names); + /// Return the filesystem mount point of the current directory. Empty string by default. + virtual QString mountPoint() { return QString(); } + /// Returns true if this VFS implementation does not need to be notified about changes in the + /// current directory. Else false. + virtual bool hasAutoUpdate() { return false; } + /// Notify this VFS that the filesystem info of the current directory may have changed. + virtual void updateFilesystemInfo() {} + /// Returns the current directory path of this VFS. inline QUrl currentDirectory() { return _currentDirectory; } /// Return the vfile for a file name in the current directory. Or 0 if not found. inline vfile *getVfile(const QString &name) { return (_vfiles)[name]; } /// Return a list of vfiles for a search query. Or an empty list if nothing was found. QList searchVfiles(const KRQuery &filter); /// The total size of all files in the current directory (only valid after refresh). // TODO unused KIO::filesize_t vfs_totalSize(); /// Return the VFS type. inline VFS_TYPE type() { return _type; } /// Return true if the current directory is local (without recognizing mount points). inline bool isLocal() { return _currentDirectory.isLocalFile(); } /// Return true if the current directory is a remote (network) location. inline bool isRemote() { const QString sc = _currentDirectory.scheme(); return (sc == "fish" || sc == "ftp" || sc == "sftp" || sc == "nfs" || sc == "smb" || sc == "webdav"); } /// Returns true if this VFS is currently refreshing the current directory. inline bool isRefreshing() { return _isRefreshing; } - /// Return a displayable string containing special filesystem meta information. Or an empty - /// string by default. - virtual QString metaInformation() { return QString(); } /// Calculate the amount of space occupied by a file or directory in the current directory /// (recursive). virtual void calcSpace(const QString &name, KIO::filesize_t *totalSize, unsigned long *totalFiles, unsigned long *totalDirs, bool *stop); /// Return the input URL with a trailing slash if absent. static QUrl ensureTrailingSlash(const QUrl &url); /// Return the input URL without trailing slash. static QUrl cleanUrl(const QUrl &url) { return url.adjusted(QUrl::StripTrailingSlash); } /// Add 'file' scheme to non-empty URL without scheme static QUrl preferLocalUrl(const QUrl &url); // set the parent window to be used for dialogs void setParentWindow(QWidget *widget) { parentWindow = widget; } public slots: /// Re-read the current directory files or change to another directory. Blocking. /// Returns true if directory was read. Returns false if failed or refresh job was killed. // optional TODO: add an async version of this bool refresh(const QUrl &directory = QUrl()); - /// Notify this VFS that the current directory content may have changed. - void mayRefresh(); signals: /// Emitted when this VFS is currently refreshing the VFS directory. void refreshJobStarted(KIO::Job *job); /// Emitted when an error occured in this VFS during refresh. void error(const QString &msg); /// Emitted when the content of a directory was changed by this VFS. void filesystemChanged(const QUrl &directory); + /// Emitted when the information for the filesystem of the current directory changed. + /// Information is either + /// * 'metaInfo': a displayable string about the fs, empty by default, OR + /// * 'total' and 'free': filesystem size and free space, both 0 by default + void filesystemInfoChanged(const QString &metaInfo, KIO::filesize_t total, KIO::filesize_t free); /// Emitted before a directory path is opened for reading. Used for automounting. void aboutToOpenDir(const QString &path); protected: /// Fill the vfs dictionary with vfiles, must be implemented for each VFS. virtual bool refreshInternal(const QUrl &origin, bool showHidden) = 0; - /// Returns true if this VFS implementation does not need to be notified about changes in the - /// current directory. - virtual bool ignoreRefresh() { return false; } /// Returns true if showing hidden files is set in config. bool showHiddenFiles(); /// Add a new vfile to the internal dictionary (while refreshing). inline void addVfile(vfile *vf) { _vfiles.insert(vf->vfile_getName(), vf); } /// Calculate the size of a file or directory (recursive). void calcSpace(const QUrl &url, KIO::filesize_t *totalSize, unsigned long *totalFiles, unsigned long *totalDirs, bool *stop); /// Calculate the size of a local file or directory (recursive). void calcSpaceLocal(const QString &path, KIO::filesize_t *totalSize, unsigned long *totalFiles, unsigned long *totalDirs, bool *stop); /// Calculate the size of any KIO file or directory. void calcSpaceKIO(const QUrl &url, KIO::filesize_t *totalSize, unsigned long *totalFiles, unsigned long *totalDirs, bool *stop); /// Return a vfile for a local file inside a directory static vfile *createLocalVFile(const QString &name, const QString &directory, bool virt = false); /// Return a vfile for a KIO result. Returns 0 if entry is not needed static vfile *createVFileFromKIO(const KIO::UDSEntry &_calcEntry, const QUrl &directory, bool virt = false); VFS_TYPE _type; // the vfs type. QUrl _currentDirectory; // the path or file the VFS originates from. bool _isRefreshing; // true if vfs is busy with refreshing QPointer parentWindow; protected slots: /// Handle result after job (except when refreshing!) finished void slotJobResult(KJob *job, bool refresh); private slots: /// Handle result of KIO::DirectorySizeJob when calculating URL size void slotCalcKdsResult(KJob *job); /// Handle result of KIO::StatJob when calculating URL size void slotCalcStatResult(KJob *job); private: /// Delete and clear vfiles. void clear(vfileDict &vfiles); vfileDict _vfiles; // The list of files in the current dictionary // used in the calcSpace function bool *_calcKdsBusy; bool _calcStatBusy; KIO::UDSEntry _calcEntry; KIO::filesize_t *_calcKdsTotalSize; unsigned long *_calcKdsTotalFiles; unsigned long *_calcKdsTotalDirs; }; #endif diff --git a/krusader/VFS/virt_vfs.cpp b/krusader/VFS/virt_vfs.cpp index 5dc886da..76218439 100644 --- a/krusader/VFS/virt_vfs.cpp +++ b/krusader/VFS/virt_vfs.cpp @@ -1,379 +1,379 @@ /***************************************************************************** * Copyright (C) 2003 Shie Erlich * * Copyright (C) 2003 Rafi Yanai * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This package is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this package; if not, write to the Free Software * * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ #include "virt_vfs.h" // QtCore #include #include #include // QtWidgets #include #include #include #include #include #include #include #include #include #include #include "../defaults.h" #include "../krglobal.h" #include "../krservices.h" #include "krpermhandler.h" #define VIRT_VFS_DB "virt_vfs.db" QHash *> virt_vfs::_virtVfsDict; QHash virt_vfs::_metaInfoDict; virt_vfs::virt_vfs() : vfs() { if (_virtVfsDict.isEmpty()) { restore(); } _type = VFS_VIRT; } virt_vfs::~virt_vfs() {} void virt_vfs::copyFiles(const QList &urls, const QUrl &destination, KIO::CopyJob::CopyMode /*mode*/, bool /*showProgressInfo*/, bool /*reverseQueueMode*/, bool /*startPaused*/) { const QString dir = QDir(destination.path()).absolutePath().remove('/'); if (dir.isEmpty()) { showError(i18n("You cannot copy files directly to the 'virt:/' folder.\n" "You can create a sub folder and copy your files into it.")); return; } if (!_virtVfsDict.contains(dir)) { mkDirInternal(dir); } QList *urlList = _virtVfsDict[dir]; for (const QUrl &fileUrl : urls) { if (!urlList->contains(fileUrl)) { urlList->push_back(fileUrl); } } emit filesystemChanged(QUrl("virt:///" + dir)); // may call refresh() } void virt_vfs::dropFiles(const QUrl &destination, QDropEvent *event) { const QList &urls = KUrlMimeData::urlsFromMimeData(event->mimeData()); // dropping on virtual vfs (sic!) is always copy operation copyFiles(urls, destination); } void virt_vfs::addFiles(const QList &fileUrls, KIO::CopyJob::CopyMode /*mode*/, QString dir) { QUrl destination(_currentDirectory); if (!dir.isEmpty()) { destination.setPath(QDir::cleanPath(destination.path() + '/' + dir)); } copyFiles(fileUrls, destination); } void virt_vfs::deleteFiles(const QStringList &fileNames, bool reallyDelete) { if (currentDir() == "/") { // remove virtual directory for (const QString &filename : fileNames) { _virtVfsDict["/"]->removeAll(QUrl(QStringLiteral("virt:/") + filename)); delete _virtVfsDict[filename]; _virtVfsDict.remove(filename); _metaInfoDict.remove(filename); } emit filesystemChanged(currentDirectory()); // will call refresh() return; } // names -> urls QList filesUrls = getUrls(fileNames); // delete or move to trash? KIO::Job *job; KConfigGroup group(krConfig, "General"); if (!reallyDelete && group.readEntry("Move To Trash", _MoveToTrash)) { job = KIO::trash(filesUrls); } else { job = KIO::del(filesUrls); } connect(job, &KIO::Job::result, this, [=](KJob* job) { slotJobResult(job, false); }); // refresh will remove the deleted files from the vfs dict... connect(job, &KIO::Job::result, [=]() { emit filesystemChanged(currentDirectory()); }); } void virt_vfs::vfs_removeFiles(QStringList *fileNames) { if (currentDir() == "/") return; // removing the URLs from the collection for (int i = 0; i < fileNames->count(); ++i) { if (_virtVfsDict.find(currentDir()) != _virtVfsDict.end()) { QList *urlList = _virtVfsDict[currentDir()]; urlList->removeAll(getUrl((*fileNames)[i])); } } emit filesystemChanged(currentDirectory()); // will call refresh() } QUrl virt_vfs::getUrl(const QString &name) { vfile *vf = getVfile(name); if (!vf) { return QUrl(); // not found } return vf->vfile_getUrl(); } void virt_vfs::mkDir(const QString &name) { if (currentDir() != "/") { showError(i18n("Creating new folders is allowed only in the 'virt:/' folder.")); return; } mkDirInternal(name); emit filesystemChanged(currentDirectory()); // will call refresh() } void virt_vfs::rename(const QString &fileName, const QString &newName) { vfile *vf = getVfile(fileName); if (!vf) return; // not found if (currentDir() == "/") { // rename virtual directory _virtVfsDict["/"]->append(QUrl(QStringLiteral("virt:/") + newName)); _virtVfsDict["/"]->removeAll(QUrl(QStringLiteral("virt:/") + fileName)); _virtVfsDict.insert(newName, _virtVfsDict.take(fileName)); refresh(); return; } // newName can be a (local) path or a full url QUrl dest(newName); if (dest.scheme().isEmpty()) dest.setScheme("file"); // add the new url to the list // the list is refreshed, only existing files remain - // so we don't have to worry if the job was successful _virtVfsDict[currentDir()]->append(dest); KIO::Job *job = KIO::moveAs(vf->vfile_getUrl(), dest, KIO::HideProgressInfo); connect(job, &KIO::Job::result, this, [=](KJob* job) { slotJobResult(job, false); }); connect(job, &KIO::Job::result, [=]() { emit filesystemChanged(currentDirectory()); }); } void virt_vfs::calcSpace(const QString &name, KIO::filesize_t *totalSize, unsigned long *totalFiles, unsigned long *totalDirs, bool *stop) { if (currentDir() == "/") { if (!_virtVfsDict.contains(name)) { return; // virtual folder not found } const QList *urlList = _virtVfsDict[name]; if (urlList) { for (int i = 0; (i != urlList->size()) && !(*stop); i++) { vfs::calcSpace((*urlList)[i], totalSize, totalFiles, totalDirs, stop); } } return; } vfs::calcSpace(name, totalSize, totalFiles, totalDirs, stop); } void virt_vfs::setMetaInformation(QString info) { - _metaInfo = info; - _metaInfoDict[currentDir()] = _metaInfo; - refresh(); + _metaInfoDict[currentDir()] = info; } // ==== protected ==== bool virt_vfs::refreshInternal(const QUrl &directory, bool /*showHidden*/) { _currentDirectory = cleanUrl(directory); _currentDirectory.setHost(""); // remove invalid subdirectories _currentDirectory.setPath("/" + _currentDirectory.path().remove('/')); if (!_virtVfsDict.contains(currentDir())) { // NOTE: silently creating non-existing directories here. The search and locate tools expect // this. (And user can enter some directory and it will be created). mkDirInternal(currentDir()); save(); // infinite loop possible //emit filesystemChanged(currentDirectory()); return true; } QList *urlList = _virtVfsDict[currentDir()]; - _metaInfo = _metaInfoDict[currentDir()]; + + const QString metaInfo = _metaInfoDict[currentDir()]; + emit filesystemInfoChanged(metaInfo.isEmpty() ? i18n("Virtual filesystem") : metaInfo, 0, 0); QMutableListIterator it(*urlList); while (it.hasNext()) { const QUrl url = it.next(); vfile *vf = createVFile(url); if (!vf) { // remove URL from the list for a file that no longer exists it.remove(); } else { addVfile(vf); } } save(); return true; } // ==== private ==== void virt_vfs::mkDirInternal(const QString &name) { // clean path, consistent with currentDir() QString dirName = name; dirName = dirName.remove('/'); if (dirName.isEmpty()) dirName = "/"; _virtVfsDict.insert(dirName, new QList()); _virtVfsDict["/"]->append(QUrl(QStringLiteral("virt:/") + dirName)); } void virt_vfs::save() { KConfig *db = &virt_vfs::getVirtDB(); db->deleteGroup("virt_db"); KConfigGroup group(db, "virt_db"); QHashIterator *> it(_virtVfsDict); while (it.hasNext()) { it.next(); QList *urlList = it.value(); QList::iterator url; QStringList entry; for (url = urlList->begin(); url != urlList->end(); ++url) { entry.append((*url).toDisplayString()); } // KDE 4.0 workaround: 'Item_' prefix is added as KConfig fails on 1 char names (such as /) group.writeEntry("Item_" + it.key(), entry); group.writeEntry("MetaInfo_" + it.key(), _metaInfoDict[it.key()]); } db->sync(); } void virt_vfs::restore() { KConfig *db = &virt_vfs::getVirtDB(); const KConfigGroup dbGrp(db, "virt_db"); const QMap map = db->entryMap("virt_db"); QMapIterator it(map); while (it.hasNext()) { it.next(); // KDE 4.0 workaround: check and remove 'Item_' prefix if (!it.key().startsWith(QLatin1String("Item_"))) continue; const QString key = it.key().mid(5); const QList urlList = KrServices::toUrlList(dbGrp.readEntry(it.key(), QStringList())); _virtVfsDict.insert(key, new QList(urlList)); _metaInfoDict.insert(key, dbGrp.readEntry("MetaInfo_" + key, QString())); } if (!_virtVfsDict["/"]) { // insert root element if missing for some reason _virtVfsDict.insert("/", new QList()); } } vfile *virt_vfs::createVFile(const QUrl &url) { if (url.scheme() == "virt") { // return a virtual directory in root QString path = url.path().mid(1); if (path.isEmpty()) path = '/'; return new vfile(path, 0, "drwxr-xr-x", time(0), false, false, getuid(), getgid(), "inode/directory", "", 0, -1, url); } const QUrl directory = url.adjusted(QUrl::RemoveFilename); if (url.isLocalFile()) { QFileInfo file(url.path()); return file.exists() ? vfs::createLocalVFile(url.fileName(), directory.path(), true) : 0; } KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); connect(statJob, &KIO::Job::result, this, &virt_vfs::slotStatResult); // ugly: we have to wait here until the stat job is finished QEventLoop eventLoop; connect(statJob, &KJob::finished, &eventLoop, &QEventLoop::quit); eventLoop.exec(); // blocking until quit() if (_fileEntry.count() == 0) { return 0; // stat job failed } if (!_fileEntry.contains(KIO::UDSEntry::UDS_MODIFICATION_TIME)) { // TODO this also happens for FTP directories return 0; // file not found } return vfs::createVFileFromKIO(_fileEntry, directory, true); } KConfig &virt_vfs::getVirtDB() { //virt_vfs_db = new KConfig("data",VIRT_VFS_DB,KConfig::NoGlobals); static KConfig db(VIRT_VFS_DB, KConfig::CascadeConfig, QStandardPaths::AppDataLocation); return db; } void virt_vfs::slotStatResult(KJob *job) { _fileEntry = job->error() ? KIO::UDSEntry() : static_cast(job)->statResult(); } void virt_vfs::showError(const QString &error) { QWidget *window = QApplication::activeWindow(); KMessageBox::sorry(window, error); // window can be null, is allowed } diff --git a/krusader/VFS/virt_vfs.h b/krusader/VFS/virt_vfs.h index 02e1f6d4..3017baac 100644 --- a/krusader/VFS/virt_vfs.h +++ b/krusader/VFS/virt_vfs.h @@ -1,109 +1,106 @@ /***************************************************************************** * Copyright (C) 2003 Shie Erlich * * Copyright (C) 2003 Rafi Yanai * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This package is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this package; if not, write to the Free Software * * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ #ifndef VIRT_VFS_H #define VIRT_VFS_H // QtCore #include #include "vfs.h" /** * Custom virtual filesystem implementation: It holds arbitrary lists of files which are only * virtual references to real files. The filename of a virtual file is the full path of the real * file. * * Only two filesystem levels are supported: On root level only directories can be created; these * virtual root directories can contain a set of virtual files and directories. Entering a directory * on the sublevel is out of scope and the real directory will be opened. * * The filesystem content is saved in a separate config file and preserved between application runs. * * Used at least by bookmarks, locate, search and synchronizer dialog. */ class virt_vfs : public vfs { Q_OBJECT public: virt_vfs(); ~virt_vfs(); /// Create virtual files in this VFS. Copy mode and showProgressInfo are ignored. void copyFiles(const QList &urls, const QUrl &destination, KIO::CopyJob::CopyMode mode = KIO::CopyJob::Copy, bool showProgressInfo = true, bool reverseQueueMode = false, bool startPaused = false) Q_DECL_OVERRIDE; /// Handle file dropping in this VFS: Always creates virtual files. void dropFiles(const QUrl &destination, QDropEvent *event) Q_DECL_OVERRIDE; /// Add virtual files to the current directory. void addFiles(const QList &fileUrls, KIO::CopyJob::CopyMode mode = KIO::CopyJob::Copy, QString dir = "") Q_DECL_OVERRIDE; /// Delete files from the current directory (real files, not virtual). void deleteFiles(const QStringList &fileNames, bool reallyDelete = true) Q_DECL_OVERRIDE; /// Remove files from the collection (only virtual, not the real file). void vfs_removeFiles(QStringList *fileNames); /// Create a virtual directory. Only possible in the root directory. void mkDir(const QString &name) Q_DECL_OVERRIDE; /// Rename a (real) file in the current directory. void rename(const QString &fileName, const QString &newName) Q_DECL_OVERRIDE; void calcSpace(const QString &name, KIO::filesize_t *totalSize, unsigned long *totalFiles, unsigned long *totalDirs, bool *stop) Q_DECL_OVERRIDE; /// Returns the URL of the real file or an empty URL if file with name does not exist. QUrl getUrl(const QString& name) Q_DECL_OVERRIDE; - virtual QString metaInformation() Q_DECL_OVERRIDE { - return _metaInfo; - } void setMetaInformation(QString info); protected: bool refreshInternal(const QUrl &origin, bool showHidden) Q_DECL_OVERRIDE; private: /// Return current dir: "/" or pure directory name inline QString currentDir() { const QString path = _currentDirectory.path().mid(1); // remove slash return path.isEmpty() ? "/" : path; } void mkDirInternal(const QString& name); /// Save the dictionary to file void save(); /// Restore the dictionary from file void restore(); /// Create local or KIO vfile. Returns 0 if file does not exist vfile *createVFile(const QUrl &url); /// Return the configuration file storing the urls of virtual files KConfig &getVirtDB(); private slots: void slotStatResult(KJob *job); private: void showError(const QString &error); static QHash *> _virtVfsDict; // map virtual directories to containing files static QHash _metaInfoDict; // map virtual directories to meta infos QString _metaInfo; // displayable string with information about the current virtual directory KIO::UDSEntry _fileEntry; // for async call, save stat job result here }; #endif