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