diff --git a/CMakeLists.txt b/CMakeLists.txt index a9007019df..9f9fe74c41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,183 +1,165 @@ # kdevplatform version set(KDEVPLATFORM_VERSION_MAJOR 4) set(KDEVPLATFORM_VERSION_MINOR 90) set(KDEVPLATFORM_VERSION_PATCH 90) # plugin versions listed in the .desktop files set(KDEV_PLUGIN_VERSION 24) # Increase this to reset incompatible item-repositories set(KDEV_ITEMREPOSITORY_VERSION 85) # library version / SO version set(KDEVPLATFORM_LIB_VERSION 10.0.0) set(KDEVPLATFORM_LIB_SOVERSION 10) ################################################################################ cmake_minimum_required(VERSION 2.8.12) project(KDevPlatform) # we need some parts of the ECM CMake helpers find_package (ECM 0.0.9 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${KDevPlatform_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH}) include(ECMAddTests) include(ECMOptionalAddSubdirectory) include(ECMInstallIcons) include(ECMSetupVersion) include(ECMMarkAsTest) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(ECMPackageConfigHelpers) include(GenerateExportHeader) include(FeatureSummary) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) include(KDevPlatformMacros) set(QT_MIN_VERSION "5.4.0") find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Core DBus Widgets Script WebKitWidgets Concurrent Test) find_package(Qt5QuickWidgets ${QT_MIN_VERSION}) set_package_properties(Qt5QuickWidgets PROPERTIES PURPOSE "Qt5 QuickWidgets library (part of Qt >=5.3). Required for the Welcome Page plugin." ) set(KF5_DEP_VERSION "5.3.0") find_package(KF5 ${KF5_DEP_VERSION} REQUIRED COMPONENTS Archive Config GuiAddons WidgetsAddons IconThemes I18n ItemModels ItemViews JobWidgets KCMUtils KIO NewStuff Notifications NotifyConfig Parts Service Sonnet TextEditor ThreadWeaver WindowSystem Declarative XmlGui ) find_package(Grantlee5) set_package_properties(Grantlee5 PROPERTIES PURPOSE "Grantlee templating library, needed for file templates" URL "http://www.grantlee.org/" TYPE RECOMMENDED) set(Boost_ADDITIONAL_VERSIONS 1.39.0 1.39) find_package(Boost 1.35.0) set_package_properties(Boost PROPERTIES PURPOSE "Boost libraries for enabling the classbrowser" URL "http://www.boost.org" TYPE REQUIRED) add_definitions( -DQT_DEPRECATED_WARNINGS -DQT_DISABLE_DEPRECATED_BEFORE=0x050400 -DQT_NO_URL_CAST_FROM_STRING -DQT_STRICT_ITERATORS -DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS ) # Turn off missing-field-initializers warning for GCC to avoid noise from false positives with empty {} # See discussion: http://mail.kde.org/pipermail/kdevelop-devel/2014-February/046910.html if (CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-field-initializers") endif() configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/config-kdevplatform.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kdevplatform.h ) include_directories(${KDevPlatform_SOURCE_DIR} ${KDevPlatform_BINARY_DIR}) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/util/) - -# Now set the usual KDEVPLATFORM_XXX_LIBRARIES variable so we can more easily move plugins around -set(KDEVPLATFORM_SUBLIME_LIBRARIES KDev::Sublime) -set(KDEVPLATFORM_INTERFACES_LIBRARIES KDev::Interfaces) -set(KDEVPLATFORM_LANGUAGE_LIBRARIES KDev::Language) -set(KDEVPLATFORM_PROJECT_LIBRARIES KDev::Project) -set(KDEVPLATFORM_UTIL_LIBRARIES KDev::Util) -set(KDEVPLATFORM_OUTPUTVIEW_LIBRARIES KDev::OutputView) -set(KDEVPLATFORM_VCS_LIBRARIES KDev::Vcs) -set(KDEVPLATFORM_SHELL_LIBRARIES KDev::Shell) -set(KDEVPLATFORM_TESTS_LIBRARIES KDev::Tests) -set(KDEVPLATFORM_DEBUGGER_LIBRARIES KDev::Debugger) -set(KDEVPLATFORM_DOCUMENTATION_LIBRARIES KDev::Documentation) -set(KDEVPLATFORM_SERIALIZATION_LIBRARIES KDev::Serialization) string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_TOLOWER) if(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug" OR CMAKE_BUILD_TYPE_TOLOWER STREQUAL "") set(COMPILER_OPTIMIZATIONS_DISABLED TRUE) else() set(COMPILER_OPTIMIZATIONS_DISABLED FALSE) endif() add_subdirectory(sublime) add_subdirectory(interfaces) add_subdirectory(project) add_subdirectory(language) add_subdirectory(shell) add_subdirectory(util) add_subdirectory(outputview) add_subdirectory(vcs) add_subdirectory(pics) -#ecm_optional_add_subdirectory(doc) add_subdirectory(debugger) add_subdirectory(documentation) add_subdirectory(serialization) - add_subdirectory(template) add_subdirectory(tests) - add_subdirectory(plugins) set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KDevPlatform") ecm_configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KDevPlatformConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KDevPlatformConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) ecm_setup_version(${KDEVPLATFORM_VERSION_MAJOR}.${KDEVPLATFORM_VERSION_MINOR}.${KDEVPLATFORM_VERSION_PATCH} VARIABLE_PREFIX KDEVPLATFORM VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kdevplatform_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KDevPlatformConfigVersion.cmake" SOVERSION ${KDEVPLATFORM_LIB_SOVERSION}) install( FILES "${KDevPlatform_BINARY_DIR}/kdevplatform_version.h" "${KDevPlatform_BINARY_DIR}/config-kdevplatform.h" DESTINATION "${KDE_INSTALL_INCLUDEDIR}/kdevplatform" ) install( FILES "${KDevPlatform_BINARY_DIR}/KDevPlatformConfig.cmake" "${KDevPlatform_BINARY_DIR}/KDevPlatformConfigVersion.cmake" cmake/modules/KDevPlatformMacros.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" ) install( EXPORT KDevPlatformTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" NAMESPACE KDev:: FILE KDevPlatformTargets.cmake ) include(CTest) # CTestCustom.cmake has to be in the CTEST_BINARY_DIR. # in the KDE build system, this is the same as CMAKE_BINARY_DIR. configure_file(${CMAKE_SOURCE_DIR}/CTestCustom.cmake ${CMAKE_BINARY_DIR}/CTestCustom.cmake) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/KDevPlatformConfig.cmake.in b/KDevPlatformConfig.cmake.in index fdfb4a81f8..12c8068513 100644 --- a/KDevPlatformConfig.cmake.in +++ b/KDevPlatformConfig.cmake.in @@ -1,60 +1,6 @@ -######################################################################### -# -# KDevPlatform Configuration File -# -# This file sets various CMake Variables -# -# KDEVPLATFORM_INCLUDE_DIR - The Include Directory for all KDEVPLATFORM libraries -# KDEVPLATFORM_INTERFACES_LIBRARIES - the interfaces library -# KDEVPLATFORM_LANGUAGE_LIBRARIES - the language library -# KDEVPLATFORM_OUTPUTVIEW_LIBRARIES - the outputview library -# KDEVPLATFORM_PROJECT_LIBRARIES - the project library -# KDEVPLATFORM_SUBLIME_LIBRARIES - the sublime library -# KDEVPLATFORM_SHELL_LIBRARIES - the shell library -# KDEVPLATFORM_TESTS_LIBRARIES - the tests library -# KDEVPLATFORM_UTIL_LIBRARIES - the util library -# KDEVPLATFORM_VCS_LIBRARIES - the vcs library -# KDEVPLATFORM_DEBUGGER_LIBRARIES - debugger module library -# KDEVPLATFORM_SERIALIZATION_LIBRARIES - serialization module library -# -# Copyright 2008 Andreas Pakulat -# Redistribution and use is allowed according to the terms of the BSD license. -###################################################################### +@PACKAGE_INIT@ -get_filename_component(_KDEVPLATFORM_CURRENT_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) -set( KDEV_PLUGIN_VERSION @KDEV_PLUGIN_VERSION@ ) +set(KDEV_PLUGIN_VERSION @KDEV_PLUGIN_VERSION@) -if(NOT WIN32) - # This is needed on non-win32 platforms, as lib-install-dir might be in a - # totally different prefix than include-install-dir. So instead hardcode the - # absolute path during buildtime - set( KDEVPLATFORM_INCLUDE_DIR "@INCLUDE_INSTALL_DIR@/kdevplatform" ) - -else(NOT WIN32) - set( KDEVPLATFORM_INCLUDE_DIR "${_KDEVPLATFORM_CURRENT_DIR}/../../../include/kdevplatform" ) -endif(NOT WIN32) - -if( NOT TARGET KDevPlatformImport__kdevplatforminterfaces ) - include("${_KDEVPLATFORM_CURRENT_DIR}/KDevPlatformTargets.cmake") -endif( NOT TARGET KDevPlatformImport__kdevplatforminterfaces ) - -macro( _kdevplatform_set_lib_vars _lib ) - string(TOUPPER ${_lib} upperLib ) - set( KDEVPLATFORM_${upperLib}_LIBRARIES KDev::${_lib} ) - mark_as_advanced(KDEVPLATFORM_${upperLib}_LIBRARIES) -endmacro( _kdevplatform_set_lib_vars ) - -_kdevplatform_set_lib_vars( Interfaces ) -_kdevplatform_set_lib_vars( Project ) -_kdevplatform_set_lib_vars( Language ) -_kdevplatform_set_lib_vars( Util ) -_kdevplatform_set_lib_vars( Shell ) -_kdevplatform_set_lib_vars( Sublime ) -_kdevplatform_set_lib_vars( Vcs ) -_kdevplatform_set_lib_vars( OutputView ) -_kdevplatform_set_lib_vars( Debugger ) -_kdevplatform_set_lib_vars( Documentation) -_kdevplatform_set_lib_vars( Tests ) -_kdevplatform_set_lib_vars( Serialization ) - -include(${_KDEVPLATFORM_CURRENT_DIR}/KDevPlatformMacros.cmake) +include("${CMAKE_CURRENT_LIST_DIR}/KDevPlatformTargets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/KDevPlatformMacros.cmake") diff --git a/serialization/itemrepositoryregistry.cpp b/serialization/itemrepositoryregistry.cpp index d507228af6..ed858efa64 100644 --- a/serialization/itemrepositoryregistry.cpp +++ b/serialization/itemrepositoryregistry.cpp @@ -1,409 +1,411 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "itemrepositoryregistry.h" #include #include #include #include #include #include +#include #include #include #include "abstractitemrepository.h" #include "debug.h" using namespace KDevelop; namespace { //If KDevelop crashed this many times consicutively, clean up the repository const int crashesBeforeCleanup = 1; void setCrashCounter(QFile& crashesFile, int count) { crashesFile.close(); crashesFile.open(QIODevice::WriteOnly | QIODevice::Truncate); QDataStream writeStream(&crashesFile); writeStream << count; } QString repositoryPathForSession(const KDevelop::ISessionLock::Ptr& session) { - QString xdgCacheDir = QProcessEnvironment::systemEnvironment().value("XDG_CACHE_HOME", QDir::homePath() + "/.cache") + "/kdevduchain"; - QString baseDir = QProcessEnvironment::systemEnvironment().value("KDEV_DUCHAIN_DIR", xdgCacheDir); + QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); + cacheDir += QStringLiteral("/kdevduchain"); + QString baseDir = QProcessEnvironment::systemEnvironment().value("KDEV_DUCHAIN_DIR", cacheDir); baseDir += QStringLiteral("/%1-%2").arg(qApp->applicationName()).arg(session->id()); return baseDir; } bool shouldClear(const QString& path) { QDir dir(path); if (!dir.exists()) { return false; } if (getenv("CLEAR_DUCHAIN_DIR")) { qCDebug(SERIALIZATION) << "clearing duchain directory because CLEAR_DUCHAIN_DIR is set"; return true; } if (dir.exists("is_writing")) { qWarning() << "repository" << path << "was write-locked, it probably is inconsistent"; return true; } if (!dir.exists(QStringLiteral("version_%1").arg(staticItemRepositoryVersion()))) { qWarning() << "version-hint not found, seems to be an old version"; return true; } QFile crashesFile(dir.filePath(QStringLiteral("crash_counter"))); if (crashesFile.open(QIODevice::ReadOnly)) { int count; QDataStream stream(&crashesFile); stream >> count; qCDebug(SERIALIZATION) << "current count of crashes: " << count; if (count >= crashesBeforeCleanup && !getenv("DONT_CLEAR_DUCHAIN_DIR")) { bool userAnswer = askUser(i18np("The previous session crashed", "Session crashed %1 times in a row", count), i18nc("@action", "Clear cache"), i18nc("@title", "Session crashed"), i18n("The crash may be caused by a corruption of cached data.\n\n" "Press OK if you want KDevelop to clear the cache, otherwise press Cancel if you are sure the crash has another origin.")); if (userAnswer) { qCDebug(SERIALIZATION) << "User chose to clean repository"; return true; } else { setCrashCounter(crashesFile, 1); qCDebug(SERIALIZATION) << "User chose to reset crash counter"; } } else { ///Increase the crash-count. It will be reset if kdevelop is shut down cleanly. setCrashCounter(crashesFile, ++count); } } else { setCrashCounter(crashesFile, 1); } return false; } } namespace KDevelop { struct ItemRepositoryRegistryPrivate { ItemRepositoryRegistry* m_owner; bool m_shallDelete; QString m_path; ISessionLock::Ptr m_sessionLock; QMap m_repositories; QMap m_customCounters; mutable QMutex m_mutex; ItemRepositoryRegistryPrivate(ItemRepositoryRegistry* owner) : m_owner(owner) , m_shallDelete(false) , m_mutex(QMutex::Recursive) { } void lockForWriting(); void unlockForWriting(); void deleteDataDirectory(const QString& path, bool recreate = true); /// @param path A shared directory-path that the item-repositories are to be loaded from. /// @returns Whether the repository registry has been opened successfully. /// If @c false, then all registered repositories should have been deleted. /// @note Currently the given path must reference a hidden directory, just to make sure we're /// not accidentally deleting something important. bool open(const QString& path); /// Close all contained repositories. /// @warning The current state is not stored to disk. void close(); }; //The global item-reposity registry ItemRepositoryRegistry* ItemRepositoryRegistry::m_self = 0; ItemRepositoryRegistry::ItemRepositoryRegistry(const ISessionLock::Ptr& session) : d(new ItemRepositoryRegistryPrivate(this)) { Q_ASSERT(session); d->open(repositoryPathForSession(session)); } void ItemRepositoryRegistry::initialize(const ISessionLock::Ptr& session) { if (!m_self) { ///We intentionally leak the registry, to prevent problems in the destruction order, where ///the actual repositories might get deleted later than the repository registry. m_self = new ItemRepositoryRegistry(session); } } ItemRepositoryRegistry* ItemRepositoryRegistry::self() { Q_ASSERT(m_self); return m_self; } void ItemRepositoryRegistry::deleteRepositoryFromDisk(const ISessionLock::Ptr& session) { // Now, as we have only the global item-repository registry, assume that if and only if // the given session is ours, its cache path is used by the said global item-repository registry. const QString repositoryPath = repositoryPathForSession(session); if(m_self && m_self->d->m_path == repositoryPath) { // remove later m_self->d->m_shallDelete = true; } else { // Otherwise, given session is not ours. // remove its item-repository directory directly. QDir(repositoryPath).removeRecursively(); } } QMutex& ItemRepositoryRegistry::mutex() { return d->m_mutex; } QAtomicInt& ItemRepositoryRegistry::getCustomCounter(const QString& identity, int initialValue) { if(!d->m_customCounters.contains(identity)) d->m_customCounters.insert(identity, new QAtomicInt(initialValue)); return *d->m_customCounters[identity]; } ///The global item-repository registry that is used by default ItemRepositoryRegistry& globalItemRepositoryRegistry() { return *ItemRepositoryRegistry::self(); } void ItemRepositoryRegistry::registerRepository(AbstractItemRepository* repository, AbstractRepositoryManager* manager) { QMutexLocker lock(&d->m_mutex); d->m_repositories.insert(repository, manager); if(!d->m_path.isEmpty()) { if(!repository->open(d->m_path)) { d->deleteDataDirectory(d->m_path); qCritical() << "failed to open a repository"; abort(); } } } QString ItemRepositoryRegistry::path() const { //We cannot lock the mutex here, since this may be called with one of the repositories locked, //and that may lead to a deadlock when at the same time a storing is requested return d->m_path; } void ItemRepositoryRegistryPrivate::lockForWriting() { QMutexLocker lock(&m_mutex); //Create is_writing QFile f(m_path + "/is_writing"); f.open(QIODevice::WriteOnly); f.close(); } void ItemRepositoryRegistry::lockForWriting() { d->lockForWriting(); } void ItemRepositoryRegistryPrivate::unlockForWriting() { QMutexLocker lock(&m_mutex); //Delete is_writing QFile::remove(m_path + "/is_writing"); } void ItemRepositoryRegistry::unlockForWriting() { d->unlockForWriting(); } void ItemRepositoryRegistry::unRegisterRepository(AbstractItemRepository* repository) { QMutexLocker lock(&d->m_mutex); Q_ASSERT(d->m_repositories.contains(repository)); repository->close(); d->m_repositories.remove(repository); } //After calling this, the data-directory may be a new one void ItemRepositoryRegistryPrivate::deleteDataDirectory(const QString& path, bool recreate) { QMutexLocker lock(&m_mutex); //lockForWriting creates a file, that prevents any other KDevelop instance from using the directory as it is. //Instead, the other instance will try to delete the directory as well. lockForWriting(); bool result = QDir(path).removeRecursively(); Q_ASSERT(result); Q_UNUSED(result); // Just recreate the directory then; leave old path (as it is dependent on appname and session only). if(recreate) { QDir().mkpath(path); } } bool ItemRepositoryRegistryPrivate::open(const QString& path) { QMutexLocker mlock(&m_mutex); if(m_path == path) { return true; } // Check if the repository shall be cleared if (shouldClear(path)) { qWarning() << QStringLiteral("The data-repository at %1 has to be cleared.").arg(m_path); deleteDataDirectory(path); } QDir().mkpath(path); foreach(AbstractItemRepository* repository, m_repositories.keys()) { if(!repository->open(path)) { deleteDataDirectory(path); qCritical() << "failed to open a repository"; abort(); } } QFile f(path + "/Counters"); if(f.open(QIODevice::ReadOnly)) { QDataStream stream(&f); while(!stream.atEnd()) { //Read in all custom counter values QString counterName; stream >> counterName; int counterValue; stream >> counterValue; m_owner->getCustomCounter(counterName, 0) = counterValue; } } m_path = path; return true; } void ItemRepositoryRegistry::store() { QMutexLocker lock(&d->m_mutex); foreach(AbstractItemRepository* repository, d->m_repositories.keys()) { repository->store(); } QFile versionFile(d->m_path + QStringLiteral("/version_%1").arg(staticItemRepositoryVersion())); if(versionFile.open(QIODevice::WriteOnly)) { versionFile.close(); } else { qWarning() << "Could not open version file for writing"; } //Store all custom counter values QFile f(d->m_path + "/Counters"); if(f.open(QIODevice::WriteOnly)) { f.resize(0); QDataStream stream(&f); for(QMap::const_iterator it = d->m_customCounters.constBegin(); it != d->m_customCounters.constEnd(); ++it) { stream << it.key(); stream << it.value()->fetchAndAddRelaxed(0); } } else { qWarning() << "Could not open counter file for writing"; } } void ItemRepositoryRegistry::printAllStatistics() const { QMutexLocker lock(&d->m_mutex); foreach(AbstractItemRepository* repository, d->m_repositories.keys()) { qCDebug(SERIALIZATION) << "statistics in" << repository->repositoryName() << ":"; qCDebug(SERIALIZATION) << repository->printStatistics(); } } int ItemRepositoryRegistry::finalCleanup() { QMutexLocker lock(&d->m_mutex); int changed = false; foreach(AbstractItemRepository* repository, d->m_repositories.keys()) { int added = repository->finalCleanup(); changed += added; qCDebug(SERIALIZATION) << "cleaned in" << repository->repositoryName() << ":" << added; } return changed; } void ItemRepositoryRegistryPrivate::close() { QMutexLocker lock(&m_mutex); foreach(AbstractItemRepository* repository, m_repositories.keys()) { repository->close(); } m_path.clear(); } ItemRepositoryRegistry::~ItemRepositoryRegistry() { QMutexLocker lock(&d->m_mutex); d->close(); foreach(QAtomicInt * counter, d->m_customCounters) { delete counter; } delete d; } void ItemRepositoryRegistry::shutdown() { QMutexLocker lock(&d->m_mutex); QString path = d->m_path; // FIXME: we don't close since this can trigger crashes at shutdown // since some items are still referenced, e.g. in static variables // d->close(); if(d->m_shallDelete) { d->deleteDataDirectory(path, false); } else { QFile::remove(path + QLatin1String("/crash_counter")); } } } diff --git a/shell/progresswidget/statusbarprogresswidget.cpp b/shell/progresswidget/statusbarprogresswidget.cpp index 79f39e90a8..46f56e9071 100644 --- a/shell/progresswidget/statusbarprogresswidget.cpp +++ b/shell/progresswidget/statusbarprogresswidget.cpp @@ -1,292 +1,298 @@ /* statusbarprogresswidget.cpp (C) 2004 Till Adam Don Sanders David Faure Copyright 2004 David Faure Includes StatusbarProgressWidget which is based on KIOLittleProgressDlg by Matt Koss KMail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2 or above, as published by the Free Software Foundation. KMail 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include "statusbarprogresswidget.h" #include "progressdialog.h" #include "progressmanager.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; //----------------------------------------------------------------------------- StatusbarProgressWidget::StatusbarProgressWidget( ProgressDialog* progressDialog, QWidget* parent, bool button ) : QFrame( parent ), mCurrentItem( 0 ), mProgressDialog( progressDialog ), mDelayTimer( 0 ), mBusyTimer( 0 ), mCleanTimer( 0 ) { m_bShowButton = button; int w = fontMetrics().width( " 999.9 kB/s 00:00:01 " ) + 8; box = new QHBoxLayout( this ); box->setMargin(0); box->setSpacing(0); m_pButton = new QPushButton( this ); + m_pButton->setAttribute(Qt::WA_MacMiniSize); m_pButton->setSizePolicy( QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ) ); QIcon smallIcon = QIcon::fromTheme( "go-up" ); m_pButton->setIcon( smallIcon ); box->addWidget( m_pButton ); stack = new QStackedWidget( this ); int maximumHeight = fontMetrics().height(); stack->setMaximumHeight( maximumHeight ); box->addWidget( stack ); m_pButton->setToolTip( i18n("Open detailed progress dialog") ); m_pProgressBar = new QProgressBar( this ); m_pProgressBar->installEventFilter( this ); m_pProgressBar->setMinimumWidth( w ); stack->insertWidget( 1,m_pProgressBar ); m_pLabel = new QLabel( QString(), this ); m_pLabel->setAlignment( Qt::AlignHCenter | Qt::AlignVCenter ); m_pLabel->installEventFilter( this ); m_pLabel->setMinimumWidth( w ); stack->insertWidget( 2, m_pLabel ); + +#ifndef Q_OS_MAC + // Currently on OSX this causes the button to be cut-off + // It isn't necessary because on OSX the button's minimumSizeHint is small enough m_pButton->setMaximumHeight( maximumHeight ); +#endif setMinimumWidth( minimumSizeHint().width() ); mode = None; setMode(); connect( m_pButton, &QPushButton::clicked, progressDialog, &ProgressDialog::slotToggleVisibility ); connect ( ProgressManager::instance(), &ProgressManager::progressItemAdded, this, &StatusbarProgressWidget::slotProgressItemAdded ); connect ( ProgressManager::instance(), &ProgressManager::progressItemCompleted, this, &StatusbarProgressWidget::slotProgressItemCompleted ); connect ( ProgressManager::instance(), &ProgressManager::progressItemUsesBusyIndicator, this, &StatusbarProgressWidget::updateBusyMode ); connect ( progressDialog, &ProgressDialog::visibilityChanged, this, &StatusbarProgressWidget::slotProgressDialogVisible ); mDelayTimer = new QTimer( this ); mDelayTimer->setSingleShot( true ); connect ( mDelayTimer, &QTimer::timeout, this, &StatusbarProgressWidget::slotShowItemDelayed ); mCleanTimer = new QTimer( this ); mCleanTimer->setSingleShot( true ); connect ( mCleanTimer, &QTimer::timeout, this, &StatusbarProgressWidget::slotClean ); } // There are three cases: no progressitem, one progressitem (connect to it directly), // or many progressitems (display busy indicator). Let's call them 0,1,N. // In slot..Added we can only end up in 1 or N. // In slot..Removed we can end up in 0, 1, or we can stay in N if we were already. void StatusbarProgressWidget::updateBusyMode() { connectSingleItem(); // if going to 1 item if ( mCurrentItem ) { // Exactly one item delete mBusyTimer; mBusyTimer = 0; mDelayTimer->start( 1000 ); } else { // N items if ( !mBusyTimer ) { mBusyTimer = new QTimer( this ); connect( mBusyTimer, &QTimer::timeout, this, &StatusbarProgressWidget::slotBusyIndicator ); mDelayTimer->start( 1000 ); } } } void StatusbarProgressWidget::slotProgressItemAdded( ProgressItem *item ) { if ( item->parent() ) return; // we are only interested in top level items updateBusyMode(); } void StatusbarProgressWidget::slotProgressItemCompleted( ProgressItem *item ) { if ( item->parent() ) { item->deleteLater(); item = 0; return; // we are only interested in top level items } item->deleteLater(); item = 0; connectSingleItem(); // if going back to 1 item if ( ProgressManager::instance()->isEmpty() ) { // No item // Done. In 5s the progress-widget will close, then we can clean up the statusbar mCleanTimer->start( 5000 ); } else if ( mCurrentItem ) { // Exactly one item delete mBusyTimer; mBusyTimer = 0; activateSingleItemMode(); } } void StatusbarProgressWidget::connectSingleItem() { if ( mCurrentItem ) { disconnect ( mCurrentItem, &ProgressItem::progressItemProgress, this, &StatusbarProgressWidget::slotProgressItemProgress ); mCurrentItem = 0; } mCurrentItem = ProgressManager::instance()->singleItem(); if ( mCurrentItem ) { connect ( mCurrentItem, &ProgressItem::progressItemProgress, this, &StatusbarProgressWidget::slotProgressItemProgress ); } } void StatusbarProgressWidget::activateSingleItemMode() { m_pProgressBar->setMaximum( 100 ); m_pProgressBar->setValue( mCurrentItem->progress() ); m_pProgressBar->setTextVisible( true ); } void StatusbarProgressWidget::slotShowItemDelayed() { bool noItems = ProgressManager::instance()->isEmpty(); if ( mCurrentItem ) { activateSingleItemMode(); } else if ( !noItems ) { // N items m_pProgressBar->setMaximum( 0 ); m_pProgressBar->setTextVisible( false ); Q_ASSERT( mBusyTimer ); if ( mBusyTimer ) mBusyTimer->start( 100 ); } if ( !noItems && mode == None ) { mode = Progress; setMode(); } } void StatusbarProgressWidget::slotBusyIndicator() { int p = m_pProgressBar->value(); m_pProgressBar->setValue( p + 10 ); } void StatusbarProgressWidget::slotProgressItemProgress( ProgressItem *item, unsigned int value ) { Q_ASSERT( item == mCurrentItem); // the only one we should be connected to Q_UNUSED( item ); m_pProgressBar->setValue( value ); } void StatusbarProgressWidget::setMode() { switch ( mode ) { case None: if ( m_bShowButton ) { m_pButton->hide(); } // show the empty label in order to make the status bar look better stack->show(); stack->setCurrentWidget( m_pLabel ); break; #if 0 case Label: if ( m_bShowButton ) { m_pButton->show(); } m_sslLabel->setState( m_sslLabel->lastState() ); stack->show(); stack->raiseWidget( m_pLabel ); break; #endif case Progress: stack->show(); stack->setCurrentWidget( m_pProgressBar ); if ( m_bShowButton ) { m_pButton->show(); } break; } } void StatusbarProgressWidget::slotClean() { // check if a new item showed up since we started the timer. If not, clear if ( ProgressManager::instance()->isEmpty() ) { m_pProgressBar->setValue( 0 ); //m_pLabel->clear(); mode = None; setMode(); } } bool StatusbarProgressWidget::eventFilter( QObject *, QEvent *ev ) { if ( ev->type() == QEvent::MouseButtonPress ) { QMouseEvent *e = (QMouseEvent*)ev; if ( e->button() == Qt::LeftButton && mode != None ) { // toggle view on left mouse button // Consensus seems to be that we should show/hide the fancy dialog when the user // clicks anywhere in the small one. mProgressDialog->slotToggleVisibility(); return true; } } return false; } void StatusbarProgressWidget::slotProgressDialogVisible( bool b ) { // Update the hide/show button when the detailed one is shown/hidden if ( b ) { m_pButton->setIcon( QIcon::fromTheme( "go-down" ) ); m_pButton->setToolTip( i18n("Hide detailed progress window") ); setMode(); } else { m_pButton->setIcon( QIcon::fromTheme( "go-up" ) ); m_pButton->setToolTip( i18n("Show detailed progress window") ); } } diff --git a/shell/runcontroller.cpp b/shell/runcontroller.cpp index f11e9c2c41..a8b6cd0342 100644 --- a/shell/runcontroller.cpp +++ b/shell/runcontroller.cpp @@ -1,1022 +1,1027 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda Copyright 2008 Aleix Pol 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 "runcontroller.h" #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "plugincontroller.h" #include "uicontroller.h" #include "projectcontroller.h" #include "mainwindow.h" #include "launchconfiguration.h" #include "launchconfigurationdialog.h" #include "debug.h" #include #include #include #include using namespace KDevelop; namespace { namespace Strings { QString LaunchConfigurationsGroup() { return QStringLiteral("Launch"); } QString LaunchConfigurationsListEntry() { return QStringLiteral("Launch Configurations"); } QString CurrentLaunchConfigProjectEntry() { return QStringLiteral("Current Launch Config Project"); } QString CurrentLaunchConfigNameEntry() { return QStringLiteral("Current Launch Config GroupName"); } QString ConfiguredFromProjectItemEntry() { return QStringLiteral("Configured from ProjectItem"); } } } typedef QPair Target; Q_DECLARE_METATYPE(Target) //TODO: Doesn't handle add/remove of launch configs in the dialog or renaming of configs //TODO: Doesn't auto-select launch configs opened from projects class DebugMode : public ILaunchMode { public: DebugMode() {} virtual QIcon icon() const override { return QIcon::fromTheme("tools-report-bug"); } virtual QString id() const override { return "debug"; } virtual QString name() const override { return i18n("Debug"); } }; class ProfileMode : public ILaunchMode { public: ProfileMode() {} virtual QIcon icon() const override { return QIcon::fromTheme("office-chart-area"); } virtual QString id() const override { return "profile"; } virtual QString name() const override { return i18n("Profile"); } }; class ExecuteMode : public ILaunchMode { public: ExecuteMode() {} virtual QIcon icon() const override { return QIcon::fromTheme("system-run"); } virtual QString id() const override { return "execute"; } virtual QString name() const override { return i18n("Execute"); } }; class RunController::RunControllerPrivate { public: QItemDelegate* delegate; IRunController::State state; RunController* q; QHash jobs; QAction* stopAction; KActionMenu* stopJobsMenu; QAction* runAction; QAction* dbgAction; KSelectAction* currentTargetAction; QMap launchConfigurationTypes; QList launchConfigurations; QMap launchModes; QSignalMapper* launchChangeMapper; QSignalMapper* launchAsMapper; QMap > launchAsInfo; KDevelop::ProjectBaseItem* contextItem; DebugMode* debugMode; ExecuteMode* executeMode; ProfileMode* profileMode; bool hasLaunchConfigType( const QString& typeId ) { return launchConfigurationTypes.contains( typeId ); } void saveCurrentLaunchAction() { if (!currentTargetAction) return; if( currentTargetAction->currentAction() ) { KConfigGroup grp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); LaunchConfiguration* l = static_cast( currentTargetAction->currentAction()->data().value() ); grp.writeEntry( Strings::CurrentLaunchConfigProjectEntry(), l->project() ? l->project()->name() : "" ); grp.writeEntry( Strings::CurrentLaunchConfigNameEntry(), l->configGroupName() ); grp.sync(); } } void configureLaunches() { LaunchConfigurationDialog dlg; dlg.exec(); } QString launchActionText( LaunchConfiguration* l ) { QString label; if( l->project() ) { label = QStringLiteral("%1 : %2").arg( l->project()->name()).arg(l->name()); } else { label = l->name(); } return label; } void launchAs( int id ) { //qCDebug(SHELL) << "Launching id:" << id; QPair info = launchAsInfo[id]; //qCDebug(SHELL) << "fetching type and mode:" << info.first << info.second; LaunchConfigurationType* type = launchConfigurationTypeForId( info.first ); ILaunchMode* mode = q->launchModeForId( info.second ); //qCDebug(SHELL) << "got mode and type:" << type << type->id() << mode << mode->id(); if( type && mode ) { ILauncher* launcher = 0; foreach (ILauncher *l, type->launchers()) { //qCDebug(SHELL) << "available launcher" << l << l->id() << l->supportedModes(); if (l->supportedModes().contains(mode->id())) { launcher = l; break; } } if (launcher) { QStringList itemPath = Core::self()->projectController()->projectModel()->pathFromIndex(contextItem->index()); ILaunchConfiguration* ilaunch = 0; foreach (LaunchConfiguration *l, launchConfigurations) { QStringList path = l->config().readEntry(Strings::ConfiguredFromProjectItemEntry(), QStringList()); if (l->type() == type && path == itemPath) { qCDebug(SHELL) << "already generated ilaunch" << path; ilaunch = l; break; } } if (!ilaunch) { ilaunch = q->createLaunchConfiguration( type, qMakePair( mode->id(), launcher->id() ), contextItem->project(), contextItem->text() ); LaunchConfiguration* launch = dynamic_cast( ilaunch ); type->configureLaunchFromItem( launch->config(), contextItem ); launch->config().writeEntry(Strings::ConfiguredFromProjectItemEntry(), itemPath); //qCDebug(SHELL) << "created config, launching"; } else { //qCDebug(SHELL) << "reusing generated config, launching"; } q->setDefaultLaunch(ilaunch); q->execute( mode->id(), ilaunch ); } } } void updateCurrentLaunchAction() { if (!currentTargetAction) return; KConfigGroup launchGrp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); QString currentLaunchProject = launchGrp.readEntry( Strings::CurrentLaunchConfigProjectEntry(), "" ); QString currentLaunchName = launchGrp.readEntry( Strings::CurrentLaunchConfigNameEntry(), "" ); LaunchConfiguration* l = 0; if( currentTargetAction->currentAction() ) { l = static_cast( currentTargetAction->currentAction()->data().value() ); } else if( !launchConfigurations.isEmpty() ) { l = launchConfigurations.at( 0 ); } if( l && ( ( !currentLaunchProject.isEmpty() && ( !l->project() || l->project()->name() != currentLaunchProject ) ) || l->configGroupName() != currentLaunchName ) ) { foreach( QAction* a, currentTargetAction->actions() ) { LaunchConfiguration* l = static_cast( qvariant_cast( a->data() ) ); if( currentLaunchName == l->configGroupName() && ( ( currentLaunchProject.isEmpty() && !l->project() ) || ( l->project() && l->project()->name() == currentLaunchProject ) ) ) { a->setChecked( true ); break; } } } if( !currentTargetAction->currentAction() ) { qCDebug(SHELL) << "oops no current action, using first if list is non-empty"; if( !currentTargetAction->actions().isEmpty() ) { currentTargetAction->actions().first()->setChecked( true ); } } } void addLaunchAction( LaunchConfiguration* l ) { if (!currentTargetAction) return; QAction* action = currentTargetAction->addAction(launchActionText( l )); action->setData(qVariantFromValue(l)); } void readLaunchConfigs( KSharedConfigPtr cfg, IProject* prj ) { KConfigGroup group(cfg, Strings::LaunchConfigurationsGroup()); QStringList configs = group.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); foreach( const QString& cfg, configs ) { KConfigGroup grp = group.group( cfg ); if( launchConfigurationTypeForId( grp.readEntry( LaunchConfiguration::LaunchConfigurationTypeEntry(), "" ) ) ) { q->addLaunchConfiguration( new LaunchConfiguration( grp, prj ) ); } } } LaunchConfigurationType* launchConfigurationTypeForId( const QString& id ) { QMap::iterator it = launchConfigurationTypes.find( id ); if( it != launchConfigurationTypes.end() ) { return it.value(); } else { qWarning() << "couldn't find type for id:" << id << ". Known types:" << launchConfigurationTypes.keys(); } return 0; } }; RunController::RunController(QObject *parent) : IRunController(parent) , d(new RunControllerPrivate) { setObjectName("RunController"); + + QDBusConnection::sessionBus().registerObject("/org/kdevelop/RunController", + this, QDBusConnection::ExportScriptableSlots); + // TODO: need to implement compile only if needed before execute // TODO: need to implement abort all running programs when project closed d->currentTargetAction = 0; d->state = Idle; d->q = this; d->delegate = new RunDelegate(this); d->launchChangeMapper = new QSignalMapper( this ); d->launchAsMapper = 0; d->contextItem = 0; d->executeMode = 0; d->debugMode = 0; d->profileMode = 0; if(!(Core::self()->setupFlags() & Core::NoUi)) { // Note that things like registerJob() do not work without the actions, it'll simply crash. setupActions(); } } RunController::~RunController() { delete d; } void KDevelop::RunController::launchChanged( LaunchConfiguration* l ) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { a->setText( d->launchActionText( l ) ); break; } } } void RunController::cleanup() { delete d->executeMode; d->executeMode = 0; delete d->profileMode; d->profileMode = 0; delete d->debugMode; d->debugMode = 0; stopAllProcesses(); d->saveCurrentLaunchAction(); } void RunController::initialize() { d->executeMode = new ExecuteMode(); addLaunchMode( d->executeMode ); d->profileMode = new ProfileMode(); addLaunchMode( d->profileMode ); d->debugMode = new DebugMode; addLaunchMode( d->debugMode ); d->readLaunchConfigs( Core::self()->activeSession()->config(), 0 ); foreach (IProject* project, Core::self()->projectController()->projects()) { slotProjectOpened(project); } connect(Core::self()->projectController(), &IProjectController::projectOpened, this, &RunController::slotProjectOpened); connect(Core::self()->projectController(), &IProjectController::projectClosing, this, &RunController::slotProjectClosing); connect(Core::self()->projectController(), &IProjectController::projectConfigurationChanged, this, &RunController::slotRefreshProject); if( (Core::self()->setupFlags() & Core::NoUi) == 0 ) { // Only do this in GUI mode d->updateCurrentLaunchAction(); } } KJob* RunController::execute(const QString& runMode, ILaunchConfiguration* launch) { if( !launch ) { qCDebug(SHELL) << "execute called without launch config!"; return 0; } LaunchConfiguration *run = dynamic_cast(launch); //TODO: Port to launch framework, probably needs to be part of the launcher //if(!run.dependencies().isEmpty()) // ICore::self()->documentController()->saveAllDocuments(IDocument::Silent); //foreach(KJob* job, run.dependencies()) //{ // jobs.append(job); //} qCDebug(SHELL) << "mode:" << runMode; QString launcherId = run->launcherForMode( runMode ); qCDebug(SHELL) << "launcher id:" << launcherId; ILauncher* launcher = run->type()->launcherForId( launcherId ); if( !launcher ) { KMessageBox::error( qApp->activeWindow(), i18n("The current launch configuration does not support the '%1' mode.", runMode), ""); return 0; } KJob* launchJob = launcher->start(runMode, run); registerJob(launchJob); return launchJob; } void RunController::setupActions() { QAction* action; // TODO not multi-window friendly, FIXME KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); action = new QAction(i18n("Configure Launches..."), this); ac->addAction("configure_launches", action); action->setMenuRole(QAction::NoRole); // OSX: Be explicit about role, prevent hiding due to conflict with "Preferences..." menu item action->setStatusTip(i18n("Open Launch Configuration Dialog")); action->setToolTip(i18nc("@info:tooltip", "Open Launch Configuration Dialog")); action->setWhatsThis(i18nc("@info:whatsthis", "Opens a dialog to setup new launch configurations, or to change the existing ones.")); connect(action, &QAction::triggered, this, [&] { d->configureLaunches(); }); d->runAction = new QAction( QIcon::fromTheme("system-run"), i18n("Execute Launch"), this); d->runAction->setIconText( i18nc("Short text for 'Execute launch' used in the toolbar", "Execute") ); ac->setDefaultShortcut( d->runAction, Qt::SHIFT + Qt::Key_F9); d->runAction->setToolTip(i18nc("@info:tooltip", "Execute current launch")); d->runAction->setStatusTip(i18n("Execute current launch")); d->runAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration.")); ac->addAction("run_execute", d->runAction); connect(d->runAction, &QAction::triggered, this, &RunController::slotExecute); d->dbgAction = new QAction( QIcon::fromTheme("debug-run"), i18n("Debug Launch"), this); ac->setDefaultShortcut( d->dbgAction, Qt::Key_F9); d->dbgAction->setIconText( i18nc("Short text for 'Debug launch' used in the toolbar", "Debug") ); d->dbgAction->setToolTip(i18nc("@info:tooltip", "Debug current launch")); d->dbgAction->setStatusTip(i18n("Debug current launch")); d->dbgAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Debugger.")); ac->addAction("run_debug", d->dbgAction); connect(d->dbgAction, &QAction::triggered, this, &RunController::slotDebug); Core::self()->uiControllerInternal()->area(0, "code")->addAction(d->dbgAction); // TODO: at least get a profile target, it's sad to have the menu entry without a profiler // QAction* profileAction = new QAction( QIcon::fromTheme(""), i18n("Profile Launch"), this); // profileAction->setToolTip(i18nc("@info:tooltip", "Profile current launch")); // profileAction->setStatusTip(i18n("Profile current launch")); // profileAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Profiler.")); // ac->addAction("run_profile", profileAction); // connect(profileAction, SIGNAL(triggered(bool)), this, SLOT(slotProfile())); action = d->stopAction = new QAction( QIcon::fromTheme("process-stop"), i18n("Stop All Jobs"), this); action->setIconText(i18nc("Short text for 'Stop All Jobs' used in the toolbar", "Stop All")); // Ctrl+Escape would be nicer, but thats taken by the ksysguard desktop shortcut ac->setDefaultShortcut( action, QKeySequence("Ctrl+Shift+Escape")); action->setToolTip(i18nc("@info:tooltip", "Stop all currently running jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "Requests that all running jobs are stopped.")); action->setEnabled(false); ac->addAction("run_stop_all", action); connect(action, &QAction::triggered, this, &RunController::stopAllProcesses); Core::self()->uiControllerInternal()->area(0, "debug")->addAction(action); action = d->stopJobsMenu = new KActionMenu( QIcon::fromTheme("process-stop"), i18n("Stop"), this); action->setIconText(i18nc("Short text for 'Stop' used in the toolbar", "Stop")); action->setToolTip(i18nc("@info:tooltip", "Menu allowing to stop individual jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "List of jobs that can be stopped individually.")); action->setEnabled(false); ac->addAction("run_stop_menu", action); d->currentTargetAction = new KSelectAction( i18n("Current Launch Configuration"), this); d->currentTargetAction->setToolTip(i18nc("@info:tooltip", "Current launch configuration")); d->currentTargetAction->setStatusTip(i18n("Current launch Configuration")); d->currentTargetAction->setWhatsThis(i18nc("@info:whatsthis", "Select which launch configuration to run when run is invoked.")); ac->addAction("run_default_target", d->currentTargetAction); } LaunchConfigurationType* RunController::launchConfigurationTypeForId( const QString& id ) { return d->launchConfigurationTypeForId( id ); } void KDevelop::RunController::slotProjectOpened(KDevelop::IProject * project) { d->readLaunchConfigs( project->projectConfiguration(), project ); d->updateCurrentLaunchAction(); } void KDevelop::RunController::slotProjectClosing(KDevelop::IProject * project) { if (!d->currentTargetAction) return; foreach (QAction* action, d->currentTargetAction->actions()) { LaunchConfiguration* l = static_cast(qvariant_cast(action->data())); if ( project == l->project() ) { l->save(); d->launchConfigurations.removeAll(l); delete l; bool wasSelected = action->isChecked(); delete action; if (wasSelected && !d->currentTargetAction->actions().isEmpty()) d->currentTargetAction->actions().first()->setChecked(true); } } } void KDevelop::RunController::slotRefreshProject(KDevelop::IProject* project) { slotProjectClosing(project); slotProjectOpened(project); } void RunController::slotDebug() { if(d->launchConfigurations.isEmpty()) { LaunchConfigurationDialog d; d.exec(); } if(!d->launchConfigurations.isEmpty()) executeDefaultLaunch( "debug" ); } void RunController::slotProfile() { if(d->launchConfigurations.isEmpty()) { LaunchConfigurationDialog d; d.exec(); } if(!d->launchConfigurations.isEmpty()) executeDefaultLaunch( "profile" ); } void RunController::slotExecute() { if(d->launchConfigurations.isEmpty()) { LaunchConfigurationDialog d; d.exec(); } if(!d->launchConfigurations.isEmpty()) executeDefaultLaunch( "execute" ); } LaunchConfiguration* KDevelop::RunController::defaultLaunch() const { QAction* projectAction = d->currentTargetAction->currentAction(); if( projectAction ) return static_cast(qvariant_cast(projectAction->data())); return 0; } void KDevelop::RunController::registerJob(KJob * job) { if (!job) return; if (!(job->capabilities() & KJob::Killable)) { // see e.g. https://bugs.kde.org/show_bug.cgi?id=314187 qWarning() << "non-killable job" << job << "registered - this might lead to crashes on shutdown."; } if (!d->jobs.contains(job)) { QAction* stopJobAction = 0; if (Core::self()->setupFlags() != Core::NoUi) { stopJobAction = new QAction(job->objectName().isEmpty() ? i18n("<%1> Unnamed job", job->staticMetaObject.className()) : job->objectName(), this); stopJobAction->setData(QVariant::fromValue(static_cast(job))); d->stopJobsMenu->addAction(stopJobAction); connect (stopJobAction, &QAction::triggered, this, &RunController::slotKillJob); job->setUiDelegate( new KDialogJobUiDelegate() ); } d->jobs.insert(job, stopJobAction); connect( job, &KJob::finished, this, &RunController::finished ); connect( job, &KJob::destroyed, this, &RunController::jobDestroyed ); IRunController::registerJob(job); emit jobRegistered(job); } job->start(); checkState(); } void KDevelop::RunController::unregisterJob(KJob * job) { IRunController::unregisterJob(job); Q_ASSERT(d->jobs.contains(job)); // Delete the stop job action QAction *action = d->jobs.take(job); if (action) action->deleteLater(); checkState(); emit jobUnregistered(job); } void KDevelop::RunController::checkState() { bool running = false; foreach (KJob* job, d->jobs.keys()) { if (!job->isSuspended()) { running = true; break; } } if ( ( d->state != Running ? false : true ) == running ) { d->state = running ? Running : Idle; emit runStateChanged(d->state); } if (Core::self()->setupFlags() != Core::NoUi) { d->stopAction->setEnabled(running); d->stopJobsMenu->setEnabled(running); } } void KDevelop::RunController::stopAllProcesses() { // composite jobs might remove child jobs, see also: // https://bugs.kde.org/show_bug.cgi?id=258904 // foreach already iterates over a copy foreach (KJob* job, d->jobs.keys()) { // now we check the real list whether it was deleted if (!d->jobs.contains(job)) continue; if (job->capabilities() & KJob::Killable) { job->kill(KJob::EmitResult); } else { qWarning() << "cannot stop non-killable job: " << job; } } } void KDevelop::RunController::slotKillJob() { QAction* action = dynamic_cast(sender()); Q_ASSERT(action); KJob* job = static_cast(qvariant_cast(action->data())); if (job->capabilities() & KJob::Killable) job->kill(); } void KDevelop::RunController::finished(KJob * job) { unregisterJob(job); switch (job->error()) { case KJob::NoError: case KJob::KilledJobError: case OutputJob::FailedShownError: break; default: { ///WARNING: do *not* use a nested event loop here, it might cause /// random crashes later on, see e.g.: /// https://bugs.kde.org/show_bug.cgi?id=309811 auto dialog = new QDialog(qApp->activeWindow()); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(i18n("Process Error")); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, dialog); KMessageBox::createKMessageBox(dialog, buttonBox, QMessageBox::Warning, job->errorString(), QStringList(), QString(), 0, KMessageBox::NoExec); dialog->show(); } } } void RunController::jobDestroyed(QObject* job) { KJob* kjob = static_cast(job); if (d->jobs.contains(kjob)) { qWarning() << "job destroyed without emitting finished signal!"; unregisterJob(kjob); } } void KDevelop::RunController::suspended(KJob * job) { Q_UNUSED(job); checkState(); } void KDevelop::RunController::resumed(KJob * job) { Q_UNUSED(job); checkState(); } QList< KJob * > KDevelop::RunController::currentJobs() const { return d->jobs.keys(); } QList RunController::launchConfigurations() const { QList configs; foreach (LaunchConfiguration *config, launchConfigurationsInternal()) configs << config; return configs; } QList RunController::launchConfigurationsInternal() const { return d->launchConfigurations; } QList RunController::launchConfigurationTypes() const { return d->launchConfigurationTypes.values(); } void RunController::addConfigurationType( LaunchConfigurationType* type ) { if( !d->launchConfigurationTypes.contains( type->id() ) ) { d->launchConfigurationTypes.insert( type->id(), type ); } } void RunController::removeConfigurationType( LaunchConfigurationType* type ) { foreach( LaunchConfiguration* l, d->launchConfigurations ) { if( l->type() == type ) { d->launchConfigurations.removeAll( l ); delete l; } } d->launchConfigurationTypes.remove( type->id() ); } void KDevelop::RunController::addLaunchMode(KDevelop::ILaunchMode* mode) { if( !d->launchModes.contains( mode->id() ) ) { d->launchModes.insert( mode->id(), mode ); } } QList< KDevelop::ILaunchMode* > KDevelop::RunController::launchModes() const { return d->launchModes.values(); } void KDevelop::RunController::removeLaunchMode(KDevelop::ILaunchMode* mode) { d->launchModes.remove( mode->id() ); } KDevelop::ILaunchMode* KDevelop::RunController::launchModeForId(const QString& id) const { QMap::iterator it = d->launchModes.find( id ); if( it != d->launchModes.end() ) { return it.value(); } return 0; } void KDevelop::RunController::addLaunchConfiguration(KDevelop::LaunchConfiguration* l) { if( !d->launchConfigurations.contains( l ) ) { d->addLaunchAction( l ); d->launchConfigurations << l; if( !d->currentTargetAction->currentAction() ) { if( !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().first()->setChecked( true ); } } connect( l, &LaunchConfiguration::nameChanged, this, &RunController::launchChanged ); } } void KDevelop::RunController::removeLaunchConfiguration(KDevelop::LaunchConfiguration* l) { KConfigGroup launcherGroup; if( l->project() ) { launcherGroup = l->project()->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() ); } else { launcherGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); } QStringList configs = launcherGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); configs.removeAll( l->configGroupName() ); launcherGroup.deleteGroup( l->configGroupName() ); launcherGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs ); launcherGroup.sync(); foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { bool wasSelected = a->isChecked(); d->currentTargetAction->removeAction( a ); if( wasSelected && !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().first()->setChecked( true ); } break; } } d->launchConfigurations.removeAll( l ); delete l; } void KDevelop::RunController::executeDefaultLaunch(const QString& runMode) { auto dl = defaultLaunch(); if( !dl ) { qWarning() << "no default launch!"; return; } execute( runMode, dl ); } void RunController::setDefaultLaunch(ILaunchConfiguration* l) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { a->setChecked(true); break; } } } bool launcherNameExists(const QString& name) { foreach(ILaunchConfiguration* config, Core::self()->runControllerInternal()->launchConfigurations()) { if(config->name()==name) return true; } return false; } QString makeUnique(const QString& name) { if(launcherNameExists(name)) { for(int i=2; ; i++) { QString proposed = QStringLiteral("%1 (%2)").arg(name).arg(i); if(!launcherNameExists(proposed)) { return proposed; } } } return name; } ILaunchConfiguration* RunController::createLaunchConfiguration ( LaunchConfigurationType* type, const QPair& launcher, IProject* project, const QString& name ) { KConfigGroup launchGroup; if( project ) { launchGroup = project->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() ); } else { launchGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); } QStringList configs = launchGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); uint num = 0; QString baseName = "Launch Configuration"; while( configs.contains( QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ) ) ) { num++; } QString groupName = QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ); KConfigGroup launchConfigGroup = launchGroup.group( groupName ); QString cfgName = name; if( name.isEmpty() ) { cfgName = i18n("New %1 Launcher", type->name() ); cfgName = makeUnique(cfgName); } launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationNameEntry(), cfgName ); launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationTypeEntry(), type->id() ); launchConfigGroup.sync(); configs << groupName; launchGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs ); launchGroup.sync(); LaunchConfiguration* l = new LaunchConfiguration( launchConfigGroup, project ); l->setLauncherForMode( launcher.first, launcher.second ); Core::self()->runControllerInternal()->addLaunchConfiguration( l ); return l; } QItemDelegate * KDevelop::RunController::delegate() const { return d->delegate; } ContextMenuExtension RunController::contextMenuExtension ( Context* ctx ) { delete d->launchAsMapper; d->launchAsMapper = new QSignalMapper( this ); connect( d->launchAsMapper, static_cast(&QSignalMapper::mapped), this, [&] (int id) { d->launchAs(id); } ); d->launchAsInfo.clear(); d->contextItem = 0; ContextMenuExtension ext; if( ctx->type() == Context::ProjectItemContext ) { KDevelop::ProjectItemContext* prjctx = dynamic_cast( ctx ); if( prjctx->items().count() == 1 ) { ProjectBaseItem* itm = prjctx->items().at( 0 ); int i = 0; foreach( ILaunchMode* mode, d->launchModes.values() ) { KActionMenu* menu = new KActionMenu( i18n("%1 As...", mode->name() ), this ); foreach( LaunchConfigurationType* type, launchConfigurationTypes() ) { bool hasLauncher = false; foreach( ILauncher* launcher, type->launchers() ) { if( launcher->supportedModes().contains( mode->id() ) ) { hasLauncher = true; } } if( hasLauncher && type->canLaunch(itm) ) { d->launchAsInfo[i] = qMakePair( type->id(), mode->id() ); QAction* act = new QAction( d->launchAsMapper ); act->setText( type->name() ); qCDebug(SHELL) << "Setting up mapping for:" << i << "for action" << act->text() << "in mode" << mode->name(); d->launchAsMapper->setMapping( act, i ); connect( act, &QAction::triggered, d->launchAsMapper, static_cast(&QSignalMapper::map) ); menu->addAction(act); i++; } } if( menu->menu()->actions().count() > 0 ) { ext.addAction( ContextMenuExtension::RunGroup, menu); } } if( ext.actions( ContextMenuExtension::RunGroup ).count() > 0 ) { d->contextItem = itm; } } } return ext; } RunDelegate::RunDelegate( QObject* parent ) : QItemDelegate(parent), runProviderBrush( KColorScheme::View, KColorScheme::PositiveText ), errorBrush( KColorScheme::View, KColorScheme::NegativeText ) { } void RunDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { QStyleOptionViewItem opt = option; QVariant status = index.data(Qt::UserRole+1); // if( status.isValid() && status.canConvert() ) // { // IRunProvider::OutputTypes type = status.value(); // if( type == IRunProvider::RunProvider ) // { // opt.palette.setBrush( QPalette::Text, runProviderBrush.brush( option.palette ) ); // } else if( type == IRunProvider::StandardError ) // { // opt.palette.setBrush( QPalette::Text, errorBrush.brush( option.palette ) ); // } // } QItemDelegate::paint(painter, opt, index); } #include "moc_runcontroller.cpp" diff --git a/shell/runcontroller.h b/shell/runcontroller.h index d742d64142..189287e04c 100644 --- a/shell/runcontroller.h +++ b/shell/runcontroller.h @@ -1,162 +1,164 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda 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. */ #ifndef KDEVPLATFORM_RUNCONTROLLER_H #define KDEVPLATFORM_RUNCONTROLLER_H #include #include #include #include #include "shellexport.h" class QStyleOptionViewItem; class QPainter; class QModelIndex; class KStatefulBrush; namespace KDevelop { class Context; class ContextMenuExtension; class IPlugin; class IProject; class LaunchConfiguration; class LaunchConfigurationType; class KDEVPLATFORMSHELL_EXPORT RunController : public IRunController { Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kdevelop.RunController") public: explicit RunController(QObject *parent); ~RunController(); virtual void registerJob(KJob *job) override; virtual void unregisterJob(KJob *job) override; virtual QList currentJobs() const override; KJob* execute(const QString& launchMode, ILaunchConfiguration* launch) override; - LaunchConfiguration* defaultLaunch() const; QList launchModes() const override; /** * @copydoc IRunController::addLaunchMode */ virtual void addLaunchMode( ILaunchMode* mode ) override; /** * @copydoc IRunController::removeLaunchMode */ virtual void removeLaunchMode( ILaunchMode* mode ) override; /** * @copydoc IRunController::launchModeForId() */ virtual KDevelop::ILaunchMode* launchModeForId(const QString& id) const override; void initialize(); void cleanup(); QItemDelegate* delegate() const; void addLaunchConfiguration( LaunchConfiguration* l ); void removeLaunchConfiguration( LaunchConfiguration* l ); QList launchConfigurationsInternal() const; virtual QList launchConfigurations() const override; /** * @copydoc IRunController::launchConfigurationTypes() */ virtual QList launchConfigurationTypes() const override; /** * @copydoc IRunController::addConfigurationType() */ virtual void addConfigurationType( LaunchConfigurationType* type ) override; /** * @copydoc IRunController::removeConfigurationType() */ virtual void removeConfigurationType( LaunchConfigurationType* type ) override; /** * Find the launch configuration type for the given @p id. * @returns the launch configuration type having the id, or 0 if no such type is known */ LaunchConfigurationType* launchConfigurationTypeForId( const QString& ) override; virtual ILaunchConfiguration* createLaunchConfiguration ( LaunchConfigurationType* type, const QPair& launcher, IProject* project = 0, const QString& name = QString() ) override; - virtual void executeDefaultLaunch(const QString& runMode) override; void setDefaultLaunch(ILaunchConfiguration* l); + LaunchConfiguration* defaultLaunch() const; ContextMenuExtension contextMenuExtension( KDevelop::Context* ctx ); public Q_SLOTS: - virtual void stopAllProcesses() override; + virtual Q_SCRIPTABLE void executeDefaultLaunch(const QString& runMode) override; + + virtual Q_SCRIPTABLE void stopAllProcesses() override; protected Q_SLOTS: virtual void finished(KJob *job) override; virtual void suspended(KJob *job) override; virtual void resumed(KJob *job) override; private Q_SLOTS: void slotRefreshProject(KDevelop::IProject* project); void slotExecute(); void slotDebug(); void slotProfile(); void slotProjectOpened(KDevelop::IProject* project); void slotProjectClosing(KDevelop::IProject* project); void slotKillJob(); void launchChanged(LaunchConfiguration*); void jobDestroyed(QObject* job); private: void setupActions(); void checkState(); Q_PRIVATE_SLOT(d, void configureLaunches()) Q_PRIVATE_SLOT(d, void launchAs(int)) class RunControllerPrivate; RunControllerPrivate* const d; }; class RunDelegate : public QItemDelegate { Q_OBJECT public: explicit RunDelegate( QObject* = 0 ); void paint(QPainter*, const QStyleOptionViewItem&, const QModelIndex&) const override; private: KStatefulBrush runProviderBrush; KStatefulBrush errorBrush; }; } #endif diff --git a/shell/sessioncontroller.cpp b/shell/sessioncontroller.cpp index c8002ae3d9..1dfa2da481 100644 --- a/shell/sessioncontroller.cpp +++ b/shell/sessioncontroller.cpp @@ -1,684 +1,684 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat Copyright 2010 David Nolden 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 "sessioncontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "session.h" #include "core.h" #include "uicontroller.h" #include "sessiondialog.h" #include "shellextension.h" #include "sessionlock.h" #include "sessionchooserdialog.h" #include "debug.h" #include #include #include #include #include namespace KDevelop { namespace { int argc = 0; char** argv = 0; }; void SessionController::setArguments(int _argc, char** _argv) { argc = _argc; argv = _argv; } static QStringList standardArguments() { QStringList ret; for(int a = 0; a < argc; ++a) { QString arg = QString::fromLocal8Bit(argv[a]); if(arg.startsWith("-graphicssystem") || arg.startsWith("-style")) { ret << '-' + arg; if(a+1 < argc) ret << QString::fromLocal8Bit(argv[a+1]); } } return ret; } class SessionControllerPrivate : public QObject { Q_OBJECT public: SessionControllerPrivate( SessionController* s ) : q(s) , activeSession(0) , grp(0) { } ~SessionControllerPrivate() { } Session* findSessionForName( const QString& name ) const { foreach( Session* s, sessionActions.keys() ) { if( s->name() == name ) return s; } return 0; } Session* findSessionForId(QString idString) { QUuid id(idString); foreach( Session* s, sessionActions.keys() ) { if( s->id() == id) return s; } return 0; } void newSession() { qsrand(QDateTime::currentDateTime().toTime_t()); Session* session = new Session( QUuid::createUuid().toString() ); KProcess::startDetached(ShellExtension::getInstance()->binaryPath(), QStringList() << "-s" << session->id().toString() << standardArguments()); delete session; #if 0 //Terminate this instance of kdevelop if the user agrees foreach(Sublime::MainWindow* window, Core::self()->uiController()->controller()->mainWindows()) window->close(); #endif } void configureSessions() { SessionDialog dlg(ICore::self()->uiController()-> activeMainWindow()); dlg.exec(); } void deleteCurrentSession() { int choice = KMessageBox::warningContinueCancel(Core::self()->uiController()->activeMainWindow(), i18n("The current session and all contained settings will be deleted. The projects will stay unaffected. Do you really want to continue?")); if(choice == KMessageBox::Continue) { q->deleteSessionFromDisk(sessionLock); q->emitQuitSession(); } } void renameSession() { QDialog dialog; dialog.setWindowTitle(i18n("Rename Session")); auto mainLayout = new QVBoxLayout(&dialog); QGroupBox box; QHBoxLayout layout(&box); box.setTitle(i18n("New Session Name")); QLineEdit edit; layout.addWidget(&edit); mainLayout->addWidget(&box); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); mainLayout->addWidget(buttonBox); edit.setText(q->activeSession()->name()); edit.setFocus(); if(dialog.exec() == QDialog::Accepted) { static_cast(q->activeSession())->setName(edit.text()); } } bool loadSessionExternally( Session* s ) { Q_ASSERT( s ); KProcess::startDetached(ShellExtension::getInstance()->binaryPath(), QStringList() << "-s" << s->id().toString() << standardArguments()); return true; } TryLockSessionResult activateSession( Session* s ) { Q_ASSERT( s ); activeSession = s; TryLockSessionResult result = SessionController::tryLockSession( s->id().toString()); if( !result.lock ) { activeSession = 0; return result; } Q_ASSERT(s->id().toString() == result.lock->id()); sessionLock = result.lock; KConfigGroup grp = KSharedConfig::openConfig()->group( SessionController::cfgSessionGroup() ); grp.writeEntry( SessionController::cfgActiveSessionEntry(), s->id().toString() ); grp.sync(); if (Core::self()->setupFlags() & Core::NoUi) return result; QHash::iterator it = sessionActions.find(s); Q_ASSERT( it != sessionActions.end() ); (*it)->setCheckable(true); (*it)->setChecked(true); for(it = sessionActions.begin(); it != sessionActions.end(); ++it) { if(it.key() != s) (*it)->setCheckable(false); } return result; } void loadSessionFromAction( QAction* a ) { foreach( Session* s, sessionActions.keys() ) { if( s->id() == QUuid( a->data().toString() ) && s != activeSession ) { loadSessionExternally( s ); break; } } } void addSession( Session* s ) { if (Core::self()->setupFlags() & Core::NoUi) { sessionActions[s] = 0; return; } QAction* a = new QAction( grp ); a->setText( s->description() ); a->setCheckable( false ); a->setData( s->id().toString() ); sessionActions[s] = a; q->actionCollection()->addAction( "session_"+s->id().toString(), a ); q->unplugActionList( "available_sessions" ); q->plugActionList( "available_sessions", grp->actions() ); connect( s, &Session::sessionUpdated, this, &SessionControllerPrivate::sessionUpdated ); sessionUpdated( s ); } SessionController* q; QHash sessionActions; ISession* activeSession; QActionGroup* grp; ISessionLock::Ptr sessionLock; static QString sessionBaseDirectory() { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +'/'+ qApp->applicationName() + "/sessions/"; } QString ownSessionDirectory() const { Q_ASSERT(activeSession); return q->sessionDirectory( activeSession->id().toString() ); } private slots: void sessionUpdated( KDevelop::ISession* s ) { sessionActions[static_cast( s )]->setText( KStringHandler::rsqueeze(s->description()) ); } }; SessionController::SessionController( QObject *parent ) : QObject( parent ), d(new SessionControllerPrivate(this)) { setObjectName("SessionController"); setComponentName(QStringLiteral("kdevsession"), QStringLiteral("KDevSession")); setXMLFile("kdevsessionui.rc"); - QDBusConnection::sessionBus().registerObject( "/kdevelop/SessionController", + QDBusConnection::sessionBus().registerObject( "/org/kdevelop/SessionController", this, QDBusConnection::ExportScriptableSlots ); if (Core::self()->setupFlags() & Core::NoUi) return; QAction* action = actionCollection()->addAction( "new_session", this, SLOT(newSession()) ); action->setText( i18nc("@action:inmenu", "Start New Session") ); action->setToolTip( i18nc("@info:tooltip", "Start a new KDevelop instance with an empty session") ); action->setIcon(QIcon::fromTheme("window-new")); action = actionCollection()->addAction( "rename_session", this, SLOT(renameSession()) ); action->setText( i18n("Rename Current Session...") ); action->setIcon(QIcon::fromTheme("edit-rename")); action = actionCollection()->addAction( "delete_session", this, SLOT(deleteCurrentSession()) ); action->setText( i18n("Delete Current Session...") ); action->setIcon(QIcon::fromTheme("edit-delete")); action = actionCollection()->addAction( "quit", this, SIGNAL(quitSession()) ); action->setText( i18n("Quit") ); action->setMenuRole( QAction::NoRole ); // OSX: prevent QT from hiding this due to conflict with 'Quit KDevelop...' actionCollection()->setDefaultShortcut( action, Qt::CTRL | Qt::Key_Q ); action->setIcon(QIcon::fromTheme("application-exit")); #if 0 action = actionCollection()->addAction( "configure_sessions", this, SLOT(configureSessions()) ); action->setText( i18n("Configure Sessions...") ); action->setToolTip( i18n("Create/Delete/Activate Sessions") ); action->setWhatsThis( i18n( "Shows a dialog to Create/Delete Sessions and set a new active session." ) ); #endif d->grp = new QActionGroup( this ); connect( d->grp, &QActionGroup::triggered, this, [&] (QAction* a) { d->loadSessionFromAction(a); } ); } SessionController::~SessionController() { delete d; } void SessionController::startNewSession() { d->newSession(); } void SessionController::cleanup() { if (d->activeSession) { Q_ASSERT(d->activeSession->id().toString() == d->sessionLock->id()); if (d->activeSession->isTemporary()) { deleteSessionFromDisk(d->sessionLock); } d->activeSession = 0; } d->sessionLock.clear(); qDeleteAll(d->sessionActions); d->sessionActions.clear(); } void SessionController::initialize( const QString& session ) { QDir sessiondir( SessionControllerPrivate::sessionBaseDirectory() ); foreach( const QString& s, sessiondir.entryList( QDir::AllDirs | QDir::NoDotAndDotDot ) ) { QUuid id( s ); if( id.isNull() ) continue; // Only create sessions for directories that represent proper uuid's Session* ses = new Session( id.toString(), this ); //Delete sessions that have no name and are empty if( ses->containedProjects().isEmpty() && ses->name().isEmpty() && (session.isEmpty() || (ses->id().toString() != session && ses->name() != session)) ) { TryLockSessionResult result = tryLockSession(s); if (result.lock) { deleteSessionFromDisk(result.lock); } delete ses; } else { d->addSession( ses ); } } loadDefaultSession( session ); } ISession* SessionController::activeSession() const { return d->activeSession; } ISessionLock::Ptr SessionController::activeSessionLock() const { return d->sessionLock; } void SessionController::loadSession( const QString& nameOrId ) { d->loadSessionExternally( session( nameOrId ) ); } QList SessionController::sessionNames() const { QStringList l; foreach( const Session* s, d->sessionActions.keys() ) { l << s->name(); } return l; } QList< const KDevelop::Session* > SessionController::sessions() const { QList< const KDevelop::Session* > ret; foreach( const Session* s, d->sessionActions.keys() ) { ret << s; } return ret; } Session* SessionController::createSession( const QString& name ) { Session* s; if(name.startsWith('{')) { s = new Session( QUuid(name).toString() ); }else{ qsrand(QDateTime::currentDateTime().toTime_t()); s = new Session( QUuid::createUuid().toString() ); s->setName( name ); } d->addSession( s ); return s; } void SessionController::deleteSession( const ISessionLock::Ptr& lock ) { Session* s = session(lock->id()); QHash::iterator it = d->sessionActions.find(s); Q_ASSERT( it != d->sessionActions.end() ); unplugActionList( "available_sessions" ); actionCollection()->removeAction(*it); if (d->grp) { // happens in unit tests d->grp->removeAction(*it); plugActionList( "available_sessions", d->grp->actions() ); } if (s == d->activeSession) { d->activeSession = nullptr; } deleteSessionFromDisk(lock); emit sessionDeleted( s->id().toString() ); d->sessionActions.remove(s); delete s; } void SessionController::deleteSessionFromDisk( const ISessionLock::Ptr& lock ) { qCDebug(SHELL) << "Deleting session:" << lock->id(); QDir(sessionDirectory(lock->id())).removeRecursively(); ItemRepositoryRegistry::deleteRepositoryFromDisk( lock ); } void SessionController::loadDefaultSession( const QString& session ) { QString load = session; if (load.isEmpty()) { KConfigGroup grp = KSharedConfig::openConfig()->group( cfgSessionGroup() ); load = grp.readEntry( cfgActiveSessionEntry(), "default" ); } // Iteratively try to load the session, asking user what to do in case of failure // If showForceOpenDialog() returns empty string, stop trying Session* s = 0; do { s = this->session( load ); if( !s ) { s = createSession( load ); } TryLockSessionResult result = d->activateSession( s ); if( result.lock ) { Q_ASSERT(d->activeSession == s); Q_ASSERT(d->sessionLock = result.lock); break; } load = handleLockedSession( s->name(), s->id().toString(), result.runInfo ); } while( !load.isEmpty() ); } Session* SessionController::session( const QString& nameOrId ) const { Session* ret = d->findSessionForName( nameOrId ); if(ret) return ret; return d->findSessionForId( nameOrId ); } QString SessionController::cloneSession( const QString& nameOrid ) { Session* origSession = session( nameOrid ); qsrand(QDateTime::currentDateTime().toTime_t()); QUuid id = QUuid::createUuid(); auto copyJob = KIO::copy(QUrl::fromLocalFile(sessionDirectory(origSession->id().toString())), QUrl::fromLocalFile(sessionDirectory( id.toString()))); KJobWidgets::setWindow(copyJob, Core::self()->uiController()->activeMainWindow()); copyJob->exec(); Session* newSession = new Session( id.toString() ); newSession->setName( i18n( "Copy of %1", origSession->name() ) ); d->addSession(newSession); return newSession->name(); } void SessionController::plugActions() { unplugActionList( "available_sessions" ); plugActionList( "available_sessions", d->grp->actions() ); } QString SessionController::cfgSessionGroup() { return "Sessions"; } QString SessionController::cfgActiveSessionEntry() { return "Active Session ID"; } QList< SessionInfo > SessionController::availableSessionInfo() { QList< SessionInfo > available; foreach( const QString& sessionId, QDir( SessionControllerPrivate::sessionBaseDirectory() ).entryList( QDir::AllDirs ) ) { if( !QUuid( sessionId ).isNull() ) { available << Session::parse( sessionId ); } } return available; } QString SessionController::sessionDirectory(const QString& sessionId) { return SessionControllerPrivate::sessionBaseDirectory() + sessionId; } TryLockSessionResult SessionController::tryLockSession(const QString& id) { return SessionLock::tryLockSession(id, true); } bool SessionController::isSessionRunning(const QString& id) { return sessionRunInfo(id).isRunning; } SessionRunInfo SessionController::sessionRunInfo(const QString& id) { return SessionLock::tryLockSession(id, false).runInfo; } QString SessionController::showSessionChooserDialog(QString headerText, bool onlyRunning) { ///FIXME: move this code into sessiondialog.cpp QListView* view = new QListView; QLineEdit* filter = new QLineEdit; filter->setClearButtonEnabled( true ); filter->setPlaceholderText(i18n("Search")); QStandardItemModel* model = new QStandardItemModel(view); QSortFilterProxyModel *proxy = new QSortFilterProxyModel(model); proxy->setSourceModel(model); proxy->setFilterKeyColumn( 1 ); proxy->setFilterCaseSensitivity( Qt::CaseInsensitive ); connect(filter, &QLineEdit::textChanged, proxy, &QSortFilterProxyModel::setFilterFixedString); SessionChooserDialog dialog(view, proxy, filter); view->setEditTriggers(QAbstractItemView::NoEditTriggers); QVBoxLayout layout(dialog.mainWidget()); if(!headerText.isEmpty()) { QLabel* heading = new QLabel(headerText); QFont font = heading->font(); font.setBold(true); heading->setFont(font); layout.addWidget(heading); } model->setColumnCount(3); model->setHeaderData(0, Qt::Horizontal,i18n("Identity")); model->setHeaderData(1, Qt::Horizontal, i18n("Contents")); model->setHeaderData(2, Qt::Horizontal,i18n("State")); view->setModel(proxy); view->setModelColumn(1); QHBoxLayout* filterLayout = new QHBoxLayout(); filterLayout->addWidget(new QLabel(i18n("Filter:"))); filterLayout->addWidget(filter); layout.addLayout(filterLayout); layout.addWidget(view); filter->setFocus(); int row = 0; QString defaultSession = KSharedConfig::openConfig()->group( cfgSessionGroup() ).readEntry( cfgActiveSessionEntry(), "default" ); foreach(const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfo()) { if ( si.name.isEmpty() && si.projects.isEmpty() ) { continue; } bool running = KDevelop::SessionController::isSessionRunning(si.uuid.toString()); if(onlyRunning && !running) continue; model->setItem(row, 0, new QStandardItem(si.uuid.toString())); model->setItem(row, 1, new QStandardItem(si.description)); model->setItem(row, 2, new QStandardItem); ++row; } model->sort(1); if(!onlyRunning) { model->setItem(row, 0, new QStandardItem); model->setItem(row, 1, new QStandardItem(QIcon::fromTheme("window-new"), i18n("Create New Session"))); } dialog.updateState(); dialog.mainWidget()->layout()->setContentsMargins(0,0,0,0); const QModelIndex defaultSessionIndex = model->match(model->index(0, 0), Qt::DisplayRole, defaultSession, 1, Qt::MatchExactly).value(0); view->selectionModel()->setCurrentIndex(proxy->mapFromSource(defaultSessionIndex), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); view->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); ///@todo We need a way to get a proper size-hint from the view, but unfortunately, that only seems possible after the view was shown. dialog.resize(QSize(900, 600)); if(dialog.exec() != QDialog::Accepted) { return QString(); } QModelIndex selected = view->selectionModel()->currentIndex(); if (!selected.isValid()) return QString(); const QString selectedSessionId = selected.sibling(selected.row(), 0).data().toString(); if (selectedSessionId.isEmpty()) { // "Create New Session" item selected, return a fresh UUID qsrand(QDateTime::currentDateTime().toTime_t()); return QUuid::createUuid().toString(); } return selectedSessionId; } QString SessionController::handleLockedSession( const QString& sessionName, const QString& sessionId, const SessionRunInfo& runInfo ) { return SessionLock::handleLockedSession(sessionName, sessionId, runInfo); } QString SessionController::sessionDir() { if( !activeSession() ) return QString(); return d->ownSessionDirectory(); } QString SessionController::sessionName() { if(!activeSession()) return QString(); return activeSession()->description(); } } #include "sessioncontroller.moc" #include "moc_sessioncontroller.cpp" diff --git a/shell/sessioncontroller.h b/shell/sessioncontroller.h index f256a728bf..198000c261 100644 --- a/shell/sessioncontroller.h +++ b/shell/sessioncontroller.h @@ -1,178 +1,180 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat Copyright 2013 Milian Wolff 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. */ #ifndef KDEVPLATFORM_SESSIONCONTROLLER_H #define KDEVPLATFORM_SESSIONCONTROLLER_H #include "shellexport.h" #include "session.h" #include #include #include namespace KDevelop { struct SessionRunInfo { SessionRunInfo() : isRunning(false) , holderPid(-1) {} bool operator==(const SessionRunInfo& o) const { return isRunning == o.isRunning && holderPid == o.holderPid && holderApp == o.holderApp && holderHostname == o.holderHostname; } bool operator!=(const SessionRunInfo& o) const { return !(operator==(o)); } // if this is true, this session is currently running in an external process bool isRunning; // if the session is running, this contains the PID of its process qint64 holderPid; // if the session is running, this contains the name of its process QString holderApp; // if the session is running, this contains the host name where the process runs QString holderHostname; }; struct TryLockSessionResult { TryLockSessionResult(const ISessionLock::Ptr& _lock) : lock(_lock) {} TryLockSessionResult(const SessionRunInfo& _runInfo) : runInfo(_runInfo) {} // if this is non-null then the session was locked ISessionLock::Ptr lock; // otherwise this contains information about who is locking the session SessionRunInfo runInfo; }; class KDEVPLATFORMSHELL_EXPORT SessionController : public QObject, public KXMLGUIClient { Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kdevelop.SessionController") + public: explicit SessionController( QObject *parent = 0 ); virtual ~SessionController(); void initialize( const QString& session ); void cleanup(); /// Returns whether the given session can be locked (i. e., is not locked currently). /// @param doLocking whether to really lock the session or just "dry-run" the locking process static TryLockSessionResult tryLockSession(const QString& id); /** * @return true when the given session is currently running, false otherwise */ static bool isSessionRunning(const QString& id); /** * @return information about whether the session @p id is running */ static SessionRunInfo sessionRunInfo(const QString& id); /// The application should call this on startup to tell the /// session-controller about the received arguments. /// Some of them may need to be passed to newly opened sessions. static void setArguments(int argc, char** argv); ///Finds a session by its name or by its UUID Session* session( const QString& nameOrId ) const; virtual ISession* activeSession() const; ISessionLock::Ptr activeSessionLock() const; QList sessionNames() const; Session* createSession( const QString& name ); QList sessions() const; void loadDefaultSession( const QString& session ); void startNewSession(); void loadSession( const QString& nameOrId ); void deleteSession( const ISessionLock::Ptr& lock ); static void deleteSessionFromDisk( const ISessionLock::Ptr& lock ); QString cloneSession( const QString& nameOrid ); /** * Path to session directory for the session with the given @p sessionId. */ static QString sessionDirectory( const QString& sessionId ); static QString cfgSessionGroup(); static QString cfgActiveSessionEntry(); static QList< SessionInfo > availableSessionInfo(); /** * Shows a dialog where the user can choose the session * @param headerText an additional text that will be shown at the top in a label * @param onlyRunning whether only currently running sessions should be shown * @return UUID on success, empty string in any other case */ static QString showSessionChooserDialog(QString headerText = QString(), bool onlyRunning = false); /// Should be called if session to be opened is locked. /// It attempts to bring existing instance's window up via a DBus call; if that succeeds, empty string is returned. /// Otherwise (if the app did not respond) it shows a dialog where the user may choose /// 1) to force-remove the lockfile and continue, /// 2) to select another session via \ref showSessionChooserDialog, /// 3) to quit the current (starting-up) instance. /// @param sessionName session name (for the message) /// @param sessionId current session GUID (to return if user chooses force-removal) /// @param runInfo the run information about the session /// @return new session GUID to try or an empty string if application startup shall be aborted static QString handleLockedSession( const QString& sessionName, const QString& currentSessionId, const SessionRunInfo& runInfo ); void plugActions(); void emitQuitSession() { emit quitSession(); } public Q_SLOTS: // Returns the pretty name of the currently active session (used in the shell integration) virtual Q_SCRIPTABLE QString sessionName(); // Returns the directory associated to the active session (used in the shell integration) virtual Q_SCRIPTABLE QString sessionDir(); Q_SIGNALS: void sessionLoaded( ISession* ); void sessionDeleted( const QString& id); void quitSession(); private: Q_PRIVATE_SLOT( d, void newSession() ) Q_PRIVATE_SLOT( d, void configureSessions() ) Q_PRIVATE_SLOT( d, void deleteCurrentSession() ) Q_PRIVATE_SLOT( d, void renameSession() ) Q_PRIVATE_SLOT( d, void loadSessionFromAction( QAction* ) ) class SessionControllerPrivate* const d; }; } #endif diff --git a/shell/statusbar.cpp b/shell/statusbar.cpp index 43642f487f..96563f84fa 100644 --- a/shell/statusbar.cpp +++ b/shell/statusbar.cpp @@ -1,257 +1,265 @@ /* This file is part of the KDE project Copyright 2007 Hamish Rodda 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 "statusbar.h" #include "progresswidget/statusbarprogresswidget.h" #include "progresswidget/progressmanager.h" #include "progresswidget/progressdialog.h" #include #include #include #include #include #include #include #include #include #include #include "plugincontroller.h" #include "core.h" namespace KDevelop { StatusBar::StatusBar(QWidget* parent) : QStatusBar(parent) , m_timer(new QTimer(this)) , m_currentView(0) , m_errorRemovalMapper(new QSignalMapper(this)) { +#ifdef Q_OS_MAC + /* At time of writing this is only required for OSX and only has effect on OSX. + ifdef for robustness to future platform dependent theme/widget changes + https://phabricator.kde.org/D656 + */ + setStyleSheet("QStatusBar{background:transparent;}"); +#endif + m_timer->setSingleShot(true); connect(m_timer, &QTimer::timeout, this, &StatusBar::slotTimeout); connect(Core::self()->pluginController(), &IPluginController::pluginLoaded, this, &StatusBar::pluginLoaded); foreach (IPlugin* plugin, Core::self()->pluginControllerInternal()->allPluginsForExtension("IStatus")) registerStatus(plugin); registerStatus(Core::self()->languageController()->backgroundParser()); connect(m_errorRemovalMapper, static_cast(&QSignalMapper::mapped), this, &StatusBar::removeError); m_progressController = Core::self()->progressController(); m_progressDialog = new ProgressDialog(this, parent); // construct this first, then progressWidget m_progressDialog->setVisible(false); m_progressWidget = new StatusbarProgressWidget(m_progressDialog, this); addPermanentWidget(m_progressWidget, 0); } void StatusBar::removeError(QWidget* w) { removeWidget(w); w->deleteLater(); } void StatusBar::viewChanged(Sublime::View* view) { if (m_currentView) m_currentView->disconnect(this); m_currentView = view; if (view) { connect(view, &Sublime::View::statusChanged, this, &StatusBar::viewStatusChanged); QStatusBar::showMessage(view->viewStatus(), 0); } } void StatusBar::viewStatusChanged(Sublime::View* view) { QStatusBar::showMessage(view->viewStatus(), 0); } void StatusBar::pluginLoaded(IPlugin* plugin) { if (qobject_cast(plugin)) registerStatus(plugin); } void StatusBar::registerStatus(QObject* status) { Q_ASSERT(qobject_cast(status)); // can't convert this to new signal slot syntax, IStatus is not a QObject connect(status, SIGNAL(clearMessage(KDevelop::IStatus*)), SLOT(clearMessage(KDevelop::IStatus*)), Qt::QueuedConnection); connect(status, SIGNAL(showMessage(KDevelop::IStatus*,QString,int)), SLOT(showMessage(KDevelop::IStatus*,QString,int)), Qt::QueuedConnection); connect(status, SIGNAL(hideProgress(KDevelop::IStatus*)), SLOT(hideProgress(KDevelop::IStatus*)), Qt::QueuedConnection); connect(status, SIGNAL(showProgress(KDevelop::IStatus*,int,int,int)), SLOT(showProgress(KDevelop::IStatus*,int,int,int)), Qt::QueuedConnection); // Don't try to connect when the status object doesn't provide an error message signal (ie. avoid warning) if (status->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("showErrorMessage(QString,int)")) != -1) { connect(status, SIGNAL(showErrorMessage(QString,int)), SLOT(showErrorMessage(QString,int)), Qt::QueuedConnection); } } QWidget* errorMessage(QWidget* parent, const QString& text) { KSqueezedTextLabel* label = new KSqueezedTextLabel(parent); KStatefulBrush red(KColorScheme::Window, KColorScheme::NegativeText); QPalette pal = label->palette(); pal.setBrush(QPalette::WindowText, red.brush(label)); label->setPalette(pal); label->setAlignment(Qt::AlignRight); label->setText(text); label->setToolTip(text); return label; } QTimer* StatusBar::errorTimeout(QWidget* error, int timeout) { QTimer* timer = new QTimer(error); timer->setSingleShot(true); timer->setInterval(1000*timeout); m_errorRemovalMapper->setMapping(timer, error); connect(timer, &QTimer::timeout, m_errorRemovalMapper, static_cast(&QSignalMapper::map)); return timer; } void StatusBar::showErrorMessage(const QString& message, int timeout) { QWidget* error = errorMessage(this, message); QTimer* timer = errorTimeout(error, timeout); addWidget(error); timer->start(); // triggers removeError() } void StatusBar::slotTimeout() { QMutableMapIterator it = m_messages; while (it.hasNext()) { it.next(); if (it.value().timeout) { it.value().timeout -= m_timer->interval(); if (it.value().timeout == 0) it.remove(); } } updateMessage(); } void StatusBar::updateMessage() { if (m_timer->isActive()) { m_timer->stop(); m_timer->setInterval(m_time.elapsed()); slotTimeout(); } QString ret; int timeout = 0; foreach (const Message& m, m_messages) { if (!ret.isEmpty()) ret += "; "; ret += m.text; if (timeout) timeout = qMin(timeout, m.timeout); else timeout = m.timeout; } if (!ret.isEmpty()) QStatusBar::showMessage(ret); else QStatusBar::clearMessage(); if (timeout) { m_time.start(); m_timer->start(timeout); } } void StatusBar::clearMessage( IStatus* status ) { if (m_messages.contains(status)) { m_messages.remove(status); updateMessage(); } } void StatusBar::showMessage( IStatus* status, const QString & message, int timeout) { if ( m_progressItems.contains(status) ) { ProgressItem* i = m_progressItems[status]; i->setStatus(message); } else { Message m; m.text = message; m.timeout = timeout; m_messages.insert(status, m); updateMessage(); } } void StatusBar::hideProgress( IStatus* status ) { if (m_progressItems.contains(status)) { m_progressItems[status]->setComplete(); m_progressItems.remove(status); } } void StatusBar::showProgress( IStatus* status, int minimum, int maximum, int value) { if (!m_progressItems.contains(status)) { bool canBeCanceled = false; m_progressItems[status] = m_progressController->createProgressItem( ProgressManager::getUniqueID(), status->statusName(), QString(), canBeCanceled);; } ProgressItem* i = m_progressItems[status]; m_progressWidget->raise(); m_progressDialog->raise(); if( minimum == 0 && maximum == 0 ) { i->setUsesBusyIndicator( true ); } else { i->setUsesBusyIndicator( false ); i->setProgress( 100*value/maximum ); } } } diff --git a/util/kdevplatform_shell_environment.sh b/util/kdevplatform_shell_environment.sh index 9120cf2b11..e4e0c396e8 100755 --- a/util/kdevplatform_shell_environment.sh +++ b/util/kdevplatform_shell_environment.sh @@ -1,808 +1,808 @@ #!/bin/bash # This file is part of KDevelop # Copyright 2011 David Nolden # # 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. if [ -e ~/.bashrc ]; then # Since this runs as a replacement for the init-file, we need to chain in the 'real' bash-rc source ~/.bashrc fi if ! [ "$APPLICATION_HOST" ]; then export APPLICATION_HOST=$(hostname) fi if ! [ "$KDEV_SHELL_ENVIRONMENT_ID" ]; then export KDEV_SHELL_ENVIRONMENT_ID="default" fi if ! [ "$KDEV_DBUS_ID" ]; then echo "The required environment variable KDEV_DBUS_ID is not set. This variable defines the dbus id of the application instance instance which is supposed to be attached." exit 5 fi # Eventually, if we are forwarding to another host, and kdevplatform_shell_environment.sh # has been located through "which kdevplatform_shell_environment.sh", then we need to update KDEV_BASEDIR. if ! [ -e "$KDEV_BASEDIR/kdevplatform_shell_environment.sh" ]; then KDEV_BASEDIR=$(dirname $(which kdevplatform_shell_environment.sh)) fi if ! [ -e "$KDEV_BASEDIR/kdev_dbus_socket_transformer" ]; then echo "The $KDEV_BASEDIR/kdev_dbus_socket_transformer utility is missing, controlling the application across ssh is not possible" fi # Takes a list of tools, and prints a warning of one of them is not available in the path function checkToolsInPath { for TOOL in $@; do if ! [ "$(which $TOOL 2> /dev/null)" ]; then echo "The utility $TOOL is not in your path, the shell integration will not work properly." fi done } # Check if all required tools are there (on the host machine) checkToolsInPath sed qdbus ls cut dirname mktemp basename readlink hostname if ! [ "$KDEV_SSH_FORWARD_CHAIN" ]; then # Check for additional utilities that are required on the client machine checkToolsInPath kioclient fi # Queries the session name from the running application instance function getSessionName { - echo "$(qdbus $KDEV_DBUS_ID /kdevelop/SessionController org.kdevelop.kdevelop.KDevelop.SessionController.sessionName)" + echo "$(qdbus $KDEV_DBUS_ID /org/kdevelop/SessionController org.kdevelop.SessionController.sessionName)" } function getSessionDir { - echo "$(qdbus $KDEV_DBUS_ID /kdevelop/SessionController org.kdevelop.kdevelop.KDevelop.SessionController.sessionDir)" + echo "$(qdbus $KDEV_DBUS_ID /org/kdevelop/SessionController org.kdevelop.SessionController.sessionDir)" } function getCurrentShellEnvPath { local ENV_ID=$KDEV_SHELL_ENVIRONMENT_ID if [ "$1" ]; then ENV_ID=$1 fi echo "$(getSessionDir)/${ENV_ID}.sh" } function help! { echo "You are controlling the $APPLICATION session '$(getSessionName)'" echo "" if [ "$1" == "" ]; then echo "Standard commands:" echo "raise! - Raise the window." echo "sync! - Synchronize the working directory with the currently open document. See \"help! sync\"" echo "open! [file] ... - Open the file(s) within the attached application. See \"help! open\"" echo "eopen! [file] ... - Open the file(s) within an external application using kde-open." echo "create! [file] [[text]] - Create and open a new file." echo "search! [pattern] [[locations]] ... - Search for the given pattern here or at the optionally given location(s)." echo "dsearch! [pattern] [[locations]] ... - Same as search, but starts the search instantly instead of showing the dialog (using previous settings)." echo "ssh! [ssh arguments] - Connect to a remote host via ssh, keeping the control-connection alive. See \"help! remote\"" echo "" echo "help! - Show help." echo "help! open - Show extended help about file opening commands." echo "help! sync - Show extended help about path synchronization commands." echo "help! remote - Show extended help about remote shell-integration through ssh." echo "help! env - Show extended help about the environment." echo "" echo "Most commands can be abbreviated by the first character(s), eg. r! instead of raise!, and se! instead of search!." fi if [ "$1" == "open" ]; then echo "Extended opening:" echo "The open! command can also be used to open files in specific tool-view configurations, by adding split-separators:" echo "- Files around the / separator will be arranged horizontally by split-view." echo "- Files around the - separator will be arranged vertically by split-view." echo "- Parens [ ... ] can be used to disambiguate the hierarchy (there must be spaces between filename and paren)." echo "- If a file is missing around a separator, the currently active view is inserted into the position." echo "" echo "Examples:" echo "open! file1 / file2 - The active view is split horizontally." echo " file1 is opened in the left view, and file2 in the right view." echo "open! file1 / [ file2 - file3 ] - The active view is split horizontally, and the right split-view is split vertically." echo " file1 is opened in the left view, file2 in the right upper view, and file3 in the right lower view." echo "open! / file1 - The active view is split horizontally." echo " - The active document is kept in the left split-view, and file1 is opened in the right split-view." echo "" echo "Short forms: o! = open!, eo! = eopen!, c! = create!" fi if [ "$1" == "sync" ]; then echo "Extended syncing:" echo "sync! [[project-name]] - If no project-name is given, then the sync! command synchronizes to the currently active document." echo " If no document is active, then it synchronizes to the currently selected item in the project tree-view." echo " If a case-insensitive project name prefix is given, then it synchronizes to the base folder of the matching project." echo "syncsel! - Synchronizes to the currently selected item in the project tree-view, independently of the active document." echo "project! [[project-name]] - Map from a path within the build directory to the corresponding path in the source directory." echo " If we're already in the source directory, map to the root of the surrounding project." echo "bdir! [[project-name]] - Map from a path within the source directory to the corresponding path in the build directory." echo " If we're already in the build directory, map to the root of the build directory." echo "" echo "Short forms: s! = sync!, ss! = syncsel!, p! = project!, b! = bdir!" fi if [ "$1" == "remote" ]; then echo "Extended remote commands:" echo "ssh! [ssh arguments] - Connect to a remote host via ssh, keeping the control-connection alive." echo " - The whole dbus environment is forwarded, KDevelop needs to be installed on both sides." echo "ssw! [ssh arguments] - Like ssh!, but preserves the current working directory." echo "exec! [cmd] [args] [file] . .. - Execute the given command on the client machine, referencing any number of local files on the host machine." echo " - The file paths will be re-encoded as fish:// urls if required." echo "cexec! [cmd] [args] [file] . .. - Execute the given command on the client machine, referencing any number of local files on the host machine." echo " - The files will be COPIED to the client machine if required." echo "copytohost! [client path] [host path] - Copy a file/directory through the fish protocol from the client machine th the host machine." echo "copytoclient! [host path] [client path]- Copy a file/directory through the fish protocol from the host machine to the client machine." echo "" echo "Short forms: e! = exec!, ce! = cexec!, cth! = copytohost!, ctc! = copytoclient!" fi if [ "$1" == "env" ]; then echo "Environment management:" echo "The environment can be used to store session-specific macros and generally manipulate the shell environment" echo "for embedded shell sessions. The environment is sourced into the shell when the shell is initialized, and" echo "whenever setenv! is called." echo "" echo "env! - List all available shell environment-ids for this session." echo "setenv! [id] - Set the shell environmnet-id for this session to the given id, or update the current one." echo "editenv! [id] - Edit the current shell environment or the one with the optionally given id." echo "showenv! [id] - Show the current shell environment or the one with the optionally given id." echo "" echo "Short forms: sev! = setenv!, ee! = editenv!, shenv! = showenv!" fi echo "" } # Short versions of the commands: function r! { raise! $@ } function s! { sync! $@ } function ss! { syncsel! } function syncsel! { sync! '[selection]' } function p! { if [ "$@" ]; then s! $@ fi project! } function b! { if [ "$@" ]; then s! $@ fi bdir! } function o! { open! $@ } function eo! { eopen! $@ } function e! { exec! $@ } function ce! { cexec! $@ } function c! { create! $@ } function se! { search! $@ } function ds! { dsearch! $@ } function h! { help! $@ } function cth! { copytohost! $@ } function ctc! { copytoclient! $@ } function sev! { setenv! $@ } function ee! { editenv! $@ } function shev! { showenv! $@ } # Internals: # Opens a document in internally in the application function openDocument { RESULT=$(qdbus $KDEV_DBUS_ID /org/kdevelop/DocumentController org.kdevelop.DocumentController.openDocumentSimple $1) if ! [ "$RESULT" == "true" ]; then echo "Failed to open $1" fi } # Opens a document in internally in the application function openDocuments { RESULT=$(qdbus $KDEV_DBUS_ID /org/kdevelop/DocumentController org.kdevelop.DocumentController.openDocumentsSimple "(" $1 ")") if ! [ "$RESULT" == "true" ]; then echo "Failed to open $1" fi } # Executes a command on the client machine using the custom-script integration. # First argument: The full command. Second argument: The working directory. function executeInApp { local CMD="$1" local WD=$2 if ! [ "$WD" ]; then WD=$(pwd) fi RESULT=$(qdbus $KDEV_DBUS_ID /org/kdevelop/ExternalScriptPlugin org.kdevelop.ExternalScriptPlugin.executeCommand "$CMD" "$WD") if ! [ "$RESULT" == "true" ]; then echo "Execution failed" fi } # First argument: The full command. Second argument: The working directory. # Executes the command silently and synchronously, and returns the output function executeInAppSync { local CMD=$1 local WD=$2 if ! [ "$WD" ]; then WD=$(pwd) fi RESULT=$(qdbus $KDEV_DBUS_ID /org/kdevelop/ExternalScriptPlugin org.kdevelop.ExternalScriptPlugin.executeCommandSync "$CMD" "$WD") echo "$RESULT" } # Getter functions: function getActiveDocument { qdbus $KDEV_DBUS_ID /org/kdevelop/DocumentController org.kdevelop.DocumentController.activeDocumentPath $@ } function getOpenDocuments { qdbus $KDEV_DBUS_ID /org/kdevelop/DocumentController org.kdevelop.DocumentController.activeDocumentPaths } function raise! { qdbus $KDEV_DBUS_ID /kdevelop/MainWindow org.kdevelop.MainWindow.ensureVisible } function bdir! { TARG=$(qdbus $KDEV_DBUS_ID /org/kdevelop/ProjectController org.kdevelop.ProjectController.mapSourceBuild "$(pwd)" false) if [ "$TARG" ]; then cd $TARG else echo "Got no path" fi } function project! { TARG=$(qdbus $KDEV_DBUS_ID /org/kdevelop/ProjectController org.kdevelop.ProjectController.mapSourceBuild "$(pwd)" true) if [ "$TARG" ]; then cd $TARG else echo "Got no path" fi } # Main functions: function raise! { qdbus $KDEV_DBUS_ID /kdevelop/MainWindow org.kdevelop.MainWindow.ensureVisible } function sync! { local P=$(getActiveDocument $@) if [ "$P" ]; then if [[ "$P" == fish://* ]]; then # This regular expression filters the user@host:port out of fish:///user@host:port/path/... LOGIN=$(echo $P | sed "s/fish\:\/\/*\([^\/]*\)\(\/.*\)/\1/") P_ON_HOST=$(echo $P | sed "s/fish\:\/\/*\([^\/]*\)\(\/.*\)/\2/") if [ "$KDEV_SSH_FORWARD_CHAIN" == "$LOGIN" ]; then P="$P_ON_HOST" else if [ "$KDEV_SSH_FORWARD_CHAIN" == "" ]; then # Try to ssh to the host machine # We need to split away the optional ":port" suffix, because the ssh command does not allow that syntax HOST=$(echo $LOGIN | cut --delimiter=':' -f 1) CMD="ssh!" if [[ "$LOGIN" == *:* ]]; then # If there is a port, extract it PORT=$(echo $LOGIN | cut --delimiter=':' -f 2) CMD="$CMD -p $PORT" fi CMD="$CMD $HOST" # Execute the ssh command echo "Executing $CMD" KDEV_WORKING_DIR="$(dirname $P_ON_HOST)" $CMD return else echo "Cannot synchronize the working directory, because the host-names do not match (app: $LOGIN, shell: $KDEV_SSH_FORWARD_CHAIN)" return fi fi elif [[ "$P" == file://* ]]; then P=$(echo $P | sed 's$^file://$$') elif [ "$KDEV_SSH_FORWARD_CHAIN" ]; then # This session is being forwarded to another machine, but the current document is not # However, we won't complain, because it's possible that the machines share the same file-system if [ $(isEqualFileOnHostAndClient $P) != "yes" ]; then echo "Cannot synchronize the working directory, because the file systems do not match" return fi fi [ -d "$P" ] || P=$(dirname "$P") cd "$P" else echo "Got no path" fi } # Take a path, and returns "yes" if the equal file is available on the host and the client # The check is performed by comparing inode-numbers function isEqualFileOnHostAndClient { function trimWhiteSpace() { echo $1 } FILE=$1 INODE_HOST=$(trimWhiteSpace $(ls --color=never -i $FILE | cut -d' ' -f1)) INODE_CLIENT=$(trimWhiteSpace $(executeInAppSync "ls --color=never -i $FILE | cut -d' ' -f1" "$(dirname $FILE)")) if [ "$INODE_HOST" == "$INODE_CLIENT" ]; then echo "yes" else echo "" fi } # Takes a relative file, returns an absolute file/url that should be valid on the client. function mapFileToClient { local RELATIVE_FILE=$1 FILE=$(readlink -f $RELATIVE_FILE) if ! [ -e "$FILE" ]; then # Try opening the file anyway, it might be an url or something else we don't understand here FILE=$RELATIVE_FILE else # We are referencing an absolute file, available on the file-system. if [ "$KDEV_SSH_FORWARD_CHAIN" ]; then # If we are forwarding, map it to the client somehow. if [ "$(isEqualFileOnHostAndClient "$FILE")" != "yes" ]; then # We can eventually map the file using the fish protocol FISH_HOST=$KDEV_SSH_FORWARD_CHAIN if [[ "$FISH_HOST" == *\,* ]]; then # Extracts everything before the first comma FISH_HOST=$(echo $FISH_HOST | sed 's/\([^,]*\),\(.*\)/\1/') echo "ssh chain is too long: $KDEV_SSH_FORWARD_CHAIN mapping anyway using $FISH_HOST" 1>&2 fi # Theoretically, we can only map through fish if the forward-chains contains no comma, which means that # we forward only once. Try anyway, there might be the same filesystem on the whole forward-chain. FILE="fish://$FISH_HOST$FILE" fi fi fi echo $FILE } function open! { FILES=$@ NEWFILES="" for RELATIVE_FILE in $FILES; do if [ "$RELATIVE_FILE" == "/" ]; then FILE=$RELATIVE_FILE else FILE=$(mapFileToClient $RELATIVE_FILE) fi NEWFILES="$NEWFILES $FILE" done openDocuments "$NEWFILES" } function eopen! { FILES=$@ for RELATIVE_FILE in $FILES; do FILE=$(mapFileToClient $RELATIVE_FILE) executeInApp "kde-open $FILE" done } function exec! { FILES=$@ ARGS="" for RELATIVE_FILE in $FILES; do if [ "$ARGS" == "" ]; then # Do not transform the command-name ARGS=$RELATIVE_FILE else FILE=$(mapFileToClient $RELATIVE_FILE) ARGS=$ARGS" "$FILE fi done echo "Executing: " $ARGS executeInApp "$ARGS" } function copytohost! { executeInApp "kioclient copy $1 $(mapFileToClient $2)" } function copytoclient! { executeInApp "kioclient copy $(mapFileToClient $1) $2" } function cexec! { FILES=$@ ARGS="" PREFIX="" TMP=1 for RELATIVE_FILE in $FILES; do if [ "$ARGS" == "" ]; then # Do not transform the command-name ARGS=$RELATIVE_FILE else FILE=$(mapFileToClient $RELATIVE_FILE) if [[ "$FILE" == fish://* ]]; then # Add a prefix to copy the file into a temporary file # Keep the baseline as suffix, so that applications can easily recognize the mimetype PREFIX+="FILE$TMP=\$(mktemp).$(basename $FILE); kioclient copy $FILE \$FILE$TMP;" # Use the temporary variable instead of the name FILE="\$FILE$TMP" TMP=$(($TMP+1)) fi ARGS=$ARGS" "$FILE fi done echo "Executing: " $ARGS executeInApp "$PREFIX $ARGS" } function create! { FILE=$(readlink -f $1) if ! [ "$FILE" ]; then echo "Error: Bad arguments." return 1 fi if [ -e "$FILE" ]; then echo "The file $FILE already exists" return 2 fi echo $2 > $FILE openDocument $(mapFileToClient $FILE) } function search! { PATTERN=$1 # if ! [ "$PATTERN" ]; then # echo "Error: No pattern given." # return 1 # fi LOCATION=$2 if ! [ "$LOCATION" ]; then LOCATION="." fi LOCATION=$(mapFileToClient $LOCATION) for LOC in $*; do if [ "$LOC" == "$1" ]; then continue; fi if [ "$LOC" == "$2" ]; then continue; fi LOCATION="$LOCATION;$(mapFileToClient $LOC)" done qdbus $KDEV_DBUS_ID /org/kdevelop/GrepViewPlugin org.kdevelop.kdevelop.GrepViewPlugin.startSearch "$PATTERN" "$LOCATION" true } function dsearch! { PATTERN=$1 if ! [ "$PATTERN" ]; then echo "Error: No pattern given." return 1 fi LOCATION=$2 if ! [ "$LOCATION" ]; then LOCATION="." fi LOCATION=$(mapFileToClient $LOCATION) for LOC in $*; do if [ "$LOC" == "$1" ]; then continue; fi if [ "$LOC" == "$2" ]; then continue; fi LOCATION="$LOCATION;$(mapFileToClient $LOC)" done qdbus $KDEV_DBUS_ID /org/kdevelop/GrepViewPlugin org.kdevelop.kdevelop.GrepViewPlugin.startSearch "$PATTERN" "$LOCATION" false } ##### SSH DBUS FORWARDING -------------------------------------------------------------------------------------------------------------------- DBUS_SOCKET_TRANSFORMER=$KDEV_BASEDIR/kdev_dbus_socket_transformer # We need this, to make sure that our forwarding-loops won't get out of control # This configures the shell to kill background jobs when it is terminated shopt -s huponexit export DBUS_ABSTRACT_SOCKET_TARGET_BASE_PATH=/tmp/dbus-forwarded-$USER-$APPLICATION_HOST export DBUS_FORWARDING_TCP_LOCAL_PORT=9000 export DBUS_FORWARDING_TCP_MAX_LOCAL_PORT=10000 export DBUS_ABSTRACT_SOCKET_TARGET_INDEX=1 export DBUS_ABSTRACT_SOCKET_MAX_TARGET_INDEX=1000 function getPortFromSSHCommand { # The port is given to ssh exclusively in the format "-p PORT" # This regular expression extracts the "4821" from "ssh -q bla1 -p 4821 bla2" local ARGS=$@ local RET=$(echo "$@" | sed "s/.*-p \+\([0-9]*\).*/\1/") if [ "$ARGS" == "$RET" ]; then # There was no match echo "" else echo ":$RET" fi } function getLoginFromSSHCommand { # The login name can be given to ssh in the format "-l NAME" # This regular expression extracts the "NAME" from "ssh -q bla1 -l NAME bla2" local ARGS=$@ local RET=$(echo "$ARGS" | sed "s/.*-l \+\([a-z,A-Z,_,0-9]*\).*/\1/") if [ "$RET" == "$ARGS" ] || [ "$RET" == "" ]; then # There was no match echo "" else echo "$RET@" fi } function getHostFromSSHCommand { # This regular expression extracts the "bla2" from "echo "ssh -q bla1 -p 4821 bla2" # Specifically, it finds the first argument which is not preceded by a "-x" parameter kind specification. local CLEANED="" local NEWCLEANED="$@" while ! [ "$NEWCLEANED" == "$CLEANED" ]; do CLEANED="$NEWCLEANED" # This expression removes one "-x ARG" parameter NEWCLEANED="$(echo $CLEANED | sed "s/\(.*\)\(-[a-z,A-Z] \+[a-z,0-9]*\)\ \(.*\)/\1\3/")" done # After cleaning, the result should only consist of the host-name followed by an optional command. # Select the host-name, by extracting the forst column. echo $CLEANED | cut --delimiter=" " -f 1 } function getSSHForwardOptionsFromCommand { HOST="$(getLoginFromSSHCommand "$@")$(getHostFromSSHCommand "$@")$(getPortFromSSHCommand "$@")" if [ "$KDEV_SSH_FORWARD_CHAIN" ]; then # We are already forwarding, so we deal with a chain of multiple ssh commands. # We still record it, although it's not sure if we can use it somehow. echo "KDEV_SSH_FORWARD_CHAIN=\"$KDEV_SSH_FORWARD_CHAIN,$HOST\""; else echo "KDEV_SSH_FORWARD_CHAIN=$HOST" fi } function getDBusAbstractSocketSuffix { # From something like DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-wYmSkVH7FE,guid=b214dad39e0292a4299778d64d761a5b # extract the /tmp/dbus-wYmSkVH7FE echo $DBUS_SESSION_BUS_ADDRESS | sed 's/unix\:abstract\=.*\(,guid\=.*\)/\1/' } function keepForwardingDBusToTCPSocket { while ! $KDEV_BASEDIR/kdev_dbus_socket_transformer $DBUS_FORWARDING_TCP_LOCAL_PORT --bind-only; do if (($DBUS_FORWARDING_TCP_LOCAL_PORT<$DBUS_FORWARDING_TCP_MAX_LOCAL_PORT)); then export DBUS_FORWARDING_TCP_LOCAL_PORT=$(($DBUS_FORWARDING_TCP_LOCAL_PORT+1)) # echo "Increased local port to " $DBUS_FORWARDING_TCP_LOCAL_PORT; else echo "Failed to allocate a local TCP port"; return 1; fi done $KDEV_BASEDIR/kdev_dbus_socket_transformer $DBUS_FORWARDING_TCP_LOCAL_PORT& return 0; } function keepForwardingDBusFromTCPSocket { while ! $KDEV_BASEDIR/kdev_dbus_socket_transformer $FORWARD_DBUS_FROM_PORT ${DBUS_ABSTRACT_SOCKET_TARGET_BASE_PATH}-${DBUS_ABSTRACT_SOCKET_TARGET_INDEX} --bind-only; do if ((${DBUS_ABSTRACT_SOCKET_TARGET_INDEX}<${DBUS_ABSTRACT_SOCKET_MAX_TARGET_INDEX})); then export DBUS_ABSTRACT_SOCKET_TARGET_INDEX=$(($DBUS_ABSTRACT_SOCKET_TARGET_INDEX+1)) else echo "Failed to allocate a local path for the abstract dbus socket"; return 1; fi done local PATH=${DBUS_ABSTRACT_SOCKET_TARGET_BASE_PATH}-${DBUS_ABSTRACT_SOCKET_TARGET_INDEX} export DBUS_SESSION_BUS_ADDRESS=unix:abstract=$PATH${DBUS_SOCKET_SUFFIX} $KDEV_BASEDIR/kdev_dbus_socket_transformer $FORWARD_DBUS_FROM_PORT $PATH& } function ssh! { keepForwardingDBusToTCPSocket # Start the dbus forwarding subprocess DBUS_FORWARDING_TCP_TARGET_PORT=$((5000+($RANDOM%50000))) ssh $@ -t -R localhost:$DBUS_FORWARDING_TCP_TARGET_PORT:localhost:$DBUS_FORWARDING_TCP_LOCAL_PORT \ " APPLICATION=$APPLICATION \ KDEV_BASEDIR=$KDEV_BASEDIR \ KDEV_DBUS_ID=$KDEV_DBUS_ID \ FORWARD_DBUS_FROM_PORT=$DBUS_FORWARDING_TCP_TARGET_PORT \ APPLICATION_HOST=$APPLICATION_HOST \ KDEV_WORKING_DIR=$KDEV_WORKING_DIR \ KDEV_SHELL_ENVIRONMENT_ID=$KDEV_SHELL_ENVIRONMENT_ID \ DBUS_SOCKET_SUFFIX=$(getDBusAbstractSocketSuffix) \ $(getSSHForwardOptionsFromCommand "$@") \ bash --init-file \ \$(if [ -e \"$KDEV_BASEDIR/kdevplatform_shell_environment.sh\" ]; \ then echo \"$KDEV_BASEDIR/kdevplatform_shell_environment.sh\"; \ elif [ -e \"$(which kdevplatform_shell_environment.sh)\" ]; then echo \"$(which kdevplatform_shell_environment.sh)\"; \ else \ echo \"~/.kdevplatform_shell_environment.sh\"; \ fi) \ -i" if [ "$FORWARD_DBUS_FROM_PORT" ]; then # We created the 2nd subprocess kill %2 # Stop the dbus forwarding subprocess else # We created the 1st subprocess kill %1 # Stop the dbus forwarding subprocess fi } # A version of ssh! that preserves the current working directory function ssw! { KDEV_WORKING_DIR=$(pwd) ssh! $@ } function env! { FILES="$(executeInAppSync "ls $(getSessionDir)/*.sh" "")" for FILE in $FILES; do FILE=$(basename $FILE) ID=${FILE%.sh} # This ugly construct strips away the .sh suffix if [ "$ID" == "$KDEV_SHELL_ENVIRONMENT_ID" ]; then echo "$ID [current]" else echo "$ID" fi done } function editenv! { local ENV_ID=$KDEV_SHELL_ENVIRONMENT_ID if [ "$1" ]; then ENV_ID=$1 fi # If the environment-file doesn't exist yet, create it executeInAppSync "if ! [ -e $(getCurrentShellEnvPath $ENV_ID) ]; then touch $(getCurrentShellEnvPath $ENV_ID); fi" "" # Open it openDocument "$(getCurrentShellEnvPath $ENV_ID)" } function setenv! { if [ "$1" ]; then KDEV_SHELL_ENVIRONMENT_ID=$1 fi # Execute the contents of the shell-environment # note: keep compatible with FreeBSD: https://bugs.kde.org/show_bug.cgi?id=311186 local TEMP=$(mktemp /tmp/$USER-XXXXXXXX) RESULT=$(executeInAppSync "cat \"$(getCurrentShellEnvPath)\"" "") echo "$RESULT" > $TEMP if ! [ "$RESULT" ]; then # If the environment shell file doesn't exist, create it executeInAppSync "if ! [ -e $(getCurrentShellEnvPath) ]; then touch $(getCurrentShellEnvPath); fi" "" fi source $TEMP rm $TEMP } function showenv! { local ENV_ID=$KDEV_SHELL_ENVIRONMENT_ID if [ "$1" ]; then ENV_ID=$1 fi echo "Environment $ENV_ID:" # Execute the contents of the shell-environment echo $(executeInAppSync "cat \"$(getCurrentShellEnvPath $ENV_ID)\"" "") } if [ "$FORWARD_DBUS_FROM_PORT" ]; then # Start the target-side dbus forwarding, transforming from the ssh pipe to the abstract unix domain socket export DBUS_SESSION_BUS_ADDRESS=unix:abstract=${DBUS_ABSTRACT_SOCKET_TARGET_BASE_PATH}-${DBUS_ABSTRACT_SOCKET_TARGET_INDEX}${DBUS_SOCKET_SUFFIX} keepForwardingDBusFromTCPSocket fi setenv! ##### INITIALIZATION -------------------------------------------------------------------------------------------------------------------- # Mark that this session is attached, by prepending a '!' character PS1="!$PS1" echo "You are controlling the $APPLICATION session '$(getSessionName)'. Type help! for more information." if [ "$KDEV_WORKING_DIR" ]; then cd $KDEV_WORKING_DIR fi