You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
415 lines
14 KiB
415 lines
14 KiB
/*
|
|
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
|
*
|
|
* 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 <QMessageBox>
|
|
#include <QAbstractItemView>
|
|
#include <QDebug>
|
|
#include "filemenu_p.h"
|
|
|
|
#include "core/archiver.h"
|
|
|
|
namespace Fm {
|
|
|
|
FileMenu::FileMenu(Fm::FileInfoList files, std::shared_ptr<const Fm::FileInfo> 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<const FileActionItem> 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<AppInfoAction*>(sender());
|
|
openFilesWithApp(action->appInfo().get());
|
|
}
|
|
|
|
void FileMenu::onCustomActionTrigerred() {
|
|
CustomAction* action = static_cast<CustomAction*>(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<QAbstractItemView*>(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
|