/* * Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "filemenu.h" #include "createnewmenu.h" #include "filepropsdialog.h" #include "utilities.h" #include "fileoperation.h" #include "filelauncher.h" #include "appchooserdialog.h" #include "customactions/fileaction.h" #include "customaction_p.h" #include #include #include #include "filemenu_p.h" #include "core/archiver.h" namespace Fm { FileMenu::FileMenu(Fm::FileInfoList files, std::shared_ptr info, Fm::FilePath cwd, bool isWritableDir, const QString& title, QWidget* parent): QMenu(title, parent), files_{std::move(files)}, info_{std::move(info)}, cwd_{std::move(cwd)}, unTrashAction_(nullptr), fileLauncher_(nullptr) { useTrash_ = true; confirmDelete_ = true; confirmTrash_ = false; // Confirm before moving files into "trash can" openAction_ = nullptr; openWithMenuAction_ = nullptr; openWithAction_ = nullptr; separator1_ = nullptr; cutAction_ = nullptr; copyAction_ = nullptr; pasteAction_ = nullptr; deleteAction_ = nullptr; unTrashAction_ = nullptr; renameAction_ = nullptr; separator2_ = nullptr; propertiesAction_ = nullptr; auto mime_type = info_->mimeType(); Fm::FilePath path = info_->path(); // check if the files are of the same type sameType_ = files_.isSameType(); // check if the files are on the same filesystem sameFilesystem_ = files_.isSameFilesystem(); // check if the files are all virtual // FIXME: allVirtual_ = sameFilesystem_ && fm_path_is_virtual(path); allVirtual_ = false; // check if the files are all in the trash can allTrash_ = sameFilesystem_ && path.hasUriScheme("trash"); openAction_ = new QAction(QIcon::fromTheme("document-open"), tr("Open"), this); connect(openAction_, &QAction::triggered, this, &FileMenu::onOpenTriggered); addAction(openAction_); openWithMenuAction_ = new QAction(tr("Open With..."), this); addAction(openWithMenuAction_); // create the "Open with..." sub menu QMenu* menu = new QMenu(this); openWithMenuAction_->setMenu(menu); if(sameType_) { /* add specific menu items for this mime type */ if(mime_type && !allVirtual_) { /* the file has a valid mime-type and its not virtual */ GList* apps = g_app_info_get_all_for_type(mime_type->name()); GList* l; for(l = apps; l; l = l->next) { Fm::GAppInfoPtr app{G_APP_INFO(l->data), false}; // check if the command really exists gchar* program_path = g_find_program_in_path(g_app_info_get_executable(app.get())); if(!program_path) { continue; } g_free(program_path); // create a QAction for the application. AppInfoAction* action = new AppInfoAction(std::move(app), menu); connect(action, &QAction::triggered, this, &FileMenu::onApplicationTriggered); menu->addAction(action); } g_list_free(apps); } } menu->addSeparator(); openWithAction_ = new QAction(tr("Other Applications"), this); connect(openWithAction_, &QAction::triggered, this, &FileMenu::onOpenWithTriggered); menu->addAction(openWithAction_); separator1_ = addSeparator(); createAction_ = new QAction(tr("Create &New"), this); Fm::FilePath dirPath = files_.size() == 1 && info_->isDir() ? path : cwd_; createAction_->setMenu(new CreateNewMenu(nullptr, dirPath, this)); addAction(createAction_); separator2_ = addSeparator(); if(allTrash_) { // all selected files are in trash:/// bool can_restore = true; /* only immediate children of trash:/// can be restored. */ auto trash_root = Fm::FilePath::fromUri("trash:///"); for(auto& file: files_) { Fm::FilePath trash_path = file->path(); if(!trash_root.isParentOf(trash_path)) { can_restore = false; break; } } if(can_restore) { unTrashAction_ = new QAction(tr("&Restore"), this); connect(unTrashAction_, &QAction::triggered, this, &FileMenu::onUnTrashTriggered); addAction(unTrashAction_); } } else { // ordinary files cutAction_ = new QAction(QIcon::fromTheme("edit-cut"), tr("Cut"), this); connect(cutAction_, &QAction::triggered, this, &FileMenu::onCutTriggered); addAction(cutAction_); copyAction_ = new QAction(QIcon::fromTheme("edit-copy"), tr("Copy"), this); connect(copyAction_, &QAction::triggered, this, &FileMenu::onCopyTriggered); addAction(copyAction_); pasteAction_ = new QAction(QIcon::fromTheme("edit-paste"), tr("Paste"), this); connect(pasteAction_, &QAction::triggered, this, &FileMenu::onPasteTriggered); addAction(pasteAction_); deleteAction_ = new QAction(QIcon::fromTheme("user-trash"), tr("&Move to Trash"), this); connect(deleteAction_, &QAction::triggered, this, &FileMenu::onDeleteTriggered); addAction(deleteAction_); renameAction_ = new QAction(tr("Rename"), this); connect(renameAction_, &QAction::triggered, this, &FileMenu::onRenameTriggered); addAction(renameAction_); // disable actons that can't be used bool hasAccessible(false); bool hasDeletable(false); bool hasRenamable(false); for(auto& file: files_) { if(file->isAccessible()) { hasAccessible = true; } if(file->isDeletable()) { hasDeletable = true; } if(file->canSetName()) { hasRenamable = true; } if (hasAccessible && hasDeletable && hasRenamable) { break; } } copyAction_->setEnabled(hasAccessible); cutAction_->setEnabled(hasDeletable); deleteAction_->setEnabled(hasDeletable); renameAction_->setEnabled(hasRenamable); if(!(sameType_ && info_->isDir() && (files_.size() > 1 ? isWritableDir : info_->isWritable()))) { pasteAction_->setEnabled(false); } } // DES-EMA custom actions integration // FIXME: port these parts to Fm API auto custom_actions = FileActionItem::get_actions_for_files(files_); for(auto& item: custom_actions) { if(item && !(item->get_target() & FILE_ACTION_TARGET_CONTEXT)) { continue; // this item is not for context menu } if(item == custom_actions.front() && !item->is_action()) { addSeparator(); // before all custom actions } addCustomActionItem(this, item); } // archiver integration // FIXME: we need to modify upstream libfm to include some Qt-based archiver programs. if(!allVirtual_) { if(sameType_) { auto archiver = Archiver::defaultArchiver(); if(archiver) { if(archiver->isMimeTypeSupported(mime_type->name())) { QAction* archiverSeparator = nullptr; if(cwd_ && archiver->canExtractArchivesTo()) { archiverSeparator = addSeparator(); QAction* action = new QAction(tr("Extract to..."), this); connect(action, &QAction::triggered, this, &FileMenu::onExtract); addAction(action); } if(archiver->canExtractArchives()) { if(!archiverSeparator) { addSeparator(); } QAction* action = new QAction(tr("Extract Here"), this); connect(action, &QAction::triggered, this, &FileMenu::onExtractHere); addAction(action); } } else if(archiver->canCreateArchive()){ addSeparator(); QAction* action = new QAction(tr("Compress"), this); connect(action, &QAction::triggered, this, &FileMenu::onCompress); addAction(action); } } } } separator3_ = addSeparator(); propertiesAction_ = new QAction(QIcon::fromTheme("document-properties"), tr("Properties"), this); connect(propertiesAction_, &QAction::triggered, this, &FileMenu::onFilePropertiesTriggered); addAction(propertiesAction_); } FileMenu::~FileMenu() { } void FileMenu::addCustomActionItem(QMenu* menu, std::shared_ptr item) { if(!item) { // separator addSeparator(); return; } // this action is not for context menu if(item->is_action() && !(item->get_target() & FILE_ACTION_TARGET_CONTEXT)) { return; } CustomAction* action = new CustomAction(item, menu); menu->addAction(action); if(item->is_menu()) { auto& subitems = item->get_sub_items(); if(!subitems.empty()) { QMenu* submenu = new QMenu(menu); for(auto& subitem: subitems) { addCustomActionItem(submenu, subitem); } action->setMenu(submenu); } } else if(item->is_action()) { connect(action, &QAction::triggered, this, &FileMenu::onCustomActionTrigerred); } } void FileMenu::onOpenTriggered() { if(fileLauncher_) { fileLauncher_->launchFiles(nullptr, files_); } else { // use the default launcher Fm::FileLauncher launcher; launcher.launchFiles(nullptr, files_); } } void FileMenu::onOpenWithTriggered() { AppChooserDialog dlg(nullptr); if(sameType_) { dlg.setMimeType(info_->mimeType()); } else { // we can only set the selected app as default if all files are of the same type dlg.setCanSetDefault(false); } if(execModelessDialog(&dlg) == QDialog::Accepted) { auto app = dlg.selectedApp(); if(app) { openFilesWithApp(app.get()); } } } void FileMenu::openFilesWithApp(GAppInfo* app) { GList* uris = nullptr; for(auto& file: files_) { auto uri = file->path().uri(); uris = g_list_prepend(uris, uri.release()); } fm_app_info_launch_uris(app, uris, nullptr, nullptr); g_list_foreach(uris, (GFunc)g_free, nullptr); g_list_free(uris); } void FileMenu::onApplicationTriggered() { AppInfoAction* action = static_cast(sender()); openFilesWithApp(action->appInfo().get()); } void FileMenu::onCustomActionTrigerred() { CustomAction* action = static_cast(sender()); auto& item = action->item(); /* g_debug("item: %s is activated, id:%s", fm_file_action_item_get_name(item), fm_file_action_item_get_id(item)); */ CStrPtr output; item->launch(nullptr, files_, output); if(output) { QMessageBox::information(this, tr("Output"), output.get()); } } void FileMenu::onFilePropertiesTriggered() { FilePropsDialog::showForFiles(files_); } void FileMenu::onCopyTriggered() { Fm::copyFilesToClipboard(files_.paths()); } void FileMenu::onCutTriggered() { Fm::cutFilesToClipboard(files_.paths()); } void FileMenu::onDeleteTriggered() { auto paths = files_.paths(); if(useTrash_) { FileOperation::trashFiles(paths, confirmTrash_); } else { FileOperation::deleteFiles(paths, confirmDelete_); } } void FileMenu::onUnTrashTriggered() { FileOperation::unTrashFiles(files_.paths()); } void FileMenu::onPasteTriggered() { Fm::pasteFilesFromClipboard(cwd_); } void FileMenu::onRenameTriggered() { // if there is a view and this is a single file, just edit the current index if(files_.size() == 1) { if (QAbstractItemView* view = qobject_cast(parentWidget())) { QModelIndexList selIndexes = view->selectionModel()->selectedIndexes(); if(selIndexes.size() > 1) { // in the detailed list mode, only the first index is editable view->setCurrentIndex(selIndexes.at(0)); } if (view->currentIndex().isValid()) { view->edit(view->currentIndex()); return; } } } for(auto& info: files_) { if(!Fm::renameFile(info, nullptr)) { break; } } } void FileMenu::setUseTrash(bool trash) { if(useTrash_ != trash) { useTrash_ = trash; if(deleteAction_) { deleteAction_->setText(useTrash_ ? tr("&Move to Trash") : tr("&Delete")); deleteAction_->setIcon(useTrash_ ? QIcon::fromTheme("user-trash") : QIcon::fromTheme("edit-delete")); } } } void FileMenu::onCompress() { auto archiver = Archiver::defaultArchiver(); if(archiver) { archiver->createArchive(nullptr, files_.paths()); } } void FileMenu::onExtract() { auto archiver = Archiver::defaultArchiver(); if(archiver) { archiver->extractArchives(nullptr, files_.paths()); } } void FileMenu::onExtractHere() { auto archiver = Archiver::defaultArchiver(); if(archiver) { archiver->extractArchivesTo(nullptr, files_.paths(), cwd_); } } } // namespace Fm