1 /*
2     SPDX-FileCopyrightText: 2004 Roberto Raggi <roberto@kdevelop.org>
3     SPDX-FileCopyrightText: 2007 Andreas Pakulat <apaku@gmx.de>
4     SPDX-FileCopyrightText: 2016, 2017 Alexander Potashev <aspotashev@gmail.com>
5 
6     SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "projectmanagerviewplugin.h"
10 
11 #include <QApplication>
12 #include <QAction>
13 #include <QClipboard>
14 #include <QInputDialog>
15 #include <QList>
16 #include <QMimeData>
17 #include <QUrl>
18 
19 #include <KActionCollection>
20 #include <KLocalizedString>
21 #include <KMessageBox>
22 #include <KParts/MainWindow>
23 #include <KPluginFactory>
24 #include <KIO/Paste>
25 #include <KFileItem>
26 #include <KUrlMimeData>
27 
28 #include <project/projectmodel.h>
29 #include <project/projectbuildsetmodel.h>
30 #include <interfaces/icore.h>
31 #include <interfaces/iproject.h>
32 #include <project/interfaces/iprojectfilemanager.h>
33 #include <project/interfaces/ibuildsystemmanager.h>
34 #include <interfaces/iuicontroller.h>
35 #include <interfaces/iruncontroller.h>
36 #include <interfaces/idocumentcontroller.h>
37 #include <util/jobstatus.h>
38 #include <util/path.h>
39 #include <interfaces/iprojectcontroller.h>
40 #include <interfaces/context.h>
41 #include <interfaces/contextmenuextension.h>
42 #include <interfaces/iselectioncontroller.h>
43 #include <sublime/message.h>
44 #include <serialization/indexedstring.h>
45 
46 #include "projectmanagerview.h"
47 #include "debug.h"
48 #include "cutcopypastehelpers.h"
49 
50 using namespace KDevelop;
51 
52 K_PLUGIN_FACTORY_WITH_JSON(ProjectManagerFactory, "kdevprojectmanagerview.json", registerPlugin<ProjectManagerViewPlugin>();)
53 
54 namespace {
55 
createSeparatorAction()56 QAction* createSeparatorAction()
57 {
58     auto* separator = new QAction(nullptr);
59     separator->setSeparator(true);
60     return separator;
61 }
62 
63 // Returns nullptr iff the list of URLs to copy/cut was empty
createClipboardMimeData(const bool cut)64 QMimeData* createClipboardMimeData(const bool cut)
65 {
66     auto* ctx = dynamic_cast<KDevelop::ProjectItemContext*>(
67         ICore::self()->selectionController()->currentSelection());
68     QList<QUrl> urls;
69     QList<QUrl> mostLocalUrls;
70     const auto& items = ctx->items();
71     for (const ProjectBaseItem* item : items) {
72         if (item->folder() || item->file()) {
73             const QUrl& url = item->path().toUrl();
74             urls << url;
75             mostLocalUrls << KFileItem(url).mostLocalUrl();
76         }
77     }
78     qCDebug(PLUGIN_PROJECTMANAGERVIEW) << urls;
79 
80     if (urls.isEmpty()) {
81         return nullptr;
82     }
83 
84     auto* mimeData = new QMimeData;
85     KIO::setClipboardDataCut(mimeData, cut);
86     KUrlMimeData::setUrls(urls, mostLocalUrls, mimeData);
87     return mimeData;
88 }
89 
90 } // anonymous namespace
91 
92 class KDevProjectManagerViewFactory: public KDevelop::IToolViewFactory
93 {
94     public:
KDevProjectManagerViewFactory(ProjectManagerViewPlugin * plugin)95         explicit KDevProjectManagerViewFactory( ProjectManagerViewPlugin *plugin ): mplugin( plugin )
96         {}
create(QWidget * parent=nullptr)97         QWidget* create( QWidget *parent = nullptr ) override
98         {
99             return new ProjectManagerView( mplugin, parent );
100         }
defaultPosition() const101         Qt::DockWidgetArea defaultPosition() const override
102         {
103             return Qt::LeftDockWidgetArea;
104         }
id() const105         QString id() const override
106         {
107             return QStringLiteral("org.kdevelop.ProjectsView");
108         }
109     private:
110         ProjectManagerViewPlugin *mplugin;
111 };
112 
113 class ProjectManagerViewPluginPrivate
114 {
115 public:
ProjectManagerViewPluginPrivate()116     ProjectManagerViewPluginPrivate()
117     {}
118     KDevProjectManagerViewFactory *factory;
119     QList<QPersistentModelIndex> ctxProjectItemList;
120     QAction* m_buildAll;
121     QAction* m_build;
122     QAction* m_install;
123     QAction* m_clean;
124     QAction* m_configure;
125     QAction* m_prune;
126 };
127 
itemsFromIndexes(const QList<QPersistentModelIndex> & indexes)128 static QList<ProjectBaseItem*> itemsFromIndexes(const QList<QPersistentModelIndex>& indexes)
129 {
130     QList<ProjectBaseItem*> items;
131     ProjectModel* model = ICore::self()->projectController()->projectModel();
132     items.reserve(indexes.size());
133     for (const QModelIndex& index : indexes) {
134         items += model->itemFromIndex(index);
135     }
136     return items;
137 }
138 
ProjectManagerViewPlugin(QObject * parent,const QVariantList &)139 ProjectManagerViewPlugin::ProjectManagerViewPlugin( QObject *parent, const QVariantList& )
140         : IPlugin( QStringLiteral("kdevprojectmanagerview"), parent ), d(new ProjectManagerViewPluginPrivate)
141 {
142     d->m_buildAll = new QAction(i18nc("@action", "Build All Projects"), this);
143     d->m_buildAll->setIcon(QIcon::fromTheme(QStringLiteral("run-build")));
144     connect( d->m_buildAll, &QAction::triggered, this, &ProjectManagerViewPlugin::buildAllProjects );
145     actionCollection()->addAction( QStringLiteral("project_buildall"), d->m_buildAll );
146 
147     d->m_build = new QAction(i18nc("@action", "Build Selection"), this);
148     d->m_build->setIconText(i18nc("@action:intoolbar", "Build"));
149     actionCollection()->setDefaultShortcut( d->m_build, Qt::Key_F8 );
150     d->m_build->setIcon(QIcon::fromTheme(QStringLiteral("run-build")));
151     d->m_build->setEnabled( false );
152     connect( d->m_build, &QAction::triggered, this, &ProjectManagerViewPlugin::buildProjectItems );
153     actionCollection()->addAction( QStringLiteral("project_build"), d->m_build );
154     d->m_install = new QAction(i18nc("@action", "Install Selection"), this);
155     d->m_install->setIconText(i18nc("@action:intoolbar", "Install"));
156     d->m_install->setIcon(QIcon::fromTheme(QStringLiteral("run-build-install")));
157     actionCollection()->setDefaultShortcut(d->m_install, Qt::SHIFT | Qt::Key_F8);
158     d->m_install->setEnabled( false );
159     connect( d->m_install, &QAction::triggered, this, &ProjectManagerViewPlugin::installProjectItems );
160     actionCollection()->addAction( QStringLiteral("project_install"), d->m_install );
161     d->m_clean = new QAction(i18nc("@action", "Clean Selection"), this);
162     d->m_clean->setIconText(i18nc("@action:intoolbar", "Clean"));
163     d->m_clean->setIcon(QIcon::fromTheme(QStringLiteral("run-build-clean")));
164     d->m_clean->setEnabled( false );
165     connect( d->m_clean, &QAction::triggered, this, &ProjectManagerViewPlugin::cleanProjectItems );
166     actionCollection()->addAction( QStringLiteral("project_clean"), d->m_clean );
167     d->m_configure = new QAction(i18nc("@action", "Configure Selection"), this);
168     d->m_configure->setMenuRole( QAction::NoRole ); // OSX: Be explicit about role, prevent hiding due to conflict with "Preferences..." menu item
169     d->m_configure->setIconText(i18nc("@action:intoolbar", "Configure"));
170     d->m_configure->setIcon(QIcon::fromTheme(QStringLiteral("run-build-configure")));
171     d->m_configure->setEnabled( false );
172     connect( d->m_configure, &QAction::triggered, this, &ProjectManagerViewPlugin::configureProjectItems );
173     actionCollection()->addAction( QStringLiteral("project_configure"), d->m_configure );
174     d->m_prune = new QAction(i18nc("@action", "Prune Selection"), this);
175     d->m_prune->setIconText(i18nc("@action:intoolbar", "Prune"));
176     d->m_prune->setIcon(QIcon::fromTheme(QStringLiteral("run-build-prune")));
177     d->m_prune->setEnabled( false );
178     connect( d->m_prune, &QAction::triggered, this, &ProjectManagerViewPlugin::pruneProjectItems );
179     actionCollection()->addAction( QStringLiteral("project_prune"), d->m_prune );
180     // only add the action so that its known in the actionCollection
181     // and so that it's shortcut etc. pp. is restored
182     // apparently that is not possible to be done in the view itself *sigh*
183     actionCollection()->addAction( QStringLiteral("locate_document") );
184     setXMLFile( QStringLiteral("kdevprojectmanagerview.rc") );
185     d->factory = new KDevProjectManagerViewFactory( this );
186     core()->uiController()->addToolView(i18nc("@title:window", "Projects"), d->factory);
187     connect(core()->selectionController(), &ISelectionController::selectionChanged,
188              this, &ProjectManagerViewPlugin::updateActionState);
189     connect(ICore::self()->projectController()->buildSetModel(), &KDevelop::ProjectBuildSetModel::rowsInserted,
190              this, &ProjectManagerViewPlugin::updateFromBuildSetChange);
191     connect(ICore::self()->projectController()->buildSetModel(), &KDevelop::ProjectBuildSetModel::rowsRemoved,
192              this, &ProjectManagerViewPlugin::updateFromBuildSetChange);
193     connect(ICore::self()->projectController()->buildSetModel(), &KDevelop::ProjectBuildSetModel::modelReset,
194              this, &ProjectManagerViewPlugin::updateFromBuildSetChange);
195 }
196 
updateFromBuildSetChange()197 void ProjectManagerViewPlugin::updateFromBuildSetChange()
198 {
199     updateActionState( core()->selectionController()->currentSelection() );
200 }
201 
updateActionState(KDevelop::Context * ctx)202 void ProjectManagerViewPlugin::updateActionState( KDevelop::Context* ctx )
203 {
204     bool isEmpty = ICore::self()->projectController()->buildSetModel()->items().isEmpty();
205     if( isEmpty )
206     {
207         isEmpty = !ctx || ctx->type() != Context::ProjectItemContext || static_cast<ProjectItemContext*>(ctx)->items().isEmpty();
208     }
209     d->m_build->setEnabled( !isEmpty );
210     d->m_install->setEnabled( !isEmpty );
211     d->m_clean->setEnabled( !isEmpty );
212     d->m_configure->setEnabled( !isEmpty );
213     d->m_prune->setEnabled( !isEmpty );
214 }
215 
~ProjectManagerViewPlugin()216 ProjectManagerViewPlugin::~ProjectManagerViewPlugin()
217 {
218     delete d;
219 }
220 
unload()221 void ProjectManagerViewPlugin::unload()
222 {
223     qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "unloading manager view";
224     core()->uiController()->removeToolView(d->factory);
225 }
226 
contextMenuExtension(KDevelop::Context * context,QWidget * parent)227 ContextMenuExtension ProjectManagerViewPlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent)
228 {
229     if( context->type() != KDevelop::Context::ProjectItemContext )
230         return IPlugin::contextMenuExtension(context, parent);
231 
232     auto* ctx = static_cast<KDevelop::ProjectItemContext*>(context);
233     const QList<KDevelop::ProjectBaseItem*> items = ctx->items();
234 
235     d->ctxProjectItemList.clear();
236 
237     if( items.isEmpty() )
238         return IPlugin::contextMenuExtension(context, parent);
239 
240     //TODO: also needs: removeTarget, removeFileFromTarget, runTargetsFromContextMenu
241     ContextMenuExtension menuExt;
242     bool needsCreateFile = true;
243     bool needsCreateFolder = true;
244     bool needsCloseProjects = true;
245     bool needsBuildItems = true;
246     bool needsFolderItems = true;
247     bool needsCutRenameRemove = true;
248     bool needsRemoveTargetFiles = true;
249     bool needsPaste = true;
250 
251     //needsCreateFile if there is one item and it's a folder or target
252     needsCreateFile &= (items.count() == 1) && (items.first()->folder() || items.first()->target());
253     //needsCreateFolder if there is one item and it's a folder
254     needsCreateFolder &= (items.count() == 1) && (items.first()->folder());
255     needsPaste = needsCreateFolder;
256 
257     d->ctxProjectItemList.reserve(items.size());
258     for (ProjectBaseItem* item : items) {
259         d->ctxProjectItemList << item->index();
260         //needsBuildItems if items are limited to targets and buildfolders
261         needsBuildItems &= item->target() || item->type() == ProjectBaseItem::BuildFolder;
262 
263         //needsCloseProjects if items are limited to top level folders (Project Folders)
264         needsCloseProjects &= item->folder() && !item->folder()->parent();
265 
266         //needsFolderItems if items are limited to folders
267         needsFolderItems &= (bool)item->folder();
268 
269         //needsRemove if items are limited to non-top-level folders or files that don't belong to targets
270         needsCutRenameRemove &= (item->folder() && item->parent()) || (item->file() && !item->parent()->target());
271 
272         //needsRemoveTargets if items are limited to file items with target parents
273         needsRemoveTargetFiles &= (item->file() && item->parent()->target());
274     }
275 
276     if ( needsCreateFile ) {
277         auto* action = new QAction(i18nc("@action:inmenu", "Create &File..."), parent);
278         action->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
279         connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::createFileFromContextMenu );
280         menuExt.addAction( ContextMenuExtension::FileGroup, action );
281     }
282     if ( needsCreateFolder ) {
283         auto* action = new QAction(i18nc("@action:inmenu", "Create F&older..."), parent);
284         action->setIcon(QIcon::fromTheme(QStringLiteral("folder-new")));
285         connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::createFolderFromContextMenu );
286         menuExt.addAction( ContextMenuExtension::FileGroup, action );
287     }
288 
289     if ( needsBuildItems ) {
290         auto* action = new QAction(i18nc("@action:inmenu", "&Build"), parent);
291         action->setIcon(QIcon::fromTheme(QStringLiteral("run-build")));
292         connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::buildItemsFromContextMenu );
293         menuExt.addAction( ContextMenuExtension::BuildGroup, action );
294         action = new QAction(i18nc("@action:inmenu", "&Install"), parent);
295         action->setIcon(QIcon::fromTheme(QStringLiteral("run-build-install")));
296         connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::installItemsFromContextMenu );
297         menuExt.addAction( ContextMenuExtension::BuildGroup, action );
298         action = new QAction(i18nc("@action:inmenu", "&Clean"), parent);
299         action->setIcon(QIcon::fromTheme(QStringLiteral("run-build-clean")));
300         connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::cleanItemsFromContextMenu );
301         menuExt.addAction( ContextMenuExtension::BuildGroup, action );
302         action = new QAction(i18nc("@action:inmenu", "&Add to Build Set"), parent);
303         action->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
304         connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::addItemsFromContextMenuToBuildset );
305         menuExt.addAction( ContextMenuExtension::BuildGroup, action );
306     }
307 
308     if ( needsCloseProjects ) {
309         auto* close = new QAction(i18ncp("@action:inmenu", "C&lose Project", "Close Projects", items.count()), parent);
310         close->setIcon(QIcon::fromTheme(QStringLiteral("project-development-close")));
311         connect( close, &QAction::triggered, this, &ProjectManagerViewPlugin::closeProjects );
312         menuExt.addAction( ContextMenuExtension::ProjectGroup, close );
313     }
314     if ( needsFolderItems ) {
315         auto* action = new QAction(i18nc("@action:inmenu", "&Reload"), parent);
316         action->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
317         connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::reloadFromContextMenu );
318         menuExt.addAction( ContextMenuExtension::FileGroup, action );
319     }
320 
321     // Populating cut/copy/paste group
322     if ( !menuExt.actions(ContextMenuExtension::FileGroup).isEmpty() ) {
323         menuExt.addAction( ContextMenuExtension::FileGroup, createSeparatorAction() );
324     }
325     if ( needsCutRenameRemove ) {
326         QAction* cut = KStandardAction::cut(this, SLOT(cutFromContextMenu()), this);
327         cut->setShortcutContext(Qt::WidgetShortcut);
328         menuExt.addAction(ContextMenuExtension::FileGroup, cut);
329     }
330     {
331         QAction* copy = KStandardAction::copy(this, SLOT(copyFromContextMenu()), this);
332         copy->setShortcutContext(Qt::WidgetShortcut);
333         menuExt.addAction( ContextMenuExtension::FileGroup, copy );
334     }
335     if (needsPaste) {
336         QAction* paste = KStandardAction::paste(this, SLOT(pasteFromContextMenu()), this);
337         paste->setShortcutContext(Qt::WidgetShortcut);
338         menuExt.addAction( ContextMenuExtension::FileGroup, paste );
339     }
340 
341     // Populating rename/remove group
342     {
343         menuExt.addAction( ContextMenuExtension::FileGroup, createSeparatorAction() );
344     }
345     if ( needsCutRenameRemove ) {
346         auto* remove = new QAction(i18nc("@action:inmenu", "Remo&ve"), parent);
347         remove->setIcon(QIcon::fromTheme(QStringLiteral("user-trash")));
348         connect( remove, &QAction::triggered, this, &ProjectManagerViewPlugin::removeFromContextMenu );
349         menuExt.addAction( ContextMenuExtension::FileGroup, remove );
350         auto* rename = new QAction(i18nc("@action:inmenu", "Re&name..."), parent);
351         rename->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
352         connect( rename, &QAction::triggered, this, &ProjectManagerViewPlugin::renameItemFromContextMenu );
353         menuExt.addAction( ContextMenuExtension::FileGroup, rename );
354     }
355     if ( needsRemoveTargetFiles ) {
356         auto* remove = new QAction(i18nc("@action:inmenu", "Remove from &Target"), parent);
357         remove->setIcon(QIcon::fromTheme(QStringLiteral("user-trash")));
358         connect( remove, &QAction::triggered, this, &ProjectManagerViewPlugin::removeTargetFilesFromContextMenu );
359         menuExt.addAction( ContextMenuExtension::FileGroup, remove );
360     }
361 
362     if ( needsCutRenameRemove || needsRemoveTargetFiles ) {
363         menuExt.addAction(ContextMenuExtension::FileGroup, createSeparatorAction());
364     }
365 
366     return menuExt;
367 }
368 
closeProjects()369 void ProjectManagerViewPlugin::closeProjects()
370 {
371     QList<KDevelop::IProject*> projectsToClose;
372     ProjectModel* model = ICore::self()->projectController()->projectModel();
373     for (const QModelIndex& index : qAsConst(d->ctxProjectItemList)) {
374         KDevelop::ProjectBaseItem* item = model->itemFromIndex(index);
375         if( !projectsToClose.contains( item->project() ) )
376         {
377             projectsToClose << item->project();
378         }
379     }
380     d->ctxProjectItemList.clear();
381     for (KDevelop::IProject* proj : qAsConst(projectsToClose)) {
382         core()->projectController()->closeProject( proj );
383     }
384 }
385 
386 
installItemsFromContextMenu()387 void ProjectManagerViewPlugin::installItemsFromContextMenu()
388 {
389     runBuilderJob( BuilderJob::Install, itemsFromIndexes(d->ctxProjectItemList) );
390     d->ctxProjectItemList.clear();
391 }
392 
cleanItemsFromContextMenu()393 void ProjectManagerViewPlugin::cleanItemsFromContextMenu()
394 {
395     runBuilderJob( BuilderJob::Clean, itemsFromIndexes( d->ctxProjectItemList ) );
396     d->ctxProjectItemList.clear();
397 }
398 
buildItemsFromContextMenu()399 void ProjectManagerViewPlugin::buildItemsFromContextMenu()
400 {
401     runBuilderJob( BuilderJob::Build, itemsFromIndexes( d->ctxProjectItemList ) );
402     d->ctxProjectItemList.clear();
403 }
404 
collectAllProjects()405 QList<ProjectBaseItem*> ProjectManagerViewPlugin::collectAllProjects()
406 {
407     QList<KDevelop::ProjectBaseItem*> items;
408     const auto projects = core()->projectController()->projects();
409     items.reserve(projects.size());
410     for (auto* project : projects) {
411         items << project->projectItem();
412     }
413     return items;
414 }
415 
buildAllProjects()416 void ProjectManagerViewPlugin::buildAllProjects()
417 {
418     runBuilderJob( BuilderJob::Build, collectAllProjects() );
419 }
420 
collectItems()421 QList<ProjectBaseItem*> ProjectManagerViewPlugin::collectItems()
422 {
423     QList<ProjectBaseItem*> items;
424     const QList<BuildItem> buildItems = ICore::self()->projectController()->buildSetModel()->items();
425     if( !buildItems.isEmpty() )
426     {
427         for (const BuildItem& buildItem : buildItems) {
428             if( ProjectBaseItem* item = buildItem.findItem() )
429             {
430                 items << item;
431             }
432         }
433 
434     } else
435     {
436         auto* ctx = static_cast<KDevelop::ProjectItemContext*>(ICore::self()->selectionController()->currentSelection());
437         items = ctx->items();
438     }
439 
440     return items;
441 }
442 
runBuilderJob(BuilderJob::BuildType type,const QList<ProjectBaseItem * > & items)443 void ProjectManagerViewPlugin::runBuilderJob( BuilderJob::BuildType type, const QList<ProjectBaseItem*>& items )
444 {
445     auto* builder = new BuilderJob;
446     builder->addItems( type, items );
447     builder->updateJobName();
448     ICore::self()->uiController()->registerStatus(new JobStatus(builder));
449     ICore::self()->runController()->registerJob( builder );
450 }
451 
installProjectItems()452 void ProjectManagerViewPlugin::installProjectItems()
453 {
454     runBuilderJob( KDevelop::BuilderJob::Install, collectItems() );
455 }
456 
pruneProjectItems()457 void ProjectManagerViewPlugin::pruneProjectItems()
458 {
459     runBuilderJob( KDevelop::BuilderJob::Prune, collectItems() );
460 }
461 
configureProjectItems()462 void ProjectManagerViewPlugin::configureProjectItems()
463 {
464     runBuilderJob( KDevelop::BuilderJob::Configure, collectItems() );
465 }
466 
cleanProjectItems()467 void ProjectManagerViewPlugin::cleanProjectItems()
468 {
469     runBuilderJob( KDevelop::BuilderJob::Clean, collectItems() );
470 }
471 
buildProjectItems()472 void ProjectManagerViewPlugin::buildProjectItems()
473 {
474     runBuilderJob( KDevelop::BuilderJob::Build, collectItems() );
475 }
476 
addItemsFromContextMenuToBuildset()477 void ProjectManagerViewPlugin::addItemsFromContextMenuToBuildset( )
478 {
479     const auto items = itemsFromIndexes(d->ctxProjectItemList);
480     for (KDevelop::ProjectBaseItem* item : items) {
481         ICore::self()->projectController()->buildSetModel()->addProjectItem( item );
482     }
483 }
484 
runTargetsFromContextMenu()485 void ProjectManagerViewPlugin::runTargetsFromContextMenu( )
486 {
487     const auto items = itemsFromIndexes(d->ctxProjectItemList);
488     for (KDevelop::ProjectBaseItem* item : items) {
489         KDevelop::ProjectExecutableTargetItem* t=item->executable();
490         if(t)
491         {
492             qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "Running target: " << t->text() << t->builtUrl();
493         }
494     }
495 }
496 
projectConfiguration()497 void ProjectManagerViewPlugin::projectConfiguration( )
498 {
499     if( !d->ctxProjectItemList.isEmpty() )
500     {
501         ProjectModel* model = ICore::self()->projectController()->projectModel();
502         core()->projectController()->configureProject( model->itemFromIndex(d->ctxProjectItemList.at( 0 ))->project() );
503     }
504 }
505 
reloadFromContextMenu()506 void ProjectManagerViewPlugin::reloadFromContextMenu( )
507 {
508     QList< KDevelop::ProjectFolderItem* > folders;
509     const auto items = itemsFromIndexes(d->ctxProjectItemList);
510     for (KDevelop::ProjectBaseItem* item : items) {
511         if ( item->folder() ) {
512             // since reloading should be recursive, only pass the upper-most items
513             bool found = false;
514             const auto currentFolders = folders;
515             for (KDevelop::ProjectFolderItem* existing : currentFolders) {
516                 if ( existing->path().isParentOf(item->folder()->path()) ) {
517                     // simply skip this child
518                     found = true;
519                     break;
520                 } else if ( item->folder()->path().isParentOf(existing->path()) ) {
521                     // remove the child in the list and add the current item instead
522                     folders.removeOne(existing);
523                     // continue since there could be more than one existing child
524                 }
525             }
526             if ( !found ) {
527                 folders << item->folder();
528             }
529         }
530     }
531     for (KDevelop::ProjectFolderItem* folder : qAsConst(folders)) {
532         folder->project()->projectFileManager()->reload(folder);
533     }
534 }
535 
createFolderFromContextMenu()536 void ProjectManagerViewPlugin::createFolderFromContextMenu( )
537 {
538     const auto items = itemsFromIndexes(d->ctxProjectItemList);
539     for (KDevelop::ProjectBaseItem* item : items) {
540         if ( item->folder() ) {
541             QWidget* window(ICore::self()->uiController()->activeMainWindow()->window());
542             QString name = QInputDialog::getText ( window,
543                 i18nc("@title:window", "Create Folder in %1", item->folder()->path().pathOrUrl() ),
544                 i18nc("@label:textbox", "Folder name:")
545             );
546             if (!name.isEmpty()) {
547                 item->project()->projectFileManager()->addFolder( Path(item->path(), name), item->folder() );
548             }
549         }
550     }
551 }
552 
removeFromContextMenu()553 void ProjectManagerViewPlugin::removeFromContextMenu()
554 {
555     removeItems(itemsFromIndexes( d->ctxProjectItemList ));
556 }
557 
removeItems(const QList<ProjectBaseItem * > & items)558 void ProjectManagerViewPlugin::removeItems(const QList< ProjectBaseItem* >& items)
559 {
560     if (items.isEmpty()) {
561         return;
562     }
563 
564     //copy the list of selected items and sort it to guarantee parents will come before children
565     QList<KDevelop::ProjectBaseItem*> sortedItems = items;
566     std::sort(sortedItems.begin(), sortedItems.end(), ProjectBaseItem::pathLessThan);
567 
568     Path lastFolder;
569     QHash< IProjectFileManager*, QList<KDevelop::ProjectBaseItem*> > filteredItems;
570     QStringList itemPaths;
571     for (KDevelop::ProjectBaseItem* item : qAsConst(sortedItems)) {
572         if (item->isProjectRoot()) {
573             continue;
574         } else if (item->folder() || item->file()) {
575             //make sure no children of folders that will be deleted are listed
576             if (lastFolder.isParentOf(item->path())) {
577                 continue;
578             } else if (item->folder()) {
579                 lastFolder = item->path();
580             }
581 
582             IProjectFileManager* manager = item->project()->projectFileManager();
583             if (manager) {
584                 filteredItems[manager] << item;
585                 itemPaths << item->path().pathOrUrl();
586             }
587         }
588     }
589 
590     if (filteredItems.isEmpty()) {
591         return;
592     }
593 
594     if (KMessageBox::warningYesNoList(
595             QApplication::activeWindow(),
596             i18np("Do you really want to delete this item?",
597                   "Do you really want to delete these %1 items?",
598                   itemPaths.size()),
599             itemPaths, i18nc("@title:window", "Delete Files"),
600             KStandardGuiItem::del(), KStandardGuiItem::cancel()
601         ) == KMessageBox::No) {
602         return;
603     }
604 
605     //Go though projectmanagers, have them remove the files and folders that they own
606     QHash< IProjectFileManager*, QList<KDevelop::ProjectBaseItem*> >::iterator it;
607     for (it = filteredItems.begin(); it != filteredItems.end(); ++it)
608     {
609         Q_ASSERT(it.key());
610         it.key()->removeFilesAndFolders(it.value());
611     }
612 }
613 
removeTargetFilesFromContextMenu()614 void ProjectManagerViewPlugin::removeTargetFilesFromContextMenu()
615 {
616     const QList<ProjectBaseItem*> items = itemsFromIndexes( d->ctxProjectItemList );
617     QHash< IBuildSystemManager*, QList<KDevelop::ProjectFileItem*> > itemsByBuildSystem;
618     for (ProjectBaseItem* item : items) {
619         itemsByBuildSystem[item->project()->buildSystemManager()].append(item->file());
620     }
621 
622     QHash< IBuildSystemManager*, QList<KDevelop::ProjectFileItem*> >::iterator it;
623     for (it = itemsByBuildSystem.begin(); it != itemsByBuildSystem.end(); ++it)
624         it.key()->removeFilesFromTargets(it.value());
625 }
626 
renameItemFromContextMenu()627 void ProjectManagerViewPlugin::renameItemFromContextMenu()
628 {
629     renameItems(itemsFromIndexes( d->ctxProjectItemList ));
630 }
631 
renameItems(const QList<ProjectBaseItem * > & items)632 void ProjectManagerViewPlugin::renameItems(const QList< ProjectBaseItem* >& items)
633 {
634     if (items.isEmpty()) {
635         return;
636     }
637 
638     QWidget* window = ICore::self()->uiController()->activeMainWindow()->window();
639 
640     for (KDevelop::ProjectBaseItem* item : items) {
641         if ((item->type()!=ProjectBaseItem::BuildFolder
642                 && item->type()!=ProjectBaseItem::Folder
643                 && item->type()!=ProjectBaseItem::File) || !item->parent())
644         {
645             continue;
646         }
647 
648         const QString src = item->text();
649 
650         //Change QInputDialog->KFileSaveDialog?
651         QString name = QInputDialog::getText(
652             window, i18nc("@window:title", "Rename"),
653             i18nc("@label:textbox", "New name for '%1':", item->text()),
654             QLineEdit::Normal, item->text()
655         );
656 
657         if (!name.isEmpty() && name != src) {
658             ProjectBaseItem::RenameStatus status = item->rename( name );
659 
660             QString errorMessageText;
661             switch(status) {
662                 case ProjectBaseItem::RenameOk:
663                     break;
664                 case ProjectBaseItem::ExistingItemSameName:
665                     errorMessageText = i18n("There is already a file named '%1'", name);
666                     break;
667                 case ProjectBaseItem::ProjectManagerRenameFailed:
668                     errorMessageText = i18n("Could not rename '%1'", name);
669                     break;
670                 case ProjectBaseItem::InvalidNewName:
671                     errorMessageText = i18n("'%1' is not a valid file name", name);
672                     break;
673             }
674             if (!errorMessageText.isEmpty()) {
675                 auto* message = new Sublime::Message(errorMessageText, Sublime::Message::Error);
676                 ICore::self()->uiController()->postMessage(message);
677             }
678         }
679     }
680 }
681 
createFile(const ProjectFolderItem * item)682 ProjectFileItem* createFile(const ProjectFolderItem* item)
683 {
684     QWidget* window = ICore::self()->uiController()->activeMainWindow()->window();
685     QString name = QInputDialog::getText(window, i18nc("@title:window", "Create File in %1", item->path().pathOrUrl()), i18nc("@label:textbox", "File name:"));
686 
687     if(name.isEmpty())
688         return nullptr;
689 
690     ProjectFileItem* ret = item->project()->projectFileManager()->addFile( Path(item->path(), name), item->folder() );
691     if (ret) {
692         ICore::self()->documentController()->openDocument( ret->path().toUrl() );
693     }
694     return ret;
695 }
696 
createFileFromContextMenu()697 void ProjectManagerViewPlugin::createFileFromContextMenu( )
698 {
699     const auto items = itemsFromIndexes(d->ctxProjectItemList);
700     for (KDevelop::ProjectBaseItem* item : items) {
701         if ( item->folder() ) {
702             createFile(item->folder());
703         } else if ( item->target() ) {
704             auto* folder=dynamic_cast<ProjectFolderItem*>(item->parent());
705             if(folder)
706             {
707                 ProjectFileItem* f=createFile(folder);
708                 if(f)
709                     item->project()->buildSystemManager()->addFilesToTarget(QList<ProjectFileItem*>() << f, item->target());
710             }
711         }
712     }
713 }
714 
copyFromContextMenu()715 void ProjectManagerViewPlugin::copyFromContextMenu()
716 {
717     qApp->clipboard()->setMimeData(createClipboardMimeData(false));
718 }
719 
cutFromContextMenu()720 void ProjectManagerViewPlugin::cutFromContextMenu()
721 {
722     qApp->clipboard()->setMimeData(createClipboardMimeData(true));
723 }
724 
selectItemsByPaths(ProjectManagerView * view,const Path::List & paths)725 static void selectItemsByPaths(ProjectManagerView* view, const Path::List& paths)
726 {
727     KDevelop::ProjectModel* projectModel = KDevelop::ICore::self()->projectController()->projectModel();
728 
729     QList<ProjectBaseItem*> newItems;
730     for (const Path& path : paths) {
731         QList<ProjectBaseItem*> items = projectModel->itemsForPath(IndexedString(path.path()));
732         newItems.append(items);
733         for (ProjectBaseItem* item : qAsConst(items)) {
734             view->expandItem(item->parent());
735         }
736     }
737     view->selectItems(newItems);
738 }
739 
pasteFromContextMenu()740 void ProjectManagerViewPlugin::pasteFromContextMenu()
741 {
742     auto* ctx = static_cast<KDevelop::ProjectItemContext*>(ICore::self()->selectionController()->currentSelection());
743     if (ctx->items().count() != 1) {
744         return; //do nothing if multiple or none items are selected
745     }
746 
747     ProjectBaseItem* destItem = ctx->items().at(0);
748     if (!destItem->folder()) {
749         return; //do nothing if the target is not a directory
750     }
751 
752     const QMimeData* data = qApp->clipboard()->mimeData();
753     qCDebug(PLUGIN_PROJECTMANAGERVIEW) << data->urls();
754     Path::List origPaths = toPathList(data->urls());
755     const bool isCut = KIO::isClipboardDataCut(data);
756 
757     const CutCopyPasteHelpers::SourceToDestinationMap map = CutCopyPasteHelpers::mapSourceToDestination(origPaths, destItem->folder()->path());
758 
759     const QVector<CutCopyPasteHelpers::TaskInfo> tasks = CutCopyPasteHelpers::copyMoveItems(
760         map.filteredPaths, destItem,
761         isCut ? CutCopyPasteHelpers::Operation::CUT : CutCopyPasteHelpers::Operation::COPY);
762 
763     // Select new items in the project manager view
764     auto* itemCtx = dynamic_cast<ProjectManagerViewItemContext*>(ICore::self()->selectionController()->currentSelection());
765     if (itemCtx) {
766         Path::List finalPathsList;
767         for (const auto& task : tasks) {
768             if (task.m_status == CutCopyPasteHelpers::TaskStatus::SUCCESS && task.m_type != CutCopyPasteHelpers::TaskType::DELETION) {
769                 finalPathsList.reserve(finalPathsList.size() + task.m_src.size());
770                 for (const Path& src : task.m_src) {
771                     finalPathsList.append(map.finalPaths[src]);
772                 }
773             }
774         }
775 
776         selectItemsByPaths(itemCtx->view(), finalPathsList);
777     }
778 
779     // If there was a single failure, display a warning dialog.
780     const bool anyFailed = std::any_of(tasks.begin(), tasks.end(),
781                                        [](const CutCopyPasteHelpers::TaskInfo& task) {
782                                            return task.m_status != CutCopyPasteHelpers::TaskStatus::SUCCESS;
783                                        });
784     if (anyFailed) {
785         QWidget* window = ICore::self()->uiController()->activeMainWindow()->window();
786         showWarningDialogForFailedPaste(window, tasks);
787     }
788 }
789 
790 #include "projectmanagerviewplugin.moc"
791 
792