kde-extraapps/kdevplatform/shell/projectcontroller.cpp
2015-07-26 14:23:17 +03:00

1155 lines
40 KiB
C++

/* This file is part of KDevelop
Copyright 2006 Adam Treat <treat@kde.org>
Copyright 2007 Andreas Pakulat <apaku@gmx.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "projectcontroller.h"
#include <QtCore/QDir>
#include <QtCore/QSignalMapper>
#include <QtGui/QAction>
#include <QtCore/QSet>
#include <QtCore/QList>
#include <QtCore/QMap>
#include <QtGui/QItemSelectionModel>
#include <QtGui/QGroupBox>
#include <QtGui/QRadioButton>
#include <QtGui/QBoxLayout>
#include <QtGui/QLabel>
#include <kaction.h>
#include <kconfig.h>
#include <klocale.h>
#include <kpassivepopup.h>
#include <kservice.h>
#include <kstandardaction.h>
#include <kmessagebox.h>
#include <kxmlguiwindow.h>
#include <kactioncollection.h>
#include <ktemporaryfile.h>
#include <kservicetypetrader.h>
#include <krecentfilesaction.h>
#include <kactionmenu.h>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
#include <ksettings/dialog.h>
#include <kio/netaccess.h>
#include <kstandarddirs.h>
#include <ksettings/dispatcher.h>
#include <sublime/area.h>
#include <interfaces/iplugin.h>
#include <interfaces/isession.h>
#include <interfaces/context.h>
#include <interfaces/contextmenuextension.h>
#include <interfaces/iselectioncontroller.h>
#include <project/interfaces/iprojectfilemanager.h>
#include <project/interfaces/ibuildsystemmanager.h>
#include <project/interfaces/iprojectbuilder.h>
#include <project/projectmodel.h>
#include <project/projectbuildsetmodel.h>
#include <interfaces/ilanguagecontroller.h>
#include <language/backgroundparser/backgroundparser.h>
#include "core.h"
#include "project.h"
#include "mainwindow.h"
#include "shellextension.h"
#include "plugincontroller.h"
#include "uicontroller.h"
#include "documentcontroller.h"
#include "openprojectdialog.h"
#include <interfaces/iruncontroller.h>
#include <language/backgroundparser/parseprojectjob.h>
#include <kio/job.h>
#include "sessioncontroller.h"
#include "session.h"
#include <QDBusConnection>
#include <QApplication>
#include <vcs/models/projectchangesmodel.h>
#include <vcs/widgets/vcsdiffpatchsources.h>
#include <vcs/widgets/vcscommitdialog.h>
namespace KDevelop
{
class ProjectControllerPrivate
{
public:
QList<IProject*> m_projects;
QMap< IProject*, QList<IPlugin*> > m_projectPlugins;
QPointer<KRecentFilesAction> m_recentAction;
Core* m_core;
// IProject* m_currentProject;
ProjectModel* model;
QItemSelectionModel* selectionModel;
QPointer<KAction> m_openProject;
QPointer<KAction> m_fetchProject;
QPointer<KAction> m_closeProject;
QPointer<KAction> m_openConfig;
IProjectDialogProvider* dialog;
QList<KUrl> m_currentlyOpening; // project-file urls that are being opened
IProject* m_configuringProject;
ProjectController* q;
ProjectBuildSetModel* buildset;
bool m_foundProjectFile; //Temporary flag used while searching the hierarchy for a project file
bool m_cleaningUp; //Temporary flag enabled while destroying the project-controller
QPointer<ProjectChangesModel> m_changesModel;
QHash< IProject*, QPointer<KJob> > m_parseJobs; // parse jobs that add files from the project to the background parser.
ProjectControllerPrivate( ProjectController* p )
: m_core(0), model(0), selectionModel(0), dialog(0), m_configuringProject(0), q(p), buildset(0), m_foundProjectFile(false), m_cleaningUp(false)
{
}
void unloadAllProjectPlugins()
{
if( m_projects.isEmpty() )
m_core->pluginControllerInternal()->unloadProjectPlugins();
}
void projectConfig( QObject * obj )
{
if( !obj )
return;
Project* proj = qobject_cast<Project*>(obj);
if( !proj )
return;
//@FIXME: Currently it is important to set a parentApp on the kcms
//that's different from the component name of the application, else
//the plugin will show up on all projects settings dialogs.
QStringList pluginsForPrj = findPluginsForProject( proj );
kDebug() << "Using pluginlist:" << pluginsForPrj;
pluginsForPrj << "kdevplatformproject"; // for project-wide env settings.
KSettings::Dialog cfgDlg( pluginsForPrj, m_core->uiController()->activeMainWindow() );
cfgDlg.setKCMArguments( QStringList()
<< proj->developerTempFile()
<< proj->projectTempFile()
<< proj->projectFile().pathOrUrl()
<< proj->developerFile().pathOrUrl()
<< proj->name() );
m_configuringProject = proj;
cfgDlg.setWindowTitle( i18n("Configure Project %1", proj->name()) );
cfgDlg.exec();
proj->projectConfiguration()->sync();
m_configuringProject = 0;
}
void saveListOfOpenedProjects()
{
KUrl::List openProjects;
openProjects.reserve( m_projects.size() );
foreach( IProject* project, m_projects ) {
openProjects.append(project->projectFile().toUrl());
}
Core::self()->activeSession()->setContainedProjects( openProjects );
}
// Recursively collects builder dependencies for a project.
static void collectBuilders( QList< IProjectBuilder* >& destination, IProjectBuilder* topBuilder, IProject* project )
{
QList< IProjectBuilder* > auxBuilders = topBuilder->additionalBuilderPlugins( project );
destination.append( auxBuilders );
foreach( IProjectBuilder* auxBuilder, auxBuilders ) {
collectBuilders( destination, auxBuilder, project );
}
}
QStringList findPluginsForProject( IProject* project )
{
QList<IPlugin*> plugins = m_core->pluginController()->loadedPlugins();
QStringList pluginnames;
QList< IProjectBuilder* > buildersForKcm;
// Important to also include the "top" builder for the project, so
// projects with only one such builder are kept working. Otherwise the project config
// dialog is empty for such cases.
if( IBuildSystemManager* buildSystemManager = project->buildSystemManager() ) {
buildersForKcm << buildSystemManager->builder();
collectBuilders( buildersForKcm, buildSystemManager->builder(), project );
}
for( QList<IPlugin*>::iterator it = plugins.begin(); it != plugins.end(); ++it )
{
IPlugin* plugin = *it;
const KPluginInfo info = m_core->pluginController()->pluginInfo( plugin );
if (info.property("X-KDevelop-Category").toString() != "Project") {
continue;
}
IProjectFileManager* manager = plugin->extension<KDevelop::IProjectFileManager>();
if( manager && manager != project->projectFileManager() )
{
// current plugin is a manager but does not apply to given project, skip
continue;
}
IProjectBuilder* builder = plugin->extension<KDevelop::IProjectBuilder>();
if ( builder && !buildersForKcm.contains( builder ) )
{
continue;
}
pluginnames << info.pluginName();
}
return pluginnames;
}
void notifyProjectConfigurationChanged()
{
if( m_configuringProject )
{
emit q->projectConfigurationChanged( m_configuringProject );
}
}
void updateActionStates( Context* ctx )
{
ProjectItemContext* itemctx = dynamic_cast<ProjectItemContext*>(ctx);
m_openConfig->setEnabled( itemctx && itemctx->items().count() == 1 );
m_closeProject->setEnabled( itemctx && itemctx->items().count() > 0 );
}
void openProjectConfig()
{
ProjectItemContext* ctx = dynamic_cast<ProjectItemContext*>( Core::self()->selectionController()->currentSelection() );
if( ctx && ctx->items().count() == 1 )
{
q->configureProject( ctx->items().at(0)->project() );
}
}
void closeSelectedProjects()
{
ProjectItemContext* ctx = dynamic_cast<ProjectItemContext*>( Core::self()->selectionController()->currentSelection() );
if( ctx && ctx->items().count() > 0 )
{
QSet<IProject*> projects;
foreach( ProjectBaseItem* item, ctx->items() )
{
projects.insert( item->project() );
}
foreach( IProject* project, projects )
{
q->closeProject( project );
}
}
}
void importProject(const KUrl& url_)
{
KUrl url(url_);
if ( url.isLocalFile() )
{
QString path = QFileInfo( url.toLocalFile() ).canonicalFilePath();
if ( !path.isEmpty() )
url.setPath( path );
}
if ( !url.isValid() )
{
KMessageBox::error(Core::self()->uiControllerInternal()->activeMainWindow(),
i18n("Invalid Location: %1", url.prettyUrl()));
return;
}
if ( m_currentlyOpening.contains(url))
{
kDebug() << "Already opening " << url << ". Aborting.";
KPassivePopup::message( i18n( "Project already being opened"),
i18n( "Already opening %1, not opening again",
url.prettyUrl() ),
m_core->uiController()->activeMainWindow() );
return;
}
foreach( IProject* project, m_projects )
{
if ( url == project->projectFileUrl() )
{
if ( dialog->userWantsReopen() )
{ // close first, then open again by falling through
q->closeProject(project);
} else { // abort
return;
}
}
}
m_currentlyOpening += url;
m_core->pluginControllerInternal()->loadProjectPlugins();
Project* project = new Project();
QObject::connect(project, SIGNAL(aboutToOpen(KDevelop::IProject*)),
q, SIGNAL(projectAboutToBeOpened(KDevelop::IProject*)));
if ( !project->open( Path(url) ) )
{
m_currentlyOpening.removeAll(url);
q->abortOpeningProject(project);
project->deleteLater();
}
}
void areaChanged(Sublime::Area* area) {
KActionCollection* ac = m_core->uiControllerInternal()->defaultMainWindow()->actionCollection();
ac->action("commit_current_project")->setEnabled(area->objectName() == "code");
ac->action("commit_current_project")->setVisible(area->objectName() == "code");
}
};
IProjectDialogProvider::IProjectDialogProvider()
{}
IProjectDialogProvider::~IProjectDialogProvider()
{}
ProjectDialogProvider::ProjectDialogProvider(ProjectControllerPrivate* const p) : d(p)
{}
ProjectDialogProvider::~ProjectDialogProvider()
{}
bool writeNewProjectFile( const QString& localConfigFile, const QString& name, const QString& manager )
{
KSharedConfig::Ptr cfg = KSharedConfig::openConfig( localConfigFile, KConfig::SimpleConfig );
if (!cfg->isConfigWritable(true)) {
kDebug() << "can't write to configfile";
return false;
}
KConfigGroup grp = cfg->group( "Project" );
grp.writeEntry( "Name", name );
grp.writeEntry( "Manager", manager );
cfg->sync();
return true;
}
bool writeProjectSettingsToConfigFile(const KUrl& projectFileUrl, const QString& projectName, const QString& projectManager)
{
if ( !projectFileUrl.isLocalFile() ) {
KTemporaryFile tmp;
if ( !tmp.open() ) {
return false;
}
if ( !writeNewProjectFile( tmp.fileName(), projectName, projectManager ) ) {
return false;
}
// explicitly close file before uploading it, see also: https://bugs.kde.org/show_bug.cgi?id=254519
tmp.close();
return KIO::NetAccess::upload( tmp.fileName(), projectFileUrl, Core::self()->uiControllerInternal()->defaultMainWindow() );
}
return writeNewProjectFile( projectFileUrl.toLocalFile(),projectName, projectManager );
}
bool projectFileExists( const KUrl& u )
{
if( u.isLocalFile() )
{
return QFileInfo( u.toLocalFile() ).exists();
} else
{
return KIO::NetAccess::exists( u, KIO::NetAccess::DestinationSide, Core::self()->uiControllerInternal()->activeMainWindow() );
}
}
bool equalProjectFile( const QString& configPath, OpenProjectDialog* dlg )
{
KSharedConfig::Ptr cfg = KSharedConfig::openConfig( configPath, KConfig::SimpleConfig );
KConfigGroup grp = cfg->group( "Project" );
QString defaultName = dlg->projectFileUrl().upUrl().fileName();
return (grp.readEntry( "Name", QString() ) == dlg->projectName() || dlg->projectName() == defaultName) &&
grp.readEntry( "Manager", QString() ) == dlg->projectManager();
}
KUrl ProjectDialogProvider::askProjectConfigLocation(bool fetch, const KUrl& startUrl)
{
Q_ASSERT(d);
OpenProjectDialog dlg( fetch, startUrl, Core::self()->uiController()->activeMainWindow() );
if(dlg.exec() == QDialog::Rejected)
return KUrl();
KUrl projectFileUrl = dlg.projectFileUrl();
kDebug() << "selected project:" << projectFileUrl << dlg.projectName() << dlg.projectManager();
// controls if existing project file should be saved
bool writeProjectConfigToFile = true;
if( projectFileExists( projectFileUrl ) )
{
// check whether config is equal
bool shouldAsk = true;
if( projectFileUrl.isLocalFile() )
{
shouldAsk = !equalProjectFile( projectFileUrl.toLocalFile(), &dlg );
} else {
QString tmpFile;
if ( KIO::NetAccess::download( projectFileUrl, tmpFile, qApp->activeWindow() ) ) {
shouldAsk = !equalProjectFile( tmpFile, &dlg );
QFile::remove(tmpFile);
} else {
shouldAsk = false;
}
}
if ( shouldAsk )
{
KGuiItem yes = KStandardGuiItem::yes();
yes.setText(i18n("Override"));
yes.setToolTip(i18nc("@info:tooltip", "Continue to open the project and use the just provided project configuration."));
yes.setIcon(KIcon());
KGuiItem no = KStandardGuiItem::no();
no.setText(i18n("Open Existing File"));
no.setToolTip(i18nc("@info:tooltip", "Continue to open the project but use the existing project configuration."));
no.setIcon(KIcon());
KGuiItem cancel = KStandardGuiItem::cancel();
cancel.setToolTip(i18nc("@info:tooltip", "Cancel and do not open the project."));
int ret = KMessageBox::questionYesNoCancel(qApp->activeWindow(),
i18n("There already exists a project configuration file at %1.\n"
"Do you want to override it or open the existing file?", projectFileUrl.pathOrUrl()),
i18n("Override existing project configuration"), yes, no, cancel );
if ( ret == KMessageBox::No )
{
writeProjectConfigToFile = false;
} else if ( ret == KMessageBox::Cancel )
{
return KUrl();
} // else fall through and write new file
} else {
writeProjectConfigToFile = false;
}
}
if (writeProjectConfigToFile) {
if (!writeProjectSettingsToConfigFile(projectFileUrl, dlg.projectName(), dlg.projectManager())) {
KMessageBox::error(d->m_core->uiControllerInternal()->defaultMainWindow(),
i18n("Unable to create configuration file %1", projectFileUrl.url()));
return KUrl();
}
}
return projectFileUrl;
}
bool ProjectDialogProvider::userWantsReopen()
{
Q_ASSERT(d);
return (KMessageBox::questionYesNo( d->m_core->uiControllerInternal()->defaultMainWindow(),
i18n( "Reopen the current project?" ) )
== KMessageBox::No) ? false : true;
}
void ProjectController::setDialogProvider(IProjectDialogProvider* dialog)
{
Q_ASSERT(d->dialog);
delete d->dialog;
d->dialog = dialog;
}
ProjectController::ProjectController( Core* core )
: IProjectController( core ), d( new ProjectControllerPrivate( this ) )
{
setObjectName("ProjectController");
d->m_core = core;
d->model = new ProjectModel();
//NOTE: this is required to be called here, such that the
// actions are available when the UI controller gets
// initialized *before* the project controller
if (Core::self()->setupFlags() != Core::NoUi) {
setupActions();
}
}
void ProjectController::setupActions()
{
KActionCollection * ac =
d->m_core->uiControllerInternal()->defaultMainWindow()->actionCollection();
KAction *action;
d->m_openProject = action = ac->addAction( "project_open" );
action->setText(i18nc( "@action", "Open / Import Project..." ) );
action->setToolTip( i18nc( "@info:tooltip", "Open or import project" ) );
action->setWhatsThis( i18nc( "@info:whatsthis", "Open an existing KDevelop 4 project or import "
"an existing Project into KDevelop 4. This entry "
"allows to select a KDevelop4 project file or an "
"existing directory to open it in KDevelop. "
"When opening an existing directory that does "
"not yet have a KDevelop4 project file, the file "
"will be created." ) );
action->setIcon(KIcon("project-open"));
connect( action, SIGNAL(triggered(bool)), SLOT(openProject()) );
d->m_fetchProject = action = ac->addAction( "project_fetch" );
action->setText(i18nc( "@action", "Fetch Project..." ) );
action->setIcon( KIcon( "download" ) );
action->setToolTip( i18nc( "@info:tooltip", "Fetch project" ) );
action->setWhatsThis( i18nc( "@info:whatsthis", "Guides the user through the project fetch "
"and then imports it into KDevelop 4." ) );
// action->setIcon(KIcon("project-open"));
connect( action, SIGNAL(triggered(bool)), SLOT(fetchProject()) );
// action = ac->addAction( "project_close" );
// action->setText( i18n( "C&lose Project" ) );
// connect( action, SIGNAL(triggered(bool)), SLOT(closeProject()) );
// action->setToolTip( i18n( "Close project" ) );
// action->setWhatsThis( i18n( "Closes the current project." ) );
// action->setEnabled( false );
d->m_closeProject = action = ac->addAction( "project_close" );
connect( action, SIGNAL(triggered(bool)), SLOT(closeSelectedProjects()) );
action->setText( i18nc( "@action", "Close Project(s)" ) );
action->setIcon( KIcon( "project-development-close" ) );
action->setToolTip( i18nc( "@info:tooltip", "Closes all currently selected projects" ) );
action->setEnabled( false );
d->m_openConfig = action = ac->addAction( "project_open_config" );
connect( action, SIGNAL(triggered(bool)), SLOT(openProjectConfig()) );
action->setText( i18n( "Open Configuration..." ) );
action->setIcon( KIcon("configure") );
action->setEnabled( false );
action = ac->addAction( "commit_current_project" );
connect( action, SIGNAL(triggered(bool)), SLOT(commitCurrentProject()) );
action->setText( i18n( "Commit Current Project..." ) );
action->setIconText( i18n( "Commit..." ) );
action->setIcon( KIcon("svn-commit") );
connect(d->m_core->uiControllerInternal()->defaultMainWindow(), SIGNAL(areaChanged(Sublime::Area*)),
SLOT(areaChanged(Sublime::Area*)));
d->m_core->uiControllerInternal()->area(0, "code")->addAction(action);
KSharedConfig * config = KGlobal::config().data();
// KConfigGroup group = config->group( "General Options" );
d->m_recentAction = KStandardAction::openRecent(this, SLOT(openProject(KUrl)), this);
ac->addAction( "project_open_recent", d->m_recentAction );
d->m_recentAction->setText( i18n( "Open Recent Project" ) );
d->m_recentAction->setWhatsThis( i18nc( "@info:whatsthis", "Opens recently opened project." ) );
d->m_recentAction->loadEntries( KConfigGroup(config, "RecentProjects") );
KAction* openProjectForFileAction = new KAction( this );
ac->addAction("project_open_for_file", openProjectForFileAction);
openProjectForFileAction->setText(i18n("Open Project for Current File"));
connect( openProjectForFileAction, SIGNAL(triggered(bool)), SLOT(openProjectForUrlSlot(bool)));
}
ProjectController::~ProjectController()
{
delete d->model;
delete d->dialog;
delete d;
}
void ProjectController::cleanup()
{
if ( d->m_currentlyOpening.isEmpty() ) {
d->saveListOfOpenedProjects();
}
d->m_cleaningUp = true;
if( buildSetModel() ) {
buildSetModel()->storeToSession( Core::self()->activeSession() );
}
foreach( IProject* project, d->m_projects ) {
closeProject( project );
}
}
void ProjectController::initialize()
{
d->buildset = new ProjectBuildSetModel( this );
buildSetModel()->loadFromSession( Core::self()->activeSession() );
connect( this, SIGNAL(projectOpened(KDevelop::IProject*)),
d->buildset, SLOT(loadFromProject(KDevelop::IProject*)) );
connect( this, SIGNAL(projectClosing(KDevelop::IProject*)),
d->buildset, SLOT(saveToProject(KDevelop::IProject*)) );
connect( this, SIGNAL(projectClosed(KDevelop::IProject*)),
d->buildset, SLOT(projectClosed(KDevelop::IProject*)) );
d->selectionModel = new QItemSelectionModel(d->model);
loadSettings(false);
d->dialog = new ProjectDialogProvider(d);
KSettings::Dispatcher::registerComponent( KComponentData("kdevplatformproject"),
this,
"notifyProjectConfigurationChanged" );
QDBusConnection::sessionBus().registerObject( "/org/kdevelop/ProjectController",
this, QDBusConnection::ExportScriptableSlots );
KSharedConfig::Ptr config = Core::self()->activeSession()->config();
KConfigGroup group = config->group( "General Options" );
KUrl::List openProjects = group.readEntry( "Open Projects", QStringList() );
QMetaObject::invokeMethod(this, "openProjects", Qt::QueuedConnection, Q_ARG(KUrl::List, openProjects));
connect( Core::self()->selectionController(), SIGNAL(selectionChanged(KDevelop::Context*)),
SLOT(updateActionStates(KDevelop::Context*)) );
}
void ProjectController::openProjects(const KUrl::List& projects)
{
foreach (const KUrl& url, projects)
openProject(url);
}
void ProjectController::loadSettings( bool projectIsLoaded )
{
Q_UNUSED(projectIsLoaded)
}
void ProjectController::saveSettings( bool projectIsLoaded )
{
Q_UNUSED( projectIsLoaded );
}
int ProjectController::projectCount() const
{
return d->m_projects.count();
}
IProject* ProjectController::projectAt( int num ) const
{
if( !d->m_projects.isEmpty() && num >= 0 && num < d->m_projects.count() )
return d->m_projects.at( num );
return 0;
}
QList<IProject*> ProjectController::projects() const
{
return d->m_projects;
}
void ProjectController::eventuallyOpenProjectFile(KIO::Job* _job, KIO::UDSEntryList entries ) {
KIO::SimpleJob* job(dynamic_cast<KIO::SimpleJob*>(_job));
Q_ASSERT(job);
foreach(const KIO::UDSEntry& entry, entries) {
if(d->m_foundProjectFile)
break;
if(!entry.isDir()) {
QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME );
if(name.endsWith(".kdev4")) {
//We have found a project-file, open it
KUrl u(job->url());
u.addPath(name);
openProject(u);
d->m_foundProjectFile = true;
}
}
}
}
void ProjectController::openProjectForUrlSlot(bool) {
if(ICore::self()->documentController()->activeDocument()) {
KUrl url = ICore::self()->documentController()->activeDocument()->url();
IProject* project = ICore::self()->projectController()->findProjectForUrl(url);
if(!project) {
openProjectForUrl(url);
}else{
KMessageBox::error(Core::self()->uiController()->activeMainWindow(), i18n("Project already open: %1", project->name()));
}
}else{
KMessageBox::error(Core::self()->uiController()->activeMainWindow(), i18n("No active document"));
}
}
void ProjectController::openProjectForUrl(const KUrl& sourceUrl) {
KUrl dirUrl = sourceUrl.upUrl();
KUrl testAt = dirUrl;
d->m_foundProjectFile = false;
while(!testAt.path().isEmpty()) {
KUrl testProjectFile(testAt);
KIO::ListJob* job = KIO::listDir(testAt);
connect(job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(eventuallyOpenProjectFile(KIO::Job*,KIO::UDSEntryList)));
KIO::NetAccess::synchronousRun(job, ICore::self()->uiController()->activeMainWindow());
if(d->m_foundProjectFile) {
//Fine! We have directly opened the project
d->m_foundProjectFile = false;
return;
}
KUrl oldTest = testAt;
testAt = testAt.upUrl();
if(oldTest == testAt)
break;
}
KUrl askForOpen = d->dialog->askProjectConfigLocation(false, dirUrl);
if(askForOpen.isValid())
openProject(askForOpen);
}
void ProjectController::openProject( const KUrl &projectFile )
{
KUrl url = projectFile;
QList<const Session*> existingSessions;
if(!Core::self()->sessionController()->activeSession()->containedProjects().contains(url))
{
foreach( const Session* session, Core::self()->sessionController()->sessions())
{
if(session->containedProjects().contains(url))
{
existingSessions << session;
#if 0
///@todo Think about this! Problem: The session might already contain files, the debugger might be active, etc.
//If this session is empty, close it
if(Core::self()->sessionController()->activeSession()->description().isEmpty())
{
//Terminate this instance of kdevelop if the user agrees
foreach(Sublime::MainWindow* window, Core::self()->uiController()->controller()->mainWindows())
window->close();
}
#endif
}
}
}
if ( ! existingSessions.isEmpty() ) {
KDialog dialog(Core::self()->uiControllerInternal()->activeMainWindow());
dialog.setButtons(KDialog::Ok | KDialog::Cancel);
dialog.setWindowTitle(i18n("Project Already Open"));
QWidget contents;
contents.setLayout(new QVBoxLayout);
contents.layout()->addWidget(new QLabel(i18n("The project you're trying to open is already open in at least one "
"other session.<br>What do you want to do?")));
QGroupBox sessions;
sessions.setLayout(new QVBoxLayout);
QRadioButton* newSession = new QRadioButton(i18n("Add project to current session"));
sessions.layout()->addWidget(newSession);
newSession->setChecked(true);
foreach ( const Session* session, existingSessions ) {
QRadioButton* button = new QRadioButton(i18n("Open session %1", session->description()));
button->setProperty("sessionid", session->id().toString());
sessions.layout()->addWidget(button);
}
sessions.layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding));
contents.layout()->addWidget(&sessions);
dialog.setMainWidget(&contents);
bool success = dialog.exec();
if (!success)
return;
foreach ( const QObject* obj, sessions.children() ) {
if ( const QRadioButton* button = qobject_cast<const QRadioButton*>(obj) ) {
QString sessionid = button->property("sessionid").toString();
if ( button->isChecked() && ! sessionid.isEmpty() ) {
Core::self()->sessionController()->loadSession(sessionid);
return;
}
}
}
}
if ( url.isEmpty() )
{
url = d->dialog->askProjectConfigLocation(false);
}
if ( !url.isEmpty() )
{
d->importProject(url);
}
}
void ProjectController::fetchProject()
{
KUrl url = d->dialog->askProjectConfigLocation(true);
if ( !url.isEmpty() )
{
d->importProject(url);
}
}
void ProjectController::projectImportingFinished( IProject* project )
{
if( !project )
{
kWarning() << "OOOPS: 0-pointer project";
return;
}
IPlugin *managerPlugin = project->managerPlugin();
QList<IPlugin*> pluglist;
pluglist.append( managerPlugin );
d->m_projectPlugins.insert( project, pluglist );
d->m_projects.append( project );
d->saveListOfOpenedProjects();
if (Core::self()->setupFlags() != Core::NoUi)
{
d->m_recentAction->addUrl( project->projectFileUrl() );
KSharedConfig * config = KGlobal::config().data();
KConfigGroup recentGroup = config->group("RecentProjects");
d->m_recentAction->saveEntries( recentGroup );
config->sync();
}
Q_ASSERT(d->m_currentlyOpening.contains(project->projectFileUrl()));
d->m_currentlyOpening.removeAll(project->projectFileUrl());
emit projectOpened( project );
reparseProject(project);
}
// helper method for closeProject()
void ProjectController::unloadUnusedProjectPlugins(IProject* proj)
{
QList<IPlugin*> pluginsForProj = d->m_projectPlugins.value( proj );
d->m_projectPlugins.remove( proj );
QList<IPlugin*> otherProjectPlugins;
Q_FOREACH( const QList<IPlugin*>& _list, d->m_projectPlugins )
{
otherProjectPlugins << _list;
}
QSet<IPlugin*> pluginsForProjSet = QSet<IPlugin*>::fromList( pluginsForProj );
QSet<IPlugin*> otherPrjPluginsSet = QSet<IPlugin*>::fromList( otherProjectPlugins );
// loaded - target = tobe unloaded.
QSet<IPlugin*> tobeRemoved = pluginsForProjSet.subtract( otherPrjPluginsSet );
Q_FOREACH( IPlugin* _plugin, tobeRemoved )
{
KPluginInfo _plugInfo = Core::self()->pluginController()->pluginInfo( _plugin );
if( _plugInfo.isValid() )
{
QString _plugName = _plugInfo.pluginName();
kDebug() << "about to unloading :" << _plugName;
Core::self()->pluginController()->unloadPlugin( _plugName );
}
}
}
// helper method for closeProject()
void ProjectController::closeAllOpenedFiles(IProject* proj)
{
foreach(IDocument* doc, Core::self()->documentController()->openDocuments()) {
if (proj->inProject(IndexedString(doc->url()))) {
doc->close();
}
}
}
// helper method for closeProject()
void ProjectController::initializePluginCleanup(IProject* proj)
{
// Unloading (and thus deleting) these plugins is not a good idea just yet
// as we're being called by the view part and it gets deleted when we unload the plugin(s)
// TODO: find a better place to unload
connect(proj, SIGNAL(destroyed(QObject*)), this, SLOT(unloadAllProjectPlugins()));
}
void ProjectController::closeProject(IProject* proj_)
{
if (!proj_)
{
return;
}
// loading might have failed
d->m_currentlyOpening.removeAll(proj_->projectFileUrl());
Project* proj = dynamic_cast<KDevelop::Project*>( proj_ );
if( !proj )
{
kWarning() << "Unknown Project subclass found!";
return;
}
d->m_projects.removeAll(proj);
emit projectClosing(proj);
//Core::self()->saveSettings(); // The project file is being closed.
// Now we can save settings for all of the Core
// objects including this one!!
unloadUnusedProjectPlugins(proj);
closeAllOpenedFiles(proj);
proj->close();
proj->deleteLater(); //be safe when deleting
if (d->m_projects.isEmpty())
{
initializePluginCleanup(proj);
}
if(!d->m_cleaningUp)
d->saveListOfOpenedProjects();
emit projectClosed(proj);
return;
}
void ProjectController::abortOpeningProject(IProject* proj)
{
d->m_currentlyOpening.removeAll(proj->projectFileUrl());
emit projectOpeningAborted(proj);
}
ProjectModel* ProjectController::projectModel()
{
return d->model;
}
IProject* ProjectController::findProjectForUrl( const KUrl& url ) const
{
if (d->m_projects.isEmpty()) {
return 0;
}
ProjectBaseItem* item = d->model->itemForPath(IndexedString(url));
if (item) {
return item->project();
}
return 0;
}
IProject* ProjectController::findProjectByName( const QString& name )
{
Q_FOREACH( IProject* proj, d->m_projects )
{
if( proj->name() == name )
{
return proj;
}
}
return 0;
}
void ProjectController::configureProject( IProject* project )
{
d->projectConfig( project );
}
void ProjectController::addProject(IProject* project)
{
d->m_projects.append( project );
}
QItemSelectionModel* ProjectController::projectSelectionModel()
{
return d->selectionModel;
}
bool ProjectController::isProjectNameUsed( const QString& name ) const
{
foreach( IProject* p, projects() )
{
if( p->name() == name )
{
return true;
}
}
return false;
}
KUrl ProjectController::projectsBaseDirectory() const
{
KConfigGroup group = KGlobal::config()->group( "Project Manager" );
return group.readEntry( "Projects Base Directory",
KUrl( QDir::homePath()+"/projects" ) );
}
QString ProjectController::prettyFilePath(const KUrl& url, FormattingOptions format) const
{
IProject* project = Core::self()->projectController()->findProjectForUrl(url);
if(!project)
{
// Find a project with the correct base directory at least
foreach(IProject* candidateProject, Core::self()->projectController()->projects())
{
if(candidateProject->folder().isParentOf(url))
{
project = candidateProject;
break;
}
}
}
QString prefixText = url.upUrl().pathOrUrl(KUrl::AddTrailingSlash);
if (project) {
if (format == FormatHtml) {
prefixText = "<i>" + project->name() + "</i>/";
} else {
prefixText = project->name() + '/';
}
QString relativePath = KUrl::relativeUrl(project->folder(), url.upUrl());
if(relativePath.startsWith("./"))
relativePath = relativePath.mid(2);
prefixText += relativePath;
}
return prefixText;
}
QString ProjectController::prettyFileName(const KUrl& url, FormattingOptions format) const
{
IProject* project = Core::self()->projectController()->findProjectForUrl(url);
if(project && project->folder().equals(url, KUrl::CompareWithoutTrailingSlash))
{
if (format == FormatHtml) {
return "<i>" + project->name() + "</i>";
} else {
return project->name();
}
}
QString prefixText = prettyFilePath( url, format );
if (format == FormatHtml) {
return prefixText + "<b>" + url.fileName() + "</b>";
} else {
return prefixText + url.fileName();
}
}
ContextMenuExtension ProjectController::contextMenuExtension ( Context* ctx )
{
ContextMenuExtension ext;
if ( ctx->type() != Context::ProjectItemContext || !static_cast<ProjectItemContext*>(ctx)->items().isEmpty() ) {
return ext;
}
ext.addAction(ContextMenuExtension::ProjectGroup, d->m_openProject);
ext.addAction(ContextMenuExtension::ProjectGroup, d->m_fetchProject);
ext.addAction(ContextMenuExtension::ProjectGroup, d->m_recentAction);
return ext;
}
ProjectBuildSetModel* ProjectController::buildSetModel()
{
return d->buildset;
}
ProjectChangesModel* ProjectController::changesModel()
{
if(!d->m_changesModel)
d->m_changesModel=new ProjectChangesModel(this);
return d->m_changesModel;
}
void ProjectController::commitCurrentProject()
{
IDocument* doc=ICore::self()->documentController()->activeDocument();
if(!doc)
return;
KUrl url=doc->url();
IProject* project = ICore::self()->projectController()->findProjectForUrl(url);
if(project && project->versionControlPlugin()) {
IPlugin* plugin = project->versionControlPlugin();
IBasicVersionControl* vcs=plugin->extension<IBasicVersionControl>();
if(vcs) {
ICore::self()->documentController()->saveAllDocuments(KDevelop::IDocument::Silent);
const Path basePath = project->path();
VCSCommitDiffPatchSource* patchSource = new VCSCommitDiffPatchSource(new VCSStandardDiffUpdater(vcs, basePath.toUrl()));
bool ret = showVcsDiff(patchSource);
if(!ret) {
VcsCommitDialog *commitDialog = new VcsCommitDialog(patchSource);
commitDialog->setCommitCandidates(patchSource->infos());
commitDialog->exec();
}
}
}
}
QString ProjectController::mapSourceBuild( const QString& path_, bool reverse, bool fallbackRoot ) const
{
Path path(path_);
IProject* sourceDirProject = 0, *buildDirProject = 0;
Q_FOREACH(IProject* proj, d->m_projects)
{
if(proj->path().isParentOf(path))
sourceDirProject = proj;
if(proj->buildSystemManager())
{
Path buildDir = proj->buildSystemManager()->buildDirectory(proj->projectItem());
if(buildDir.isValid() && buildDir.isParentOf(path))
buildDirProject = proj;
}
}
if(!reverse)
{
// Map-target is the build directory
if(sourceDirProject && sourceDirProject->buildSystemManager())
{
// We're in the source, map into the build directory
QString relativePath = sourceDirProject->path().relativePath(path);
Path build = sourceDirProject->buildSystemManager()->buildDirectory(sourceDirProject->projectItem());
build.addPath(relativePath);
while(!QFile::exists(build.path()))
build = build.parent();
return build.pathOrUrl();
}else if(buildDirProject && fallbackRoot)
{
// We're in the build directory, map to the build directory root
return buildDirProject->buildSystemManager()->buildDirectory(buildDirProject->projectItem()).pathOrUrl();
}
}else{
// Map-target is the source directory
if(buildDirProject)
{
Path build = buildDirProject->buildSystemManager()->buildDirectory(buildDirProject->projectItem());
// We're in the source, map into the build directory
QString relativePath = build.relativePath(path);
Path source = buildDirProject->path();
source.addPath(relativePath);
while(!QFile::exists(source.path()))
source = source.parent();
return source.pathOrUrl();
}else if(sourceDirProject && fallbackRoot)
{
// We're in the source directory, map to the root
return sourceDirProject->path().pathOrUrl();
}
}
return QString();
}
void ProjectController::reparseProject( IProject* project, bool forceUpdate )
{
if (auto job = d->m_parseJobs.value(project)) {
job->kill();
}
d->m_parseJobs[project] = new KDevelop::ParseProjectJob(project, forceUpdate);
ICore::self()->runController()->registerJob(d->m_parseJobs[project]);
}
}
#include "moc_projectcontroller.cpp"