1 /*
2  * Copyright (C) 2012 - 2015  Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
3  * Copyright (C) 2012 - 2014 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  */
20 
21 
22 #include "foldermenu.h"
23 #include "createnewmenu.h"
24 #include "filepropsdialog.h"
25 #include "folderview.h"
26 #include "utilities.h"
27 #include "fileoperation.h"
28 #include <cstring> // for memset
29 #include <QDebug>
30 #include "customaction_p.h"
31 #include "customactions/fileaction.h"
32 #include <QMessageBox>
33 
34 namespace Fm {
35 
FolderMenu(FolderView * view,QWidget * parent)36 FolderMenu::FolderMenu(FolderView* view, QWidget* parent):
37     QMenu(parent),
38     view_(view) {
39 
40     createAction_ = nullptr;
41     pasteAction_ = nullptr;
42     separator1_ = nullptr;
43     separator2_ = nullptr;
44 
45     ProxyFolderModel* model = view_->model();
46 
47     bool insideTrash(view_->path() && view_->path().hasUriScheme("trash"));
48     if(insideTrash) {
49         if(auto folder = view_->folder()) {
50             if(!folder->isEmpty()) {
51                 auto trashAction = new QAction(tr("Empty Trash"), this);
52                 addAction(trashAction);
53                 connect(trashAction, &QAction::triggered, []() {
54                     Fm::FilePathList files;
55                     files.push_back(Fm::FilePath::fromUri("trash:///"));
56                     Fm::FileOperation::deleteFiles(std::move(files), true);
57                 });
58                 addSeparator();
59             }
60         }
61     }
62     else {
63         createAction_ = new QAction(tr("Create &New"), this);
64         addAction(createAction_);
65         createAction_->setMenu(new CreateNewMenu(view_, view_->path(), this));
66         separator1_ = addSeparator();
67 
68         pasteAction_ = new QAction(QIcon::fromTheme(QStringLiteral("edit-paste")), tr("&Paste"), this);
69         addAction(pasteAction_);
70         connect(pasteAction_, &QAction::triggered, this, &FolderMenu::onPasteActionTriggered);
71         separator2_ = addSeparator();
72     }
73 
74     auto selMode = view_->childView()->selectionMode();
75     if(selMode == QAbstractItemView::SingleSelection || selMode == QAbstractItemView::NoSelection) {
76         selectAllAction_ = nullptr;
77         invertSelectionAction_ = nullptr;
78         separator3_ = nullptr;
79     }
80     else {
81         selectAllAction_ = new QAction(tr("Select &All"), this);
82         addAction(selectAllAction_);
83         connect(selectAllAction_, &QAction::triggered, this, &FolderMenu::onSelectAllActionTriggered);
84 
85         invertSelectionAction_ = new QAction(tr("Invert Selection"), this);
86         addAction(invertSelectionAction_);
87         connect(invertSelectionAction_, &QAction::triggered, this, &FolderMenu::onInvertSelectionActionTriggered);
88 
89         separator3_ = addSeparator();
90     }
91 
92     sortAction_ = new QAction(tr("Sorting"), this);
93     addAction(sortAction_);
94     createSortMenu();
95     sortAction_->setMenu(sortMenu_);
96 
97     showHiddenAction_ = new QAction(tr("Show Hidden"), this);
98     addAction(showHiddenAction_);
99     showHiddenAction_->setCheckable(true);
100     showHiddenAction_->setChecked(model->showHidden());
101     connect(showHiddenAction_, &QAction::triggered, this, &FolderMenu::onShowHiddenActionTriggered);
102 
103     auto folderInfo = view_->folderInfo();
104     if(folderInfo) { // should never be null (see FolderView::onFileClicked)
105         // DES-EMA custom actions integration
106         FileInfoList files;
107         files.push_back(folderInfo);
108         auto custom_actions = FileActionItem::get_actions_for_files(files);
109         bool firstAction = true;
110         for(auto& item: custom_actions) {
111             if(item && !(item->get_target() & FILE_ACTION_TARGET_CONTEXT)) {
112                 continue;  // this item is not for context menu
113             }
114             if(firstAction) {
115                 addSeparator(); // before all custom actions
116                 firstAction = false;
117             }
118             addCustomActionItem(this, item);
119         }
120 
121         // disable actons that can't be used
122         if(pasteAction_) {
123             pasteAction_->setEnabled(folderInfo->isWritable());
124         }
125         if(createAction_) {
126             createAction_->setEnabled(folderInfo->isWritable());
127         }
128     }
129 
130     separator4_ = addSeparator();
131 
132     propertiesAction_ = new QAction(tr("Folder Pr&operties"), this);
133     addAction(propertiesAction_);
134     connect(propertiesAction_, &QAction::triggered, this, &FolderMenu::onPropertiesActionTriggered);
135 }
136 
~FolderMenu()137 FolderMenu::~FolderMenu() {
138 }
139 
addCustomActionItem(QMenu * menu,std::shared_ptr<const FileActionItem> item)140 void FolderMenu::addCustomActionItem(QMenu* menu, std::shared_ptr<const FileActionItem> item) {
141     if(!item) {
142         return;
143     }
144     if(item->is_action() && !(item->get_target() & FILE_ACTION_TARGET_CONTEXT)) {
145         return;
146     }
147 
148     CustomAction* action = new CustomAction(item, menu);
149     menu->addAction(action);
150     if(item->is_menu()) {
151         auto& subitems = item->get_sub_items();
152         if(!subitems.empty()) {
153             QMenu* submenu = new QMenu(menu);
154             for(auto& subitem: subitems) {
155                 addCustomActionItem(submenu, subitem);
156             }
157             action->setMenu(submenu);
158         }
159     }
160     else if(item->is_action()) {
161         connect(action, &QAction::triggered, this, &FolderMenu::onCustomActionTrigerred);
162     }
163 }
164 
onCustomActionTrigerred()165 void FolderMenu::onCustomActionTrigerred() {
166     CustomAction* action = static_cast<CustomAction*>(sender());
167     auto& item = action->item();
168     auto folderInfo = view_->folderInfo();
169     if(folderInfo) {
170         CStrPtr output;
171         FileInfoList file_list;
172         file_list.push_back(folderInfo);
173         item->launch(nullptr, file_list, output);
174         if(output) {
175             QMessageBox::information(this, tr("Output"), QString::fromUtf8(output.get()));
176         }
177     }
178 }
179 
addSortMenuItem(const QString & title,int id)180 void FolderMenu::addSortMenuItem(const QString &title, int id) {
181     QAction* action = new QAction(title, this);
182     action->setData(QVariant(id));
183     sortMenu_->addAction(action);
184     action->setCheckable(true);
185     action->setChecked(id == view_->model()->sortColumn());
186     sortActionGroup_->addAction(action);
187     connect(action, &QAction::triggered, this, &FolderMenu::onSortActionTriggered);
188 }
189 
createSortMenu()190 void FolderMenu::createSortMenu() {
191     ProxyFolderModel* model = view_->model();
192 
193     sortMenu_ = new QMenu(this);
194     sortActionGroup_ = new QActionGroup(sortMenu_);
195     sortActionGroup_->setExclusive(true);
196 
197     addSortMenuItem(tr("By File Name"), FolderModel::ColumnFileName);
198     addSortMenuItem(tr("By Modification Time"), FolderModel::ColumnFileMTime);
199     addSortMenuItem(tr("By Creation Time"), FolderModel::ColumnFileCrTime);
200     if(auto folderPath = view_->path()) {
201         if(strcmp(folderPath.toString().get(), "trash:///") == 0) {
202             addSortMenuItem(tr("By Deletion Time"), FolderModel::ColumnFileDTime);
203         }
204     }
205     addSortMenuItem(tr("By File Size"), FolderModel::ColumnFileSize);
206     addSortMenuItem(tr("By File Type"), FolderModel::ColumnFileType);
207     addSortMenuItem(tr("By File Owner"), FolderModel::ColumnFileOwner);
208     addSortMenuItem(tr("By File Group"), FolderModel::ColumnFileGroup);
209 
210     sortMenu_->addSeparator();
211 
212     QActionGroup* group = new QActionGroup(this);
213     group->setExclusive(true);
214     actionAscending_ = new QAction(tr("Ascending"), this);
215     actionAscending_->setCheckable(true);
216     sortMenu_->addAction(actionAscending_);
217     group->addAction(actionAscending_);
218 
219     actionDescending_ = new QAction(tr("Descending"), this);
220     actionDescending_->setCheckable(true);
221     sortMenu_->addAction(actionDescending_);
222     group->addAction(actionDescending_);
223 
224     if(model->sortOrder() == Qt::AscendingOrder) {
225         actionAscending_->setChecked(true);
226     }
227     else {
228         actionDescending_->setChecked(true);
229     }
230 
231     connect(actionAscending_, &QAction::triggered, this, &FolderMenu::onSortOrderActionTriggered);
232     connect(actionDescending_, &QAction::triggered, this, &FolderMenu::onSortOrderActionTriggered);
233 
234     sortMenu_->addSeparator();
235 
236     QAction* actionFolderFirst = new QAction(tr("Folder First"), this);
237     sortMenu_->addAction(actionFolderFirst);
238     actionFolderFirst->setCheckable(true);
239     if(model->folderFirst()) {
240         actionFolderFirst->setChecked(true);
241     }
242     connect(actionFolderFirst, &QAction::triggered, this, &FolderMenu::onFolderFirstActionTriggered);
243 
244     QAction* actionHiddenLast = new QAction(tr("Hidden Last"), this);
245     sortMenu_->addAction(actionHiddenLast);
246     actionHiddenLast->setCheckable(true);
247     if(model->hiddenLast()) {
248         actionHiddenLast->setChecked(true);
249     }
250     connect(actionHiddenLast, &QAction::triggered, this, &FolderMenu::onHiddenLastActionTriggered);
251 
252     QAction* actionCaseSensitive = new QAction(tr("Case Sensitive"), this);
253     sortMenu_->addAction(actionCaseSensitive);
254     actionCaseSensitive->setCheckable(true);
255 
256     if(model->sortCaseSensitivity() == Qt::CaseSensitive) {
257         actionCaseSensitive->setChecked(true);
258     }
259 
260     connect(actionCaseSensitive, &QAction::triggered, this, &FolderMenu::onCaseSensitiveActionTriggered);
261 }
262 
onPasteActionTriggered()263 void FolderMenu::onPasteActionTriggered() {
264     auto folderPath = view_->path();
265     if(folderPath) {
266         pasteFilesFromClipboard(folderPath);
267     }
268 }
269 
onSelectAllActionTriggered()270 void FolderMenu::onSelectAllActionTriggered() {
271     view_->selectAll();
272 }
273 
onInvertSelectionActionTriggered()274 void FolderMenu::onInvertSelectionActionTriggered() {
275     view_->invertSelection();
276 }
277 
onSortActionTriggered(bool)278 void FolderMenu::onSortActionTriggered(bool /*checked*/) {
279     ProxyFolderModel* model = view_->model();
280 
281     if(model && sortActionGroup_) {
282         QAction* action = static_cast<QAction*>(sender());
283 
284         const auto actions = sortActionGroup_->actions();
285         for(auto a : actions) {
286             if(a == action) {
287                 int col = a->data().toInt();
288                 if(col >= 0 && col < FolderModel::NumOfColumns) {
289                     model->sort(col, model->sortOrder());
290                 }
291                 break;
292             }
293         }
294     }
295 }
296 
onSortOrderActionTriggered(bool)297 void FolderMenu::onSortOrderActionTriggered(bool /*checked*/) {
298     ProxyFolderModel* model = view_->model();
299 
300     if(model) {
301         QAction* action = static_cast<QAction*>(sender());
302         Qt::SortOrder order;
303 
304         if(action == actionAscending_) {
305             order = Qt::AscendingOrder;
306         }
307         else {
308             order = Qt::DescendingOrder;
309         }
310 
311         model->sort(model->sortColumn(), order);
312     }
313 }
314 
onShowHiddenActionTriggered(bool checked)315 void FolderMenu::onShowHiddenActionTriggered(bool checked) {
316     ProxyFolderModel* model = view_->model();
317 
318     if(model) {
319         qDebug("show hidden: %d", checked);
320         model->setShowHidden(checked);
321     }
322 }
323 
onCaseSensitiveActionTriggered(bool checked)324 void FolderMenu::onCaseSensitiveActionTriggered(bool checked) {
325     ProxyFolderModel* model = view_->model();
326 
327     if(model) {
328         model->setSortCaseSensitivity(checked ? Qt::CaseSensitive : Qt::CaseInsensitive);
329     }
330 }
331 
onFolderFirstActionTriggered(bool checked)332 void FolderMenu::onFolderFirstActionTriggered(bool checked) {
333     ProxyFolderModel* model = view_->model();
334 
335     if(model) {
336         model->setFolderFirst(checked);
337     }
338 }
339 
onHiddenLastActionTriggered(bool checked)340 void FolderMenu::onHiddenLastActionTriggered(bool checked) {
341     ProxyFolderModel* model = view_->model();
342 
343     if(model) {
344         model->setHiddenLast(checked);
345     }
346 }
347 
onPropertiesActionTriggered()348 void FolderMenu::onPropertiesActionTriggered() {
349     auto folderInfo = view_->folderInfo();
350     if(folderInfo) {
351         FilePropsDialog::showForFile(folderInfo);
352     }
353 }
354 
355 } // namespace Fm
356