diff --git a/krusader/Locate/locate.cpp b/krusader/Locate/locate.cpp index 825a7c42..a35a86f8 100644 --- a/krusader/Locate/locate.cpp +++ b/krusader/Locate/locate.cpp @@ -1,692 +1,692 @@ /***************************************************************************** * Copyright (C) 2004 Csaba Karai * * Copyright (C) 2004-2018 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader 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. * * * * Krusader 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 Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "locate.h" #include "../kractions.h" #include "../krglobal.h" #include "../filelisticon.h" #include "../krslots.h" #include "../krusaderview.h" #include "../Panel/krpanel.h" #include "../Panel/panelfunc.h" #include "../GUI/krtreewidget.h" #include "../defaults.h" #include "../krservices.h" #include "../FileSystem/filesystem.h" #include "../FileSystem/virtualfilesystem.h" #include "../KViewer/krviewer.h" #include "../panelmanager.h" // QtCore #include #include #include #include // QtGui #include #include #include #include #include #include // QtWidgets #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // these are the values that will exist in the menu #define VIEW_ID 90 #define EDIT_ID 91 #define FIND_ID 92 #define FIND_NEXT_ID 93 #define FIND_PREV_ID 94 #define COPY_SELECTED_TO_CLIPBOARD 95 #define COMPARE_ID 96 ////////////////////////////////////////////////////////// class LocateListView : public KrTreeWidget { public: explicit LocateListView(QWidget * parent) : KrTreeWidget(parent) { setAlternatingRowColors(true); } void startDrag(Qt::DropActions supportedActs) { Q_UNUSED(supportedActs); QList urls; QList list = selectedItems(); QListIterator it(list); while (it.hasNext()) { QTreeWidgetItem * item = it.next(); urls.push_back(QUrl::fromLocalFile(item->text(0))); } if (urls.count() == 0) return; QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; mimeData->setImageData(FileListIcon("file").pixmap()); mimeData->setUrls(urls); drag->setMimeData(mimeData); drag->start(); } }; KProcess * LocateDlg::updateProcess = 0; LocateDlg * LocateDlg::LocateDialog = 0; -LocateDlg::LocateDlg() : QDialog(0), isFeedToListBox(false) +LocateDlg::LocateDlg(QWidget *parent) : QDialog(parent), isFeedToListBox(false) { setWindowTitle(i18n("Krusader::Locate")); setWindowModality(Qt::NonModal); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); QGridLayout *grid = new QGridLayout(); grid->setSpacing(6); grid->setContentsMargins(11, 11, 11, 11); QWidget *hboxWidget = new QWidget(this); QHBoxLayout *hbox = new QHBoxLayout(hboxWidget); hbox->setContentsMargins(0, 0, 0, 0); QLabel *label = new QLabel(i18n("Search for:"), hboxWidget); hbox->addWidget(label); locateSearchFor = new KHistoryComboBox(false, hboxWidget); locateSearchFor->setMinimumContentsLength(10); hbox->addWidget(locateSearchFor); label->setBuddy(locateSearchFor); KConfigGroup group(krConfig, "Locate"); QStringList list = group.readEntry("Search For", QStringList()); locateSearchFor->setMaxCount(25); // remember 25 items locateSearchFor->setHistoryItems(list); locateSearchFor->setEditable(true); locateSearchFor->setDuplicatesEnabled(false); locateSearchFor->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); locateSearchFor->lineEdit()->setFocus(); grid->addWidget(hboxWidget, 0, 0); QWidget *hboxWidget2 = new QWidget(this); QHBoxLayout * hbox2 = new QHBoxLayout(hboxWidget2); hbox2->setContentsMargins(0, 0, 0, 0); QSpacerItem* spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hbox2->addItem(spacer); dontSearchInPath = new QCheckBox(i18n("Do not search in path"), hboxWidget2); hbox2->addWidget(dontSearchInPath); dontSearchInPath->setChecked(group.readEntry("Don't Search In Path", false)); existingFiles = new QCheckBox(i18n("Show only the existing files"), hboxWidget2); existingFiles->setChecked(group.readEntry("Existing Files", false)); hbox2->addWidget(existingFiles); caseSensitive = new QCheckBox(i18n("Case Sensitive"), hboxWidget2); caseSensitive->setChecked(group.readEntry("Case Sensitive", false)); hbox2->addWidget(caseSensitive); grid->addWidget(hboxWidget2, 1, 0); QFrame *line1 = new QFrame(this); line1->setFrameStyle(QFrame::HLine | QFrame::Sunken); grid->addWidget(line1, 2, 0); resultList = new LocateListView(this); // create the main container resultList->setColumnCount(1); resultList->setHeaderLabel(i18n("Results")); resultList->setColumnWidth(0, QFontMetrics(resultList->font()).width("W") * 60); KConfigGroup gl(krConfig, "Look&Feel"); resultList->setFont(gl.readEntry("Filelist Font", _FilelistFont)); resultList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); resultList->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); resultList->header()->setSortIndicatorShown(false); resultList->setSortingEnabled(false); resultList->setSelectionMode(QAbstractItemView::ExtendedSelection); resultList->setDragEnabled(true); connect(resultList, SIGNAL(itemRightClicked(QTreeWidgetItem*,QPoint,int)), this, SLOT(slotRightClick(QTreeWidgetItem*,QPoint))); connect(resultList, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(slotDoubleClick(QTreeWidgetItem*))); connect(resultList, SIGNAL(itemActivated(QTreeWidgetItem*,int)), this, SLOT(slotDoubleClick(QTreeWidgetItem*))); grid->addWidget(resultList, 3, 0); QFrame *line2 = new QFrame(this); line2->setFrameStyle(QFrame::HLine | QFrame::Sunken); grid->addWidget(line2, 4, 0); mainLayout->addLayout(grid); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); mainLayout->addWidget(buttonBox); locateButton = new QPushButton(i18n("Locate")); locateButton->setIcon(Icon(QStringLiteral("system-search"))); locateButton->setDefault(true); buttonBox->addButton(locateButton, QDialogButtonBox::ActionRole); updateDbButton = new QPushButton(i18n("Update DB")); updateDbButton->setIcon(Icon(QStringLiteral("view-refresh"))); buttonBox->addButton(updateDbButton, QDialogButtonBox::ActionRole); feedStopButton = new QPushButton; buttonBox->addButton(feedStopButton, QDialogButtonBox::ActionRole); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); connect(locateButton, SIGNAL(clicked()), this, SLOT(slotLocate())); connect(updateDbButton, SIGNAL(clicked()), this, SLOT(slotUpdateDb())); connect(feedStopButton, SIGNAL(clicked()), this, SLOT(slotFeedStop())); updateButtons(false); if (updateProcess) { if (updateProcess->state() == QProcess::Running) { connect(updateProcess, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(updateFinished())); updateDbButton->setEnabled(false); } else updateFinished(); } show(); LocateDialog = this; } void LocateDlg::slotFeedStop() /* The stop / feed to listbox button */ { if (isFeedToListBox) feedToListBox(); else locateProc->kill(); } void LocateDlg::slotUpdateDb() /* The Update DB button */ { if (!updateProcess) { KConfigGroup group(krConfig, "Locate"); updateProcess = new KProcess(); // don't set the parent to 'this'! That would cause this process to be deleted once the dialog is closed *updateProcess << KrServices::fullPathName("updatedb"); *updateProcess << KShell::splitArgs(group.readEntry("UpdateDB Arguments")); connect(updateProcess, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(updateFinished())); updateProcess->start(); updateDbButton->setEnabled(false); } } void LocateDlg::updateFinished() { delete updateProcess; updateProcess = 0; updateDbButton->setEnabled(true); } void LocateDlg::slotLocate() /* The locate button */ { locateSearchFor->addToHistory(locateSearchFor->currentText()); QStringList list = locateSearchFor->historyItems(); KConfigGroup group(krConfig, "Locate"); group.writeEntry("Search For", list); group.writeEntry("Don't Search In Path", dontSearchPath = dontSearchInPath->isChecked()); group.writeEntry("Existing Files", onlyExist = existingFiles->isChecked()); group.writeEntry("Case Sensitive", isCs = caseSensitive->isChecked()); if (!KrServices::cmdExist("locate")) { KMessageBox::error(0, i18n("Cannot start 'locate'. Check the 'Dependencies' page in konfigurator.")); return; } resultList->clear(); lastItem = 0; remaining = ""; updateButtons(true); isFeedToListBox = false; resultList->setFocus(); qApp->processEvents(); //FIXME - whats's this for ? locateProc = new KProcess(this); locateProc->setOutputChannelMode(KProcess::SeparateChannels); // default is forwarding to the parent channels connect(locateProc, SIGNAL(readyReadStandardOutput()), SLOT(processStdout())); connect(locateProc, SIGNAL(readyReadStandardError()), SLOT(processStderr())); connect(locateProc, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(locateFinished())); connect(locateProc, SIGNAL(error(QProcess::ProcessError)), SLOT(locateError())); *locateProc << KrServices::fullPathName("locate"); if (!isCs) *locateProc << "-i"; *locateProc << (pattern = locateSearchFor->currentText()); if (!pattern.startsWith('*')) pattern = '*' + pattern; if (!pattern.endsWith('*')) pattern = pattern + '*'; collectedErr = ""; locateProc->start(); } void LocateDlg::locateError() { if (locateProc->error() == QProcess::FailedToStart) KMessageBox::error(krMainWindow, i18n("Error during the start of 'locate' process.")); } void LocateDlg::locateFinished() { if (locateProc->exitStatus() != QProcess::NormalExit || locateProc->exitStatus()) { if (!collectedErr.isEmpty()) KMessageBox::error(krMainWindow, i18n("Locate produced the following error message:\n\n%1", collectedErr)); } if (resultList->topLevelItemCount() == 0) { locateSearchFor->setFocus(); isFeedToListBox = false; } else { isFeedToListBox = true; } updateButtons(false); } void LocateDlg::processStdout() { remaining += QString::fromLocal8Bit(locateProc->readAllStandardOutput()); QStringList list = remaining.split('\n'); int items = list.size(); for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) { if (--items == 0 && !remaining.endsWith('\n')) remaining = *it; else { if (dontSearchPath) { QRegExp regExp(pattern, isCs ? Qt::CaseSensitive : Qt::CaseInsensitive, QRegExp::Wildcard); QString fileName = (*it).trimmed(); if (fileName.endsWith(QLatin1String("/")) && fileName != "/") { fileName.truncate(fileName.length() - 1); } fileName = fileName.mid(fileName.lastIndexOf('/') + 1); if (!regExp.exactMatch(fileName)) continue; } if (onlyExist) { KFileItem file(QUrl::fromLocalFile((*it).trimmed())); if (!file.isReadable()) continue; } if (lastItem) lastItem = new QTreeWidgetItem(resultList, lastItem); else lastItem = new QTreeWidgetItem(resultList); lastItem->setText(0, *it); } } } void LocateDlg::processStderr() { collectedErr += QString::fromLocal8Bit(locateProc->readAllStandardError()); } void LocateDlg::slotRightClick(QTreeWidgetItem *item, const QPoint &pos) { if (!item) return; // create the menu QMenu popup; popup.setTitle(i18nc("@title:menu", "Locate")); QAction * actView = popup.addAction(i18n("View (F3)")); QAction * actEdit = popup.addAction(i18n("Edit (F4)")); QAction * actComp = popup.addAction(i18n("Compare by content (F10)")); if (resultList->selectedItems().count() != 2) actComp->setEnabled(false); popup.addSeparator(); QAction * actFind = popup.addAction(i18n("Find (Ctrl+F)")); QAction * actNext = popup.addAction(i18n("Find next (Ctrl+N)")); QAction * actPrev = popup.addAction(i18n("Find previous (Ctrl+P)")); popup.addSeparator(); QAction * actClip = popup.addAction(i18n("Copy selected to clipboard")); QAction * result = popup.exec(pos); int ret = -1; if (result == actView) ret = VIEW_ID; else if (result == actEdit) ret = EDIT_ID; else if (result == actFind) ret = FIND_ID; else if (result == actNext) ret = FIND_NEXT_ID; else if (result == actPrev) ret = FIND_PREV_ID; else if (result == actClip) ret = COPY_SELECTED_TO_CLIPBOARD; else if (result == actComp) ret = COMPARE_ID; if (ret != - 1) operate(item, ret); } void LocateDlg::slotDoubleClick(QTreeWidgetItem *item) { if (!item) return; QString dirName = item->text(0); QString fileName; if (!QDir(dirName).exists()) { fileName = dirName.mid(dirName.lastIndexOf('/') + 1); dirName.truncate(dirName.lastIndexOf('/')); } ACTIVE_FUNC->openUrl(QUrl::fromLocalFile(dirName), fileName); QDialog::accept(); } void LocateDlg::keyPressEvent(QKeyEvent *e) { if (KrGlobal::copyShortcut == QKeySequence(e->key() | e->modifiers())) { operate(0, COPY_SELECTED_TO_CLIPBOARD); e->accept(); return; } switch (e->key()) { case Qt::Key_M : if (e->modifiers() == Qt::ControlModifier) { resultList->setFocus(); e->accept(); } break; case Qt::Key_F3 : if (resultList->currentItem()) operate(resultList->currentItem(), VIEW_ID); break; case Qt::Key_F4 : if (resultList->currentItem()) operate(resultList->currentItem(), EDIT_ID); break; case Qt::Key_F10 : operate(0, COMPARE_ID); break; case Qt::Key_N : if (e->modifiers() == Qt::ControlModifier) operate(resultList->currentItem(), FIND_NEXT_ID); break; case Qt::Key_P : if (e->modifiers() == Qt::ControlModifier) operate(resultList->currentItem(), FIND_PREV_ID); break; case Qt::Key_F : if (e->modifiers() == Qt::ControlModifier) operate(resultList->currentItem(), FIND_ID); break; } QDialog::keyPressEvent(e); } void LocateDlg::operate(QTreeWidgetItem *item, int task) { QUrl name; if (item != 0) name = QUrl::fromLocalFile(item->text(0)); switch (task) { case VIEW_ID: KrViewer::view(name, this); // view the file break; case EDIT_ID: KrViewer::edit(name, this); // view the file break; case COMPARE_ID: { QList list = resultList->selectedItems(); if (list.count() != 2) break; QUrl url1 = QUrl::fromLocalFile(list[ 0 ]->text(0)); QUrl url2 = QUrl::fromLocalFile(list[ 1 ]->text(0)); SLOTS->compareContent(url1,url2); } break; case FIND_ID: { KConfigGroup group(krConfig, "Locate"); long options = group.readEntry("Find Options", (long long)0); QStringList list = group.readEntry("Find Patterns", QStringList()); QPointer dlg = new KFindDialog(this, options, list); if (dlg->exec() != QDialog::Accepted) { delete dlg; return; } QString first; if (list.count() != 0) { first = list.first(); } if (first != (findPattern = dlg->pattern())) { list.push_front(dlg->pattern()); } group.writeEntry("Find Options", (long long)(findOptions = dlg->options())); group.writeEntry("Find Patterns", list); if (!(findOptions & KFind::FromCursor) && resultList->topLevelItemCount()) resultList->setCurrentItem((findOptions & KFind::FindBackwards) ? resultList->topLevelItem(resultList->topLevelItemCount() - 1) : resultList->topLevelItem(0)); findCurrentItem = resultList->currentItem(); if (find() && findCurrentItem) { resultList->selectionModel()->clearSelection(); // HACK: QT 4 is not able to paint the focus frame because of a bug resultList->setCurrentItem(findCurrentItem); } else { KMessageBox::information(this, i18n("Search string not found.")); } resultList->scrollTo(resultList->currentIndex()); delete dlg; } break; case FIND_NEXT_ID: case FIND_PREV_ID: { if (task == FIND_PREV_ID) findOptions ^= KFind::FindBackwards; findCurrentItem = resultList->currentItem(); nextLine(); if (find() && findCurrentItem) { resultList->selectionModel()->clearSelection(); // HACK: QT 4 is not able to paint the focus frame because of a bug resultList->setCurrentItem(findCurrentItem); } else KMessageBox::information(this, i18n("Search string not found.")); resultList->scrollTo(resultList->currentIndex()); if (task == FIND_PREV_ID) findOptions ^= KFind::FindBackwards; } break; case COPY_SELECTED_TO_CLIPBOARD: { QList urls; QTreeWidgetItemIterator it(resultList); while (*it) { if ((*it)->isSelected()) urls.push_back(QUrl::fromLocalFile((*it)->text(0))); it++; } if (urls.count() == 0) return; QMimeData *mimeData = new QMimeData; mimeData->setImageData(FileListIcon("file").pixmap()); mimeData->setUrls(urls); QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); } break; } } void LocateDlg::nextLine() { if (findOptions & KFind::FindBackwards) findCurrentItem = resultList->itemAbove(findCurrentItem); else findCurrentItem = resultList->itemBelow(findCurrentItem); } bool LocateDlg::find() { while (findCurrentItem) { QString item = findCurrentItem->text(0); if (findOptions & KFind::RegularExpression) { if (item.contains(QRegExp(findPattern, ((findOptions & KFind::CaseSensitive) != 0) ? Qt::CaseSensitive : Qt::CaseInsensitive))) return true; } else { if (item.contains(findPattern, ((findOptions & KFind::CaseSensitive) != 0) ? Qt::CaseSensitive : Qt::CaseInsensitive)) return true; } nextLine(); } return false; } void LocateDlg::feedToListBox() { VirtualFileSystem virtFilesystem; virtFilesystem.scanDir(QUrl::fromLocalFile(QStringLiteral("/"))); KConfigGroup group(krConfig, "Locate"); int listBoxNum = group.readEntry("Feed To Listbox Counter", 1); QString queryName; do { queryName = i18n("Locate results") + QString(" %1").arg(listBoxNum++); } while (virtFilesystem.getFileItem(queryName) != 0); group.writeEntry("Feed To Listbox Counter", listBoxNum); KConfigGroup ga(krConfig, "Advanced"); if (ga.readEntry("Confirm Feed to Listbox", _ConfirmFeedToListbox)) { bool ok; queryName = QInputDialog::getText(this, i18n("Query Name"), i18n("Here you can name the file collection:"), QLineEdit::Normal, queryName, &ok); if (! ok) return; } QList urlList; QTreeWidgetItemIterator it(resultList); while (*it) { QTreeWidgetItem * item = *it; urlList.push_back(QUrl::fromLocalFile(item->text(0))); it++; } QUrl url = QUrl(QStringLiteral("virt:/") + queryName); virtFilesystem.refresh(url); // create directory if it does not exist virtFilesystem.addFiles(urlList); //ACTIVE_FUNC->openUrl(url); ACTIVE_MNG->slotNewTab(url); accept(); } void LocateDlg::reset() { locateSearchFor->lineEdit()->setFocus(); locateSearchFor->lineEdit()->selectAll(); } void LocateDlg::updateButtons(bool locateIsRunning) { locateButton->setEnabled(!locateIsRunning); if (locateIsRunning) { feedStopButton->setEnabled(true); feedStopButton->setText(i18n("Stop")); feedStopButton->setIcon(Icon(QStringLiteral("process-stop"))); } else { if (resultList->topLevelItemCount() == 0) { feedStopButton->setEnabled(false); feedStopButton->setText(i18n("Stop")); feedStopButton->setIcon(Icon(QStringLiteral("process-stop"))); } else { feedStopButton->setEnabled(true); feedStopButton->setText(i18n("Feed to listbox")); feedStopButton->setIcon(Icon(QStringLiteral("list-add"))); } } } diff --git a/krusader/Locate/locate.h b/krusader/Locate/locate.h index 3a4914e2..c5f84a16 100644 --- a/krusader/Locate/locate.h +++ b/krusader/Locate/locate.h @@ -1,106 +1,106 @@ /***************************************************************************** * Copyright (C) 2004 Csaba Karai * * Copyright (C) 2004-2018 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader 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. * * * * Krusader 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 Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #ifndef LOCATE_H #define LOCATE_H // QtGui #include // QtWidgets #include #include #include #include class KProcess; class KrTreeWidget; class QTreeWidgetItem; class LocateDlg : public QDialog { Q_OBJECT public: - LocateDlg(); + explicit LocateDlg(QWidget *parent); static LocateDlg *LocateDialog; virtual void feedToListBox(); void reset(); public slots: void slotFeedStop(); void slotUpdateDb(); void slotLocate(); void processStdout(); void processStderr(); void locateFinished(); void locateError(); void slotRightClick(QTreeWidgetItem *, const QPoint &); void slotDoubleClick(QTreeWidgetItem *); void updateFinished(); protected: void keyPressEvent(QKeyEvent *) Q_DECL_OVERRIDE; private: void operate(QTreeWidgetItem *item, int task); bool find(); void nextLine(); void updateButtons(bool locateIsRunning); bool dontSearchPath; bool onlyExist; bool isCs; bool isFeedToListBox; QString pattern; KHistoryComboBox *locateSearchFor; KrTreeWidget *resultList; QString remaining; QTreeWidgetItem *lastItem; QString collectedErr; long findOptions; QString findPattern; QTreeWidgetItem *findCurrentItem; QCheckBox *dontSearchInPath; QCheckBox *existingFiles; QCheckBox *caseSensitive; QPushButton *feedStopButton; QPushButton *updateDbButton; QPushButton *locateButton; KProcess *locateProc; static KProcess *updateProcess; }; #endif /* __LOCATE_H__ */ diff --git a/krusader/krmainwindow.h b/krusader/krmainwindow.h index 7f74a281..bc776e6f 100644 --- a/krusader/krmainwindow.h +++ b/krusader/krmainwindow.h @@ -1,70 +1,69 @@ /***************************************************************************** * Copyright (C) 2010 Jan Lepper * * Copyright (C) 2010-2018 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader 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. * * * * Krusader 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 Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #ifndef KRMAINWINDOW_H #define KRMAINWINDOW_H #include #include -#include "krmainwindow.h" #include "abstractpanelmanager.h" class KActionCollection; class KrPanel; class KrActions; class KrView; class ListPanelActions; class PopularUrls; class TabActions; class ViewActions; /** * @brief Abstract file manager main window */ class KrMainWindow { public: virtual ~KrMainWindow() {} virtual QWidget *widget() = 0; virtual KrView *activeView() = 0; virtual ViewActions *viewActions() = 0; virtual KActionCollection *actions() = 0; virtual AbstractPanelManager *activeManager() = 0; virtual AbstractPanelManager *leftManager() = 0; virtual AbstractPanelManager *rightManager() = 0; virtual PopularUrls *popularUrls() = 0; virtual KrActions *krActions() = 0; virtual ListPanelActions *listPanelActions() = 0; virtual TabActions *tabActions() = 0; virtual void plugActionList(const char *name, QList &list) = 0; KrPanel *activePanel() { return activeManager()->currentPanel(); } KrPanel *leftPanel() { return leftManager()->currentPanel(); } KrPanel *rightPanel() { return rightManager()->currentPanel(); } }; #endif // KRMAINWINDOW_H diff --git a/krusader/krslots.cpp b/krusader/krslots.cpp index a7083e7e..dc8906d5 100644 --- a/krusader/krslots.cpp +++ b/krusader/krslots.cpp @@ -1,747 +1,747 @@ /***************************************************************************** * Copyright (C) 2001 Shie Erlich * * Copyright (C) 2001 Rafi Yanai * * Copyright (C) 2004-2018 Krusader Krew [https://krusader.org] * * * * This file is part of Krusader [https://krusader.org]. * * * * Krusader 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. * * * * Krusader 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 Krusader. If not, see [http://www.gnu.org/licenses/]. * *****************************************************************************/ #include "krslots.h" // QtCore #include #include #include #include #include #include // QtGui #include #include // QtWidgets #include #include #include #include #include #include #ifdef __KJSEMBED__ #include #include "KrJS/krjs.h" #endif #include "defaults.h" #include "icon.h" #include "kractions.h" #include "krservices.h" #include "krtrashhandler.h" #include "krusader.h" #include "krusaderview.h" #include "panelmanager.h" #include "ActionMan/actionman.h" #include "BookMan/krbookmarkbutton.h" #include "BookMan/krbookmarkhandler.h" #include "Dialogs/krdialogs.h" #include "Dialogs/krspecialwidgets.h" #include "Dialogs/krspwidgets.h" #include "DiskUsage/diskusagegui.h" #include "FileSystem/fileitem.h" #include "FileSystem/filesystem.h" #include "FileSystem/krquery.h" #include "GUI/dirhistorybutton.h" #include "GUI/kcmdline.h" #include "GUI/kfnkeys.h" #include "GUI/krusaderstatus.h" #include "GUI/mediabutton.h" #include "GUI/terminaldock.h" #include "KViewer/krviewer.h" #include "Konfigurator/konfigurator.h" #include "Locate/locate.h" #include "MountMan/kmountman.h" #include "Panel/PanelView/krselectionmode.h" #include "Panel/PanelView/krview.h" #include "Panel/PanelView/krviewfactory.h" #include "Panel/PanelView/krviewitem.h" #include "Panel/listpanel.h" #include "Panel/panelfunc.h" #include "Panel/sidebar.h" #include "Search/krsearchdialog.h" #include "Search/krsearchmod.h" #include "Splitter/combiner.h" #include "Splitter/splitter.h" #include "Splitter/splittergui.h" #ifdef SYNCHRONIZER_ENABLED #include "Synchronizer/synchronizergui.h" #endif #define ACTIVE_VIEW _mainWindow->activeView() KRslots::KRslots(QObject *parent) : QObject(parent), _mainWindow(krApp) { } void KRslots::sendFileByEmail(const QList &urls) { if (urls.count() == 0) { KMessageBox::error(0, i18n("No selected files to send.")); return; } QString mailProg; QStringList lst = KrServices::supportedTools(); if (lst.contains("MAIL")) mailProg = lst[lst.indexOf("MAIL") + 1]; else { KMessageBox::error(0, i18n("Krusader cannot find a supported mail client. Please install one to your path. Hint: Krusader supports KMail.")); return; } QString subject, separator; foreach(const QUrl &url, urls) { subject += separator + url.fileName(); separator = ','; } subject = i18np("Sending file: %2", "Sending files: %2", urls.count(), subject); KProcess proc; QString executable = QUrl::fromLocalFile(mailProg).fileName(); if (executable == QStringLiteral("kmail")) { proc << mailProg << "--subject" << subject; foreach(const QUrl &url2, urls) proc << "--attach" << url2.toDisplayString(); } else if (executable == QStringLiteral("thunderbird")) { QString param = "attachment=\'"; separator = ""; foreach(const QUrl &url2, urls) { param += separator + url2.toDisplayString(); separator = ','; } param += "\',subject=\'" + subject + "\'"; proc << mailProg << "--compose" << param; } else if (executable == QStringLiteral("evolution")) { QString param = "mailto:?cc=&subject=" + subject + "&attach="; separator = ""; foreach(const QUrl &url2, urls) { param += separator + url2.toDisplayString(); separator = "&attach="; } proc << mailProg << param + ""; } if (!proc.startDetached()) KMessageBox::error(0, i18n("Error executing %1.", mailProg)); } void KRslots::compareContent() { const QStringList lstLeft = LEFT_PANEL->getSelectedNames(); const QStringList lstRight = RIGHT_PANEL->getSelectedNames(); const QStringList lstActive = ACTIVE_PANEL->gui->isLeft() ? lstLeft : lstRight; QUrl name1, name2; if (lstLeft.count() == 1 && lstRight.count() == 1) { // first, see if we've got exactly 1 selected file in each panel: name1 = LEFT_PANEL->func->files()->getUrl(lstLeft[0]); name2 = RIGHT_PANEL->func->files()->getUrl(lstRight[0]); } else if (lstActive.count() == 2) { // next try: are in the current panel exacty 2 files selected? name1 = ACTIVE_PANEL->func->files()->getUrl(lstActive[0]); name2 = ACTIVE_PANEL->func->files()->getUrl(lstActive[1]); } else if (ACTIVE_PANEL->otherPanel()->func->files()->getFileItem(ACTIVE_VIEW->getCurrentItem())) { // next try: is in the other panel a file with the same name? name1 = ACTIVE_PANEL->func->files()->getUrl(ACTIVE_VIEW->getCurrentItem()); name2 = ACTIVE_PANEL->otherPanel()->func->files()->getUrl(ACTIVE_VIEW->getCurrentItem()); } else { // if we got here, then we can't be sure what file to diff KMessageBox::sorry(0, "" + i18n("Do not know which files to compare.") + "

" + i18n("To compare two files by content, you can either:
  • Select one file in the left panel, and one in the right panel.
  • Select exactly two files in the active panel.
  • Make sure there is a file in the other panel, with the same name as the current file in the active panel.
") + "
"); return; } // else implied: all ok, let's call an external program to compare files // but if any of the files isn't local, download it first compareContent(name1, name2); } bool downloadToTemp(const QUrl &url, QString &dest) { QTemporaryFile tmpFile; tmpFile.setAutoRemove(false); if (tmpFile.open()) { dest = tmpFile.fileName(); KIO::Job* job = KIO::file_copy(url, QUrl::fromLocalFile(dest), -1, KIO::Overwrite | KIO::HideProgressInfo); if(!job->exec()) { KMessageBox::error(krApp, i18n("Krusader is unable to download %1", url.fileName())); return false; } return true; } return false; } void KRslots::compareContent(QUrl url1, QUrl url2) { QString diffProg; QStringList lst = KrServices::supportedTools(); if (lst.contains("DIFF")) diffProg = lst[lst.indexOf("DIFF") + 1]; else { KMessageBox::error(0, i18n("Krusader cannot find any of the supported diff-frontends. Please install one to your path. Hint: Krusader supports Kompare, KDiff3 and Xxdiff.")); return; } QString tmp1; QString tmp2; // kdiff3 sucks with spaces if (QUrl::fromLocalFile(diffProg).fileName() == "kdiff3" && !url1.toDisplayString().contains(" ") && !url2.toDisplayString().contains(" ")) { tmp1 = url1.toDisplayString(); tmp2 = url2.toDisplayString(); } else { if (!url1.isLocalFile()) { if (!downloadToTemp(url1, tmp1)) { return; } } else tmp1 = url1.path(); if (!url2.isLocalFile()) { if (!downloadToTemp(url2, tmp2)) { if (tmp1 != url1.path()) QFile::remove(tmp1); return; } } else tmp2 = url2.path(); } KrProcess *p = new KrProcess(tmp1 != url1.path() ? tmp1 : QString(), tmp2 != url2.path() ? tmp2 : QString()); *p << diffProg << tmp1 << tmp2; p->start(); if (!p->waitForStarted()) KMessageBox::error(0, i18n("Error executing %1.", diffProg)); } // GUI toggle slots void KRslots::toggleFnkeys() { if (MAIN_VIEW->fnKeys()->isVisible()) MAIN_VIEW->fnKeys()->hide(); else MAIN_VIEW->fnKeys()->show(); } void KRslots::toggleCmdline() { if (MAIN_VIEW->cmdLine()->isVisible()) MAIN_VIEW->cmdLine()->hide(); else MAIN_VIEW->cmdLine()->show(); } void KRslots::updateStatusbarVisibility() { krApp->statusBar()->setVisible(KrActions::actShowStatusBar->isChecked()); } void KRslots::toggleTerminal() { MAIN_VIEW->setTerminalEmulator(KrActions::actToggleTerminal->isChecked()); } void KRslots::insertFileName(bool fullPath) { QString filename = ACTIVE_VIEW->getCurrentItem(); if (filename.isEmpty()) { return; } if (fullPath) { const QString path = FileSystem::ensureTrailingSlash(ACTIVE_PANEL->virtualPath()) .toDisplayString(QUrl::PreferLocalFile); filename = path + filename; } filename = KrServices::quote(filename); if (MAIN_VIEW->cmdLine()->isVisible() || !MAIN_VIEW->terminalDock()->isTerminalVisible()) { QString current = MAIN_VIEW->cmdLine()->text(); if (current.length() != 0 && !current.endsWith(' ')) current += ' '; MAIN_VIEW->cmdLine()->setText(current + filename); MAIN_VIEW->cmdLine()->setFocus(); } else if (MAIN_VIEW->terminalDock()->isTerminalVisible()) { filename = ' ' + filename + ' '; MAIN_VIEW->terminalDock()->sendInput(filename, false); MAIN_VIEW->terminalDock()->setFocus(); } } void KRslots::refresh(const QUrl &u) { ACTIVE_FUNC->openUrl(u); } void KRslots::runKonfigurator(bool firstTime) { Konfigurator *konfigurator = new Konfigurator(firstTime); connect(konfigurator, SIGNAL(configChanged(bool)), SLOT(configChanged(bool))); //FIXME - no need to exec konfigurator->exec(); delete konfigurator; } void KRslots::configChanged(bool isGUIRestartNeeded) { krConfig->sync(); if (isGUIRestartNeeded) { krApp->setUpdatesEnabled(false); KConfigGroup group(krConfig, "Look&Feel"); FileItem::loadUserDefinedFolderIcons(group.readEntry("Load User Defined Folder Icons", _UserDefinedFolderIcons)); bool leftActive = ACTIVE_PANEL->gui->isLeft(); MAIN_VIEW->leftManager()->slotRecreatePanels(); MAIN_VIEW->rightManager()->slotRecreatePanels(); if(leftActive) LEFT_PANEL->slotFocusOnMe(); else RIGHT_PANEL->slotFocusOnMe(); MAIN_VIEW->fnKeys()->updateShortcuts(); KrSelectionMode::resetSelectionHandler(); krApp->setUpdatesEnabled(true); } krApp->setTray(); // really ugly, but reload the Fn keys just in case - csaba: any better idea? MAIN_VIEW->fnKeys()->updateShortcuts(); const bool showHidden = KConfigGroup(krConfig, "Look&Feel") .readEntry("Show Hidden", KrActions::actToggleHidden->isChecked()); if (showHidden != KrActions::actToggleHidden->isChecked()) { KrActions::actToggleHidden->setChecked(showHidden); MAIN_VIEW->leftManager()->reloadConfig(); MAIN_VIEW->rightManager()->reloadConfig(); } } void KRslots::showHiddenFiles(bool show) { KConfigGroup group(krConfig, "Look&Feel"); group.writeEntry("Show Hidden", show); MAIN_VIEW->leftManager()->reloadConfig(); MAIN_VIEW->rightManager()->reloadConfig(); } void KRslots::swapPanels() { QUrl leftURL = LEFT_PANEL->virtualPath(); QUrl rightURL = RIGHT_PANEL->virtualPath(); LEFT_PANEL->func->openUrl(rightURL); RIGHT_PANEL->func->openUrl(leftURL); } void KRslots::toggleSwapSides() { MAIN_VIEW->swapSides(); } void KRslots::search() { if (KrSearchDialog::SearchDialog != 0) { KConfigGroup group(krConfig, "Search"); if (group.readEntry("Window Maximized", false)) KrSearchDialog::SearchDialog->showMaximized(); else KrSearchDialog::SearchDialog->showNormal(); KrSearchDialog::SearchDialog->raise(); KrSearchDialog::SearchDialog->activateWindow(); } else KrSearchDialog::SearchDialog = new KrSearchDialog(); } void KRslots::locate() { if (!KrServices::cmdExist("locate")) { KMessageBox::error(krApp, i18n("Cannot find the 'locate' command. Please install the " "findutils-locate package of GNU, or set its dependencies in " "Konfigurator")); return; } - if (LocateDlg::LocateDialog != 0) { + if (LocateDlg::LocateDialog != nullptr) { LocateDlg::LocateDialog->showNormal(); LocateDlg::LocateDialog->raise(); LocateDlg::LocateDialog->activateWindow(); LocateDlg::LocateDialog->reset(); } else - LocateDlg::LocateDialog = new LocateDlg(); + LocateDlg::LocateDialog = new LocateDlg(krApp); } void KRslots::runTerminal(const QString & dir) { KProcess proc; proc.setWorkingDirectory(dir); KConfigGroup group(krConfig, "General"); QString term = group.readEntry("Terminal", _Terminal); QStringList sepdArgs = KShell::splitArgs(term, KShell::TildeExpand); if (sepdArgs.isEmpty()) { KMessageBox::error(krMainWindow, i18nc("Arg is a string containing the bad quoting.", "Bad quoting in terminal command:\n%1", term)); return; } for (int i = 0; i < sepdArgs.size(); i++) { if (sepdArgs[i] == "%d") { sepdArgs[i] = dir; } } proc << sepdArgs; if (!proc.startDetached()) KMessageBox::sorry(krApp, i18n("Error executing %1.", term)); } void KRslots::homeTerminal() { runTerminal(QDir::homePath()); } void KRslots::multiRename() { QStringList lst = KrServices::supportedTools(); int i = lst.indexOf("RENAME"); if (i == -1) { KMessageBox::sorry(krApp, i18n("Cannot find a batch rename tool.\nYou can get KRename at http://www.krename.net")); return; } QString pathToRename = lst[i+1]; const QStringList names = ACTIVE_PANEL->gui->getSelectedNames(); if (names.isEmpty()) { return; } KProcess proc; proc << pathToRename; for (const QString name: names) { FileItem *file = ACTIVE_FUNC->files()->getFileItem(name); if (!file) continue; const QUrl url = file->getUrl(); // KRename only supports the recursive option combined with a local directory path if (file->isDir() && url.scheme() == "file") { proc << "-r" << url.path(); } else { proc << url.toString(); } } if (!proc.startDetached()) KMessageBox::error(0, i18n("Error executing '%1'.", proc.program().join(" "))); } void KRslots::rootKrusader() { if (KMessageBox::warningContinueCancel( krApp, i18n("Improper operations in root mode can damage your operating system. " "

Furthermore, running UI applications as root is insecure and can " "allow attackers to gain root access."), QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "Confirm Root Mode", KMessageBox::Notify | KMessageBox::Dangerous) != KMessageBox::Continue) return; if (!KrServices::isExecutable(KDESU_PATH)) { KMessageBox::sorry(krApp, i18n("Cannot start root mode Krusader, %1 not found or not executable. " "Please verify that kde-cli-tools are installed.", QString(KDESU_PATH))); return; } KProcess proc; proc << KDESU_PATH << "-c" << QApplication::instance()->applicationFilePath() + " --left=" + KrServices::quote(LEFT_PANEL->virtualPath().toDisplayString(QUrl::PreferLocalFile)) + " --right=" + KrServices::quote(RIGHT_PANEL->virtualPath().toDisplayString(QUrl::PreferLocalFile)); if (!proc.startDetached()) KMessageBox::error(0, i18n("Error executing %1.", proc.program()[0])); } void KRslots::slotSplit() { const QStringList list = ACTIVE_PANEL->gui->getSelectedNames(); QString name; // first, see if we've got exactly 1 selected file, if not, try the current one if (list.count() == 1) name = list[0]; if (name.isEmpty()) { // if we got here, then one of the panel can't be sure what file to diff KMessageBox::error(0, i18n("Do not know which file to split.")); return; } QUrl fileURL = ACTIVE_FUNC->files()->getUrl(name); if (fileURL.isEmpty()) return; if (ACTIVE_FUNC->files()->getFileItem(name)->isDir()) { KMessageBox::sorry(krApp, i18n("You cannot split a folder.")); return; } const QUrl destDir = ACTIVE_PANEL->otherPanel()->virtualPath(); SplitterGUI splitterGUI(MAIN_VIEW, fileURL, destDir); if (splitterGUI.exec() == QDialog::Accepted) { bool splitToOtherPanel = splitterGUI.getDestinationDir().matches(ACTIVE_PANEL->otherPanel()->virtualPath(), QUrl::StripTrailingSlash); Splitter split(MAIN_VIEW, fileURL, splitterGUI.getDestinationDir(), splitterGUI.overWriteFiles()); split.split(splitterGUI.getSplitSize()); if (splitToOtherPanel) ACTIVE_PANEL->otherPanel()->func->refresh(); } } void KRslots::slotCombine() { const QStringList list = ACTIVE_PANEL->gui->getSelectedNames(); if (list.isEmpty()) { KMessageBox::error(0, i18n("Do not know which files to combine.")); return; } QUrl baseURL; bool unixStyle = false; bool windowsStyle = false; QString commonName; int commonLength = 0; /* checking splitter names */ for (QStringList::ConstIterator it = list.begin(); it != list.end(); ++it) { QUrl url = ACTIVE_FUNC->files()->getUrl(*it); if (url.isEmpty()) return; if (ACTIVE_FUNC->files()->getFileItem(*it)->isDir()) { KMessageBox::sorry(krApp, i18n("You cannot combine a folder.")); return; } if (!unixStyle) { QString name = url.fileName(); int extPos = name.lastIndexOf('.'); QString ext = name.mid(extPos + 1); name.truncate(extPos); url = url.adjusted(QUrl::RemoveFilename); url.setPath(url.path() + name); bool isExtInt; ext.toInt(&isExtInt, 10); if (extPos < 1 || ext.isEmpty() || (ext != "crc" && !isExtInt)) { if (windowsStyle) { KMessageBox::error(0, i18n("Not a split file: %1.", url.toDisplayString(QUrl::PreferLocalFile))); return; } unixStyle = true; } else { if (ext != "crc") windowsStyle = true; if (baseURL.isEmpty()) baseURL = url; else if (baseURL != url) { KMessageBox::error(0, i18n("Select only one split file.")); return; } } } if (unixStyle) { bool error = true; do { QString shortName = *it; QChar lastChar = shortName.at(shortName.length() - 1); if (lastChar.isLetter()) { char fillLetter = (lastChar.toUpper() == lastChar) ? 'A' : 'a'; if (commonName.isNull()) { commonLength = shortName.length(); commonName = shortName; while (commonName.length()) { QString shorter = commonName.left(commonName.length() - 1); QString testFile = shorter.leftJustified(commonLength, fillLetter); if (ACTIVE_FUNC->files()->getFileItem(testFile) == 0) break; else { commonName = shorter; baseURL = ACTIVE_PANEL->virtualPath().adjusted(QUrl::StripTrailingSlash); baseURL.setPath(baseURL.path() + '/' + (testFile)); } } error = (commonName == shortName); } else if (commonLength == shortName.length() && shortName.startsWith(commonName)) error = false; } } while (false); if (error) { KMessageBox::error(0, i18n("Not a split file: %1.", url.toDisplayString(QUrl::PreferLocalFile))); return; } } } // ask the user for the copy dest QUrl dest = KChooseDir::getDir(i18n("Combining %1.* to folder:", baseURL.toDisplayString(QUrl::PreferLocalFile)), ACTIVE_PANEL->otherPanel()->virtualPath(), ACTIVE_PANEL->virtualPath()); if (dest.isEmpty()) return ; // the user canceled bool combineToOtherPanel = (dest.matches(ACTIVE_PANEL->otherPanel()->virtualPath(), QUrl::StripTrailingSlash)); Combiner combine(MAIN_VIEW, baseURL, dest, unixStyle); combine.combine(); if (combineToOtherPanel) ACTIVE_PANEL->otherPanel()->func->refresh(); } void KRslots::manageUseractions() { ActionMan actionMan(MAIN_VIEW); } #ifdef SYNCHRONIZER_ENABLED void KRslots::slotSynchronizeDirs(QStringList selected) { SynchronizerGUI *synchronizerDialog = new SynchronizerGUI(MAIN_VIEW, LEFT_PANEL->virtualPath(), RIGHT_PANEL->virtualPath(), selected); synchronizerDialog->show(); // destroyed on close } #endif void KRslots::compareSetup() { for (int i = 0; KrActions::compareArray[i] != 0; i++) if ((*KrActions::compareArray[i])->isChecked()) { KConfigGroup group(krConfig, "Private"); group.writeEntry("Compare Mode", i); break; } } /** called by actions actExec* to choose the built-in command line mode */ void KRslots::execTypeSetup() { for (int i = 0; KrActions::execTypeArray[i] != 0; i++) if ((*KrActions::execTypeArray[i])->isChecked()) { if (*KrActions::execTypeArray[i] == KrActions::actExecTerminalEmbedded) { // if commands are to be executed in the TE, it must be loaded MAIN_VIEW->terminalDock()->initialise(); } KConfigGroup grp(krConfig, "Private"); grp.writeEntry("Command Execution Mode", i); break; } } void KRslots::slotDiskUsage() { DiskUsageGUI *diskUsageDialog = new DiskUsageGUI(ACTIVE_PANEL->virtualPath()); diskUsageDialog->askDirAndShow(); } void KRslots::applicationStateChanged() { if (MAIN_VIEW == 0) { /* CRASH FIX: it's possible that the method is called after destroying the main view */ return; } if(qApp->applicationState() == Qt::ApplicationActive || qApp->applicationState() == Qt::ApplicationInactive) { LEFT_PANEL->panelVisible(); RIGHT_PANEL->panelVisible(); } else { LEFT_PANEL->panelHidden(); RIGHT_PANEL->panelHidden(); } } void KRslots::emptyTrash() { KrTrashHandler::emptyTrash(); } #define OPEN_ID 100001 #define EMPTY_TRASH_ID 100002 void KRslots::trashPopupMenu() { QMenu trashMenu(krApp); QAction * act = trashMenu.addAction(Icon("document-open"), i18n("Open trash bin")); act->setData(QVariant(OPEN_ID)); act = trashMenu.addAction(Icon("trash-empty"), i18n("Empty trash bin")); act->setData(QVariant(EMPTY_TRASH_ID)); int result = -1; QAction *res = trashMenu.exec(QCursor::pos()); if (res && res->data().canConvert ()) result = res->data().toInt(); if (result == OPEN_ID) { ACTIVE_FUNC->openUrl(QUrl(QStringLiteral("trash:/"))); } else if (result == EMPTY_TRASH_ID) { KrTrashHandler::emptyTrash(); } } //shows the JavaScript-Console void KRslots::jsConsole() { #ifdef __KJSEMBED__ if (! krJS) krJS = new KrJS(); krJS->view()->show(); #endif } void KRslots::addBookmark() { krBookMan->bookmarkCurrent(ACTIVE_PANEL->virtualPath()); } void KRslots::cmdlinePopup() { MAIN_VIEW->cmdLine()->popup(); }