Commit cf789ad5 authored by Sebastian Eichelbaum's avatar Sebastian Eichelbaum
Browse files

[ADD #100,#99] added possibility to add more custom module search paths....

[ADD #100,#99] added possibility to add more custom module search paths. Additionally, the config file position has changed to home/.OpenWalnut.
parent b5f45c7f
......@@ -25,6 +25,7 @@
#include <string>
#include <vector>
#include <cstdlib>
#include <algorithm>
#include <boost/tokenizer.hpp>
......@@ -53,9 +54,10 @@ boost::shared_ptr< WPathHelper > WPathHelper::getPathHelper()
return m_instance;
}
void WPathHelper::setAppPath( boost::filesystem::path appPath )
void WPathHelper::setBasePaths( boost::filesystem::path appPath, boost::filesystem::path homePath )
{
m_appPath = appPath;
m_homePath = homePath;
m_sharePath = m_appPath / "../share/openwalnut";
m_docPath = m_appPath / "../share/doc";
m_configPath = m_appPath / "../share/openwalnut";
......@@ -94,6 +96,11 @@ boost::filesystem::path WPathHelper::getModulePath()
return getPathHelper()->m_modulePath;
}
boost::filesystem::path WPathHelper::getHomePath()
{
return getPathHelper()->m_homePath;
}
boost::filesystem::path WPathHelper::getLibPath()
{
return getPathHelper()->m_libPath;
......@@ -120,6 +127,7 @@ std::vector< boost::filesystem::path > WPathHelper::getAllModulePaths()
std::vector< boost::filesystem::path > paths;
// the first element always is the global search path
paths.push_back( getModulePath() );
paths.push_back( getHomePath() / "modules" );
// the environment variable stores the additional paths
std::string additionalPaths( getenv( "OW_MODULE_PATH" ) ? getenv( "OW_MODULE_PATH" ) : "" );
......@@ -133,6 +141,29 @@ std::vector< boost::filesystem::path > WPathHelper::getAllModulePaths()
paths.push_back( boost::filesystem::path( *it ) );
}
// add the additional paths
for( std::vector< boost::filesystem::path >::const_iterator it = getPathHelper()->m_additionalModulePaths.begin();
it != getPathHelper()->m_additionalModulePaths.end();
++it )
{
if( !std::count( paths.begin(), paths.end(), *it ) )
{
paths.push_back( *it );
}
}
return paths;
}
void WPathHelper::addAdditionalModulePath( const boost::filesystem::path& path )
{
if( !std::count( m_additionalModulePaths.begin(), m_additionalModulePaths.end(), path ) )
{
m_additionalModulePaths.push_back( path );
}
}
const std::vector< boost::filesystem::path >& WPathHelper::getAdditionalModulePaths() const
{
return m_additionalModulePaths;
}
......@@ -57,11 +57,13 @@ public:
static boost::shared_ptr< WPathHelper > getPathHelper();
/**
* Set the current application path. This should be called only once.
* Set the current application path. This should be called only once. The home path hereby is NOT the users home. It is an directory, where
* OW can write user specific data. A good default here is to specify USERHOME/.OpenWalnut for example.
*
* \param appPath the application path
* \param homePath the OW home path
*/
void setAppPath( boost::filesystem::path appPath );
void setBasePaths( boost::filesystem::path appPath, boost::filesystem::path homePath );
/**
* The path where the binary file resides in. This is for example /usr/bin.
......@@ -127,13 +129,37 @@ public:
static boost::filesystem::path getModulePath();
/**
* This returns a list of search paths for modules. This list is defined by the environment variable "OW_MODULE_PATH". All of these
* The path to the OW dir in the user's home. This will not be the home dir directly. It is something like $HOME/.OpenWalnut.
*
* \return OW home path
*/
static boost::filesystem::path getHomePath();
/**
* This returns a list of search paths for modules. This list is defined by the environment variable "OW_MODULE_PATH" and the list of additional
* module paths. All of these
* directories CAN contain modules. On startup, they get searched in the specified order.
*
* \return list of search paths for modules
*/
static std::vector< boost::filesystem::path > getAllModulePaths();
/**
* This method adds the given path to the list of module paths. This way, arbitrary paths can be specified to search for modules. Each path
* is searched recursively.
*
* \param path the path to add.
*/
void addAdditionalModulePath( const boost::filesystem::path& path );
/**
* Returns the list of paths added using addAdditionalModulePath. This does NOT contain the paths in OW_MODULE_PATH. Use getAllModulePaths
* for this.
*
* \return the list of additional paths
*/
const std::vector< boost::filesystem::path >& getAdditionalModulePaths() const;
/**
* The path to the OW libs. You normally should not need this.
*
......@@ -202,6 +228,17 @@ private:
*/
boost::filesystem::path m_libPath;
/**
* The path of a user specific OW directory.
*/
boost::filesystem::path m_homePath;
/**
* A list of additional paths to search for modules. This does not contain the paths in the environment variable OW_MODULE_PATH. This method
* is not thread-safe. You should only use it before the module factory loads the modules.
*/
std::vector< boost::filesystem::path > m_additionalModulePaths;
/**
* Singleton instance of WPathHelper.
*/
......
......@@ -320,7 +320,7 @@ void WMainWindow::setupGUI()
QMenu* settingsMenu = m_menuBar->addMenu( "Settings" );
settingsMenu->addAction( m_autoDisplaySetting );
settingsMenu->addAction( m_controlPanel->getModuleExcluder().getConfigureAction() );
settingsMenu->addAction( m_controlPanel->getModuleConfig().getConfigureAction() );
settingsMenu->addSeparator();
settingsMenu->addAction( mtViews );
settingsMenu->addSeparator();
......
......@@ -36,6 +36,7 @@
#include <QtGui/QApplication>
#include <QtGui/QFileDialog>
#include <QtCore/QDir>
#include <QtCore/QSettings>
#include "WMainWindow.h" // this has to be included before any other includes
......@@ -60,6 +61,8 @@
#include "events/WRoiAssocEvent.h"
#include "events/WRoiRemoveEvent.h"
#include "events/WUpdateTextureSorterEvent.h"
#include "WQtModuleConfig.h"
#include "WQt4Gui.h"
WMainWindow* WQt4Gui::m_mainWindow = NULL;
......@@ -70,7 +73,6 @@ WQt4Gui::WQt4Gui( const boost::program_options::variables_map& options, int argc
: WGUI( argc, argv ),
m_optionsMap( options )
{
m_settings = new QSettings( "OpenWalnut.org", "OpenWalnut" );
}
WQt4Gui::~WQt4Gui()
......@@ -98,9 +100,12 @@ int WQt4Gui::run()
// the call path of the application, this uses QApplication which needs to be instantiated.
boost::filesystem::path walnutBin = boost::filesystem::path( QApplication::applicationDirPath().toStdString() );
// setup path helper which provides several paths to others^
WPathHelper::getPathHelper()->setAppPath( walnutBin );
WPathHelper::getPathHelper()->setBasePaths( walnutBin, boost::filesystem::path( QDir::homePath().toStdString() ) / ".OpenWalnut" );
// with the correct paths, we can load the settings
m_settings = new QSettings( QString::fromStdString( ( WPathHelper::getHomePath() / "config.qt4gui" ).string() ), QSettings::IniFormat );
WQtModuleConfig::initPathHelper();
// get the minimum log level from preferences
LogLevel logLevel = static_cast< LogLevel >( WQt4Gui::getSettings().value( "qt4gui/logLevel", LL_INFO ).toInt() );
......
......@@ -25,7 +25,7 @@
#include "WQtCombinerActionList.h"
WQtCombinerActionList::WQtCombinerActionList( QWidget* parent, WIconManager* icons, WCombinerTypes::WCompatiblesList compatibles,
const WQtModuleExcluder* exclusionPredicate, bool advancedText ):
const WQtModuleConfig* exclusionPredicate, bool advancedText ):
QList< QAction* >()
{
// create an action for each group:
......
......@@ -40,7 +40,7 @@
#include "core/kernel/WModuleCombiner.h"
#include "guiElements/WQtModuleOneToOneCombinerAction.h"
#include "WQt4Gui.h"
#include "WQtModuleExcluder.h"
#include "WQtModuleConfig.h"
#include "WIconManager.h"
......@@ -63,7 +63,7 @@ public:
* \tparam PredicateT the predicate used for excluding modules
*/
WQtCombinerActionList( QWidget* parent, WIconManager* icons, WCombinerTypes::WCompatiblesList compatibles,
const WQtModuleExcluder* exclusionPredicate = 0, bool advancedText = false );
const WQtModuleConfig* exclusionPredicate = 0, bool advancedText = false );
/**
* This constructor creates a list of actions recursively from the specified disconnects list.
* \param parent the parent widget of this widget, i.e. the widget that manages it.
......
......@@ -22,18 +22,25 @@
//
//---------------------------------------------------------------------------
#include <iostream>
#include <string>
#include <vector>
#ifndef BOOST_FILESYSTEM_VERSION
#define BOOST_FILESYSTEM_VERSION 2
#endif
#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>
#include <QtCore/QDir>
#include <QtGui/QFileDialog>
#include <QtGui/QVBoxLayout>
#include <QtGui/QGridLayout>
#include <QtGui/QLabel>
#include <QtGui/QCheckBox>
#include <QtGui/QPushButton>
#include <QtGui/QDialogButtonBox>
#include <QtGui/QTabWidget>
#include "core/kernel/WModuleFactory.h"
#include "core/common/WPathHelper.h"
......@@ -41,8 +48,8 @@
#include "WQt4Gui.h"
#include "WMainWindow.h"
#include "WQtModuleExcluder.h"
#include "WQtModuleExcluder.moc"
#include "WQtModuleConfig.h"
#include "WQtModuleConfig.moc"
/**
* Simple modified checkbox which is two-state by using an additional flag but tristate under the hood. See nextCheckState.
......@@ -116,7 +123,7 @@ private:
bool m_isRecommended;
};
WQtModuleExcluder::WQtModuleExcluder( QWidget* parent, Qt::WindowFlags f ):
WQtModuleConfig::WQtModuleConfig( QWidget* parent, Qt::WindowFlags f ):
QDialog( parent, f )
{
// configure the dialog
......@@ -137,16 +144,40 @@ WQtModuleExcluder::WQtModuleExcluder( QWidget* parent, Qt::WindowFlags f ):
}
// initialize members
QVBoxLayout* layout = new QVBoxLayout;
QVBoxLayout* layoutAllowedModules = new QVBoxLayout;
QWidget* p1 = new QWidget();
p1->setLayout( layoutAllowedModules );
QVBoxLayout* layoutModulePaths = new QVBoxLayout;
QWidget* p2 = new QWidget();
p2->setLayout( layoutModulePaths );
// setup tab widget.
QTabWidget* tab = new QTabWidget( this );
tab->addTab( p1, "Allowed Modules" );
tab->addTab( p2, "Module Paths" );
QVBoxLayout* masterLayout = new QVBoxLayout();
masterLayout->addWidget( tab );
setLayout( masterLayout );
QString helpText = "This dialog allows you to modify the list of modules used everywhere in OpenWalnut. The list contains all loaded modules."
" Select the modules you want "
"to use and disable those you won't use. This way, the module toolbar and the context menu stay clean. The "
"OpenWalnut-Team provides a list of recommended modules. This list is always active, unless you turn it off. Recommended "
"modules are visually marked by being partially checked.";
QLabel* hint = new QLabel( helpText, this );
QLabel* hint = new QLabel( helpText );
hint->setWordWrap( true );
layout->addWidget( hint );
layoutAllowedModules->addWidget( hint );
QString helpTextPaths = "You can add more search paths here, where OpenWalnut searches modules during startup. Each path is searched "
"recursively. Use this list to help OpenWalnut find your downloaded or self-made modules. An alternative option is to "
"set the environment variable \"OW_MODULE_PATH\", which is a semicolon-separated list of search paths. After "
"restarting OpenWalnut, the modules in the added paths appear in the list of allowed modules.";
QLabel* hintPaths = new QLabel( helpTextPaths );
hintPaths->setWordWrap( true );
layoutModulePaths->addWidget( hintPaths );
// always show all modules?
m_showThemAll = new QCheckBox( "Always show all modules.", this );
......@@ -154,19 +185,37 @@ WQtModuleExcluder::WQtModuleExcluder( QWidget* parent, Qt::WindowFlags f ):
"Recommended option for developer. This ensures that all modules get shown all them time, regardless of the list below."
);
connect( m_showThemAll, SIGNAL( stateChanged( int ) ), this, SLOT( showThemAllUpdated() ) );
layout->addWidget( m_showThemAll );
layoutAllowedModules->addWidget( m_showThemAll );
m_ignoreRecommends = new QCheckBox( "Ignore official recommendation.", this );
m_ignoreRecommends->setToolTip(
"By default, OpenWalnut provides a list of recommended modules. This list overrides your custom selection. To disable this, activate this "
"option."
);
layout->addWidget( m_ignoreRecommends );
layoutAllowedModules->addWidget( m_ignoreRecommends );
// create the module list
m_list = new QListWidget( this );
layout->addWidget( m_list );
setLayout( layout );
m_list = new QListWidget();
layoutAllowedModules->addWidget( m_list );
// the path list
m_pathList = new QListWidget();
layoutModulePaths->addWidget( m_pathList );
// the path list also needs some add/remove buttons
QHBoxLayout* addRemLayout = new QHBoxLayout();
QWidget* addRemWidget = new QWidget();
addRemWidget->setLayout( addRemLayout );
layoutModulePaths->addWidget( addRemWidget );
QPushButton* addButton = new QPushButton( "Add Path" );
m_removePathButton = new QPushButton( "Remove Path" );
m_removePathButton->setEnabled( false );
addRemLayout->addWidget( addButton );
addRemLayout->addWidget( m_removePathButton );
connect( addButton, SIGNAL( clicked( bool ) ), this, SLOT( addModulePath() ) );
connect( m_removePathButton, SIGNAL( clicked( bool ) ), this, SLOT( removeModulePath() ) );
connect( m_pathList, SIGNAL( itemSelectionChanged() ), this, SLOT( pathListSelectionChanged() ) );
// for modules without icon, use this
QIcon noIcon = WQt4Gui::getMainWindow()->getIconManager()->getIcon( "DefaultModuleIcon" );
......@@ -241,18 +290,30 @@ WQtModuleExcluder::WQtModuleExcluder( QWidget* parent, Qt::WindowFlags f ):
connect( defButtons, SIGNAL( accepted() ), this, SLOT( accept() ) );
connect( defButtons, SIGNAL( rejected() ), this, SLOT( reject() ) );
connect( defButtons->button( QDialogButtonBox::RestoreDefaults ), SIGNAL( clicked() ), this, SLOT( reset() ) );
layout->addWidget( defButtons );
masterLayout->addWidget( defButtons );
// initialize the widgets
loadListsFromSettings();
}
WQtModuleExcluder::~WQtModuleExcluder()
WQtModuleConfig::~WQtModuleConfig()
{
// cleanup
}
void WQtModuleExcluder::loadListsFromSettings( bool recommendsOnly )
void WQtModuleConfig::initPathHelper()
{
// we allow the user to specify additional module paths. They need to be loaded before the WModuleFactory initiates the module-load stuff.
// Therefore, we grab the setting here and add it to WPathHelper
QList< QVariant > paths = WQt4Gui::getSettings().value( "qt4gui/additionalModulePaths" ).toList();
for( QList< QVariant >::const_iterator it = paths.begin(); it != paths.end(); ++it )
{
std::string p = ( *it ).toString().toStdString();
WPathHelper::getPathHelper()->addAdditionalModulePath( p );
}
}
void WQtModuleConfig::loadListsFromSettings( bool recommendsOnly, bool defaultModulePaths )
{
// update checkbox too
bool ignoreAllowedList = WQt4Gui::getSettings().value( "qt4gui/modules/IgnoreAllowedList", false ).toBool();
......@@ -290,9 +351,22 @@ void WQtModuleExcluder::loadListsFromSettings( bool recommendsOnly )
{
std::copy( m_recommendedModules.begin(), m_recommendedModules.end(), std::back_inserter( m_allowedModules ) );
}
if( !defaultModulePaths )
{
// now, also fill the list
// NOTE: we do not use the list in WPathHelper. This list will NOT be updated directly to ensure consistency between the path list in
// WPathHelper and the loaded modules in WModuleFactory. WPathHelper is set correctly on next restart.
QList< QVariant > paths = WQt4Gui::getSettings().value( "qt4gui/additionalModulePaths" ).toList();
for( QList< QVariant >::const_iterator it = paths.begin(); it != paths.end(); ++it )
{
std::string p = ( *it ).toString().toStdString();
m_pathList->addItem( QString::fromStdString( p ) );
}
}
}
void WQtModuleExcluder::saveListToSettings()
void WQtModuleConfig::saveListToSettings()
{
// rebuild list of allowed modules
m_allowedModules.clear();
......@@ -313,14 +387,22 @@ void WQtModuleExcluder::saveListToSettings()
WQt4Gui::getSettings().setValue( "qt4gui/modules/allowedList", QString::fromStdString( allowedAsString ) );
WQt4Gui::getSettings().setValue( "qt4gui/modules/IgnoreAllowedList", ( m_showThemAll->checkState() == Qt::Checked ) );
WQt4Gui::getSettings().setValue( "qt4gui/modules/IgnoreRecommendedList", ( m_ignoreRecommends->checkState() == Qt::Checked ) );
// also write the path list
QList< QVariant > paths;
for( int i = 0; i < m_pathList->count(); ++i )
{
paths.push_back( m_pathList->item( i )->text() );
}
WQt4Gui::getSettings().setValue( "qt4gui/additionalModulePaths", paths );
}
void WQtModuleExcluder::enforceAllModules()
void WQtModuleConfig::enforceAllModules()
{
WQt4Gui::getSettings().setValue( "qt4gui/modules/IgnoreAllowedList", true );
}
void WQtModuleExcluder::loadRecommends()
void WQtModuleConfig::loadRecommends()
{
m_recommendedModules.clear();
......@@ -355,57 +437,57 @@ void WQtModuleExcluder::loadRecommends()
}
else
{
wlog::error( "WQtModuleExcluder" ) << "No recommended modules specified in \"" << confFile.string() <<
wlog::error( "WQtModuleConfig" ) << "No recommended modules specified in \"" << confFile.string() <<
"\". Enabling all modules as fall-back.";
enforceAllModules();
}
}
catch( const po::error &e )
{
wlog::error( "WQtModuleExcluder" ) << "Invalid configuration file \"" << confFile.string() <<
wlog::error( "WQtModuleConfig" ) << "Invalid configuration file \"" << confFile.string() <<
"\". Enabling all modules as fall-back. Error was: " << e.what();
enforceAllModules();
}
}
else
{
wlog::error( "WQtModuleExcluder" ) << "No \"" << confFile.string() << "\" found. Enabling all modules as fall-back.";
wlog::error( "WQtModuleConfig" ) << "No \"" << confFile.string() << "\" found. Enabling all modules as fall-back.";
enforceAllModules();
}
}
bool WQtModuleExcluder::operator()( std::string const& name ) const
bool WQtModuleConfig::operator()( std::string const& name ) const
{
return ( m_showThemAll->checkState() != Qt::Checked ) &&
( std::find( m_allowedModules.begin(), m_allowedModules.end(), name ) == m_allowedModules.end() );
}
bool WQtModuleExcluder::operator()( WModule::ConstSPtr module ) const
bool WQtModuleConfig::operator()( WModule::ConstSPtr module ) const
{
return operator()( module->getName() );
}
void WQtModuleExcluder::configure()
void WQtModuleConfig::configure()
{
show();
}
QAction* WQtModuleExcluder::getConfigureAction() const
QAction* WQtModuleConfig::getConfigureAction() const
{
QAction* a = new QAction( "Configure Allowed Modules", parent() );
QAction* a = new QAction( "Configure Modules", parent() );
a->setToolTip( "Allows you to configure the list of modules, which is used for selecting modules in OpenWalnut (i.e. in the toolbar)." );
connect( a, SIGNAL( triggered( bool ) ), this, SLOT( configure() ) );
return a;
}
void WQtModuleExcluder::accept()
void WQtModuleConfig::accept()
{
saveListToSettings();
emit updated();
QDialog::accept();
}
void WQtModuleExcluder::reject()
void WQtModuleConfig::reject()
{
// reset everything to the current state in the settings:
loadListsFromSettings();
......@@ -413,7 +495,7 @@ void WQtModuleExcluder::reject()
QDialog::reject();
}
void WQtModuleExcluder::showThemAllUpdated()
void WQtModuleConfig::showThemAllUpdated()
{
if( m_showThemAll->checkState() == Qt::Checked )
{
......@@ -425,17 +507,44 @@ void WQtModuleExcluder::showThemAllUpdated()
}
}
void WQtModuleExcluder::reset()
void WQtModuleConfig::reset()
{
m_pathList->clear();
// reset all checkboxes
for( std::vector< WModule::ConstSPtr >::const_iterator iter = m_moduleList.begin(); iter != m_moduleList.end(); ++iter )
{
// we later need to find the checkbox for one module easily:
m_moduleItemMap[ ( *iter )->getName() ]->setCheckState( Qt::Unchecked );
}
loadListsFromSettings( true );
loadListsFromSettings( true, true );
m_showThemAll->setCheckState( Qt::Unchecked );
m_ignoreRecommends->setCheckState( Qt::Unchecked );
m_list->setDisabled( false );
}
void WQtModuleConfig::addModulePath()
{
QString dir = QFileDialog::getExistingDirectory( this, "Select Directory",
QString::fromStdString( WPathHelper::getHomePath().string() ),
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks );
m_pathList->addItem( dir );
}
void WQtModuleConfig::removeModulePath()
{
qDeleteAll( m_pathList->selectedItems() );
}
void WQtModuleConfig::pathListSelectionChanged()
{
if( m_pathList->selectedItems().size() )
{
m_removePathButton->setEnabled( true );
}
else
{
m_removePathButton->setEnabled( false );
}
}
......@@ -22,8 +22,8 @@
//
//---------------------------------------------------------------------------
#ifndef WQTMODULEEXCLUDER_H
#define WQTMODULEEXCLUDER_H
#ifndef WQTMODULECONFIG_H
#define WQTMODULECONFIG_H
#include <map>
#include <vector>
......@@ -40,7 +40,7 @@
* A class which acts as a binary predicate to check exclusion of modules by name using a whitelist and a blacklist. It automatically handles the
* settings for it and provides proper QActions.
*/
class WQtModuleExcluder: public QDialog
class WQtModuleConfig: public QDialog
{
Q_OBJECT
public:
......@@ -50,12 +50,12 @@ public:
* \param parent parent widget
* \param f window flags
*/
WQtModuleExcluder( QWidget* parent = 0, Qt::WindowFlags f = 0 );
WQtModuleConfig( QWidget* parent = 0, Qt::WindowFlags f = 0 );
/**
* Destructor.
*/
virtual ~WQtModuleExcluder();
virtual ~WQtModuleConfig();
/**
* Checks exclusion by name.
......@@ -82,6 +82,12 @@ public:
*/
QAction* getConfigureAction() const;
/**
* This function initializes the path helper by loading the module path settings. This is needed since the pathhelper needs to know all paths
* before the GUI really shows up.
*/
static void initPathHelper();
signals:
/**
* Signal getting emitted if the exclusion-lists changes.
......@@ -104,6 +110,23 @@ public slots:
*/
virtual void reject();
/**
* Add a path to m_pathList;
*/
virtual void addModulePath();
/**
* Remove the selected item from m_pathList;
*/
virtual void removeModulePath();
private slots: