mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-25 03:12:53 +00:00
1155 lines
40 KiB
C++
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"
|