diff --git a/src/dolphinmainwindow.cpp b/src/dolphinmainwindow.cpp index 8d580432f..3d6f8489d 100644 --- a/src/dolphinmainwindow.cpp +++ b/src/dolphinmainwindow.cpp @@ -1,1540 +1,1545 @@ /*************************************************************************** * Copyright (C) 2006 by Peter Penz * * Copyright (C) 2006 by Stefan Monov * * Copyright (C) 2006 by Cvetoslav Ludmiloff * * * * 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 program 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 program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "dolphinmainwindow.h" #include "global.h" #include "dolphindockwidget.h" #include "dolphincontextmenu.h" #include "dolphinnewfilemenu.h" #include "dolphinrecenttabsmenu.h" #include "dolphintabwidget.h" #include "dolphinviewcontainer.h" #include "dolphintabpage.h" #include "panels/folders/folderspanel.h" #include "panels/places/placespanel.h" #include "panels/information/informationpanel.h" #include "settings/dolphinsettingsdialog.h" #include "statusbar/dolphinstatusbar.h" #include "views/dolphinviewactionhandler.h" #include "views/dolphinremoteencoding.h" #include "views/draganddrophelper.h" #include "views/viewproperties.h" #include "views/dolphinnewfilemenuobserver.h" #ifndef Q_OS_WIN #include "panels/terminal/terminalpanel.h" #endif #include "dolphin_generalsettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { // Used for GeneralSettings::version() to determine whether // an updated version of Dolphin is running. const int CurrentDolphinVersion = 200; } DolphinMainWindow::DolphinMainWindow() : KXmlGuiWindow(0), m_newFileMenu(0), m_tabWidget(0), m_activeViewContainer(0), m_actionHandler(0), m_remoteEncoding(0), m_settingsDialog(), m_controlButton(0), m_updateToolBarTimer(0), m_lastHandleUrlStatJob(0) { setObjectName(QStringLiteral("Dolphin#")); connect(&DolphinNewFileMenuObserver::instance(), &DolphinNewFileMenuObserver::errorMessage, this, &DolphinMainWindow::showErrorMessage); KIO::FileUndoManager* undoManager = KIO::FileUndoManager::self(); undoManager->setUiInterface(new UndoUiInterface()); connect(undoManager, static_cast(&KIO::FileUndoManager::undoAvailable), this, &DolphinMainWindow::slotUndoAvailable); connect(undoManager, &KIO::FileUndoManager::undoTextChanged, this, &DolphinMainWindow::slotUndoTextChanged); connect(undoManager, &KIO::FileUndoManager::jobRecordingStarted, this, &DolphinMainWindow::clearStatusBar); connect(undoManager, &KIO::FileUndoManager::jobRecordingFinished, this, &DolphinMainWindow::showCommand); GeneralSettings* generalSettings = GeneralSettings::self(); const bool firstRun = (generalSettings->version() < 200); if (firstRun) { generalSettings->setViewPropsTimestamp(QDateTime::currentDateTime()); } setAcceptDrops(true); m_tabWidget = new DolphinTabWidget(this); m_tabWidget->setObjectName("tabWidget"); connect(m_tabWidget, &DolphinTabWidget::activeViewChanged, this, &DolphinMainWindow::activeViewChanged); connect(m_tabWidget, &DolphinTabWidget::tabCountChanged, this, &DolphinMainWindow::tabCountChanged); connect(m_tabWidget, &DolphinTabWidget::currentUrlChanged, this, &DolphinMainWindow::setUrlAsCaption); setCentralWidget(m_tabWidget); setupActions(); m_actionHandler = new DolphinViewActionHandler(actionCollection(), this); connect(m_actionHandler, &DolphinViewActionHandler::actionBeingHandled, this, &DolphinMainWindow::clearStatusBar); connect(m_actionHandler, &DolphinViewActionHandler::createDirectory, this, &DolphinMainWindow::createDirectory); m_remoteEncoding = new DolphinRemoteEncoding(this, m_actionHandler); connect(this, &DolphinMainWindow::urlChanged, m_remoteEncoding, &DolphinRemoteEncoding::slotAboutToOpenUrl); setupDockWidgets(); setupGUI(Keys | Save | Create | ToolBar); stateChanged(QStringLiteral("new_file")); QClipboard* clipboard = QApplication::clipboard(); connect(clipboard, &QClipboard::dataChanged, this, &DolphinMainWindow::updatePasteAction); QAction* showFilterBarAction = actionCollection()->action(QStringLiteral("show_filter_bar")); showFilterBarAction->setChecked(generalSettings->filterBar()); if (firstRun) { menuBar()->setVisible(false); // Assure a proper default size if Dolphin runs the first time resize(750, 500); } const bool showMenu = !menuBar()->isHidden(); QAction* showMenuBarAction = actionCollection()->action(KStandardAction::name(KStandardAction::ShowMenubar)); showMenuBarAction->setChecked(showMenu); // workaround for bug #171080 if (!showMenu) { createControlButton(); } } DolphinMainWindow::~DolphinMainWindow() { } void DolphinMainWindow::openDirectories(const QList& dirs, bool splitView) { m_tabWidget->openDirectories(dirs, splitView); } void DolphinMainWindow::openFiles(const QList& files, bool splitView) { m_tabWidget->openFiles(files, splitView); } void DolphinMainWindow::showCommand(CommandType command) { DolphinStatusBar* statusBar = m_activeViewContainer->statusBar(); switch (command) { case KIO::FileUndoManager::Copy: statusBar->setText(i18nc("@info:status", "Successfully copied.")); break; case KIO::FileUndoManager::Move: statusBar->setText(i18nc("@info:status", "Successfully moved.")); break; case KIO::FileUndoManager::Link: statusBar->setText(i18nc("@info:status", "Successfully linked.")); break; case KIO::FileUndoManager::Trash: statusBar->setText(i18nc("@info:status", "Successfully moved to trash.")); break; case KIO::FileUndoManager::Rename: statusBar->setText(i18nc("@info:status", "Successfully renamed.")); break; case KIO::FileUndoManager::Mkdir: statusBar->setText(i18nc("@info:status", "Created folder.")); break; default: break; } } void DolphinMainWindow::pasteIntoFolder() { m_activeViewContainer->view()->pasteIntoFolder(); } void DolphinMainWindow::changeUrl(const QUrl &url) { if (!KProtocolManager::supportsListing(url)) { // The URL navigator only checks for validity, not // if the URL can be listed. An error message is // shown due to DolphinViewContainer::restoreView(). return; } m_activeViewContainer->setUrl(url); updateEditActions(); updatePasteAction(); updateViewActions(); updateGoActions(); emit urlChanged(url); } void DolphinMainWindow::slotTerminalDirectoryChanged(const QUrl& url) { m_activeViewContainer->setAutoGrabFocus(false); changeUrl(url); m_activeViewContainer->setAutoGrabFocus(true); } void DolphinMainWindow::slotEditableStateChanged(bool editable) { KToggleAction* editableLocationAction = static_cast(actionCollection()->action(QStringLiteral("editable_location"))); editableLocationAction->setChecked(editable); } void DolphinMainWindow::slotSelectionChanged(const KFileItemList& selection) { updateEditActions(); const int selectedUrlsCount = m_tabWidget->currentTabPage()->selectedItemsCount(); QAction* compareFilesAction = actionCollection()->action(QStringLiteral("compare_files")); if (selectedUrlsCount == 2) { compareFilesAction->setEnabled(isKompareInstalled()); } else { compareFilesAction->setEnabled(false); } emit selectionChanged(selection); } void DolphinMainWindow::updateHistory() { const KUrlNavigator* urlNavigator = m_activeViewContainer->urlNavigator(); const int index = urlNavigator->historyIndex(); QAction* backAction = actionCollection()->action(QStringLiteral("go_back")); if (backAction) { backAction->setToolTip(i18nc("@info", "Go back")); backAction->setEnabled(index < urlNavigator->historySize() - 1); } QAction* forwardAction = actionCollection()->action(QStringLiteral("go_forward")); if (forwardAction) { forwardAction->setToolTip(i18nc("@info", "Go forward")); forwardAction->setEnabled(index > 0); } } void DolphinMainWindow::updateFilterBarAction(bool show) { QAction* showFilterBarAction = actionCollection()->action(QStringLiteral("show_filter_bar")); showFilterBarAction->setChecked(show); } void DolphinMainWindow::openNewMainWindow() { Dolphin::openNewWindow({m_activeViewContainer->url()}, this); } void DolphinMainWindow::openNewActivatedTab() { m_tabWidget->openNewActivatedTab(); } void DolphinMainWindow::openNewTab(const QUrl& url) { m_tabWidget->openNewTab(url); } void DolphinMainWindow::openInNewTab() { const KFileItemList& list = m_activeViewContainer->view()->selectedItems(); - if (list.isEmpty()) { - openNewTab(m_activeViewContainer->url()); - } else { - foreach (const KFileItem& item, list) { - const QUrl& url = DolphinView::openItemAsFolderUrl(item); - if (!url.isEmpty()) { - openNewTab(url); - } + bool tabCreated = false; + + foreach (const KFileItem& item, list) { + const QUrl& url = DolphinView::openItemAsFolderUrl(item); + if (!url.isEmpty()) { + openNewTab(url); + tabCreated = true; } } + + // if no new tab has been created from the selection + // open the current directory in a new tab + if (!tabCreated) { + openNewTab(m_activeViewContainer->url()); + } } void DolphinMainWindow::openInNewWindow() { QUrl newWindowUrl; const KFileItemList list = m_activeViewContainer->view()->selectedItems(); if (list.isEmpty()) { newWindowUrl = m_activeViewContainer->url(); } else if (list.count() == 1) { const KFileItem& item = list.first(); newWindowUrl = DolphinView::openItemAsFolderUrl(item); } if (!newWindowUrl.isEmpty()) { Dolphin::openNewWindow({newWindowUrl}, this); } } void DolphinMainWindow::showEvent(QShowEvent* event) { KXmlGuiWindow::showEvent(event); if (!event->spontaneous()) { m_activeViewContainer->view()->setFocus(); } } void DolphinMainWindow::closeEvent(QCloseEvent* event) { // Find out if Dolphin is closed directly by the user or // by the session manager because the session is closed bool closedByUser = true; if (qApp->isSavingSession()) { closedByUser = false; } if (m_tabWidget->count() > 1 && GeneralSettings::confirmClosingMultipleTabs() && closedByUser) { // Ask the user if he really wants to quit and close all tabs. // Open a confirmation dialog with 3 buttons: // QDialogButtonBox::Yes -> Quit // QDialogButtonBox::No -> Close only the current tab // QDialogButtonBox::Cancel -> do nothing QDialog *dialog = new QDialog(this, Qt::Dialog); dialog->setWindowTitle(i18nc("@title:window", "Confirmation")); dialog->setModal(true); QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); KGuiItem::assign(buttons->button(QDialogButtonBox::Yes), KStandardGuiItem::quit()); KGuiItem::assign(buttons->button(QDialogButtonBox::No), KGuiItem(i18n("C&lose Current Tab"), QIcon::fromTheme(QStringLiteral("tab-close")))); KGuiItem::assign(buttons->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel()); buttons->button(QDialogButtonBox::Yes)->setDefault(true); bool doNotAskAgainCheckboxResult = false; const int result = KMessageBox::createKMessageBox(dialog, buttons, QMessageBox::Warning, i18n("You have multiple tabs open in this window, are you sure you want to quit?"), QStringList(), i18n("Do not ask again"), &doNotAskAgainCheckboxResult, KMessageBox::Notify); if (doNotAskAgainCheckboxResult) { GeneralSettings::setConfirmClosingMultipleTabs(false); } switch (result) { case QDialogButtonBox::Yes: // Quit break; case QDialogButtonBox::No: // Close only the current tab m_tabWidget->closeTab(); default: event->ignore(); return; } } GeneralSettings::setVersion(CurrentDolphinVersion); GeneralSettings::self()->save(); KXmlGuiWindow::closeEvent(event); } void DolphinMainWindow::saveProperties(KConfigGroup& group) { m_tabWidget->saveProperties(group); } void DolphinMainWindow::readProperties(const KConfigGroup& group) { m_tabWidget->readProperties(group); } void DolphinMainWindow::updateNewMenu() { m_newFileMenu->setViewShowsHiddenFiles(activeViewContainer()->view()->hiddenFilesShown()); m_newFileMenu->checkUpToDate(); m_newFileMenu->setPopupFiles(activeViewContainer()->url()); } void DolphinMainWindow::createDirectory() { m_newFileMenu->setViewShowsHiddenFiles(activeViewContainer()->view()->hiddenFilesShown()); m_newFileMenu->setPopupFiles(activeViewContainer()->url()); m_newFileMenu->createDirectory(); } void DolphinMainWindow::quit() { close(); } void DolphinMainWindow::showErrorMessage(const QString& message) { m_activeViewContainer->showMessage(message, DolphinViewContainer::Error); } void DolphinMainWindow::slotUndoAvailable(bool available) { QAction* undoAction = actionCollection()->action(KStandardAction::name(KStandardAction::Undo)); if (undoAction) { undoAction->setEnabled(available); } } void DolphinMainWindow::slotUndoTextChanged(const QString& text) { QAction* undoAction = actionCollection()->action(KStandardAction::name(KStandardAction::Undo)); if (undoAction) { undoAction->setText(text); } } void DolphinMainWindow::undo() { clearStatusBar(); KIO::FileUndoManager::self()->uiInterface()->setParentWidget(this); KIO::FileUndoManager::self()->undo(); } void DolphinMainWindow::cut() { m_activeViewContainer->view()->cutSelectedItems(); } void DolphinMainWindow::copy() { m_activeViewContainer->view()->copySelectedItems(); } void DolphinMainWindow::paste() { m_activeViewContainer->view()->paste(); } void DolphinMainWindow::find() { m_activeViewContainer->setSearchModeEnabled(true); } void DolphinMainWindow::updatePasteAction() { QAction* pasteAction = actionCollection()->action(KStandardAction::name(KStandardAction::Paste)); QPair pasteInfo = m_activeViewContainer->view()->pasteInfo(); pasteAction->setEnabled(pasteInfo.first); pasteAction->setText(pasteInfo.second); } void DolphinMainWindow::slotDirectoryLoadingCompleted() { updatePasteAction(); } void DolphinMainWindow::selectAll() { clearStatusBar(); // if the URL navigator is editable and focused, select the whole // URL instead of all items of the view KUrlNavigator* urlNavigator = m_activeViewContainer->urlNavigator(); QLineEdit* lineEdit = urlNavigator->editor()->lineEdit(); // krazy:exclude=qclasses const bool selectUrl = urlNavigator->isUrlEditable() && lineEdit->hasFocus(); if (selectUrl) { lineEdit->selectAll(); } else { m_activeViewContainer->view()->selectAll(); } } void DolphinMainWindow::invertSelection() { clearStatusBar(); m_activeViewContainer->view()->invertSelection(); } void DolphinMainWindow::toggleSplitView() { DolphinTabPage* tabPage = m_tabWidget->currentTabPage(); tabPage->setSplitViewEnabled(!tabPage->splitViewEnabled()); updateViewActions(); } void DolphinMainWindow::toggleSplitStash() { DolphinTabPage* tabPage = m_tabWidget->currentTabPage(); tabPage->setSplitViewEnabled(false); tabPage->setSplitViewEnabled(true, QUrl("stash:/")); } void DolphinMainWindow::reloadView() { clearStatusBar(); m_activeViewContainer->reload(); } void DolphinMainWindow::stopLoading() { m_activeViewContainer->view()->stopLoading(); } void DolphinMainWindow::enableStopAction() { actionCollection()->action(QStringLiteral("stop"))->setEnabled(true); } void DolphinMainWindow::disableStopAction() { actionCollection()->action(QStringLiteral("stop"))->setEnabled(false); } void DolphinMainWindow::showFilterBar() { m_activeViewContainer->setFilterBarVisible(true); } void DolphinMainWindow::toggleEditLocation() { clearStatusBar(); QAction* action = actionCollection()->action(QStringLiteral("editable_location")); KUrlNavigator* urlNavigator = m_activeViewContainer->urlNavigator(); urlNavigator->setUrlEditable(action->isChecked()); } void DolphinMainWindow::replaceLocation() { KUrlNavigator* navigator = m_activeViewContainer->urlNavigator(); navigator->setUrlEditable(true); navigator->setFocus(); // select the whole text of the combo box editor QLineEdit* lineEdit = navigator->editor()->lineEdit(); // krazy:exclude=qclasses lineEdit->selectAll(); } void DolphinMainWindow::togglePanelLockState() { const bool newLockState = !GeneralSettings::lockPanels(); foreach (QObject* child, children()) { DolphinDockWidget* dock = qobject_cast(child); if (dock) { dock->setLocked(newLockState); } } GeneralSettings::setLockPanels(newLockState); } void DolphinMainWindow::goBack() { KUrlNavigator* urlNavigator = m_activeViewContainer->urlNavigator(); urlNavigator->goBack(); if (urlNavigator->locationState().isEmpty()) { // An empty location state indicates a redirection URL, // which must be skipped too urlNavigator->goBack(); } } void DolphinMainWindow::goForward() { m_activeViewContainer->urlNavigator()->goForward(); } void DolphinMainWindow::goUp() { m_activeViewContainer->urlNavigator()->goUp(); } void DolphinMainWindow::goHome() { m_activeViewContainer->urlNavigator()->goHome(); } void DolphinMainWindow::goBack(Qt::MouseButtons buttons) { // The default case (left button pressed) is handled in goBack(). if (buttons == Qt::MiddleButton) { KUrlNavigator* urlNavigator = activeViewContainer()->urlNavigator(); const int index = urlNavigator->historyIndex() + 1; openNewTab(urlNavigator->locationUrl(index)); } } void DolphinMainWindow::goForward(Qt::MouseButtons buttons) { // The default case (left button pressed) is handled in goForward(). if (buttons == Qt::MiddleButton) { KUrlNavigator* urlNavigator = activeViewContainer()->urlNavigator(); const int index = urlNavigator->historyIndex() - 1; openNewTab(urlNavigator->locationUrl(index)); } } void DolphinMainWindow::goUp(Qt::MouseButtons buttons) { // The default case (left button pressed) is handled in goUp(). if (buttons == Qt::MiddleButton) { openNewTab(KIO::upUrl(activeViewContainer()->url())); } } void DolphinMainWindow::goHome(Qt::MouseButtons buttons) { // The default case (left button pressed) is handled in goHome(). if (buttons == Qt::MiddleButton) { openNewTab(Dolphin::homeUrl()); } } void DolphinMainWindow::compareFiles() { const KFileItemList items = m_tabWidget->currentTabPage()->selectedItems(); if (items.count() != 2) { // The action is disabled in this case, but it could have been triggered // via D-Bus, see https://bugs.kde.org/show_bug.cgi?id=325517 return; } QUrl urlA = items.at(0).url(); QUrl urlB = items.at(1).url(); QString command(QStringLiteral("kompare -c \"")); command.append(urlA.toDisplayString(QUrl::PreferLocalFile)); command.append("\" \""); command.append(urlB.toDisplayString(QUrl::PreferLocalFile)); command.append('\"'); KRun::runCommand(command, QStringLiteral("Kompare"), QStringLiteral("kompare"), this); } void DolphinMainWindow::toggleShowMenuBar() { const bool visible = menuBar()->isVisible(); menuBar()->setVisible(!visible); if (visible) { createControlButton(); } else { deleteControlButton(); } } void DolphinMainWindow::openTerminal() { QString dir(QDir::homePath()); // If the given directory is not local, it can still be the URL of an // ioslave using UDS_LOCAL_PATH which to be converted first. KIO::StatJob* statJob = KIO::mostLocalUrl(m_activeViewContainer->url()); KJobWidgets::setWindow(statJob, this); statJob->exec(); QUrl url = statJob->mostLocalUrl(); //If the URL is local after the above conversion, set the directory. if (url.isLocalFile()) { dir = url.toLocalFile(); } KToolInvocation::invokeTerminal(QString(), dir); } void DolphinMainWindow::editSettings() { if (!m_settingsDialog) { DolphinViewContainer* container = activeViewContainer(); container->view()->writeSettings(); const QUrl url = container->url(); DolphinSettingsDialog* settingsDialog = new DolphinSettingsDialog(url, this); connect(settingsDialog, &DolphinSettingsDialog::settingsChanged, this, &DolphinMainWindow::refreshViews); settingsDialog->setAttribute(Qt::WA_DeleteOnClose); settingsDialog->show(); m_settingsDialog = settingsDialog; } else { m_settingsDialog.data()->raise(); } } void DolphinMainWindow::handleUrl(const QUrl& url) { delete m_lastHandleUrlStatJob; m_lastHandleUrlStatJob = 0; if (url.isLocalFile() && QFileInfo(url.toLocalFile()).isDir()) { activeViewContainer()->setUrl(url); } else if (KProtocolManager::supportsListing(url)) { // stat the URL to see if it is a dir or not m_lastHandleUrlStatJob = KIO::stat(url, KIO::HideProgressInfo); if (m_lastHandleUrlStatJob->uiDelegate()) { KJobWidgets::setWindow(m_lastHandleUrlStatJob, this); } connect(m_lastHandleUrlStatJob, &KIO::Job::result, this, &DolphinMainWindow::slotHandleUrlStatFinished); } else { new KRun(url, this); // Automatically deletes itself after being finished } } void DolphinMainWindow::slotHandleUrlStatFinished(KJob* job) { m_lastHandleUrlStatJob = 0; const KIO::UDSEntry entry = static_cast(job)->statResult(); const QUrl url = static_cast(job)->url(); if (entry.isDir()) { activeViewContainer()->setUrl(url); } else { new KRun(url, this); // Automatically deletes itself after being finished } } void DolphinMainWindow::slotWriteStateChanged(bool isFolderWritable) { newFileMenu()->setEnabled(isFolderWritable); } void DolphinMainWindow::openContextMenu(const QPoint& pos, const KFileItem& item, const QUrl& url, const QList& customActions) { QPointer contextMenu = new DolphinContextMenu(this, pos, item, url); contextMenu.data()->setCustomActions(customActions); const DolphinContextMenu::Command command = contextMenu.data()->open(); switch (command) { case DolphinContextMenu::OpenParentFolder: changeUrl(KIO::upUrl(item.url())); break; case DolphinContextMenu::OpenParentFolderInNewWindow: Dolphin::openNewWindow({KIO::upUrl(item.url())}, this); break; case DolphinContextMenu::OpenParentFolderInNewTab: openNewTab(KIO::upUrl(item.url())); break; case DolphinContextMenu::None: default: break; } // Delete the menu, unless it has been deleted in its own nested event loop already. if (contextMenu) { contextMenu->deleteLater(); } } void DolphinMainWindow::updateControlMenu() { QMenu* menu = qobject_cast(sender()); Q_ASSERT(menu); // All actions get cleared by QMenu::clear(). This includes the sub-menus // because 'menu' is their parent. menu->clear(); KActionCollection* ac = actionCollection(); // Add "Edit" actions bool added = addActionToMenu(ac->action(KStandardAction::name(KStandardAction::Undo)), menu) | addActionToMenu(ac->action(KStandardAction::name(KStandardAction::Find)), menu) | addActionToMenu(ac->action(QStringLiteral("select_all")), menu) | addActionToMenu(ac->action(QStringLiteral("invert_selection")), menu); if (added) { menu->addSeparator(); } // Add "View" actions if (!GeneralSettings::showZoomSlider()) { addActionToMenu(ac->action(KStandardAction::name(KStandardAction::ZoomIn)), menu); addActionToMenu(ac->action(KStandardAction::name(KStandardAction::ZoomOut)), menu); menu->addSeparator(); } added = addActionToMenu(ac->action(QStringLiteral("view_mode")), menu) | addActionToMenu(ac->action(QStringLiteral("sort")), menu) | addActionToMenu(ac->action(QStringLiteral("additional_info")), menu) | addActionToMenu(ac->action(QStringLiteral("show_preview")), menu) | addActionToMenu(ac->action(QStringLiteral("show_in_groups")), menu) | addActionToMenu(ac->action(QStringLiteral("show_hidden_files")), menu); if (added) { menu->addSeparator(); } added = addActionToMenu(ac->action(QStringLiteral("split_view")), menu) | addActionToMenu(ac->action(QStringLiteral("reload")), menu) | addActionToMenu(ac->action(QStringLiteral("view_properties")), menu); if (added) { menu->addSeparator(); } addActionToMenu(ac->action(QStringLiteral("panels")), menu); QMenu* locationBarMenu = new QMenu(i18nc("@action:inmenu", "Location Bar"), menu); locationBarMenu->addAction(ac->action(QStringLiteral("editable_location"))); locationBarMenu->addAction(ac->action(QStringLiteral("replace_location"))); menu->addMenu(locationBarMenu); menu->addSeparator(); // Add "Go" menu QMenu* goMenu = new QMenu(i18nc("@action:inmenu", "Go"), menu); goMenu->addAction(ac->action(KStandardAction::name(KStandardAction::Back))); goMenu->addAction(ac->action(KStandardAction::name(KStandardAction::Forward))); goMenu->addAction(ac->action(KStandardAction::name(KStandardAction::Up))); goMenu->addAction(ac->action(KStandardAction::name(KStandardAction::Home))); goMenu->addAction(ac->action(QStringLiteral("closed_tabs"))); menu->addMenu(goMenu); // Add "Tool" menu QMenu* toolsMenu = new QMenu(i18nc("@action:inmenu", "Tools"), menu); toolsMenu->addAction(ac->action(QStringLiteral("show_filter_bar"))); toolsMenu->addAction(ac->action(QStringLiteral("compare_files"))); toolsMenu->addAction(ac->action(QStringLiteral("open_terminal"))); toolsMenu->addAction(ac->action(QStringLiteral("change_remote_encoding"))); menu->addMenu(toolsMenu); // Add "Settings" menu entries addActionToMenu(ac->action(KStandardAction::name(KStandardAction::KeyBindings)), menu); addActionToMenu(ac->action(KStandardAction::name(KStandardAction::ConfigureToolbars)), menu); addActionToMenu(ac->action(KStandardAction::name(KStandardAction::Preferences)), menu); // Add "Help" menu QMenu* helpMenu = new QMenu(i18nc("@action:inmenu", "Help"), menu); helpMenu->addAction(ac->action(KStandardAction::name(KStandardAction::HelpContents))); helpMenu->addAction(ac->action(KStandardAction::name(KStandardAction::WhatsThis))); helpMenu->addSeparator(); helpMenu->addAction(ac->action(KStandardAction::name(KStandardAction::ReportBug))); helpMenu->addSeparator(); helpMenu->addAction(ac->action(KStandardAction::name(KStandardAction::Donate))); helpMenu->addSeparator(); helpMenu->addAction(ac->action(KStandardAction::name(KStandardAction::SwitchApplicationLanguage))); helpMenu->addSeparator(); helpMenu->addAction(ac->action(KStandardAction::name(KStandardAction::AboutApp))); helpMenu->addAction(ac->action(KStandardAction::name(KStandardAction::AboutKDE))); menu->addMenu(helpMenu); menu->addSeparator(); addActionToMenu(ac->action(KStandardAction::name(KStandardAction::ShowMenubar)), menu); } void DolphinMainWindow::updateToolBar() { if (!menuBar()->isVisible()) { createControlButton(); } } void DolphinMainWindow::slotControlButtonDeleted() { m_controlButton = 0; m_updateToolBarTimer->start(); } void DolphinMainWindow::slotPlaceActivated(const QUrl& url) { DolphinViewContainer* view = activeViewContainer(); if (view->url() == url) { // We can end up here if the user clicked a device in the Places Panel // which had been unmounted earlier, see https://bugs.kde.org/show_bug.cgi?id=161385. reloadView(); } else { changeUrl(url); } } void DolphinMainWindow::closedTabsCountChanged(unsigned int count) { actionCollection()->action(QStringLiteral("undo_close_tab"))->setEnabled(count > 0); } void DolphinMainWindow::activeViewChanged(DolphinViewContainer* viewContainer) { DolphinViewContainer* oldViewContainer = m_activeViewContainer; Q_ASSERT(viewContainer); m_activeViewContainer = viewContainer; if (oldViewContainer) { // Disconnect all signals between the old view container (container, // view and url navigator) and main window. oldViewContainer->disconnect(this); oldViewContainer->view()->disconnect(this); oldViewContainer->urlNavigator()->disconnect(this); } connectViewSignals(viewContainer); m_actionHandler->setCurrentView(viewContainer->view()); updateHistory(); updateEditActions(); updatePasteAction(); updateViewActions(); updateGoActions(); const QUrl url = viewContainer->url(); emit urlChanged(url); } void DolphinMainWindow::tabCountChanged(int count) { const bool enableTabActions = (count > 1); actionCollection()->action(QStringLiteral("close_tab"))->setEnabled(enableTabActions); actionCollection()->action(QStringLiteral("activate_next_tab"))->setEnabled(enableTabActions); actionCollection()->action(QStringLiteral("activate_prev_tab"))->setEnabled(enableTabActions); } void DolphinMainWindow::setUrlAsCaption(const QUrl& url) { QString caption; if (!url.isLocalFile()) { caption.append(url.scheme() + " - "); if (!url.host().isEmpty()) { caption.append(url.host() + " - "); } } if (GeneralSettings::showFullPathInTitlebar()) { const QString path = url.adjusted(QUrl::StripTrailingSlash).path(); caption.append(path); } else { QString fileName = url.adjusted(QUrl::StripTrailingSlash).fileName(); if (fileName.isEmpty()) { fileName = '/'; } caption.append(fileName); } setWindowTitle(caption); } void DolphinMainWindow::setupActions() { // setup 'File' menu m_newFileMenu = new DolphinNewFileMenu(actionCollection(), this); QMenu* menu = m_newFileMenu->menu(); menu->setTitle(i18nc("@title:menu Create new folder, file, link, etc.", "Create New")); menu->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); m_newFileMenu->setDelayed(false); connect(menu, &QMenu::aboutToShow, this, &DolphinMainWindow::updateNewMenu); QAction* newWindow = actionCollection()->addAction(QStringLiteral("new_window")); newWindow->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); newWindow->setText(i18nc("@action:inmenu File", "New &Window")); actionCollection()->setDefaultShortcut(newWindow, Qt::CTRL | Qt::Key_N); connect(newWindow, &QAction::triggered, this, &DolphinMainWindow::openNewMainWindow); QAction* newTab = actionCollection()->addAction(QStringLiteral("new_tab")); newTab->setIcon(QIcon::fromTheme(QStringLiteral("tab-new"))); newTab->setText(i18nc("@action:inmenu File", "New Tab")); actionCollection()->setDefaultShortcuts(newTab, {Qt::CTRL | Qt::Key_T, Qt::CTRL | Qt::SHIFT | Qt::Key_N}); connect(newTab, &QAction::triggered, this, static_cast(&DolphinMainWindow::openNewActivatedTab)); QAction* closeTab = actionCollection()->addAction(QStringLiteral("close_tab")); closeTab->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); closeTab->setText(i18nc("@action:inmenu File", "Close Tab")); actionCollection()->setDefaultShortcut(closeTab, Qt::CTRL | Qt::Key_W); closeTab->setEnabled(false); connect(closeTab, &QAction::triggered, m_tabWidget, static_cast(&DolphinTabWidget::closeTab)); KStandardAction::quit(this, SLOT(quit()), actionCollection()); // setup 'Edit' menu KStandardAction::undo(this, SLOT(undo()), actionCollection()); KStandardAction::cut(this, SLOT(cut()), actionCollection()); KStandardAction::copy(this, SLOT(copy()), actionCollection()); QAction* paste = KStandardAction::paste(this, SLOT(paste()), actionCollection()); // The text of the paste-action is modified dynamically by Dolphin // (e. g. to "Paste One Folder"). To prevent that the size of the toolbar changes // due to the long text, the text "Paste" is used: paste->setIconText(i18nc("@action:inmenu Edit", "Paste")); KStandardAction::find(this, SLOT(find()), actionCollection()); QAction* selectAll = actionCollection()->addAction(QStringLiteral("select_all")); selectAll->setText(i18nc("@action:inmenu Edit", "Select All")); actionCollection()->setDefaultShortcut(selectAll, Qt::CTRL | Qt::Key_A); connect(selectAll, &QAction::triggered, this, &DolphinMainWindow::selectAll); QAction* invertSelection = actionCollection()->addAction(QStringLiteral("invert_selection")); invertSelection->setText(i18nc("@action:inmenu Edit", "Invert Selection")); actionCollection()->setDefaultShortcut(invertSelection, Qt::CTRL | Qt::SHIFT | Qt::Key_A); connect(invertSelection, &QAction::triggered, this, &DolphinMainWindow::invertSelection); // setup 'View' menu // (note that most of it is set up in DolphinViewActionHandler) QAction* split = actionCollection()->addAction(QStringLiteral("split_view")); actionCollection()->setDefaultShortcut(split, Qt::Key_F3); connect(split, &QAction::triggered, this, &DolphinMainWindow::toggleSplitView); QAction* stashSplit = actionCollection()->addAction(QStringLiteral("split_stash")); actionCollection()->setDefaultShortcut(stashSplit, Qt::CTRL | Qt::Key_S); stashSplit->setText(i18nc("@action:intoolbar Stash", "Stash")); stashSplit->setToolTip(i18nc("@info", "Opens the stash virtual directory in a split window")); stashSplit->setIcon(QIcon::fromTheme(QStringLiteral("folder-visiting"))); stashSplit->setCheckable(false); stashSplit->setVisible(KProtocolInfo::isKnownProtocol("stash")); connect(stashSplit, &QAction::triggered, this, &DolphinMainWindow::toggleSplitStash); QAction* reload = actionCollection()->addAction(QStringLiteral("reload")); reload->setText(i18nc("@action:inmenu View", "Reload")); actionCollection()->setDefaultShortcut(reload, Qt::Key_F5); reload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect(reload, &QAction::triggered, this, &DolphinMainWindow::reloadView); QAction* stop = actionCollection()->addAction(QStringLiteral("stop")); stop->setText(i18nc("@action:inmenu View", "Stop")); stop->setToolTip(i18nc("@info", "Stop loading")); stop->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); connect(stop, &QAction::triggered, this, &DolphinMainWindow::stopLoading); KToggleAction* editableLocation = actionCollection()->add(QStringLiteral("editable_location")); editableLocation->setText(i18nc("@action:inmenu Navigation Bar", "Editable Location")); actionCollection()->setDefaultShortcut(editableLocation, Qt::Key_F6); connect(editableLocation, &KToggleAction::triggered, this, &DolphinMainWindow::toggleEditLocation); QAction* replaceLocation = actionCollection()->addAction(QStringLiteral("replace_location")); replaceLocation->setText(i18nc("@action:inmenu Navigation Bar", "Replace Location")); actionCollection()->setDefaultShortcut(replaceLocation, Qt::CTRL | Qt::Key_L); connect(replaceLocation, &QAction::triggered, this, &DolphinMainWindow::replaceLocation); // setup 'Go' menu QAction* backAction = KStandardAction::back(this, SLOT(goBack()), actionCollection()); auto backShortcuts = backAction->shortcuts(); backShortcuts.append(QKeySequence(Qt::Key_Backspace)); actionCollection()->setDefaultShortcuts(backAction, backShortcuts); DolphinRecentTabsMenu* recentTabsMenu = new DolphinRecentTabsMenu(this); actionCollection()->addAction(QStringLiteral("closed_tabs"), recentTabsMenu); connect(m_tabWidget, &DolphinTabWidget::rememberClosedTab, recentTabsMenu, &DolphinRecentTabsMenu::rememberClosedTab); connect(recentTabsMenu, &DolphinRecentTabsMenu::restoreClosedTab, m_tabWidget, &DolphinTabWidget::restoreClosedTab); connect(recentTabsMenu, &DolphinRecentTabsMenu::closedTabsCountChanged, this, &DolphinMainWindow::closedTabsCountChanged); QAction* undoCloseTab = actionCollection()->addAction(QStringLiteral("undo_close_tab")); undoCloseTab->setText(i18nc("@action:inmenu File", "Undo close tab")); actionCollection()->setDefaultShortcut(undoCloseTab, Qt::CTRL | Qt::SHIFT | Qt::Key_T); undoCloseTab->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo"))); undoCloseTab->setEnabled(false); connect(undoCloseTab, &QAction::triggered, recentTabsMenu, &DolphinRecentTabsMenu::undoCloseTab); auto undoAction = actionCollection()->action(KStandardAction::name(KStandardAction::Undo)); undoAction->setEnabled(false); // undo should be disabled by default KStandardAction::forward(this, SLOT(goForward()), actionCollection()); KStandardAction::up(this, SLOT(goUp()), actionCollection()); KStandardAction::home(this, SLOT(goHome()), actionCollection()); // setup 'Tools' menu QAction* showFilterBar = actionCollection()->addAction(QStringLiteral("show_filter_bar")); showFilterBar->setText(i18nc("@action:inmenu Tools", "Show Filter Bar")); showFilterBar->setIcon(QIcon::fromTheme(QStringLiteral("view-filter"))); actionCollection()->setDefaultShortcut(showFilterBar, Qt::CTRL | Qt::Key_I); connect(showFilterBar, &QAction::triggered, this, &DolphinMainWindow::showFilterBar); QAction* compareFiles = actionCollection()->addAction(QStringLiteral("compare_files")); compareFiles->setText(i18nc("@action:inmenu Tools", "Compare Files")); compareFiles->setIcon(QIcon::fromTheme(QStringLiteral("kompare"))); compareFiles->setEnabled(false); connect(compareFiles, &QAction::triggered, this, &DolphinMainWindow::compareFiles); if (KAuthorized::authorize(QStringLiteral("shell_access"))) { QAction* openTerminal = actionCollection()->addAction(QStringLiteral("open_terminal")); openTerminal->setText(i18nc("@action:inmenu Tools", "Open Terminal")); openTerminal->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal"))); actionCollection()->setDefaultShortcut(openTerminal, Qt::SHIFT | Qt::Key_F4); connect(openTerminal, &QAction::triggered, this, &DolphinMainWindow::openTerminal); } // setup 'Settings' menu KToggleAction* showMenuBar = KStandardAction::showMenubar(0, 0, actionCollection()); connect(showMenuBar, &KToggleAction::triggered, // Fixes #286822 this, &DolphinMainWindow::toggleShowMenuBar, Qt::QueuedConnection); KStandardAction::preferences(this, SLOT(editSettings()), actionCollection()); // not in menu actions QList nextTabKeys = KStandardShortcut::tabNext(); nextTabKeys.append(QKeySequence(Qt::CTRL | Qt::Key_Tab)); QList prevTabKeys = KStandardShortcut::tabPrev(); prevTabKeys.append(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Tab)); QAction* activateNextTab = actionCollection()->addAction(QStringLiteral("activate_next_tab")); activateNextTab->setIconText(i18nc("@action:inmenu", "Next Tab")); activateNextTab->setText(i18nc("@action:inmenu", "Activate Next Tab")); activateNextTab->setEnabled(false); connect(activateNextTab, &QAction::triggered, m_tabWidget, &DolphinTabWidget::activateNextTab); actionCollection()->setDefaultShortcuts(activateNextTab, nextTabKeys); QAction* activatePrevTab = actionCollection()->addAction(QStringLiteral("activate_prev_tab")); activatePrevTab->setIconText(i18nc("@action:inmenu", "Previous Tab")); activatePrevTab->setText(i18nc("@action:inmenu", "Activate Previous Tab")); activatePrevTab->setEnabled(false); connect(activatePrevTab, &QAction::triggered, m_tabWidget, &DolphinTabWidget::activatePrevTab); actionCollection()->setDefaultShortcuts(activatePrevTab, prevTabKeys); // for context menu QAction* openInNewTab = actionCollection()->addAction(QStringLiteral("open_in_new_tab")); openInNewTab->setText(i18nc("@action:inmenu", "Open in New Tab")); openInNewTab->setIcon(QIcon::fromTheme(QStringLiteral("tab-new"))); connect(openInNewTab, &QAction::triggered, this, &DolphinMainWindow::openInNewTab); QAction* openInNewTabs = actionCollection()->addAction(QStringLiteral("open_in_new_tabs")); openInNewTabs->setText(i18nc("@action:inmenu", "Open in New Tabs")); openInNewTabs->setIcon(QIcon::fromTheme(QStringLiteral("tab-new"))); connect(openInNewTabs, &QAction::triggered, this, &DolphinMainWindow::openInNewTab); QAction* openInNewWindow = actionCollection()->addAction(QStringLiteral("open_in_new_window")); openInNewWindow->setText(i18nc("@action:inmenu", "Open in New Window")); openInNewWindow->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); connect(openInNewWindow, &QAction::triggered, this, &DolphinMainWindow::openInNewWindow); } void DolphinMainWindow::setupDockWidgets() { const bool lock = GeneralSettings::lockPanels(); KDualAction* lockLayoutAction = actionCollection()->add(QStringLiteral("lock_panels")); lockLayoutAction->setActiveText(i18nc("@action:inmenu Panels", "Unlock Panels")); lockLayoutAction->setActiveIcon(QIcon::fromTheme(QStringLiteral("object-unlocked"))); lockLayoutAction->setInactiveText(i18nc("@action:inmenu Panels", "Lock Panels")); lockLayoutAction->setInactiveIcon(QIcon::fromTheme(QStringLiteral("object-locked"))); lockLayoutAction->setActive(lock); connect(lockLayoutAction, &KDualAction::triggered, this, &DolphinMainWindow::togglePanelLockState); // Setup "Information" DolphinDockWidget* infoDock = new DolphinDockWidget(i18nc("@title:window", "Information")); infoDock->setLocked(lock); infoDock->setObjectName(QStringLiteral("infoDock")); infoDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); InformationPanel* infoPanel = new InformationPanel(infoDock); infoPanel->setCustomContextMenuActions({lockLayoutAction}); connect(infoPanel, &InformationPanel::urlActivated, this, &DolphinMainWindow::handleUrl); infoDock->setWidget(infoPanel); QAction* infoAction = infoDock->toggleViewAction(); createPanelAction(QIcon::fromTheme(QStringLiteral("dialog-information")), Qt::Key_F11, infoAction, QStringLiteral("show_information_panel")); addDockWidget(Qt::RightDockWidgetArea, infoDock); connect(this, &DolphinMainWindow::urlChanged, infoPanel, &InformationPanel::setUrl); connect(this, &DolphinMainWindow::selectionChanged, infoPanel, &InformationPanel::setSelection); connect(this, &DolphinMainWindow::requestItemInfo, infoPanel, &InformationPanel::requestDelayedItemInfo); // Setup "Folders" DolphinDockWidget* foldersDock = new DolphinDockWidget(i18nc("@title:window", "Folders")); foldersDock->setLocked(lock); foldersDock->setObjectName(QStringLiteral("foldersDock")); foldersDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); FoldersPanel* foldersPanel = new FoldersPanel(foldersDock); foldersPanel->setCustomContextMenuActions({lockLayoutAction}); foldersDock->setWidget(foldersPanel); QAction* foldersAction = foldersDock->toggleViewAction(); createPanelAction(QIcon::fromTheme(QStringLiteral("folder")), Qt::Key_F7, foldersAction, QStringLiteral("show_folders_panel")); addDockWidget(Qt::LeftDockWidgetArea, foldersDock); connect(this, &DolphinMainWindow::urlChanged, foldersPanel, &FoldersPanel::setUrl); connect(foldersPanel, &FoldersPanel::folderActivated, this, &DolphinMainWindow::changeUrl); connect(foldersPanel, &FoldersPanel::folderMiddleClicked, this, &DolphinMainWindow::openNewTab); connect(foldersPanel, &FoldersPanel::errorMessage, this, &DolphinMainWindow::showErrorMessage); // Setup "Terminal" #ifndef Q_OS_WIN if (KAuthorized::authorize(QStringLiteral("shell_access"))) { DolphinDockWidget* terminalDock = new DolphinDockWidget(i18nc("@title:window Shell terminal", "Terminal")); terminalDock->setLocked(lock); terminalDock->setObjectName(QStringLiteral("terminalDock")); terminalDock->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea); TerminalPanel* terminalPanel = new TerminalPanel(terminalDock); terminalPanel->setCustomContextMenuActions({lockLayoutAction}); terminalDock->setWidget(terminalPanel); connect(terminalPanel, &TerminalPanel::hideTerminalPanel, terminalDock, &DolphinDockWidget::hide); connect(terminalPanel, &TerminalPanel::changeUrl, this, &DolphinMainWindow::slotTerminalDirectoryChanged); connect(terminalDock, &DolphinDockWidget::visibilityChanged, terminalPanel, &TerminalPanel::dockVisibilityChanged); QAction* terminalAction = terminalDock->toggleViewAction(); createPanelAction(QIcon::fromTheme(QStringLiteral("utilities-terminal")), Qt::Key_F4, terminalAction, QStringLiteral("show_terminal_panel")); addDockWidget(Qt::BottomDockWidgetArea, terminalDock); connect(this, &DolphinMainWindow::urlChanged, terminalPanel, &TerminalPanel::setUrl); if (GeneralSettings::version() < 200) { terminalDock->hide(); } } #endif if (GeneralSettings::version() < 200) { infoDock->hide(); foldersDock->hide(); } // Setup "Places" DolphinDockWidget* placesDock = new DolphinDockWidget(i18nc("@title:window", "Places")); placesDock->setLocked(lock); placesDock->setObjectName(QStringLiteral("placesDock")); placesDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); PlacesPanel* placesPanel = new PlacesPanel(placesDock); placesPanel->setCustomContextMenuActions({lockLayoutAction}); placesDock->setWidget(placesPanel); QAction* placesAction = placesDock->toggleViewAction(); createPanelAction(QIcon::fromTheme(QStringLiteral("bookmarks")), Qt::Key_F9, placesAction, QStringLiteral("show_places_panel")); addDockWidget(Qt::LeftDockWidgetArea, placesDock); connect(placesPanel, &PlacesPanel::placeActivated, this, &DolphinMainWindow::slotPlaceActivated); connect(placesPanel, &PlacesPanel::placeMiddleClicked, this, &DolphinMainWindow::openNewTab); connect(placesPanel, &PlacesPanel::errorMessage, this, &DolphinMainWindow::showErrorMessage); connect(this, &DolphinMainWindow::urlChanged, placesPanel, &PlacesPanel::setUrl); connect(placesDock, &DolphinDockWidget::visibilityChanged, m_tabWidget, &DolphinTabWidget::slotPlacesPanelVisibilityChanged); connect(this, &DolphinMainWindow::settingsChanged, placesPanel, &PlacesPanel::readSettings); m_tabWidget->slotPlacesPanelVisibilityChanged(placesPanel->isVisible()); // Add actions into the "Panels" menu KActionMenu* panelsMenu = new KActionMenu(i18nc("@action:inmenu View", "Panels"), this); actionCollection()->addAction(QStringLiteral("panels"), panelsMenu); panelsMenu->setDelayed(false); const KActionCollection* ac = actionCollection(); panelsMenu->addAction(ac->action(QStringLiteral("show_places_panel"))); panelsMenu->addAction(ac->action(QStringLiteral("show_information_panel"))); panelsMenu->addAction(ac->action(QStringLiteral("show_folders_panel"))); #ifndef Q_OS_WIN panelsMenu->addAction(ac->action(QStringLiteral("show_terminal_panel"))); #endif panelsMenu->addSeparator(); panelsMenu->addAction(lockLayoutAction); } void DolphinMainWindow::updateEditActions() { const KFileItemList list = m_activeViewContainer->view()->selectedItems(); if (list.isEmpty()) { stateChanged(QStringLiteral("has_no_selection")); } else { stateChanged(QStringLiteral("has_selection")); KActionCollection* col = actionCollection(); QAction* renameAction = col->action(QStringLiteral("rename")); QAction* moveToTrashAction = col->action(QStringLiteral("move_to_trash")); QAction* deleteAction = col->action(KStandardAction::name(KStandardAction::DeleteFile)); QAction* cutAction = col->action(KStandardAction::name(KStandardAction::Cut)); QAction* deleteWithTrashShortcut = col->action(QStringLiteral("delete_shortcut")); // see DolphinViewActionHandler KFileItemListProperties capabilities(list); const bool enableMoveToTrash = capabilities.isLocal() && capabilities.supportsMoving(); renameAction->setEnabled(capabilities.supportsMoving()); moveToTrashAction->setEnabled(enableMoveToTrash); deleteAction->setEnabled(capabilities.supportsDeleting()); deleteWithTrashShortcut->setEnabled(capabilities.supportsDeleting() && !enableMoveToTrash); cutAction->setEnabled(capabilities.supportsMoving()); } } void DolphinMainWindow::updateViewActions() { m_actionHandler->updateViewActions(); QAction* showFilterBarAction = actionCollection()->action(QStringLiteral("show_filter_bar")); showFilterBarAction->setChecked(m_activeViewContainer->isFilterBarVisible()); updateSplitAction(); QAction* editableLocactionAction = actionCollection()->action(QStringLiteral("editable_location")); const KUrlNavigator* urlNavigator = m_activeViewContainer->urlNavigator(); editableLocactionAction->setChecked(urlNavigator->isUrlEditable()); } void DolphinMainWindow::updateGoActions() { QAction* goUpAction = actionCollection()->action(KStandardAction::name(KStandardAction::Up)); const QUrl currentUrl = m_activeViewContainer->url(); goUpAction->setEnabled(KIO::upUrl(currentUrl) != currentUrl); } void DolphinMainWindow::createControlButton() { if (m_controlButton) { return; } Q_ASSERT(!m_controlButton); m_controlButton = new QToolButton(this); m_controlButton->setIcon(QIcon::fromTheme(QStringLiteral("application-menu"))); m_controlButton->setText(i18nc("@action", "Control")); m_controlButton->setPopupMode(QToolButton::InstantPopup); m_controlButton->setToolButtonStyle(toolBar()->toolButtonStyle()); QMenu* controlMenu = new QMenu(m_controlButton); connect(controlMenu, &QMenu::aboutToShow, this, &DolphinMainWindow::updateControlMenu); m_controlButton->setMenu(controlMenu); toolBar()->addWidget(m_controlButton); connect(toolBar(), &KToolBar::iconSizeChanged, m_controlButton, &QToolButton::setIconSize); connect(toolBar(), &KToolBar::toolButtonStyleChanged, m_controlButton, &QToolButton::setToolButtonStyle); // The added widgets are owned by the toolbar and may get deleted when e.g. the toolbar // gets edited. In this case we must add them again. The adding is done asynchronously by // m_updateToolBarTimer. connect(m_controlButton, &QToolButton::destroyed, this, &DolphinMainWindow::slotControlButtonDeleted); m_updateToolBarTimer = new QTimer(this); m_updateToolBarTimer->setInterval(500); connect(m_updateToolBarTimer, &QTimer::timeout, this, &DolphinMainWindow::updateToolBar); } void DolphinMainWindow::deleteControlButton() { delete m_controlButton; m_controlButton = 0; delete m_updateToolBarTimer; m_updateToolBarTimer = 0; } bool DolphinMainWindow::addActionToMenu(QAction* action, QMenu* menu) { Q_ASSERT(action); Q_ASSERT(menu); const KToolBar* toolBarWidget = toolBar(); foreach (const QWidget* widget, action->associatedWidgets()) { if (widget == toolBarWidget) { return false; } } menu->addAction(action); return true; } void DolphinMainWindow::refreshViews() { m_tabWidget->refreshViews(); if (GeneralSettings::modifiedStartupSettings()) { // The startup settings have been changed by the user (see bug #254947). // Synchronize the split-view setting with the active view: const bool splitView = GeneralSettings::splitView(); m_tabWidget->currentTabPage()->setSplitViewEnabled(splitView); updateSplitAction(); setUrlAsCaption(activeViewContainer()->url()); } emit settingsChanged(); } void DolphinMainWindow::clearStatusBar() { m_activeViewContainer->statusBar()->resetToDefaultText(); } void DolphinMainWindow::connectViewSignals(DolphinViewContainer* container) { connect(container, &DolphinViewContainer::showFilterBarChanged, this, &DolphinMainWindow::updateFilterBarAction); connect(container, &DolphinViewContainer::writeStateChanged, this, &DolphinMainWindow::slotWriteStateChanged); const DolphinView* view = container->view(); connect(view, &DolphinView::selectionChanged, this, &DolphinMainWindow::slotSelectionChanged); connect(view, &DolphinView::requestItemInfo, this, &DolphinMainWindow::requestItemInfo); connect(view, &DolphinView::tabRequested, this, &DolphinMainWindow::openNewTab); connect(view, &DolphinView::requestContextMenu, this, &DolphinMainWindow::openContextMenu); connect(view, &DolphinView::directoryLoadingStarted, this, &DolphinMainWindow::enableStopAction); connect(view, &DolphinView::directoryLoadingCompleted, this, &DolphinMainWindow::disableStopAction); connect(view, &DolphinView::directoryLoadingCompleted, this, &DolphinMainWindow::slotDirectoryLoadingCompleted); connect(view, &DolphinView::goBackRequested, this, static_cast(&DolphinMainWindow::goBack)); connect(view, &DolphinView::goForwardRequested, this, static_cast(&DolphinMainWindow::goForward)); connect(view, &DolphinView::urlActivated, this, &DolphinMainWindow::handleUrl); const KUrlNavigator* navigator = container->urlNavigator(); connect(navigator, &KUrlNavigator::urlChanged, this, &DolphinMainWindow::changeUrl); connect(navigator, &KUrlNavigator::historyChanged, this, &DolphinMainWindow::updateHistory); connect(navigator, &KUrlNavigator::editableStateChanged, this, &DolphinMainWindow::slotEditableStateChanged); connect(navigator, &KUrlNavigator::tabRequested, this, &DolphinMainWindow::openNewTab); } void DolphinMainWindow::updateSplitAction() { QAction* splitAction = actionCollection()->action(QStringLiteral("split_view")); const DolphinTabPage* tabPage = m_tabWidget->currentTabPage(); if (tabPage->splitViewEnabled()) { if (tabPage->primaryViewActive()) { splitAction->setText(i18nc("@action:intoolbar Close left view", "Close")); splitAction->setToolTip(i18nc("@info", "Close left view")); splitAction->setIcon(QIcon::fromTheme(QStringLiteral("view-left-close"))); } else { splitAction->setText(i18nc("@action:intoolbar Close right view", "Close")); splitAction->setToolTip(i18nc("@info", "Close right view")); splitAction->setIcon(QIcon::fromTheme(QStringLiteral("view-right-close"))); } } else { splitAction->setText(i18nc("@action:intoolbar Split view", "Split")); splitAction->setToolTip(i18nc("@info", "Split view")); splitAction->setIcon(QIcon::fromTheme(QStringLiteral("view-right-new"))); } } bool DolphinMainWindow::isKompareInstalled() const { static bool initialized = false; static bool installed = false; if (!initialized) { // TODO: maybe replace this approach later by using a menu // plugin like kdiff3plugin.cpp installed = !QStandardPaths::findExecutable(QStringLiteral("kompare")).isEmpty(); initialized = true; } return installed; } void DolphinMainWindow::createPanelAction(const QIcon& icon, const QKeySequence& shortcut, QAction* dockAction, const QString& actionName) { QAction* panelAction = actionCollection()->addAction(actionName); panelAction->setCheckable(true); panelAction->setChecked(dockAction->isChecked()); panelAction->setText(dockAction->text()); panelAction->setIcon(icon); actionCollection()->setDefaultShortcut(panelAction, shortcut); connect(panelAction, &QAction::triggered, dockAction, &QAction::trigger); connect(dockAction, &QAction::toggled, panelAction, &QAction::setChecked); } DolphinMainWindow::UndoUiInterface::UndoUiInterface() : KIO::FileUndoManager::UiInterface() { } DolphinMainWindow::UndoUiInterface::~UndoUiInterface() { } void DolphinMainWindow::UndoUiInterface::jobError(KIO::Job* job) { DolphinMainWindow* mainWin= qobject_cast(parentWidget()); if (mainWin) { DolphinViewContainer* container = mainWin->activeViewContainer(); container->showMessage(job->errorString(), DolphinViewContainer::Error); } else { KIO::FileUndoManager::UiInterface::jobError(job); } } diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp index a95969771..3731895d0 100644 --- a/src/kitemviews/kitemlistcontroller.cpp +++ b/src/kitemviews/kitemlistcontroller.cpp @@ -1,1328 +1,1328 @@ /*************************************************************************** * Copyright (C) 2011 by Peter Penz * * Copyright (C) 2012 by Frank Reininghaus * * * * Based on the Itemviews NG project from Trolltech Labs: * * http://qt.gitorious.org/qt-labs/itemviews-ng * * * * 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 program 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 program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "kitemlistcontroller.h" #include "kitemlistview.h" #include "kitemlistselectionmanager.h" #include "private/kitemlistrubberband.h" #include "private/kitemlistkeyboardsearchmanager.h" #include #include #include #include #include #include #include #include #include KItemListController::KItemListController(KItemModelBase* model, KItemListView* view, QObject* parent) : QObject(parent), m_singleClickActivationEnforced(false), m_selectionTogglePressed(false), m_clearSelectionIfItemsAreNotDragged(false), m_selectionBehavior(NoSelection), m_autoActivationBehavior(ActivationAndExpansion), m_mouseDoubleClickAction(ActivateItemOnly), m_model(0), m_view(0), m_selectionManager(new KItemListSelectionManager(this)), m_keyboardManager(new KItemListKeyboardSearchManager(this)), m_pressedIndex(-1), m_pressedMousePos(), m_autoActivationTimer(0), m_oldSelection(), m_keyboardAnchorIndex(-1), m_keyboardAnchorPos(0) { connect(m_keyboardManager, &KItemListKeyboardSearchManager::changeCurrentItem, this, &KItemListController::slotChangeCurrentItem); connect(m_selectionManager, &KItemListSelectionManager::currentChanged, m_keyboardManager, &KItemListKeyboardSearchManager::slotCurrentChanged); m_autoActivationTimer = new QTimer(this); m_autoActivationTimer->setSingleShot(true); m_autoActivationTimer->setInterval(-1); connect(m_autoActivationTimer, &QTimer::timeout, this, &KItemListController::slotAutoActivationTimeout); setModel(model); setView(view); } KItemListController::~KItemListController() { setView(0); Q_ASSERT(!m_view); setModel(0); Q_ASSERT(!m_model); } void KItemListController::setModel(KItemModelBase* model) { if (m_model == model) { return; } KItemModelBase* oldModel = m_model; if (oldModel) { oldModel->deleteLater(); } m_model = model; if (m_model) { m_model->setParent(this); } if (m_view) { m_view->setModel(m_model); } m_selectionManager->setModel(m_model); emit modelChanged(m_model, oldModel); } KItemModelBase* KItemListController::model() const { return m_model; } KItemListSelectionManager* KItemListController::selectionManager() const { return m_selectionManager; } void KItemListController::setView(KItemListView* view) { if (m_view == view) { return; } KItemListView* oldView = m_view; if (oldView) { disconnect(oldView, &KItemListView::scrollOffsetChanged, this, &KItemListController::slotViewScrollOffsetChanged); oldView->deleteLater(); } m_view = view; if (m_view) { m_view->setParent(this); m_view->setController(this); m_view->setModel(m_model); connect(m_view, &KItemListView::scrollOffsetChanged, this, &KItemListController::slotViewScrollOffsetChanged); updateExtendedSelectionRegion(); } emit viewChanged(m_view, oldView); } KItemListView* KItemListController::view() const { return m_view; } void KItemListController::setSelectionBehavior(SelectionBehavior behavior) { m_selectionBehavior = behavior; updateExtendedSelectionRegion(); } KItemListController::SelectionBehavior KItemListController::selectionBehavior() const { return m_selectionBehavior; } void KItemListController::setAutoActivationBehavior(AutoActivationBehavior behavior) { m_autoActivationBehavior = behavior; } KItemListController::AutoActivationBehavior KItemListController::autoActivationBehavior() const { return m_autoActivationBehavior; } void KItemListController::setMouseDoubleClickAction(MouseDoubleClickAction action) { m_mouseDoubleClickAction = action; } KItemListController::MouseDoubleClickAction KItemListController::mouseDoubleClickAction() const { return m_mouseDoubleClickAction; } void KItemListController::setAutoActivationDelay(int delay) { m_autoActivationTimer->setInterval(delay); } int KItemListController::autoActivationDelay() const { return m_autoActivationTimer->interval(); } void KItemListController::setSingleClickActivationEnforced(bool singleClick) { m_singleClickActivationEnforced = singleClick; } bool KItemListController::singleClickActivationEnforced() const { return m_singleClickActivationEnforced; } bool KItemListController::showEvent(QShowEvent* event) { Q_UNUSED(event); return false; } bool KItemListController::hideEvent(QHideEvent* event) { Q_UNUSED(event); return false; } bool KItemListController::keyPressEvent(QKeyEvent* event) { int index = m_selectionManager->currentItem(); int key = event->key(); // Handle the expanding/collapsing of items if (m_view->supportsItemExpanding() && m_model->isExpandable(index)) { if (key == Qt::Key_Right) { if (m_model->setExpanded(index, true)) { return true; } } else if (key == Qt::Key_Left) { if (m_model->setExpanded(index, false)) { return true; } } } const bool shiftPressed = event->modifiers() & Qt::ShiftModifier; const bool controlPressed = event->modifiers() & Qt::ControlModifier; const bool shiftOrControlPressed = shiftPressed || controlPressed; const int itemCount = m_model->count(); // For horizontal scroll orientation, transform // the arrow keys to simplify the event handling. if (m_view->scrollOrientation() == Qt::Horizontal) { switch (key) { case Qt::Key_Up: key = Qt::Key_Left; break; case Qt::Key_Down: key = Qt::Key_Right; break; case Qt::Key_Left: key = Qt::Key_Up; break; case Qt::Key_Right: key = Qt::Key_Down; break; default: break; } } const bool selectSingleItem = m_selectionBehavior != NoSelection && itemCount == 1 && (key == Qt::Key_Home || key == Qt::Key_End || key == Qt::Key_Up || key == Qt::Key_Down || key == Qt::Key_Left || key == Qt::Key_Right); if (selectSingleItem) { const int current = m_selectionManager->currentItem(); m_selectionManager->setSelected(current); return true; } switch (key) { case Qt::Key_Home: index = 0; m_keyboardAnchorIndex = index; m_keyboardAnchorPos = keyboardAnchorPos(index); break; case Qt::Key_End: index = itemCount - 1; m_keyboardAnchorIndex = index; m_keyboardAnchorPos = keyboardAnchorPos(index); break; case Qt::Key_Left: if (index > 0) { const int expandedParentsCount = m_model->expandedParentsCount(index); if (expandedParentsCount == 0) { --index; } else { // Go to the parent of the current item. do { --index; } while (index > 0 && m_model->expandedParentsCount(index) == expandedParentsCount); } m_keyboardAnchorIndex = index; m_keyboardAnchorPos = keyboardAnchorPos(index); } break; case Qt::Key_Right: if (index < itemCount - 1) { ++index; m_keyboardAnchorIndex = index; m_keyboardAnchorPos = keyboardAnchorPos(index); } break; case Qt::Key_Up: updateKeyboardAnchor(); index = previousRowIndex(index); break; case Qt::Key_Down: updateKeyboardAnchor(); index = nextRowIndex(index); break; case Qt::Key_PageUp: if (m_view->scrollOrientation() == Qt::Horizontal) { // The new current index should correspond to the first item in the current column. int newIndex = qMax(index - 1, 0); while (newIndex != index && m_view->itemRect(newIndex).topLeft().y() < m_view->itemRect(index).topLeft().y()) { index = newIndex; newIndex = qMax(index - 1, 0); } m_keyboardAnchorIndex = index; m_keyboardAnchorPos = keyboardAnchorPos(index); } else { const qreal currentItemBottom = m_view->itemRect(index).bottomLeft().y(); const qreal height = m_view->geometry().height(); // The new current item should be the first item in the current // column whose itemRect's top coordinate is larger than targetY. const qreal targetY = currentItemBottom - height; updateKeyboardAnchor(); int newIndex = previousRowIndex(index); do { index = newIndex; updateKeyboardAnchor(); newIndex = previousRowIndex(index); } while (m_view->itemRect(newIndex).topLeft().y() > targetY && newIndex != index); } break; case Qt::Key_PageDown: if (m_view->scrollOrientation() == Qt::Horizontal) { // The new current index should correspond to the last item in the current column. int newIndex = qMin(index + 1, m_model->count() - 1); while (newIndex != index && m_view->itemRect(newIndex).topLeft().y() > m_view->itemRect(index).topLeft().y()) { index = newIndex; newIndex = qMin(index + 1, m_model->count() - 1); } m_keyboardAnchorIndex = index; m_keyboardAnchorPos = keyboardAnchorPos(index); } else { const qreal currentItemTop = m_view->itemRect(index).topLeft().y(); const qreal height = m_view->geometry().height(); // The new current item should be the last item in the current // column whose itemRect's bottom coordinate is smaller than targetY. const qreal targetY = currentItemTop + height; updateKeyboardAnchor(); int newIndex = nextRowIndex(index); do { index = newIndex; updateKeyboardAnchor(); newIndex = nextRowIndex(index); } while (m_view->itemRect(newIndex).bottomLeft().y() < targetY && newIndex != index); } break; case Qt::Key_Enter: case Qt::Key_Return: { const KItemSet selectedItems = m_selectionManager->selectedItems(); if (selectedItems.count() >= 2) { emit itemsActivated(selectedItems); } else if (selectedItems.count() == 1) { emit itemActivated(selectedItems.first()); } else { emit itemActivated(index); } break; } case Qt::Key_Menu: { // Emit the signal itemContextMenuRequested() in case if at least one // item is selected. Otherwise the signal viewContextMenuRequested() will be emitted. const KItemSet selectedItems = m_selectionManager->selectedItems(); int index = -1; if (selectedItems.count() >= 2) { const int currentItemIndex = m_selectionManager->currentItem(); index = selectedItems.contains(currentItemIndex) ? currentItemIndex : selectedItems.first(); } else if (selectedItems.count() == 1) { index = selectedItems.first(); } if (index >= 0) { const QRectF contextRect = m_view->itemContextRect(index); const QPointF pos(m_view->scene()->views().first()->mapToGlobal(contextRect.bottomRight().toPoint())); emit itemContextMenuRequested(index, pos); } else { emit viewContextMenuRequested(QCursor::pos()); } break; } case Qt::Key_Escape: if (m_selectionBehavior != SingleSelection) { m_selectionManager->clearSelection(); } m_keyboardManager->cancelSearch(); emit escapePressed(); break; case Qt::Key_Space: if (m_selectionBehavior == MultiSelection) { if (controlPressed) { // Toggle the selection state of the current item. m_selectionManager->endAnchoredSelection(); m_selectionManager->setSelected(index, 1, KItemListSelectionManager::Toggle); m_selectionManager->beginAnchoredSelection(index); break; } else { // Select the current item if it is not selected yet. const int current = m_selectionManager->currentItem(); if (!m_selectionManager->isSelected(current)) { m_selectionManager->setSelected(current); break; } } } // Fall through to the default case and add the Space to the current search string. default: m_keyboardManager->addKeys(event->text()); // Make sure unconsumed events get propagated up the chain. #302329 event->ignore(); return false; } if (m_selectionManager->currentItem() != index) { switch (m_selectionBehavior) { case NoSelection: m_selectionManager->setCurrentItem(index); break; case SingleSelection: m_selectionManager->setCurrentItem(index); m_selectionManager->clearSelection(); m_selectionManager->setSelected(index, 1); break; case MultiSelection: if (controlPressed) { m_selectionManager->endAnchoredSelection(); } m_selectionManager->setCurrentItem(index); if (!shiftOrControlPressed) { m_selectionManager->clearSelection(); m_selectionManager->setSelected(index, 1); } if (!shiftPressed) { m_selectionManager->beginAnchoredSelection(index); } break; } m_view->scrollToItem(index); } return true; } void KItemListController::slotChangeCurrentItem(const QString& text, bool searchFromNextItem) { if (!m_model || m_model->count() == 0) { return; } const int currentIndex = m_selectionManager->currentItem(); int index; if (searchFromNextItem) { index = m_model->indexForKeyboardSearch(text, (currentIndex + 1) % m_model->count()); } else { index = m_model->indexForKeyboardSearch(text, currentIndex); } if (index >= 0) { m_selectionManager->setCurrentItem(index); if (m_selectionBehavior != NoSelection) { m_selectionManager->clearSelection(); m_selectionManager->setSelected(index, 1); m_selectionManager->beginAnchoredSelection(index); } m_view->scrollToItem(index); } } void KItemListController::slotAutoActivationTimeout() { if (!m_model || !m_view) { return; } const int index = m_autoActivationTimer->property("index").toInt(); if (index < 0 || index >= m_model->count()) { return; } /* m_view->isUnderMouse() fixes a bug in the Folder-View-Panel and in the * Places-Panel. * * Bug: When you drag a file onto a Folder-View-Item or a Places-Item and * then move away before the auto-activation timeout triggers, than the * item still becomes activated/expanded. * * See Bug 293200 and 305783 */ if (m_model->supportsDropping(index) && m_view->isUnderMouse()) { if (m_view->supportsItemExpanding() && m_model->isExpandable(index)) { const bool expanded = m_model->isExpanded(index); m_model->setExpanded(index, !expanded); } else if (m_autoActivationBehavior != ExpansionOnly) { emit itemActivated(index); } } } bool KItemListController::inputMethodEvent(QInputMethodEvent* event) { Q_UNUSED(event); return false; } bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform) { if (!m_view) { return false; } m_pressedMousePos = transform.map(event->pos()); m_pressedIndex = m_view->itemAt(m_pressedMousePos); emit mouseButtonPressed(m_pressedIndex, event->buttons()); if (event->buttons() & (Qt::BackButton | Qt::ForwardButton)) { // Do not select items when clicking the back/forward buttons, see // https://bugs.kde.org/show_bug.cgi?id=327412. return true; } if (m_view->isAboveExpansionToggle(m_pressedIndex, m_pressedMousePos)) { m_selectionManager->endAnchoredSelection(); m_selectionManager->setCurrentItem(m_pressedIndex); m_selectionManager->beginAnchoredSelection(m_pressedIndex); return true; } m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos); if (m_selectionTogglePressed) { m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle); // The previous anchored selection has been finished already in // KItemListSelectionManager::setSelected(). We can safely change // the current item and start a new anchored selection now. m_selectionManager->setCurrentItem(m_pressedIndex); m_selectionManager->beginAnchoredSelection(m_pressedIndex); return true; } const bool shiftPressed = event->modifiers() & Qt::ShiftModifier; const bool controlPressed = event->modifiers() & Qt::ControlModifier; // The previous selection is cleared if either // 1. The selection mode is SingleSelection, or // 2. the selection mode is MultiSelection, and *none* of the following conditions are met: // a) Shift or Control are pressed. // b) The clicked item is selected already. In that case, the user might want to: // - start dragging multiple items, or // - open the context menu and perform an action for all selected items. const bool shiftOrControlPressed = shiftPressed || controlPressed; const bool pressedItemAlreadySelected = m_pressedIndex >= 0 && m_selectionManager->isSelected(m_pressedIndex); const bool clearSelection = m_selectionBehavior == SingleSelection || (!shiftOrControlPressed && !pressedItemAlreadySelected); if (clearSelection) { m_selectionManager->clearSelection(); } else if (pressedItemAlreadySelected && !shiftOrControlPressed && (event->buttons() & Qt::LeftButton)) { // The user might want to start dragging multiple items, but if he clicks the item // in order to trigger it instead, the other selected items must be deselected. // However, we do not know yet what the user is going to do. // -> remember that the user pressed an item which had been selected already and // clear the selection in mouseReleaseEvent(), unless the items are dragged. m_clearSelectionIfItemsAreNotDragged = true; } if (!shiftPressed) { // Finish the anchored selection before the current index is changed m_selectionManager->endAnchoredSelection(); } if (m_pressedIndex >= 0) { m_selectionManager->setCurrentItem(m_pressedIndex); switch (m_selectionBehavior) { case NoSelection: break; case SingleSelection: m_selectionManager->setSelected(m_pressedIndex); break; case MultiSelection: if (controlPressed && !shiftPressed) { m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle); m_selectionManager->beginAnchoredSelection(m_pressedIndex); } else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) { // Select the pressed item and start a new anchored selection m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select); m_selectionManager->beginAnchoredSelection(m_pressedIndex); } break; default: Q_ASSERT(false); break; } if (event->buttons() & Qt::RightButton) { emit itemContextMenuRequested(m_pressedIndex, event->screenPos()); } return true; } if (event->buttons() & Qt::RightButton) { const QRectF headerBounds = m_view->headerBoundaries(); if (headerBounds.contains(event->pos())) { emit headerContextMenuRequested(event->screenPos()); } else { emit viewContextMenuRequested(event->screenPos()); } return true; } if (m_selectionBehavior == MultiSelection) { QPointF startPos = m_pressedMousePos; if (m_view->scrollOrientation() == Qt::Vertical) { startPos.ry() += m_view->scrollOffset(); if (m_view->itemSize().width() < 0) { // Use a special rubberband for views that have only one column and // expand the rubberband to use the whole width of the view. startPos.setX(0); } } else { startPos.rx() += m_view->scrollOffset(); } m_oldSelection = m_selectionManager->selectedItems(); KItemListRubberBand* rubberBand = m_view->rubberBand(); rubberBand->setStartPosition(startPos); rubberBand->setEndPosition(startPos); rubberBand->setActive(true); connect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged); m_view->setAutoScroll(true); } return false; } bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform) { if (!m_view) { return false; } if (m_pressedIndex >= 0) { // Check whether a dragging should be started if (event->buttons() & Qt::LeftButton) { const QPointF pos = transform.map(event->pos()); if ((pos - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) { if (!m_selectionManager->isSelected(m_pressedIndex)) { // Always assure that the dragged item gets selected. Usually this is already // done on the mouse-press event, but when using the selection-toggle on a // selected item the dragged item is not selected yet. m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle); } else { // A selected item has been clicked to drag all selected items // -> the selection should not be cleared when the mouse button is released. m_clearSelectionIfItemsAreNotDragged = false; } startDragging(); } } } else { KItemListRubberBand* rubberBand = m_view->rubberBand(); if (rubberBand->isActive()) { QPointF endPos = transform.map(event->pos()); // Update the current item. const int newCurrent = m_view->itemAt(endPos); if (newCurrent >= 0) { // It's expected that the new current index is also the new anchor (bug 163451). m_selectionManager->endAnchoredSelection(); m_selectionManager->setCurrentItem(newCurrent); m_selectionManager->beginAnchoredSelection(newCurrent); } if (m_view->scrollOrientation() == Qt::Vertical) { endPos.ry() += m_view->scrollOffset(); if (m_view->itemSize().width() < 0) { // Use a special rubberband for views that have only one column and // expand the rubberband to use the whole width of the view. endPos.setX(m_view->size().width()); } } else { endPos.rx() += m_view->scrollOffset(); } rubberBand->setEndPosition(endPos); } } return false; } bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform) { if (!m_view) { return false; } emit mouseButtonReleased(m_pressedIndex, event->buttons()); const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos); if (isAboveSelectionToggle) { m_selectionTogglePressed = false; return true; } if (!isAboveSelectionToggle && m_selectionTogglePressed) { m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle); m_selectionTogglePressed = false; return true; } const bool shiftOrControlPressed = event->modifiers() & Qt::ShiftModifier || event->modifiers() & Qt::ControlModifier; KItemListRubberBand* rubberBand = m_view->rubberBand(); if (rubberBand->isActive()) { disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged); rubberBand->setActive(false); m_oldSelection.clear(); m_view->setAutoScroll(false); } const QPointF pos = transform.map(event->pos()); const int index = m_view->itemAt(pos); if (index >= 0 && index == m_pressedIndex) { // The release event is done above the same item as the press event if (m_clearSelectionIfItemsAreNotDragged) { // A selected item has been clicked, but no drag operation has been started // -> clear the rest of the selection. m_selectionManager->clearSelection(); m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select); m_selectionManager->beginAnchoredSelection(m_pressedIndex); } if (event->button() & Qt::LeftButton) { bool emitItemActivated = true; if (m_view->isAboveExpansionToggle(index, pos)) { const bool expanded = m_model->isExpanded(index); m_model->setExpanded(index, !expanded); emit itemExpansionToggleClicked(index); emitItemActivated = false; } else if (shiftOrControlPressed) { // The mouse click should only update the selection, not trigger the item emitItemActivated = false; } else if (!(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced)) { emitItemActivated = false; } if (emitItemActivated) { emit itemActivated(index); } } else if (event->button() & Qt::MidButton) { emit itemMiddleClicked(index); } } m_pressedMousePos = QPointF(); m_pressedIndex = -1; m_clearSelectionIfItemsAreNotDragged = false; return false; } bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform) { const QPointF pos = transform.map(event->pos()); const int index = m_view->itemAt(pos); // Expand item if desired - See Bug 295573 if (m_mouseDoubleClickAction != ActivateItemOnly) { if (m_view && m_model && m_view->supportsItemExpanding() && m_model->isExpandable(index)) { const bool expanded = m_model->isExpanded(index); m_model->setExpanded(index, !expanded); } } if (event->button() & Qt::RightButton) { m_selectionManager->clearSelection(); if (index >= 0) { m_selectionManager->setSelected(index); emit itemContextMenuRequested(index, event->screenPos()); } else { const QRectF headerBounds = m_view->headerBoundaries(); if (headerBounds.contains(event->pos())) { emit headerContextMenuRequested(event->screenPos()); } else { emit viewContextMenuRequested(event->screenPos()); } } return true; } bool emitItemActivated = !(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced) && (event->button() & Qt::LeftButton) && index >= 0 && index < m_model->count(); if (emitItemActivated) { emit itemActivated(index); } return false; } bool KItemListController::dragEnterEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform) { Q_UNUSED(event); Q_UNUSED(transform); return false; } bool KItemListController::dragLeaveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform) { Q_UNUSED(event); Q_UNUSED(transform); m_view->setAutoScroll(false); m_view->hideDropIndicator(); KItemListWidget* widget = hoveredWidget(); if (widget) { widget->setHovered(false); emit itemUnhovered(widget->index()); } return false; } bool KItemListController::dragMoveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform) { if (!m_model || !m_view) { return false; } event->acceptProposedAction(); KItemListWidget* oldHoveredWidget = hoveredWidget(); const QPointF pos = transform.map(event->pos()); KItemListWidget* newHoveredWidget = widgetForPos(pos); if (oldHoveredWidget != newHoveredWidget) { m_autoActivationTimer->stop(); if (oldHoveredWidget) { oldHoveredWidget->setHovered(false); emit itemUnhovered(oldHoveredWidget->index()); } } if (newHoveredWidget) { bool droppingBetweenItems = false; if (m_model->sortRole().isEmpty()) { // The model supports inserting items between other items. droppingBetweenItems = (m_view->showDropIndicator(pos) >= 0); } const int index = newHoveredWidget->index(); if (!droppingBetweenItems) { if (m_model->supportsDropping(index)) { // Something has been dragged on an item. m_view->hideDropIndicator(); if (!newHoveredWidget->isHovered()) { newHoveredWidget->setHovered(true); emit itemHovered(index); } if (!m_autoActivationTimer->isActive() && m_autoActivationTimer->interval() >= 0) { m_autoActivationTimer->setProperty("index", index); m_autoActivationTimer->start(); } } } else { m_autoActivationTimer->stop(); if (newHoveredWidget && newHoveredWidget->isHovered()) { newHoveredWidget->setHovered(false); emit itemUnhovered(index); } } } else { m_view->hideDropIndicator(); } return false; } bool KItemListController::dropEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform) { if (!m_view) { return false; } m_autoActivationTimer->stop(); m_view->setAutoScroll(false); const QPointF pos = transform.map(event->pos()); int dropAboveIndex = -1; if (m_model->sortRole().isEmpty()) { // The model supports inserting of items between other items. dropAboveIndex = m_view->showDropIndicator(pos); } if (dropAboveIndex >= 0) { // Something has been dropped between two items. m_view->hideDropIndicator(); emit aboveItemDropEvent(dropAboveIndex, event); - } else { + } else if (!event->mimeData()->hasFormat(m_model->blacklistItemDropEventMimeType())) { // Something has been dropped on an item or on an empty part of the view. emit itemDropEvent(m_view->itemAt(pos), event); } QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropEnd); QAccessible::updateAccessibility(&accessibilityEvent); return true; } bool KItemListController::hoverEnterEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform) { Q_UNUSED(event); Q_UNUSED(transform); return false; } bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform) { Q_UNUSED(transform); if (!m_model || !m_view) { return false; } KItemListWidget* oldHoveredWidget = hoveredWidget(); const QPointF pos = transform.map(event->pos()); KItemListWidget* newHoveredWidget = widgetForPos(pos); if (oldHoveredWidget != newHoveredWidget) { if (oldHoveredWidget) { oldHoveredWidget->setHovered(false); emit itemUnhovered(oldHoveredWidget->index()); } if (newHoveredWidget) { newHoveredWidget->setHovered(true); const QPointF mappedPos = newHoveredWidget->mapFromItem(m_view, pos); newHoveredWidget->setHoverPosition(mappedPos); emit itemHovered(newHoveredWidget->index()); } } else if (oldHoveredWidget) { const QPointF mappedPos = oldHoveredWidget->mapFromItem(m_view, pos); oldHoveredWidget->setHoverPosition(mappedPos); } return false; } bool KItemListController::hoverLeaveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform) { Q_UNUSED(event); Q_UNUSED(transform); if (!m_model || !m_view) { return false; } foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) { if (widget->isHovered()) { widget->setHovered(false); emit itemUnhovered(widget->index()); } } return false; } bool KItemListController::wheelEvent(QGraphicsSceneWheelEvent* event, const QTransform& transform) { Q_UNUSED(event); Q_UNUSED(transform); return false; } bool KItemListController::resizeEvent(QGraphicsSceneResizeEvent* event, const QTransform& transform) { Q_UNUSED(event); Q_UNUSED(transform); return false; } bool KItemListController::processEvent(QEvent* event, const QTransform& transform) { if (!event) { return false; } switch (event->type()) { case QEvent::KeyPress: return keyPressEvent(static_cast(event)); case QEvent::InputMethod: return inputMethodEvent(static_cast(event)); case QEvent::GraphicsSceneMousePress: return mousePressEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneMouseMove: return mouseMoveEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneMouseRelease: return mouseReleaseEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneMouseDoubleClick: return mouseDoubleClickEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneWheel: return wheelEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneDragEnter: return dragEnterEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneDragLeave: return dragLeaveEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneDragMove: return dragMoveEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneDrop: return dropEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneHoverEnter: return hoverEnterEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneHoverMove: return hoverMoveEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneHoverLeave: return hoverLeaveEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneResize: return resizeEvent(static_cast(event), transform); default: break; } return false; } void KItemListController::slotViewScrollOffsetChanged(qreal current, qreal previous) { if (!m_view) { return; } KItemListRubberBand* rubberBand = m_view->rubberBand(); if (rubberBand->isActive()) { const qreal diff = current - previous; // TODO: Ideally just QCursor::pos() should be used as // new end-position but it seems there is no easy way // to have something like QWidget::mapFromGlobal() for QGraphicsWidget // (... or I just missed an easy way to do the mapping) QPointF endPos = rubberBand->endPosition(); if (m_view->scrollOrientation() == Qt::Vertical) { endPos.ry() += diff; } else { endPos.rx() += diff; } rubberBand->setEndPosition(endPos); } } void KItemListController::slotRubberBandChanged() { if (!m_view || !m_model || m_model->count() <= 0) { return; } const KItemListRubberBand* rubberBand = m_view->rubberBand(); const QPointF startPos = rubberBand->startPosition(); const QPointF endPos = rubberBand->endPosition(); QRectF rubberBandRect = QRectF(startPos, endPos).normalized(); const bool scrollVertical = (m_view->scrollOrientation() == Qt::Vertical); if (scrollVertical) { rubberBandRect.translate(0, -m_view->scrollOffset()); } else { rubberBandRect.translate(-m_view->scrollOffset(), 0); } if (!m_oldSelection.isEmpty()) { // Clear the old selection that was available before the rubberband has // been activated in case if no Shift- or Control-key are pressed const bool shiftOrControlPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier || QApplication::keyboardModifiers() & Qt::ControlModifier; if (!shiftOrControlPressed) { m_oldSelection.clear(); } } KItemSet selectedItems; // Select all visible items that intersect with the rubberband foreach (const KItemListWidget* widget, m_view->visibleItemListWidgets()) { const int index = widget->index(); const QRectF widgetRect = m_view->itemRect(index); if (widgetRect.intersects(rubberBandRect)) { const QRectF iconRect = widget->iconRect().translated(widgetRect.topLeft()); const QRectF textRect = widget->textRect().translated(widgetRect.topLeft()); if (iconRect.intersects(rubberBandRect) || textRect.intersects(rubberBandRect)) { selectedItems.insert(index); } } } // Select all invisible items that intersect with the rubberband. Instead of // iterating all items only the area which might be touched by the rubberband // will be checked. const bool increaseIndex = scrollVertical ? startPos.y() > endPos.y(): startPos.x() > endPos.x(); int index = increaseIndex ? m_view->lastVisibleIndex() + 1 : m_view->firstVisibleIndex() - 1; bool selectionFinished = false; do { const QRectF widgetRect = m_view->itemRect(index); if (widgetRect.intersects(rubberBandRect)) { selectedItems.insert(index); } if (increaseIndex) { ++index; selectionFinished = (index >= m_model->count()) || ( scrollVertical && widgetRect.top() > rubberBandRect.bottom()) || (!scrollVertical && widgetRect.left() > rubberBandRect.right()); } else { --index; selectionFinished = (index < 0) || ( scrollVertical && widgetRect.bottom() < rubberBandRect.top()) || (!scrollVertical && widgetRect.right() < rubberBandRect.left()); } } while (!selectionFinished); if (QApplication::keyboardModifiers() & Qt::ControlModifier) { // If Control is pressed, the selection state of all items in the rubberband is toggled. // Therefore, the new selection contains: // 1. All previously selected items which are not inside the rubberband, and // 2. all items inside the rubberband which have not been selected previously. m_selectionManager->setSelectedItems(m_oldSelection ^ selectedItems); } else { m_selectionManager->setSelectedItems(selectedItems + m_oldSelection); } } void KItemListController::startDragging() { if (!m_view || !m_model) { return; } const KItemSet selectedItems = m_selectionManager->selectedItems(); if (selectedItems.isEmpty()) { return; } QMimeData* data = m_model->createMimeData(selectedItems); if (!data) { return; } // The created drag object will be owned and deleted // by QApplication::activeWindow(). QDrag* drag = new QDrag(QApplication::activeWindow()); drag->setMimeData(data); const QPixmap pixmap = m_view->createDragPixmap(selectedItems); drag->setPixmap(pixmap); const QPoint hotSpot((pixmap.width() / pixmap.devicePixelRatio()) / 2, 0); drag->setHotSpot(hotSpot); drag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::CopyAction); QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropStart); QAccessible::updateAccessibility(&accessibilityEvent); } KItemListWidget* KItemListController::hoveredWidget() const { Q_ASSERT(m_view); foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) { if (widget->isHovered()) { return widget; } } return 0; } KItemListWidget* KItemListController::widgetForPos(const QPointF& pos) const { Q_ASSERT(m_view); foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) { const QPointF mappedPos = widget->mapFromItem(m_view, pos); const bool hovered = widget->contains(mappedPos) && !widget->expansionToggleRect().contains(mappedPos); if (hovered) { return widget; } } return 0; } void KItemListController::updateKeyboardAnchor() { const bool validAnchor = m_keyboardAnchorIndex >= 0 && m_keyboardAnchorIndex < m_model->count() && keyboardAnchorPos(m_keyboardAnchorIndex) == m_keyboardAnchorPos; if (!validAnchor) { const int index = m_selectionManager->currentItem(); m_keyboardAnchorIndex = index; m_keyboardAnchorPos = keyboardAnchorPos(index); } } int KItemListController::nextRowIndex(int index) const { if (m_keyboardAnchorIndex < 0) { return index; } const int maxIndex = m_model->count() - 1; if (index == maxIndex) { return index; } // Calculate the index of the last column inside the row of the current index int lastColumnIndex = index; while (keyboardAnchorPos(lastColumnIndex + 1) > keyboardAnchorPos(lastColumnIndex)) { ++lastColumnIndex; if (lastColumnIndex >= maxIndex) { return index; } } // Based on the last column index go to the next row and calculate the nearest index // that is below the current index int nextRowIndex = lastColumnIndex + 1; int searchIndex = nextRowIndex; qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(nextRowIndex)); while (searchIndex < maxIndex && keyboardAnchorPos(searchIndex + 1) > keyboardAnchorPos(searchIndex)) { ++searchIndex; const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex)); if (searchDiff < minDiff) { minDiff = searchDiff; nextRowIndex = searchIndex; } } return nextRowIndex; } int KItemListController::previousRowIndex(int index) const { if (m_keyboardAnchorIndex < 0 || index == 0) { return index; } // Calculate the index of the first column inside the row of the current index int firstColumnIndex = index; while (keyboardAnchorPos(firstColumnIndex - 1) < keyboardAnchorPos(firstColumnIndex)) { --firstColumnIndex; if (firstColumnIndex <= 0) { return index; } } // Based on the first column index go to the previous row and calculate the nearest index // that is above the current index int previousRowIndex = firstColumnIndex - 1; int searchIndex = previousRowIndex; qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(previousRowIndex)); while (searchIndex > 0 && keyboardAnchorPos(searchIndex - 1) < keyboardAnchorPos(searchIndex)) { --searchIndex; const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex)); if (searchDiff < minDiff) { minDiff = searchDiff; previousRowIndex = searchIndex; } } return previousRowIndex; } qreal KItemListController::keyboardAnchorPos(int index) const { const QRectF itemRect = m_view->itemRect(index); if (!itemRect.isEmpty()) { return (m_view->scrollOrientation() == Qt::Vertical) ? itemRect.x() : itemRect.y(); } return 0; } void KItemListController::updateExtendedSelectionRegion() { if (m_view) { const bool extend = (m_selectionBehavior != MultiSelection); KItemListStyleOption option = m_view->styleOption(); if (option.extendedSelectionRegion != extend) { option.extendedSelectionRegion = extend; m_view->setStyleOption(option); } } } diff --git a/src/kitemviews/kitemmodelbase.cpp b/src/kitemviews/kitemmodelbase.cpp index bf41b1c84..ee7e81084 100644 --- a/src/kitemviews/kitemmodelbase.cpp +++ b/src/kitemviews/kitemmodelbase.cpp @@ -1,161 +1,166 @@ /*************************************************************************** * Copyright (C) 2011 by Peter Penz * * * * Based on the Itemviews NG project from Trolltech Labs: * * http://qt.gitorious.org/qt-labs/itemviews-ng * * * * 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 program 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 program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "kitemmodelbase.h" KItemModelBase::KItemModelBase(QObject* parent) : QObject(parent), m_groupedSorting(false), m_sortRole(), m_sortOrder(Qt::AscendingOrder) { } KItemModelBase::KItemModelBase(const QByteArray& sortRole, QObject* parent) : QObject(parent), m_groupedSorting(false), m_sortRole(sortRole), m_sortOrder(Qt::AscendingOrder) { } KItemModelBase::~KItemModelBase() { } bool KItemModelBase::setData(int index, const QHash &values) { Q_UNUSED(index); Q_UNUSED(values); return false; } void KItemModelBase::setGroupedSorting(bool grouped) { if (m_groupedSorting != grouped) { m_groupedSorting = grouped; onGroupedSortingChanged(grouped); emit groupedSortingChanged(grouped); } } bool KItemModelBase::groupedSorting() const { return m_groupedSorting; } void KItemModelBase::setSortRole(const QByteArray& role) { if (role != m_sortRole) { const QByteArray previous = m_sortRole; m_sortRole = role; onSortRoleChanged(role, previous); emit sortRoleChanged(role, previous); } } QByteArray KItemModelBase::sortRole() const { return m_sortRole; } void KItemModelBase::setSortOrder(Qt::SortOrder order) { if (order != m_sortOrder) { const Qt::SortOrder previous = m_sortOrder; m_sortOrder = order; onSortOrderChanged(order, previous); emit sortOrderChanged(order, previous); } } QString KItemModelBase::roleDescription(const QByteArray& role) const { return role; } QList > KItemModelBase::groups() const { return QList >(); } bool KItemModelBase::setExpanded(int index, bool expanded) { Q_UNUSED(index); Q_UNUSED(expanded); return false; } bool KItemModelBase::isExpanded(int index) const { Q_UNUSED(index); return false; } bool KItemModelBase::isExpandable(int index) const { Q_UNUSED(index); return false; } int KItemModelBase::expandedParentsCount(int index) const { Q_UNUSED(index); return 0; } QMimeData* KItemModelBase::createMimeData(const KItemSet& indexes) const { Q_UNUSED(indexes); return 0; } int KItemModelBase::indexForKeyboardSearch(const QString& text, int startFromIndex) const { Q_UNUSED(text); Q_UNUSED(startFromIndex); return -1; } bool KItemModelBase::supportsDropping(int index) const { Q_UNUSED(index); return false; } +QString KItemModelBase::blacklistItemDropEventMimeType() const +{ + return QStringLiteral("application/x-dolphin-blacklist-drop"); +} + void KItemModelBase::onGroupedSortingChanged(bool current) { Q_UNUSED(current); } void KItemModelBase::onSortRoleChanged(const QByteArray& current, const QByteArray& previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemModelBase::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) { Q_UNUSED(current); Q_UNUSED(previous); } diff --git a/src/kitemviews/kitemmodelbase.h b/src/kitemviews/kitemmodelbase.h index bd5ca1d65..45ad1f61a 100644 --- a/src/kitemviews/kitemmodelbase.h +++ b/src/kitemviews/kitemmodelbase.h @@ -1,271 +1,281 @@ /*************************************************************************** * Copyright (C) 2011 by Peter Penz * * * * Based on the Itemviews NG project from Trolltech Labs: * * http://qt.gitorious.org/qt-labs/itemviews-ng * * * * 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 program 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 program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #ifndef KITEMMODELBASE_H #define KITEMMODELBASE_H #include "dolphin_export.h" #include #include #include #include #include class QMimeData; /** * @brief Base class for model implementations used by KItemListView and KItemListController. * * An item-model consists of a variable number of items. The number of items * is given by KItemModelBase::count(). The data of an item is accessed by a unique index * with KItemModelBase::data(). The indexes are integer-values counting from 0 to the * KItemModelBase::count() - 1. * * One item consists of a variable number of role/value-pairs. * * A model can optionally provide sorting- and grouping-capabilities. * * Also optionally it is possible to provide a tree of items by implementing the methods * setExpanded(), isExpanded(), isExpandable() and expandedParentsCount(). */ class DOLPHIN_EXPORT KItemModelBase : public QObject { Q_OBJECT public: KItemModelBase(QObject* parent = 0); explicit KItemModelBase(const QByteArray& sortRole, QObject* parent = 0); virtual ~KItemModelBase(); /** @return The number of items. */ virtual int count() const = 0; virtual QHash data(int index) const = 0; /** * Sets the data for the item at \a index to the given \a values. Returns true * if the data was set on the item; returns false otherwise. * * The default implementation does not set the data, and will always return * false. */ virtual bool setData(int index, const QHash& values); /** * Enables/disables the grouped sorting. The method KItemModelBase::onGroupedSortingChanged() will be * called so that model-implementations can react on the grouped-sorting change. Afterwards the * signal groupedSortingChanged() will be emitted. If the grouped sorting is enabled, the method * KItemModelBase::groups() must be implemented. */ void setGroupedSorting(bool grouped); bool groupedSorting() const; /** * Sets the sort-role to \a role. The method KItemModelBase::onSortRoleChanged() will be * called so that model-implementations can react on the sort-role change. Afterwards the * signal sortRoleChanged() will be emitted. */ void setSortRole(const QByteArray& role); QByteArray sortRole() const; /** * Sets the sort order to \a order. The method KItemModelBase::onSortOrderChanged() will be * called so that model-implementations can react on the sort order change. Afterwards the * signal sortOrderChanged() will be emitted. */ void setSortOrder(Qt::SortOrder order); Qt::SortOrder sortOrder() const; /** * @return Translated description for the \p role. The description is e.g. used * for the header in KItemListView. */ virtual QString roleDescription(const QByteArray& role) const; /** * @return List of group headers. Each list-item consists of the index of the item * that represents the first item of a group and a value represented * as QVariant. The value is shown by an instance of KItemListGroupHeader. * Per default an empty list is returned. */ virtual QList > groups() const; /** * Expands the item with the index \a index if \a expanded is true. * If \a expanded is false the item will be collapsed. * * Per default no expanding of items is implemented. When implementing * this method it is mandatory to overwrite KItemModelBase::isExpandable() * and KItemListView::supportsExpandableItems() to return true. * * @return True if the operation has been successful. */ virtual bool setExpanded(int index, bool expanded); /** * @return True if the item with the index \a index is expanded. * Per default no expanding of items is implemented. When implementing * this method it is mandatory to overwrite KItemModelBase::isExpandable() * and KItemListView::supportsExpandableItems() to return true. */ virtual bool isExpanded(int index) const; /** * @return True if expanding and collapsing of the item with the index \a index * is supported. Per default false is returned. */ virtual bool isExpandable(int index) const; /** * @return Number of expanded parent items for the item with the given index. * Per default 0 is returned. */ virtual int expandedParentsCount(int index) const; /** * @return MIME-data for the items given by \a indexes. The default implementation * returns 0. The ownership of the returned instance is in the hand of the * caller of this method. The method must be implemented if dragging of * items should be possible. */ virtual QMimeData* createMimeData(const KItemSet& indexes) const; /** * @return Reimplement this to return the index for the first item * beginning with string typed in through the keyboard, -1 if not found. * @param text the text which has been typed in through the keyboard * @param startFromIndex the index from which to start searching from */ virtual int indexForKeyboardSearch(const QString& text, int startFromIndex = 0) const; /** * @return True, if the item with the index \a index basically supports dropping. * Per default false is returned. * * The information is used only to give a visual feedback during a drag operation * and not to decide whether a drop event gets emitted. It is it is still up to * the receiver of KItemListController::itemDropEvent() to decide how to handle * the drop event. */ // TODO: Should the MIME-data be passed too so that the model can do a more specific // decision whether it accepts the drop? virtual bool supportsDropping(int index) const; + /** + * @return An internal mimetype to signal that an itemDropEvent() should be rejected by + * the receiving model. + * + * This mimeType can be used in createMimeData() to notify that the + * drop-onto-items events should be ignored, while the drop-between-items + * ones should be still accepted. + */ + QString blacklistItemDropEventMimeType() const; + signals: /** * Is emitted if one or more items have been inserted. Each item-range consists * of: * - an index where items have been inserted * - the number of inserted items. * The index of each item-range represents the index of the model * before the items have been inserted. * * For the item-ranges it is assured that: * - They don't overlap * - The index of item-range n is smaller than the index of item-range n + 1. */ void itemsInserted(const KItemRangeList& itemRanges); /** * Is emitted if one or more items have been removed. Each item-range consists * of: * - an index where items have been inserted * - the number of inserted items. * The index of each item-range represents the index of the model * before the items have been removed. * * For the item-ranges it is assured that: * - They don't overlap * - The index of item-range n is smaller than the index of item-range n + 1. */ void itemsRemoved(const KItemRangeList& itemRanges); /** * Is emitted if one ore more items get moved. * @param itemRange Item-range that gets moved to a new position. * @param movedToIndexes New positions for each element of the item-range. * * For example if the model has 10 items and the items 0 and 1 get exchanged * with the items 5 and 6 then the parameters look like this: * - itemRange: has the index 0 and a count of 7. * - movedToIndexes: Contains the seven values 5, 6, 2, 3, 4, 0, 1 * * This signal implies that the groups might have changed. Therefore, * gropusChanged() is not emitted if this signal is emitted. */ void itemsMoved(const KItemRange& itemRange, const QList& movedToIndexes); void itemsChanged(const KItemRangeList& itemRanges, const QSet& roles); /** * Is emitted if the groups have changed, even though the order of the * items has not been modified. */ void groupsChanged(); void groupedSortingChanged(bool current); void sortRoleChanged(const QByteArray& current, const QByteArray& previous); void sortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous); protected: /** * Is invoked if the grouped sorting has been changed by KItemModelBase::setGroupedSorting(). Allows * to react on the changed grouped sorting before the signal groupedSortingChanged() will be emitted. */ virtual void onGroupedSortingChanged(bool current); /** * Is invoked if the sort role has been changed by KItemModelBase::setSortRole(). Allows * to react on the changed sort role before the signal sortRoleChanged() will be emitted. * The implementation must assure that the items are sorted by the role given by \a current. * Usually the most efficient way is to emit a * itemsRemoved() signal for all items, reorder the items internally and to emit a * itemsInserted() signal afterwards. */ virtual void onSortRoleChanged(const QByteArray& current, const QByteArray& previous); /** * Is invoked if the sort order has been changed by KItemModelBase::setSortOrder(). Allows * to react on the changed sort order before the signal sortOrderChanged() will be emitted. * The implementation must assure that the items are sorted by the order given by \a current. * Usually the most efficient way is to emit a * itemsRemoved() signal for all items, reorder the items internally and to emit a * itemsInserted() signal afterwards. */ virtual void onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous); private: bool m_groupedSorting; QByteArray m_sortRole; Qt::SortOrder m_sortOrder; }; inline Qt::SortOrder KItemModelBase::sortOrder() const { return m_sortOrder; } #endif diff --git a/src/kitemviews/private/kitemlistsmoothscroller.cpp b/src/kitemviews/private/kitemlistsmoothscroller.cpp index cb1dd61ff..6bfdba4c9 100644 --- a/src/kitemviews/private/kitemlistsmoothscroller.cpp +++ b/src/kitemviews/private/kitemlistsmoothscroller.cpp @@ -1,206 +1,205 @@ /*************************************************************************** * Copyright (C) 2011 by Peter Penz * * * * 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 program 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 program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "kitemlistsmoothscroller.h" #include #include #include #include #include #include KItemListSmoothScroller::KItemListSmoothScroller(QScrollBar* scrollBar, QObject* parent) : QObject(parent), m_scrollBarPressed(false), m_smoothScrolling(true), m_scrollBar(scrollBar), m_animation(0) { m_animation = new QPropertyAnimation(this); - const int duration = m_scrollBar->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, m_scrollBar) ? 300 : 1; + const int duration = m_scrollBar->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, m_scrollBar) ? 100 : 1; m_animation->setDuration(duration); - m_animation->setEasingCurve(QEasingCurve::InOutQuart); connect(m_animation, &QPropertyAnimation::stateChanged, this, &KItemListSmoothScroller::slotAnimationStateChanged); m_scrollBar->installEventFilter(this); } KItemListSmoothScroller::~KItemListSmoothScroller() { } void KItemListSmoothScroller::setScrollBar(QScrollBar *scrollBar) { m_scrollBar = scrollBar; } QScrollBar* KItemListSmoothScroller::scrollBar() const { return m_scrollBar; } void KItemListSmoothScroller::setTargetObject(QObject* target) { m_animation->setTargetObject(target); } QObject* KItemListSmoothScroller::targetObject() const { return m_animation->targetObject(); } void KItemListSmoothScroller::setPropertyName(const QByteArray& propertyName) { m_animation->setPropertyName(propertyName); } QByteArray KItemListSmoothScroller::propertyName() const { return m_animation->propertyName(); } void KItemListSmoothScroller::scrollContentsBy(qreal distance) { QObject* target = targetObject(); if (!target) { return; } const QByteArray name = propertyName(); const qreal currentOffset = target->property(name).toReal(); if (static_cast(currentOffset) == m_scrollBar->value()) { // The current offset is already synchronous to the scrollbar return; } const bool animRunning = (m_animation->state() == QAbstractAnimation::Running); if (animRunning) { // Stopping a running animation means skipping the range from the current offset // until the target offset. To prevent skipping of the range the difference // is added to the new target offset. const qreal oldEndOffset = m_animation->endValue().toReal(); distance += (currentOffset - oldEndOffset); } const qreal endOffset = currentOffset - distance; if (m_smoothScrolling || animRunning) { qreal startOffset = currentOffset; if (animRunning) { // If the animation was running and has been interrupted by assigning a new end-offset // one frame must be added to the start-offset to keep the animation smooth. This also // assures that animation proceeds even in cases where new end-offset are triggered // within a very short timeslots. startOffset += (endOffset - currentOffset) * 1000 / (m_animation->duration() * 60); if (currentOffset < endOffset) { startOffset = qMin(startOffset, endOffset); } else { startOffset = qMax(startOffset, endOffset); } } m_animation->stop(); m_animation->setStartValue(startOffset); m_animation->setEndValue(endOffset); m_animation->setEasingCurve(animRunning ? QEasingCurve::OutQuad : QEasingCurve::InOutQuad); m_animation->start(); target->setProperty(name, startOffset); } else { target->setProperty(name, endOffset); } } void KItemListSmoothScroller::scrollTo(qreal position) { int newValue = position; newValue = qBound(0, newValue, m_scrollBar->maximum()); if (newValue != m_scrollBar->value()) { m_smoothScrolling = true; m_scrollBar->setValue(newValue); } } bool KItemListSmoothScroller::requestScrollBarUpdate(int newMaximum) { if (m_animation->state() == QAbstractAnimation::Running) { if (newMaximum == m_scrollBar->maximum()) { // The value has been changed by the animation, no update // of the scrollbars is required as their target state will be // reached with the end of the animation. return false; } // The maximum has been changed which indicates that the content // of the view has been changed. Stop the animation in any case and // update the scrollbars immediately. m_animation->stop(); } return true; } bool KItemListSmoothScroller::eventFilter(QObject* obj, QEvent* event) { Q_ASSERT(obj == m_scrollBar); switch (event->type()) { case QEvent::MouseButtonPress: m_scrollBarPressed = true; m_smoothScrolling = true; break; case QEvent::MouseButtonRelease: m_scrollBarPressed = false; m_smoothScrolling = false; break; case QEvent::Wheel: return false; // we're the ones sending them default: break; } return QObject::eventFilter(obj, event); } void KItemListSmoothScroller::slotAnimationStateChanged(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) { Q_UNUSED(oldState); if (newState == QAbstractAnimation::Stopped && m_smoothScrolling && !m_scrollBarPressed) { m_smoothScrolling = false; } } void KItemListSmoothScroller::handleWheelEvent(QWheelEvent* event) { const bool previous = m_smoothScrolling; m_smoothScrolling = true; QWheelEvent copy = *event; QApplication::sendEvent(m_scrollBar, ©); event->setAccepted(copy.isAccepted()); m_smoothScrolling = previous; } diff --git a/src/panels/places/placesitemmodel.cpp b/src/panels/places/placesitemmodel.cpp index a4741e746..04dac81b7 100644 --- a/src/panels/places/placesitemmodel.cpp +++ b/src/panels/places/placesitemmodel.cpp @@ -1,1209 +1,1212 @@ /*************************************************************************** * Copyright (C) 2012 by Peter Penz * * * * Based on KFilePlacesModel from kdelibs: * * Copyright (C) 2007 Kevin Ottens * * Copyright (C) 2007 David Faure * * * * 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 program 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 program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "placesitemmodel.h" #include "dolphin_generalsettings.h" #include #include #include "dolphindebug.h" #include #include #include #include #include #include "placesitem.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_BALOO #include #include #endif namespace { // As long as KFilePlacesView from kdelibs is available in parallel, the // system-bookmarks for "Recently Saved" and "Search For" should be // shown only inside the Places Panel. This is necessary as the stored // URLs needs to get translated to a Baloo-search-URL on-the-fly to // be independent from changes in the Baloo-search-URL-syntax. // Hence a prefix to the application-name of the stored bookmarks is // added, which is only read by PlacesItemModel. const char AppNamePrefix[] = "-places-panel"; } PlacesItemModel::PlacesItemModel(QObject* parent) : KStandardItemModel(parent), m_fileIndexingEnabled(false), m_hiddenItemsShown(false), m_availableDevices(), m_predicate(), m_bookmarkManager(0), m_systemBookmarks(), m_systemBookmarksIndexes(), m_bookmarkedItems(), m_hiddenItemToRemove(-1), m_updateBookmarksTimer(0), m_storageSetupInProgress() { #ifdef HAVE_BALOO Baloo::IndexerConfig config; m_fileIndexingEnabled = config.fileIndexingEnabled(); #endif const QString file = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/user-places.xbel"; m_bookmarkManager = KBookmarkManager::managerForExternalFile(file); createSystemBookmarks(); initializeAvailableDevices(); loadBookmarks(); const int syncBookmarksTimeout = 100; m_updateBookmarksTimer = new QTimer(this); m_updateBookmarksTimer->setInterval(syncBookmarksTimeout); m_updateBookmarksTimer->setSingleShot(true); connect(m_updateBookmarksTimer, &QTimer::timeout, this, &PlacesItemModel::updateBookmarks); connect(m_bookmarkManager, &KBookmarkManager::changed, m_updateBookmarksTimer, static_cast(&QTimer::start)); } PlacesItemModel::~PlacesItemModel() { qDeleteAll(m_bookmarkedItems); m_bookmarkedItems.clear(); } PlacesItem* PlacesItemModel::createPlacesItem(const QString& text, const QUrl& url, const QString& iconName) { const KBookmark bookmark = PlacesItem::createBookmark(m_bookmarkManager, text, url, iconName); return new PlacesItem(bookmark); } PlacesItem* PlacesItemModel::placesItem(int index) const { return dynamic_cast(item(index)); } int PlacesItemModel::hiddenCount() const { int modelIndex = 0; int hiddenItemCount = 0; foreach (const PlacesItem* item, m_bookmarkedItems) { if (item) { ++hiddenItemCount; } else { if (placesItem(modelIndex)->isHidden()) { ++hiddenItemCount; } ++modelIndex; } } return hiddenItemCount; } void PlacesItemModel::setHiddenItemsShown(bool show) { if (m_hiddenItemsShown == show) { return; } m_hiddenItemsShown = show; if (show) { // Move all items that are part of m_bookmarkedItems to the model. QList itemsToInsert; QList insertPos; int modelIndex = 0; for (int i = 0; i < m_bookmarkedItems.count(); ++i) { if (m_bookmarkedItems[i]) { itemsToInsert.append(m_bookmarkedItems[i]); m_bookmarkedItems[i] = 0; insertPos.append(modelIndex); } ++modelIndex; } // Inserting the items will automatically insert an item // to m_bookmarkedItems in PlacesItemModel::onItemsInserted(). // The items are temporary saved in itemsToInsert, so // m_bookmarkedItems can be shrinked now. m_bookmarkedItems.erase(m_bookmarkedItems.begin(), m_bookmarkedItems.begin() + itemsToInsert.count()); for (int i = 0; i < itemsToInsert.count(); ++i) { insertItem(insertPos[i], itemsToInsert[i]); } Q_ASSERT(m_bookmarkedItems.count() == count()); } else { // Move all items of the model, where the "isHidden" property is true, to // m_bookmarkedItems. Q_ASSERT(m_bookmarkedItems.count() == count()); for (int i = count() - 1; i >= 0; --i) { if (placesItem(i)->isHidden()) { hideItem(i); } } } #ifdef PLACESITEMMODEL_DEBUG qCDebug(DolphinDebug) << "Changed visibility of hidden items"; showModelState(); #endif } bool PlacesItemModel::hiddenItemsShown() const { return m_hiddenItemsShown; } int PlacesItemModel::closestItem(const QUrl& url) const { int foundIndex = -1; int maxLength = 0; for (int i = 0; i < count(); ++i) { const QUrl itemUrl = placesItem(i)->url(); if (url == itemUrl) { // We can't find a closer one, so stop here. foundIndex = i; break; } else if (itemUrl.isParentOf(url)) { const int length = itemUrl.path().length(); if (length > maxLength) { foundIndex = i; maxLength = length; } } } return foundIndex; } void PlacesItemModel::appendItemToGroup(PlacesItem* item) { if (!item) { return; } int i = 0; while (i < count() && placesItem(i)->group() != item->group()) { ++i; } bool inserted = false; while (!inserted && i < count()) { if (placesItem(i)->group() != item->group()) { insertItem(i, item); inserted = true; } ++i; } if (!inserted) { appendItem(item); } } QAction* PlacesItemModel::ejectAction(int index) const { const PlacesItem* item = placesItem(index); if (item && item->device().is()) { return new QAction(QIcon::fromTheme(QStringLiteral("media-eject")), i18nc("@item", "Eject"), 0); } return 0; } QAction* PlacesItemModel::teardownAction(int index) const { const PlacesItem* item = placesItem(index); if (!item) { return 0; } Solid::Device device = item->device(); const bool providesTearDown = device.is() && device.as()->isAccessible(); if (!providesTearDown) { return 0; } Solid::StorageDrive* drive = device.as(); if (!drive) { drive = device.parent().as(); } bool hotPluggable = false; bool removable = false; if (drive) { hotPluggable = drive->isHotpluggable(); removable = drive->isRemovable(); } QString iconName; QString text; if (device.is()) { text = i18nc("@item", "Release"); } else if (removable || hotPluggable) { text = i18nc("@item", "Safely Remove"); iconName = QStringLiteral("media-eject"); } else { text = i18nc("@item", "Unmount"); iconName = QStringLiteral("media-eject"); } if (iconName.isEmpty()) { return new QAction(text, 0); } return new QAction(QIcon::fromTheme(iconName), text, 0); } void PlacesItemModel::requestEject(int index) { const PlacesItem* item = placesItem(index); if (item) { Solid::OpticalDrive* drive = item->device().parent().as(); if (drive) { connect(drive, &Solid::OpticalDrive::ejectDone, this, &PlacesItemModel::slotStorageTeardownDone); drive->eject(); } else { const QString label = item->text(); const QString message = i18nc("@info", "The device '%1' is not a disk and cannot be ejected.", label); emit errorMessage(message); } } } void PlacesItemModel::requestTeardown(int index) { const PlacesItem* item = placesItem(index); if (item) { Solid::StorageAccess* access = item->device().as(); if (access) { connect(access, &Solid::StorageAccess::teardownDone, this, &PlacesItemModel::slotStorageTeardownDone); access->teardown(); } } } bool PlacesItemModel::storageSetupNeeded(int index) const { const PlacesItem* item = placesItem(index); return item ? item->storageSetupNeeded() : false; } void PlacesItemModel::requestStorageSetup(int index) { const PlacesItem* item = placesItem(index); if (!item) { return; } Solid::Device device = item->device(); const bool setup = device.is() && !m_storageSetupInProgress.contains(device.as()) && !device.as()->isAccessible(); if (setup) { Solid::StorageAccess* access = device.as(); m_storageSetupInProgress[access] = index; connect(access, &Solid::StorageAccess::setupDone, this, &PlacesItemModel::slotStorageSetupDone); access->setup(); } } QMimeData* PlacesItemModel::createMimeData(const KItemSet& indexes) const { QList urls; QByteArray itemData; QDataStream stream(&itemData, QIODevice::WriteOnly); for (int index : indexes) { const QUrl itemUrl = placesItem(index)->url(); if (itemUrl.isValid()) { urls << itemUrl; } stream << index; } QMimeData* mimeData = new QMimeData(); if (!urls.isEmpty()) { mimeData->setUrls(urls); + } else { + // #378954: prevent itemDropEvent() drops if there isn't a source url. + mimeData->setData(blacklistItemDropEventMimeType(), QByteArrayLiteral("true")); } mimeData->setData(internalMimeType(), itemData); return mimeData; } bool PlacesItemModel::supportsDropping(int index) const { return index >= 0 && index < count(); } void PlacesItemModel::dropMimeDataBefore(int index, const QMimeData* mimeData) { if (mimeData->hasFormat(internalMimeType())) { // The item has been moved inside the view QByteArray itemData = mimeData->data(internalMimeType()); QDataStream stream(&itemData, QIODevice::ReadOnly); int oldIndex; stream >> oldIndex; if (oldIndex == index || oldIndex == index - 1) { // No moving has been done return; } PlacesItem* oldItem = placesItem(oldIndex); if (!oldItem) { return; } PlacesItem* newItem = new PlacesItem(oldItem->bookmark()); removeItem(oldIndex); if (oldIndex < index) { --index; } const int dropIndex = groupedDropIndex(index, newItem); insertItem(dropIndex, newItem); } else if (mimeData->hasFormat(QStringLiteral("text/uri-list"))) { // One or more items must be added to the model const QList urls = KUrlMimeData::urlsFromMimeData(mimeData); for (int i = urls.count() - 1; i >= 0; --i) { const QUrl& url = urls[i]; QString text = url.fileName(); if (text.isEmpty()) { text = url.host(); } if ((url.isLocalFile() && !QFileInfo(url.toLocalFile()).isDir()) || url.scheme() == QLatin1String("trash")) { // Only directories outside the trash are allowed continue; } PlacesItem* newItem = createPlacesItem(text, url); const int dropIndex = groupedDropIndex(index, newItem); insertItem(dropIndex, newItem); } } } QUrl PlacesItemModel::convertedUrl(const QUrl& url) { QUrl newUrl = url; if (url.scheme() == QLatin1String("timeline")) { newUrl = createTimelineUrl(url); } else if (url.scheme() == QLatin1String("search")) { newUrl = createSearchUrl(url); } return newUrl; } void PlacesItemModel::onItemInserted(int index) { const PlacesItem* insertedItem = placesItem(index); if (insertedItem) { // Take care to apply the PlacesItemModel-order of the inserted item // also to the bookmark-manager. const KBookmark insertedBookmark = insertedItem->bookmark(); const PlacesItem* previousItem = placesItem(index - 1); KBookmark previousBookmark; if (previousItem) { previousBookmark = previousItem->bookmark(); } m_bookmarkManager->root().moveBookmark(insertedBookmark, previousBookmark); } if (index == count() - 1) { // The item has been appended as last item to the list. In this // case assure that it is also appended after the hidden items and // not before (like done otherwise). m_bookmarkedItems.append(0); } else { int modelIndex = -1; int bookmarkIndex = 0; while (bookmarkIndex < m_bookmarkedItems.count()) { if (!m_bookmarkedItems[bookmarkIndex]) { ++modelIndex; if (modelIndex + 1 == index) { break; } } ++bookmarkIndex; } m_bookmarkedItems.insert(bookmarkIndex, 0); } #ifdef PLACESITEMMODEL_DEBUG qCDebug(DolphinDebug) << "Inserted item" << index; showModelState(); #endif } void PlacesItemModel::onItemRemoved(int index, KStandardItem* removedItem) { PlacesItem* placesItem = dynamic_cast(removedItem); if (placesItem) { const KBookmark bookmark = placesItem->bookmark(); m_bookmarkManager->root().deleteBookmark(bookmark); } const int boomarkIndex = bookmarkIndex(index); Q_ASSERT(!m_bookmarkedItems[boomarkIndex]); m_bookmarkedItems.removeAt(boomarkIndex); #ifdef PLACESITEMMODEL_DEBUG qCDebug(DolphinDebug) << "Removed item" << index; showModelState(); #endif } void PlacesItemModel::onItemChanged(int index, const QSet& changedRoles) { const PlacesItem* changedItem = placesItem(index); if (changedItem) { // Take care to apply the PlacesItemModel-order of the changed item // also to the bookmark-manager. const KBookmark insertedBookmark = changedItem->bookmark(); const PlacesItem* previousItem = placesItem(index - 1); KBookmark previousBookmark; if (previousItem) { previousBookmark = previousItem->bookmark(); } m_bookmarkManager->root().moveBookmark(insertedBookmark, previousBookmark); } if (changedRoles.contains("isHidden")) { if (!m_hiddenItemsShown && changedItem->isHidden()) { m_hiddenItemToRemove = index; QTimer::singleShot(0, this, static_cast(&PlacesItemModel::hideItem)); } } } void PlacesItemModel::slotDeviceAdded(const QString& udi) { const Solid::Device device(udi); if (!m_predicate.matches(device)) { return; } m_availableDevices << udi; const KBookmark bookmark = PlacesItem::createDeviceBookmark(m_bookmarkManager, udi); appendItem(new PlacesItem(bookmark)); } void PlacesItemModel::slotDeviceRemoved(const QString& udi) { if (!m_availableDevices.contains(udi)) { return; } for (int i = 0; i < m_bookmarkedItems.count(); ++i) { PlacesItem* item = m_bookmarkedItems[i]; if (item && item->udi() == udi) { m_bookmarkedItems.removeAt(i); delete item; return; } } for (int i = 0; i < count(); ++i) { if (placesItem(i)->udi() == udi) { removeItem(i); return; } } } void PlacesItemModel::slotStorageTeardownDone(Solid::ErrorType error, const QVariant& errorData) { if (error && errorData.isValid()) { emit errorMessage(errorData.toString()); } } void PlacesItemModel::slotStorageSetupDone(Solid::ErrorType error, const QVariant& errorData, const QString& udi) { Q_UNUSED(udi); const int index = m_storageSetupInProgress.take(sender()); const PlacesItem* item = placesItem(index); if (!item) { return; } if (error != Solid::NoError) { if (errorData.isValid()) { emit errorMessage(i18nc("@info", "An error occurred while accessing '%1', the system responded: %2", item->text(), errorData.toString())); } else { emit errorMessage(i18nc("@info", "An error occurred while accessing '%1'", item->text())); } emit storageSetupDone(index, false); } else { emit storageSetupDone(index, true); } } void PlacesItemModel::hideItem() { hideItem(m_hiddenItemToRemove); m_hiddenItemToRemove = -1; } void PlacesItemModel::updateBookmarks() { // Verify whether new bookmarks have been added or existing // bookmarks have been changed. KBookmarkGroup root = m_bookmarkManager->root(); KBookmark newBookmark = root.first(); while (!newBookmark.isNull()) { if (acceptBookmark(newBookmark, m_availableDevices)) { bool found = false; int modelIndex = 0; for (int i = 0; i < m_bookmarkedItems.count(); ++i) { PlacesItem* item = m_bookmarkedItems[i]; if (!item) { item = placesItem(modelIndex); ++modelIndex; } const KBookmark oldBookmark = item->bookmark(); if (equalBookmarkIdentifiers(newBookmark, oldBookmark)) { // The bookmark has been found in the model or as // a hidden item. The content of the bookmark might // have been changed, so an update is done. found = true; if (newBookmark.metaDataItem(QStringLiteral("UDI")).isEmpty()) { item->setBookmark(newBookmark); item->setText(i18nc("KFile System Bookmarks", newBookmark.text().toUtf8().constData())); } break; } } if (!found) { const QString udi = newBookmark.metaDataItem(QStringLiteral("UDI")); /* * See Bug 304878 * Only add a new places item, if the item text is not empty * and if the device is available. Fixes the strange behaviour - * add a places item without text in the Places section - when you * remove a device (e.g. a usb stick) without unmounting. */ if (udi.isEmpty() || Solid::Device(udi).isValid()) { PlacesItem* item = new PlacesItem(newBookmark); if (item->isHidden() && !m_hiddenItemsShown) { m_bookmarkedItems.append(item); } else { appendItemToGroup(item); } } } } newBookmark = root.next(newBookmark); } // Remove items that are not part of the bookmark-manager anymore int modelIndex = 0; for (int i = m_bookmarkedItems.count() - 1; i >= 0; --i) { PlacesItem* item = m_bookmarkedItems[i]; const bool itemIsPartOfModel = (item == 0); if (itemIsPartOfModel) { item = placesItem(modelIndex); } bool hasBeenRemoved = true; const KBookmark oldBookmark = item->bookmark(); KBookmark newBookmark = root.first(); while (!newBookmark.isNull()) { if (equalBookmarkIdentifiers(newBookmark, oldBookmark)) { hasBeenRemoved = false; break; } newBookmark = root.next(newBookmark); } if (hasBeenRemoved) { if (m_bookmarkedItems[i]) { delete m_bookmarkedItems[i]; m_bookmarkedItems.removeAt(i); } else { removeItem(modelIndex); --modelIndex; } } if (itemIsPartOfModel) { ++modelIndex; } } } void PlacesItemModel::saveBookmarks() { m_bookmarkManager->emitChanged(m_bookmarkManager->root()); } void PlacesItemModel::loadBookmarks() { KBookmarkGroup root = m_bookmarkManager->root(); KBookmark bookmark = root.first(); QSet devices = m_availableDevices; QSet missingSystemBookmarks; foreach (const SystemBookmarkData& data, m_systemBookmarks) { missingSystemBookmarks.insert(data.url); } // The bookmarks might have a mixed order of places, devices and search-groups due // to the compatibility with the KFilePlacesPanel. In Dolphin's places panel the // items should always be collected in one group so the items are collected first // in separate lists before inserting them. QList placesItems; QList recentlySavedItems; QList searchForItems; QList devicesItems; while (!bookmark.isNull()) { if (acceptBookmark(bookmark, devices)) { PlacesItem* item = new PlacesItem(bookmark); if (item->groupType() == PlacesItem::DevicesType) { devices.remove(item->udi()); devicesItems.append(item); } else { const QUrl url = bookmark.url(); if (missingSystemBookmarks.contains(url)) { missingSystemBookmarks.remove(url); // Try to retranslate the text of system bookmarks to have translated // items when changing the language. In case if the user has applied a custom // text, the retranslation will fail and the users custom text is still used. // It is important to use "KFile System Bookmarks" as context (see // createSystemBookmarks()). item->setText(i18nc("KFile System Bookmarks", bookmark.text().toUtf8().constData())); item->setSystemItem(true); } switch (item->groupType()) { case PlacesItem::PlacesType: placesItems.append(item); break; case PlacesItem::RecentlySavedType: recentlySavedItems.append(item); break; case PlacesItem::SearchForType: searchForItems.append(item); break; case PlacesItem::DevicesType: default: Q_ASSERT(false); break; } } } bookmark = root.next(bookmark); } if (!missingSystemBookmarks.isEmpty()) { // The current bookmarks don't contain all system-bookmarks. Add the missing // bookmarks. foreach (const SystemBookmarkData& data, m_systemBookmarks) { if (missingSystemBookmarks.contains(data.url)) { PlacesItem* item = createSystemPlacesItem(data); switch (item->groupType()) { case PlacesItem::PlacesType: placesItems.append(item); break; case PlacesItem::RecentlySavedType: recentlySavedItems.append(item); break; case PlacesItem::SearchForType: searchForItems.append(item); break; case PlacesItem::DevicesType: default: Q_ASSERT(false); break; } } } } // Create items for devices that have not been stored as bookmark yet devicesItems.reserve(devicesItems.count() + devices.count()); foreach (const QString& udi, devices) { const KBookmark bookmark = PlacesItem::createDeviceBookmark(m_bookmarkManager, udi); devicesItems.append(new PlacesItem(bookmark)); } QList items; items.append(placesItems); items.append(recentlySavedItems); items.append(searchForItems); items.append(devicesItems); foreach (PlacesItem* item, items) { if (!m_hiddenItemsShown && item->isHidden()) { m_bookmarkedItems.append(item); } else { appendItem(item); } } #ifdef PLACESITEMMODEL_DEBUG qCDebug(DolphinDebug) << "Loaded bookmarks"; showModelState(); #endif } bool PlacesItemModel::acceptBookmark(const KBookmark& bookmark, const QSet& availableDevices) const { const QString udi = bookmark.metaDataItem(QStringLiteral("UDI")); const QUrl url = bookmark.url(); const QString appName = bookmark.metaDataItem(QStringLiteral("OnlyInApp")); const bool deviceAvailable = availableDevices.contains(udi); const bool allowedHere = (appName.isEmpty() || appName == KAboutData::applicationData().componentName() || appName == KAboutData::applicationData().componentName() + AppNamePrefix) && (m_fileIndexingEnabled || (url.scheme() != QLatin1String("timeline") && url.scheme() != QLatin1String("search"))); return (udi.isEmpty() && allowedHere) || deviceAvailable; } PlacesItem* PlacesItemModel::createSystemPlacesItem(const SystemBookmarkData& data) { KBookmark bookmark = PlacesItem::createBookmark(m_bookmarkManager, data.text, data.url, data.icon); const QString protocol = data.url.scheme(); if (protocol == QLatin1String("timeline") || protocol == QLatin1String("search")) { // As long as the KFilePlacesView from kdelibs is available, the system-bookmarks // for "Recently Saved" and "Search For" should be a setting available only // in the Places Panel (see description of AppNamePrefix for more details). const QString appName = KAboutData::applicationData().componentName() + AppNamePrefix; bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), appName); } PlacesItem* item = new PlacesItem(bookmark); item->setSystemItem(true); // Create default view-properties for all "Search For" and "Recently Saved" bookmarks // in case if the user has not already created custom view-properties for a corresponding // query yet. const bool createDefaultViewProperties = (item->groupType() == PlacesItem::SearchForType || item->groupType() == PlacesItem::RecentlySavedType) && !GeneralSettings::self()->globalViewProps(); if (createDefaultViewProperties) { ViewProperties props(convertedUrl(data.url)); if (!props.exist()) { const QString path = data.url.path(); if (path == QLatin1String("/documents")) { props.setViewMode(DolphinView::DetailsView); props.setPreviewsShown(false); props.setVisibleRoles({"text", "path"}); } else if (path == QLatin1String("/images")) { props.setViewMode(DolphinView::IconsView); props.setPreviewsShown(true); props.setVisibleRoles({"text", "imageSize"}); } else if (path == QLatin1String("/audio")) { props.setViewMode(DolphinView::DetailsView); props.setPreviewsShown(false); props.setVisibleRoles({"text", "artist", "album"}); } else if (path == QLatin1String("/videos")) { props.setViewMode(DolphinView::IconsView); props.setPreviewsShown(true); props.setVisibleRoles({"text"}); } else if (data.url.scheme() == QLatin1String("timeline")) { props.setViewMode(DolphinView::DetailsView); props.setVisibleRoles({"text", "modificationtime"}); } } } return item; } void PlacesItemModel::createSystemBookmarks() { Q_ASSERT(m_systemBookmarks.isEmpty()); Q_ASSERT(m_systemBookmarksIndexes.isEmpty()); // Note: The context of the I18N_NOOP2 must be "KFile System Bookmarks". The real // i18nc call is done after reading the bookmark. The reason why the i18nc call is not // done here is because otherwise switching the language would not result in retranslating the // bookmarks. m_systemBookmarks.append(SystemBookmarkData(QUrl::fromLocalFile(QDir::homePath()), QStringLiteral("user-home"), I18N_NOOP2("KFile System Bookmarks", "Home"))); m_systemBookmarks.append(SystemBookmarkData(QUrl(QStringLiteral("remote:/")), QStringLiteral("network-workgroup"), I18N_NOOP2("KFile System Bookmarks", "Network"))); m_systemBookmarks.append(SystemBookmarkData(QUrl::fromLocalFile(QStringLiteral("/")), QStringLiteral("folder-red"), I18N_NOOP2("KFile System Bookmarks", "Root"))); m_systemBookmarks.append(SystemBookmarkData(QUrl(QStringLiteral("trash:/")), QStringLiteral("user-trash"), I18N_NOOP2("KFile System Bookmarks", "Trash"))); if (m_fileIndexingEnabled) { m_systemBookmarks.append(SystemBookmarkData(QUrl(QStringLiteral("timeline:/today")), QStringLiteral("go-jump-today"), I18N_NOOP2("KFile System Bookmarks", "Today"))); m_systemBookmarks.append(SystemBookmarkData(QUrl(QStringLiteral("timeline:/yesterday")), QStringLiteral("view-calendar-day"), I18N_NOOP2("KFile System Bookmarks", "Yesterday"))); m_systemBookmarks.append(SystemBookmarkData(QUrl(QStringLiteral("timeline:/thismonth")), QStringLiteral("view-calendar-month"), I18N_NOOP2("KFile System Bookmarks", "This Month"))); m_systemBookmarks.append(SystemBookmarkData(QUrl(QStringLiteral("timeline:/lastmonth")), QStringLiteral("view-calendar-month"), I18N_NOOP2("KFile System Bookmarks", "Last Month"))); m_systemBookmarks.append(SystemBookmarkData(QUrl(QStringLiteral("search:/documents")), QStringLiteral("folder-text"), I18N_NOOP2("KFile System Bookmarks", "Documents"))); m_systemBookmarks.append(SystemBookmarkData(QUrl(QStringLiteral("search:/images")), QStringLiteral("folder-images"), I18N_NOOP2("KFile System Bookmarks", "Images"))); m_systemBookmarks.append(SystemBookmarkData(QUrl(QStringLiteral("search:/audio")), QStringLiteral("folder-sound"), I18N_NOOP2("KFile System Bookmarks", "Audio Files"))); m_systemBookmarks.append(SystemBookmarkData(QUrl(QStringLiteral("search:/videos")), QStringLiteral("folder-videos"), I18N_NOOP2("KFile System Bookmarks", "Videos"))); } for (int i = 0; i < m_systemBookmarks.count(); ++i) { m_systemBookmarksIndexes.insert(m_systemBookmarks[i].url, i); } } void PlacesItemModel::clear() { m_bookmarkedItems.clear(); KStandardItemModel::clear(); } void PlacesItemModel::initializeAvailableDevices() { QString predicate(QStringLiteral("[[[[ StorageVolume.ignored == false AND [ StorageVolume.usage == 'FileSystem' OR StorageVolume.usage == 'Encrypted' ]]" " OR " "[ IS StorageAccess AND StorageDrive.driveType == 'Floppy' ]]" " OR " "OpticalDisc.availableContent & 'Audio' ]" " OR " "StorageAccess.ignored == false ]")); if (KProtocolInfo::isKnownProtocol(QStringLiteral("mtp"))) { predicate.prepend("["); predicate.append(" OR PortableMediaPlayer.supportedProtocols == 'mtp']"); } m_predicate = Solid::Predicate::fromString(predicate); Q_ASSERT(m_predicate.isValid()); Solid::DeviceNotifier* notifier = Solid::DeviceNotifier::instance(); connect(notifier, &Solid::DeviceNotifier::deviceAdded, this, &PlacesItemModel::slotDeviceAdded); connect(notifier, &Solid::DeviceNotifier::deviceRemoved, this, &PlacesItemModel::slotDeviceRemoved); const QList& deviceList = Solid::Device::listFromQuery(m_predicate); foreach (const Solid::Device& device, deviceList) { m_availableDevices << device.udi(); } } int PlacesItemModel::bookmarkIndex(int index) const { int bookmarkIndex = 0; int modelIndex = 0; while (bookmarkIndex < m_bookmarkedItems.count()) { if (!m_bookmarkedItems[bookmarkIndex]) { if (modelIndex == index) { break; } ++modelIndex; } ++bookmarkIndex; } return bookmarkIndex >= m_bookmarkedItems.count() ? -1 : bookmarkIndex; } void PlacesItemModel::hideItem(int index) { PlacesItem* shownItem = placesItem(index); if (!shownItem) { return; } shownItem->setHidden(true); if (m_hiddenItemsShown) { // Removing items from the model is not allowed if all hidden // items should be shown. return; } const int newIndex = bookmarkIndex(index); if (newIndex >= 0) { const KBookmark hiddenBookmark = shownItem->bookmark(); PlacesItem* hiddenItem = new PlacesItem(hiddenBookmark); const PlacesItem* previousItem = placesItem(index - 1); KBookmark previousBookmark; if (previousItem) { previousBookmark = previousItem->bookmark(); } const bool updateBookmark = (m_bookmarkManager->root().indexOf(hiddenBookmark) >= 0); removeItem(index); if (updateBookmark) { // removeItem() also removed the bookmark from m_bookmarkManager in // PlacesItemModel::onItemRemoved(). However for hidden items the // bookmark should still be remembered, so readd it again: m_bookmarkManager->root().addBookmark(hiddenBookmark); m_bookmarkManager->root().moveBookmark(hiddenBookmark, previousBookmark); } m_bookmarkedItems.insert(newIndex, hiddenItem); } } QString PlacesItemModel::internalMimeType() const { return "application/x-dolphinplacesmodel-" + QString::number((qptrdiff)this); } int PlacesItemModel::groupedDropIndex(int index, const PlacesItem* item) const { Q_ASSERT(item); int dropIndex = index; const PlacesItem::GroupType type = item->groupType(); const int itemCount = count(); if (index < 0) { dropIndex = itemCount; } // Search nearest previous item with the same group int previousIndex = -1; for (int i = dropIndex - 1; i >= 0; --i) { if (placesItem(i)->groupType() == type) { previousIndex = i; break; } } // Search nearest next item with the same group int nextIndex = -1; for (int i = dropIndex; i < count(); ++i) { if (placesItem(i)->groupType() == type) { nextIndex = i; break; } } // Adjust the drop-index to be inserted to the // nearest item with the same group. if (previousIndex >= 0 && nextIndex >= 0) { dropIndex = (dropIndex - previousIndex < nextIndex - dropIndex) ? previousIndex + 1 : nextIndex; } else if (previousIndex >= 0) { dropIndex = previousIndex + 1; } else if (nextIndex >= 0) { dropIndex = nextIndex; } return dropIndex; } bool PlacesItemModel::equalBookmarkIdentifiers(const KBookmark& b1, const KBookmark& b2) { const QString udi1 = b1.metaDataItem(QStringLiteral("UDI")); const QString udi2 = b2.metaDataItem(QStringLiteral("UDI")); if (!udi1.isEmpty() && !udi2.isEmpty()) { return udi1 == udi2; } else { return b1.metaDataItem(QStringLiteral("ID")) == b2.metaDataItem(QStringLiteral("ID")); } } QUrl PlacesItemModel::createTimelineUrl(const QUrl& url) { // TODO: Clarify with the Baloo-team whether it makes sense // provide default-timeline-URLs like 'yesterday', 'this month' // and 'last month'. QUrl timelineUrl; const QString path = url.toDisplayString(QUrl::PreferLocalFile); if (path.endsWith(QLatin1String("yesterday"))) { const QDate date = QDate::currentDate().addDays(-1); const int year = date.year(); const int month = date.month(); const int day = date.day(); timelineUrl = QUrl("timeline:/" + timelineDateString(year, month) + '/' + timelineDateString(year, month, day)); } else if (path.endsWith(QLatin1String("thismonth"))) { const QDate date = QDate::currentDate(); timelineUrl = QUrl("timeline:/" + timelineDateString(date.year(), date.month())); } else if (path.endsWith(QLatin1String("lastmonth"))) { const QDate date = QDate::currentDate().addMonths(-1); timelineUrl = QUrl("timeline:/" + timelineDateString(date.year(), date.month())); } else { Q_ASSERT(path.endsWith(QLatin1String("today"))); timelineUrl = url; } return timelineUrl; } QString PlacesItemModel::timelineDateString(int year, int month, int day) { QString date = QString::number(year) + '-'; if (month < 10) { date += '0'; } date += QString::number(month); if (day >= 1) { date += '-'; if (day < 10) { date += '0'; } date += QString::number(day); } return date; } QUrl PlacesItemModel::createSearchUrl(const QUrl& url) { QUrl searchUrl; #ifdef HAVE_BALOO const QString path = url.toDisplayString(QUrl::PreferLocalFile); if (path.endsWith(QLatin1String("documents"))) { searchUrl = searchUrlForType(QStringLiteral("Document")); } else if (path.endsWith(QLatin1String("images"))) { searchUrl = searchUrlForType(QStringLiteral("Image")); } else if (path.endsWith(QLatin1String("audio"))) { searchUrl = searchUrlForType(QStringLiteral("Audio")); } else if (path.endsWith(QLatin1String("videos"))) { searchUrl = searchUrlForType(QStringLiteral("Video")); } else { Q_ASSERT(false); } #else Q_UNUSED(url); #endif return searchUrl; } #ifdef HAVE_BALOO QUrl PlacesItemModel::searchUrlForType(const QString& type) { Baloo::Query query; query.addType(type); return query.toSearchUrl(); } #endif #ifdef PLACESITEMMODEL_DEBUG void PlacesItemModel::showModelState() { qCDebug(DolphinDebug) << "================================="; qCDebug(DolphinDebug) << "Model:"; qCDebug(DolphinDebug) << "hidden-index model-index text"; int modelIndex = 0; for (int i = 0; i < m_bookmarkedItems.count(); ++i) { if (m_bookmarkedItems[i]) { qCDebug(DolphinDebug) << i << "(Hidden) " << " " << m_bookmarkedItems[i]->dataValue("text").toString(); } else { if (item(modelIndex)) { qCDebug(DolphinDebug) << i << " " << modelIndex << " " << item(modelIndex)->dataValue("text").toString(); } else { qCDebug(DolphinDebug) << i << " " << modelIndex << " " << "(not available yet)"; } ++modelIndex; } } qCDebug(DolphinDebug); qCDebug(DolphinDebug) << "Bookmarks:"; int bookmarkIndex = 0; KBookmarkGroup root = m_bookmarkManager->root(); KBookmark bookmark = root.first(); while (!bookmark.isNull()) { const QString udi = bookmark.metaDataItem("UDI"); const QString text = udi.isEmpty() ? bookmark.text() : udi; if (bookmark.metaDataItem("IsHidden") == QLatin1String("true")) { qCDebug(DolphinDebug) << bookmarkIndex << "(Hidden)" << text; } else { qCDebug(DolphinDebug) << bookmarkIndex << " " << text; } bookmark = root.next(bookmark); ++bookmarkIndex; } } #endif