diff options
Diffstat (limited to 'kutils')
52 files changed, 13108 insertions, 0 deletions
diff --git a/kutils/Mainpage.dox b/kutils/Mainpage.dox new file mode 100644 index 000000000..f649fcc3d --- /dev/null +++ b/kutils/Mainpage.dox @@ -0,0 +1,24 @@ +/** @mainpage Some random bits and pieces + +Some utility classes for KSettings, KCModule and KPlugin. + +@authors +Matthias Kretz \<kretz@kde.org\><br> +Rafael Fernández López \<ereslibre@kde.org\><br> +Matthias Elter \<elter@kde.org\><br> +Daniel Molkentin \<molkentin@kde.org\><br> +Frans Englich \<frans.englich@telia.com\><br> +Tobias Koenig \<tokoe@kde.org\><br> +Matthias Hoelzer-Kluepfel \<hoelzer@kde.org\> + +@maintainers +[Unknown/None] + +@licenses +@lgpl + +*/ + +// DOXYGEN_REFERENCES = kdecore kdeui kio +// DOXYGEN_SET_PROJECT_NAME = KUtils +// vim:ts=4:sw=4:expandtab:filetype=doxygen diff --git a/kutils/Makefile.am b/kutils/Makefile.am new file mode 100644 index 000000000..2206d798f --- /dev/null +++ b/kutils/Makefile.am @@ -0,0 +1,31 @@ +SUBDIRS = ksettings . +INCLUDES = -I$(top_srcdir)/interfaces/kregexpeditor $(all_includes) + +lib_LTLIBRARIES = libkutils.la +libkutils_la_LDFLAGS = $(all_libraries) -no-undefined -version-info 3:0:2 +# Needs KTrader +libkutils_la_LIBADD = ../kio/libkio.la ksettings/libksettings.la + +libkutils_la_SOURCES = kfind.cpp kfinddialog.cpp kreplace.cpp \ + kreplacedialog.cpp kcmoduleinfo.cpp kcmoduleloader.cpp \ + kcmultidialog.cpp kmultitabbar.cpp kplugininfo.cpp \ + kcmoduleproxy.cpp kpluginselector.cpp \ + kcmodulecontainer.cpp kcmoduleproxyIface.skel \ + kcmoduleproxyIfaceImpl.cpp + +include_HEADERS = kfind.h kfinddialog.h kreplace.h kreplacedialog.h \ + kcmoduleinfo.h kcmoduleloader.h kcmultidialog.h \ + kmultitabbar.h kplugininfo.h kcmoduleproxy.h \ + kpluginselector.h kcmodulecontainer.h + +noinst_HEADERS = kmultitabbar_p.h kpluginselector_p.h \ + kcmoduleproxyIface.h kcmoduleproxyIfaceImpl.h + +kde_servicetypes_DATA = kplugininfo.desktop + +METASOURCES = AUTO + +DOXYGEN_REFERENCES = kdecore kdeui kio +include ../admin/Doxyfile.am + +# vim: et diff --git a/kutils/TODO b/kutils/TODO new file mode 100644 index 000000000..82f28e129 --- /dev/null +++ b/kutils/TODO @@ -0,0 +1,25 @@ + +This is a list of random changes to do in KDE 4 with the various KCM* classes. + +* KCModuleinfo; + - Make it inherit KService; saves a lot of code, simplifies ctors/overloads in surrounding classes + - Implement docPath in KService + - Get rid of loadAll() and do lazy loading in each getter + +* Merge KCModuleProxy and its DCOP Object class(MI) + +* Get rid of `QStringlist arguments` in KCModule and all the helper classes(simplify API) + +* Move stuff to d pointers.. Too much hackish solutions otherwise + +* Get rid of bool fallback argument in helper classes(never fails, simplify API) + +* Re-implement the QScrollView(d->view) in KCModuleProxy; the protection against violation of HIG-clause. It needs kde-core-devel approval. + +* The API in KCModule, KCModuleProxy, and the various desktop entries, concerning root loading are butt ugly. It needs cleaning and simplification. + +* There's big confusion regarding /when/ the changed(bool) signal should be emitted, abd by /whom/. This can't be fixed properly in 3.x since it means breaking behavior. In KDE 4 it must be made clear if KCModuleProxy should take care of emitting appropriate signals for load()/save()/defaults, or if modules manually should handle it(is there a reason?). + +* Root modules in KCModuleProxy doesn't support D&D, or it is perhaps implemented in QXEmbed? Anyway, this is what the XEmbed Protocol Specification says: + +Solving the drag-and-drop problem, however, is quite easy, since the XDND protocol was carefully designed in a way that makes it possible to support embedded windows. Basically, the embedder has to operate as drag-and-drop proxy for the client. Any XDND messages like XdndEnter, Xdnd,Leave, etc. simply have to be passed through. A toolkit's XDND implementation has to take this situation in consideration. diff --git a/kutils/groups.dox b/kutils/groups.dox new file mode 100644 index 000000000..95d926d73 --- /dev/null +++ b/kutils/groups.dox @@ -0,0 +1,41 @@ +/** +@defgroup main Main classes +*/ + +/** +@defgroup internal Internal classes +*/ + +/** +@defgroup findreplace Find and Replace classes + +These classes implement a generic find/replace function, along with +extensible dialogs. +*/ + +/** +@defgroup plugin Plugin handling classes + +These classes provide an easy and generic way for handling plugins. +*/ + +/** +@defgroup settings Settings UI classes + +These classes provide widgets to build settings dialogs. See KSettings +namespace description for an overview. +*/ + +/** +@defgroup kcmodule KControl module classes + +Thwse classes provide the KControl module API. +*/ + +/** +@defgroup multitabbar KMultiTabBar related classes + +These classes implement the tab bar often found on the border of KDE +applications' main windows (such as Kate or KDevelop). +*/ + diff --git a/kutils/kcmodulecontainer.cpp b/kutils/kcmodulecontainer.cpp new file mode 100644 index 000000000..000189e0d --- /dev/null +++ b/kutils/kcmodulecontainer.cpp @@ -0,0 +1,258 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004 Frans Englich <frans.englich@telia.com> + + 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 <qlayout.h> +#include <qpixmap.h> +#include <qstringlist.h> +#include <qtabwidget.h> +#include <qtooltip.h> +#include <qvaluelist.h> + +#include <kcmodule.h> +#include <kcmoduleinfo.h> +#include <kcmoduleloader.h> +#include <kcmoduleproxy.h> +#include <kdebug.h> +#include <kdialog.h> +#include <kglobal.h> +#include <kiconloader.h> +#include <kpushbutton.h> +#include <kservice.h> +#include <kstdguiitem.h> + +#include "kcmodulecontainer.h" +#include "kcmodulecontainer.moc" + +/***********************************************************************/ +class KCModuleContainer::KCModuleContainerPrivate +{ + public: + KCModuleContainerPrivate( const QStringList& mods ) + : modules( mods ) + , tabWidget( 0 ) + , buttons( 0 ) + , hasRootKCM( false ) + , btnRootMode( 0 ) + , btnLayout( 0 ) + , topLayout( 0 ) + {} + + QStringList modules; + QTabWidget *tabWidget; + int buttons; + bool hasRootKCM: 1; + KPushButton *btnRootMode; + QHBoxLayout *btnLayout; + QVBoxLayout *topLayout; + + +}; +/***********************************************************************/ + + + + + +/***********************************************************************/ +KCModuleContainer::KCModuleContainer( QWidget* parent, const char* name, + const QString& mods ) + : KCModule( parent, name ) +{ + d = new KCModuleContainerPrivate( QStringList::split( ",", QString(mods).remove( " " )) ); + init(); +} + +KCModuleContainer::KCModuleContainer( QWidget* parent, const char* name, + const QStringList& mods ) + : KCModule( parent, name ), d( new KCModuleContainerPrivate( mods ) ) +{ + init(); +} + +void KCModuleContainer::init() +{ + d->topLayout = new QVBoxLayout( this, 0, KDialog::spacingHint(), "topLayout" ); + d->tabWidget = new QTabWidget(this, "tabWidget"); + d->tabWidget->setMargin(KDialog::marginHint()); + connect( d->tabWidget, SIGNAL( currentChanged( QWidget* ) ), SLOT( tabSwitched( QWidget* ) )); + d->topLayout->addWidget( d->tabWidget ); + + if ( !d->modules.isEmpty() ) + { + /* Add our modules */ + for ( QStringList::Iterator it = d->modules.begin(); it != d->modules.end(); ++it ) + addModule( (*it) ); + + finalize(); + } + +} + +void KCModuleContainer::finalize() +{ + setButtons( d->buttons ); + if ( d->hasRootKCM ) /* Add a root mode button */ + { + if(!d->btnLayout) /* It could already be added */ + { + d->btnLayout = new QHBoxLayout(this, 0, 0, "btnLayout"); + d->btnRootMode = new KPushButton(KStdGuiItem::adminMode(), this, "btnRootMode"); + + d->btnLayout->addWidget( d->btnRootMode ); + d->btnLayout->addStretch(); + d->topLayout->addLayout( d->btnLayout ); + } + } +} + +void KCModuleContainer::addModule( const QString& module ) +{ + /* In case it doesn't exist we just silently drop it. + * This allows people to easily extend containers. + * For example, KCM monitor gamma can be in kdegraphics. + */ + if ( !KService::serviceByDesktopName( module ) ) + { + kdDebug(713) << "KCModuleContainer: module '" << + module << "' was not found and thus not loaded" << endl; + return; + } + + if( !KCModuleLoader::testModule( module )) + return; + + KCModuleProxy* proxy = new KCModuleProxy( module, false, d->tabWidget, module.latin1()); + allModules.append( proxy ); + + d->tabWidget->addTab( proxy, QIconSet(KGlobal::iconLoader()->loadIcon( + proxy->moduleInfo().icon(), KIcon::Desktop)), + /* QT eats ampersands for dinner. But not this time. */ + proxy->moduleInfo().moduleName().replace( "&", "&&" )); + + d->tabWidget->setTabToolTip( proxy, proxy->moduleInfo().comment() ); + + connect( proxy, SIGNAL(changed(KCModuleProxy *)), SLOT(moduleChanged(KCModuleProxy *))); + + /* Collect our buttons - we go for the common deliminator */ + d->buttons = d->buttons | proxy->realModule()->buttons(); + + /* If we should add an Administrator Mode button */ + if ( proxy->moduleInfo().needsRootPrivileges() ) + d->hasRootKCM=true; + + +} + +void KCModuleContainer::tabSwitched( QWidget * module ) +{ + if ( !d->hasRootKCM ) + return; + + /* Not like this. Not like this. */ + disconnect( d->btnRootMode, 0, 0, 0 ); + /* Welcome to the real world huh baby? */ + + KCModuleProxy* mod = (KCModuleProxy *) module; + + if ( mod->moduleInfo().needsRootPrivileges() && !mod->rootMode() ) + { + d->btnRootMode->setEnabled( true ); + connect( d->btnRootMode, SIGNAL( clicked() ), + SLOT( runAsRoot() )); + connect( mod, SIGNAL( childClosed() ), + SLOT ( rootExited() )); + } + else + d->btnRootMode->setEnabled( false ); + + setQuickHelp( mod->quickHelp() ); + setAboutData( const_cast<KAboutData*>(mod->aboutData()) ); + +} + +void KCModuleContainer::runAsRoot() +{ + if ( d->tabWidget->currentPage() ) + ( (KCModuleProxy *) d->tabWidget->currentPage() )->runAsRoot(); + d->btnRootMode->setEnabled( false ); +} + +void KCModuleContainer::rootExited() +{ + connect( d->btnRootMode, SIGNAL( clicked() ), SLOT( runAsRoot() )); + d->btnRootMode->setEnabled( true ); +} + +void KCModuleContainer::save() +{ + ModuleList list = changedModules; + ModuleList::iterator it; + for ( it = list.begin() ; it !=list.end() ; ++it ) + { + (*it)->save(); + } + + emit changed( false ); + +} + +void KCModuleContainer::load() +{ + ModuleList list = allModules; + ModuleList::iterator it; + for ( it = list.begin() ; it !=list.end() ; ++it ) + { + (*it)->load(); + } + + emit changed( false ); +} + +void KCModuleContainer::defaults() +{ + ModuleList list = allModules; + ModuleList::iterator it; + for ( it = list.begin() ; it !=list.end() ; ++it ) + { + (*it)->defaults(); + } + + emit changed( true ); +} + + +void KCModuleContainer::moduleChanged(KCModuleProxy * proxy) +{ + changedModules.append( proxy ); + if( changedModules.isEmpty() ) + return; + + emit changed(true); +} + +KCModuleContainer::~KCModuleContainer() +{ + delete d; +} + +/***********************************************************************/ + + + + diff --git a/kutils/kcmodulecontainer.h b/kutils/kcmodulecontainer.h new file mode 100644 index 000000000..6b23a16c9 --- /dev/null +++ b/kutils/kcmodulecontainer.h @@ -0,0 +1,207 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004 Frans Englich <frans.englich@telia.com> + + 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 KCMODULECONTAINER_H +#define KCMODULECONTAINER_H + +#include <qvaluelist.h> +#include <qstring.h> +#include <qstringlist.h> + +#include <kcmodule.h> +#include <kcmoduleloader.h> + +class QTabWidget; +class QWidget; +class QVBoxLayout; + +class KCModuleProxy; + +/** + * @ingroup kcmodule + * @brief KCModuleContainer is a convenience class encapsulating several KCModules. + * + * The KCModuleContainer class is a convenience class for organizing a multiple set + * of KCModule. KCModuleContainer is a sub class of KCModule and builds an interface mainly + * consisting of a tab widget where each tab contains one of the modules specified via one of the + * constructors. KCModuleContainer can handle modules which requires root permissions. What you + * most likely want is the KCMODULECONTAINER macro. \n + * Sometimes it is of interest to detect in runtime whether a module should be loaded or not. This + * can be achieved by sub classing KCModuleContainer, doing the probing/testing checks and then manually + * call addModule for each module which should be displayed. When all calls to addModule is done, call + * finalize() which performs some necessary final steps. + * + * @author Frans Englich <frans.englich@telia.com> + * @since 3.4 + */ +class KUTILS_EXPORT KCModuleContainer : public KCModule +{ + Q_OBJECT + public: + /** + * Creates a KCModuleContainer with tabs, each one containing one of the + * specified modules in @p mods. + * + * @param parent the parent QWidget. + * @param name the module's name. + * @param mods The list of KCModules to be loaded. The name of each + * KCModule is its service name, that is the name of the desktop file without + * the ".desktop" part + * + */ + KCModuleContainer( QWidget* parent, const char* name, const QStringList& mods ); + + /** + * This is a convenience function, instead of building a QStringList you + * can specify the modules in a comma separated QString. For example; + * \code + * KCModuleContainer* cont = KCModuleContainer( this, "kcm_misc", QString("kcm_energy, kcm_keyboard ,kcm_useraccount, kcm_mouse") ); + * \endcode + * The other constructor takes its modules in a QStringlist which also can be constructed from a + * string and thus you will have to be explicit on the data type. + * + * What you probably want is the KCMODULECONTAINER macro which builds an KCModule + * for you, taking the modules you want as argument. + * + * @param parent The parent widget + * @param name The service name + * @param mods The modules to load + * @return The KCModule containing the requested modules. + */ + KCModuleContainer( QWidget *parent, const char* name, const QString& mods = QString() ); + + /** + * Adds the specified module to the tab widget. Setting the tab icon, text, + * tool tip, connecting the signals is what it does. + * + * @param module the name of the module to add. The name is the desktop file's name + * without the ".desktop" part. + */ + void addModule( const QString& module ); + + /** + * Default destructor. + */ + virtual ~KCModuleContainer(); + + /** + * Reimplemented for internal purposes. + * @internal + */ + void save(); + + /** + * Reimplemented for internal purposes. + * @internal + */ + void load(); + + /** + * Reimplemented for internal purposes. + * @internal + */ + void defaults(); + + protected: + + /** + * Sets this KCM's buttons and adds a AdminMode button + * if necessary. If KCModuleContainer is subclassed finalize() + * should be called in the constructor after all calls to addModule + * have been done. Call it once. + */ + void finalize(); + + typedef QValueList<KCModuleProxy*> ModuleList; + + /** + * A list containing KCModuleProxy objects which + * have changed and must be saved. + */ + ModuleList changedModules; + + /** + * A list of all modules which are encapsulated. + */ + ModuleList allModules; // KDE 4 put in the Private class and abstract with getter + + private slots: + + /** + * Enables/disables the Admin Mode button, as appropriate. + */ + void tabSwitched( QWidget * module ); + + void moduleChanged(KCModuleProxy *proxy); + + /** + * Called when the user clicks our custom root button. + */ + void runAsRoot(); + + /** + * Enables the admin mode button + */ + void rootExited(); + + private: + + void init(); + + class KCModuleContainerPrivate; + KCModuleContainerPrivate *d; + +}; + +/** + * @ingroup kcmodule + * This macro creates an factory declaration which when run creates an KCModule with specified + * modules. For example: + * \code + * KCMODULECONTAINER( "kcm_fonts, kcm_keyboard,kcm_fonts", misc_modules) + * \endcode + * would create a KCModule with three tabs, each containing one of the specified KCMs. Each + * use of the macro must be accompanied by a desktop file where the factory name equals + * the second argument in the macro(in this example, misc_modules). \n + * The module container takes care of testing the contained modules when being shown, as well + * as when the module itself is asked whether it should be shown. + * + * @param modules the modules to put in the container + * @param factoryName what factory name the module should have + */ +#define KCMODULECONTAINER( modules, factoryName ) \ +extern "C" \ +{ \ + KCModule *create_## factoryName(QWidget *parent, const char *name) \ + { \ + return new KCModuleContainer( parent, name, QString( modules ) ); \ + } \ + \ + bool test_## factoryName() \ + { \ + QStringList modList = QStringList::split( ",", QString(modules).remove( " " )); \ + for ( QStringList::Iterator it = modList.begin(); it != modList.end(); ++it ) \ + if ( KCModuleLoader::testModule( *it ) ) \ + return true; \ + return false; \ + } \ +} + +#endif // KCMODULECONTAINER_H + diff --git a/kutils/kcmoduleinfo.cpp b/kutils/kcmoduleinfo.cpp new file mode 100644 index 000000000..ababaadba --- /dev/null +++ b/kutils/kcmoduleinfo.cpp @@ -0,0 +1,240 @@ +/* + Copyright (c) 1999 Matthias Hoelzer-Kluepfel <hoelzer@kde.org> + Copyright (c) 2000 Matthias Elter <elter@kde.org> + Copyright (c) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (c) 2003 Matthias Kretz <kretz@kde.org> + + This file is part of the KDE project + + 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 <qvariant.h> + +#include <kdesktopfile.h> +#include <kdebug.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <klocale.h> + +#include "kcmoduleinfo.h" + +class KCModuleInfo::KCModuleInfoPrivate +{ + public: + KCModuleInfoPrivate() : + testModule( false ) + {} + ~KCModuleInfoPrivate() + { } + + QString factoryName; + bool testModule; + +}; + +KCModuleInfo::KCModuleInfo() +{ + _allLoaded = false; + d = new KCModuleInfoPrivate; +} + +KCModuleInfo::KCModuleInfo(const QString& desktopFile) +{ + init( KService::serviceByStorageId(desktopFile) ); +} + +KCModuleInfo::KCModuleInfo( KService::Ptr moduleInfo ) +{ + init(moduleInfo); +} + +KCModuleInfo::KCModuleInfo( const KCModuleInfo &rhs ) +{ + d = new KCModuleInfoPrivate; + ( *this ) = rhs; +} + +// this re-implementation exists to ensure that other code always calls +// our re-implementation, so in case we add data to the d pointer in the future +// we can be sure that we get called when we are copied. +KCModuleInfo &KCModuleInfo::operator=( const KCModuleInfo &rhs ) +{ + _keywords = rhs._keywords; + _name = rhs._name; + _icon = rhs._icon; + _lib = rhs._lib; + _handle = rhs._handle; + _fileName = rhs._fileName; + _doc = rhs._doc; + _comment = rhs._comment; + _needsRootPrivileges = rhs._needsRootPrivileges; + _isHiddenByDefault = rhs._isHiddenByDefault; + _allLoaded = rhs._allLoaded; + _service = rhs._service; + + *d = *(rhs.d); + + return *this; +} + +QString KCModuleInfo::factoryName() const +{ + if( d->factoryName.isEmpty() ) + { + d->factoryName = _service->property("X-KDE-FactoryName", QVariant::String).toString(); + if ( d->factoryName.isEmpty() ) + d->factoryName = library(); + } + + return d->factoryName; +} + +bool KCModuleInfo::operator==( const KCModuleInfo & rhs ) const +{ + return ( ( _name == rhs._name ) && ( _lib == rhs._lib ) && ( _fileName == rhs._fileName ) ); +} + +bool KCModuleInfo::operator!=( const KCModuleInfo & rhs ) const +{ + return ! operator==( rhs ); +} + +KCModuleInfo::~KCModuleInfo() +{ + delete d; +} + +void KCModuleInfo::init(KService::Ptr s) +{ + _allLoaded = false; + d = new KCModuleInfoPrivate; + + if ( s ) + _service = s; + else + { + kdDebug(712) << "Could not find the service." << endl; + return; + } + + // set the modules simple attributes + setName(_service->name()); + setComment(_service->comment()); + setIcon(_service->icon()); + + _fileName = ( _service->desktopEntryPath() ); + + // library and factory + setLibrary(_service->library()); + + // get the keyword list + setKeywords(_service->keywords()); +} + +void +KCModuleInfo::loadAll() +{ + if( !_service ) /* We have a bogus service. All get functions will return empty/zero values */ + return; + + _allLoaded = true; + + // library and factory + setHandle(_service->property("X-KDE-FactoryName", QVariant::String).toString()); + + QVariant tmp; + + // read weight + tmp = _service->property( "X-KDE-Weight", QVariant::Int ); + setWeight( tmp.isValid() ? tmp.toInt() : 100 ); + + // does the module need super user privileges? + tmp = _service->property( "X-KDE-RootOnly", QVariant::Bool ); + setNeedsRootPrivileges( tmp.isValid() ? tmp.toBool() : false ); + + // does the module need to be shown to root only? + // Deprecated ! KDE 4 + tmp = _service->property( "X-KDE-IsHiddenByDefault", QVariant::Bool ); + setIsHiddenByDefault( tmp.isValid() ? tmp.toBool() : false ); + + // get the documentation path + setDocPath( _service->property( "DocPath", QVariant::String ).toString() ); + + tmp = _service->property( "X-KDE-Test-Module", QVariant::Bool ); + setNeedsTest( tmp.isValid() ? tmp.asBool() : false ); +} + +QString +KCModuleInfo::docPath() const +{ + if (!_allLoaded) + const_cast<KCModuleInfo*>(this)->loadAll(); + + return _doc; +} + +QString +KCModuleInfo::handle() const +{ + if (!_allLoaded) + const_cast<KCModuleInfo*>(this)->loadAll(); + + if (_handle.isEmpty()) + return _lib; + + return _handle; +} + +int +KCModuleInfo::weight() const +{ + if (!_allLoaded) + const_cast<KCModuleInfo*>(this)->loadAll(); + + return _weight; +} + +bool +KCModuleInfo::needsRootPrivileges() const +{ + if (!_allLoaded) + const_cast<KCModuleInfo*>(this)->loadAll(); + + return _needsRootPrivileges; +} + +bool +KCModuleInfo::isHiddenByDefault() const +{ + if (!_allLoaded) + const_cast<KCModuleInfo*>(this)->loadAll(); + + return _isHiddenByDefault; +} + +bool KCModuleInfo::needsTest() const +{ + return d->testModule; +} + +void KCModuleInfo::setNeedsTest( bool val ) +{ + d->testModule = val; +} + + + +// vim: ts=2 sw=2 et diff --git a/kutils/kcmoduleinfo.h b/kutils/kcmoduleinfo.h new file mode 100644 index 000000000..7ccaceb94 --- /dev/null +++ b/kutils/kcmoduleinfo.h @@ -0,0 +1,296 @@ +/* + Copyright (c) 1999 Matthias Hoelzer-Kluepfel <hoelzer@kde.org> + Copyright (c) 2000 Matthias Elter <elter@kde.org> + Copyright (c) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (c) 2003 Matthias Kretz <kretz@kde.org> + + This file is part of the KDE project + + 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. +*/ + +#ifndef KCMODULEINFO_H +#define KCMODULEINFO_H + +#include <kservice.h> + +class QPixmap; +class QString; +class QStringList; + +/** + * @ingroup kcmodule + * A class that provides information about a KCModule + * + * KCModuleInfo provides various technical information, such as icon, library + * etc. about a KCModule.n + * @note Any values set with the set* functions is not + * written back with KCModuleInfo it only reads value from the desktop file. + * + * @internal + * @author Matthias Hoelzer-Kluepfel <mhk@kde.org> + * @author Matthias Elter <elter@kde.org> + * @author Daniel Molkentin <molkentin@kde.org> + * @since 3.2 + * + */ +class KUTILS_EXPORT KCModuleInfo +{ + +public: + + /** + * Constructs a KCModuleInfo. + * @note a KCModuleInfo object will have to be manually deleted, it is not + * done automatically for you. + * @param desktopFile the desktop file representing the module, or + * the name of the module. + */ + KCModuleInfo(const QString& desktopFile); + + /** + * Same as above but takes a KService::Ptr as argument. + * + * @note @p moduleInfo must be a valid pointer. + * + * @param moduleInfo specifies the module + */ + KCModuleInfo( KService::Ptr moduleInfo ); + + + /** + * Same as above but takes a KCModuleInfo as argument. + * + * @param rhs specifies the module + */ + KCModuleInfo( const KCModuleInfo &rhs ); + + /** + * Same as above but creates an empty KCModuleInfo. + * You should not normally call this. + * @since 3.4 + */ + KCModuleInfo(); + + /** + * Assignment operator + */ + KCModuleInfo &operator=( const KCModuleInfo &rhs ); + + /** + * Equal operator + * + * @return true if @p rhs equals itself + */ + + bool operator==( const KCModuleInfo &rhs ) const; + + /** + * @return true if @p rhs is not equal itself + */ + bool operator!=( const KCModuleInfo &rhs ) const; + + /** + * Default destructor. + */ + ~KCModuleInfo(); + + /** + * @return the filename of the .desktop file that describes the KCM + */ + QString fileName() const { return _fileName; } + + /** + * @return the keywords associated with this KCM. + */ + const QStringList &keywords() const { return _keywords; } + + /** + * Returns the module's factory name, if it's set. If not, the library + * name is returned. + * @returns the module's factory name + * @since 3.4 + */ + QString factoryName() const; + + /** + * @return the module\'s (translated) name + */ + QString moduleName() const { return _name; } + // changed from name() to avoid ambiguity with QObject::name() on multiple inheritance + + /** + * @return a KSharedPtr to KService created from the modules .desktop file + */ + KService::Ptr service() const { return _service; } + + /** + * @return the module's (translated) comment field + */ + QString comment() const { return _comment; } + + /** + * @return the module's icon name + */ + QString icon() const { return _icon; } + + /** + * @return the path of the module's documentation + */ + QString docPath() const; + + /** + * @return the library name + */ + QString library() const { return _lib; } + + /** + * @return a handle (usually the contents of the FactoryName field) + */ + QString handle() const; + + /** + * @return the weight of the module which determines the order of the pages in + * the KCMultiDialog. It's set by the X-KDE-Weight field. + */ + int weight() const; + + /** + * @return whether the module might require root permissions + */ + bool needsRootPrivileges() const; + + /** + * @deprecated + * @return the isHiddenByDefault attribute. + */ + bool isHiddenByDefault() const KDE_DEPRECATED; + + + /** + * @returns true if the module should be conditionally + * loaded. + * @since 3.4 + */ + bool needsTest() const; + + +protected: + + /** + * Sets the object's keywords. + * @param keyword the new keywords + */ + void setKeywords(const QStringList &keyword) { _keywords = keyword; } + + /** + * Sets the object's name. + * @param name the new name + */ + void setName(const QString &name) { _name = name; } + + /** + * Sets the object's name. + * @param comment the new comment + */ + void setComment(const QString &comment) { _comment = comment; } + + /** + * Sets the object's icon. + * @param icon the name of the new icon + */ + void setIcon(const QString &icon) { _icon = icon; } + + /** + * Set the object's library + * @param lib the name of the new library without any extensions or prefixs. + */ + void setLibrary(const QString &lib) { _lib = lib; } + + /** + * Sets the factory name + * @param handle The new factory name + */ + void setHandle(const QString &handle) { _handle = handle; } + + /** + * Sets the object's weight property which determines in what + * order modules will be displayed. Default is 100. + * + * @param weight the new weight + */ + void setWeight(int weight) { _weight = weight; } + + + /** + * Sets if the module should be tested for loading. + * @param val the value to set + * @since 3.4 + */ + void setNeedsTest( bool val ); + + /** + * Toggles whether the represented module needs root privileges. + * Use with caution. + * @param needsRootPrivileges if module needs root privilges + */ + void setNeedsRootPrivileges(bool needsRootPrivileges) + { _needsRootPrivileges = needsRootPrivileges; } + + /** + * @deprecated + */ + void setIsHiddenByDefault(bool isHiddenByDefault) + { _isHiddenByDefault = isHiddenByDefault; } + + /** + * Sets the object's documentation path + * @param p the new documentation path + */ + void setDocPath(const QString &p) { _doc = p; } + + /** + * Reads the service entries specific for KCModule from the desktop file. + * The usual desktop entries are read in init. + */ + void loadAll(); + +private: + + /** + * Reads the service entries. Called by the constructors. + */ + void init(KService::Ptr s); + +private: + + // KDE4 These needs to be moved to KCModuleInfoPrivate + QStringList _keywords; + QString _name, _icon, _lib, _handle, _fileName, _doc, _comment; + bool _needsRootPrivileges : 1; + bool _isHiddenByDefault : 1; + bool _allLoaded : 1; + int _weight; + + KService::Ptr _service; + + class KCModuleInfoPrivate; + KCModuleInfoPrivate *d; + +}; + +#endif // KCMODULEINFO_H + +// vim: ts=2 sw=2 et diff --git a/kutils/kcmoduleloader.cpp b/kutils/kcmoduleloader.cpp new file mode 100644 index 000000000..24a5fa8b3 --- /dev/null +++ b/kutils/kcmoduleloader.cpp @@ -0,0 +1,288 @@ +/* + Copyright (c) 1999 Matthias Hoelzer-Kluepfel <hoelzer@kde.org> + Copyright (c) 2000 Matthias Elter <elter@kde.org> + Copyright (c) 2003,2004 Matthias Kretz <kretz@kde.org> + Copyright (c) 2004 Frans Englich <frans.englich@telia.com> + + This file is part of the KDE project + + 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 <qfile.h> +#include <qlabel.h> +#include <qlayout.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kparts/componentfactory.h> + +#include "kcmoduleloader.h" + + +/***************************************************************/ +/** + * When something goes wrong in loading the module, this one + * jumps in as a "dummy" module. + */ +class KCMError : public KCModule +{ + public: + KCMError( const QString& msg, const QString& details, QWidget* parent ) + : KCModule( parent, "KCMError" ) + { + QVBoxLayout* topLayout = new QVBoxLayout( this ); + topLayout->addWidget( new QLabel( msg, this ) ); + topLayout->addWidget( new QLabel( details, this ) ); + } +}; +/***************************************************************/ + + + + +KCModule* KCModuleLoader::load(const KCModuleInfo &mod, const QString &libname, + KLibLoader *loader, ErrorReporting report, QWidget * parent, + const char * name, const QStringList & args ) +{ + // attempt to load modules with ComponentFactory, only if the symbol init_<lib> exists + // (this is because some modules, e.g. kcmkio with multiple modules in the library, + // cannot be ported to KGenericFactory) + KLibrary *lib = loader->library(QFile::encodeName(libname.arg(mod.library()))); + if (lib) { + QString initSym("init_"); + initSym += libname.arg(mod.library()); + + if ( lib->hasSymbol(QFile::encodeName(initSym)) ) + { + KLibFactory *factory = lib->factory(); + if ( factory ) + { + KCModule *module = KParts::ComponentFactory::createInstanceFromFactory<KCModule>( factory, parent, name ? name : mod.handle().latin1(), args ); + if (module) + return module; + } + // else do a fallback + kdDebug(1208) << "Unable to load module using ComponentFactory. Falling back to old loader." << endl; + } + + // get the create_ function + QString factory("create_%1"); + void *create = lib->symbol(QFile::encodeName(factory.arg(mod.handle()))); + + if (create) + { + // create the module + KCModule* (*func)(QWidget *, const char *); + func = (KCModule* (*)(QWidget *, const char *)) create; + return func( parent, name ? name : mod.handle().latin1() ); + } + else + { + QString libFileName = lib->fileName(); + lib->unload(); + return reportError( report, i18n("<qt>There was an error when loading the module '%1'.<br><br>" + "The desktop file (%2) as well as the library (%3) was found but " + "yet the module could not be loaded properly. Most likely " + "the factory declaration was wrong, or the " + "create_* function was missing.</qt>") + .arg( mod.moduleName() ) + .arg( mod.fileName() ) + .arg( libFileName ), + QString::null, parent ); + } + + lib->unload(); + } + return reportError( report, i18n("The specified library %1 could not be found.") + .arg( mod.library() ), QString::null, parent ); + return 0; +} + +KCModule* KCModuleLoader::loadModule(const KCModuleInfo &mod, bool withfallback, QWidget * parent, const char * name, const QStringList & args ) +{ + return loadModule( mod, None, withfallback, parent, name, args ); +} + +KCModule* KCModuleLoader::loadModule(const KCModuleInfo &mod, ErrorReporting report, bool withfallback, QWidget * parent, const char * name, const QStringList & args ) +{ + /* + * Simple libraries as modules are the easiest case: + * We just have to load the library and get the module + * from the factory. + */ + + if ( !mod.service() ) + { + return reportError( report, + i18n("The module %1 could not be found.") + .arg( mod.moduleName() ), i18n("<qt><p>The diagnostics is:<br>The desktop file %1 could not be found.</qt>").arg(mod.fileName()), parent ); + } + + if (!mod.library().isEmpty()) + { + // get the library loader instance + + KLibLoader *loader = KLibLoader::self(); + + KCModule *module = load(mod, "kcm_%1", loader, report, parent, name, args ); + /* + * Only try to load libkcm_* if it exists, otherwise KLibLoader::lastErrorMessage would say + * "libkcm_foo not found" instead of the real problem with loading kcm_foo. + */ + if (!KLibLoader::findLibrary( QCString( "libkcm_" ) + QFile::encodeName( mod.library() ) ).isEmpty() ) + module = load(mod, "libkcm_%1", loader, report, parent, name, args ); + if (module) + return module; + return reportError( report, + i18n("The module %1 could not be loaded.") + .arg( mod.moduleName() ), QString::null, parent ); + } + + /* + * Ok, we could not load the library. + * Try to run it as an executable. + * This must not be done when calling from kcmshell, or you'll + * have infinite recursion + * (startService calls kcmshell which calls modloader which calls startService...) + * + */ + if(withfallback) + { + KApplication::startServiceByDesktopPath(mod.fileName(), QString::null); + } + else + { + return reportError( report, + i18n("The module %1 is not a valid configuration module.") + .arg( mod.moduleName() ), i18n("<qt><p>The diagnostics is:<br>The desktop file %1 does not specify a library.</qt>").arg(mod.fileName()), parent ); + } + + return 0; +} + +KCModule* KCModuleLoader::loadModule(const QString &module, QWidget *parent, + const char *name, const QStringList & args) +{ + return loadModule(KCModuleInfo(module), None, false, parent, name, args); +} + +KCModule* KCModuleLoader::loadModule(const QString &module, ErrorReporting + report, QWidget *parent, const char *name, const QStringList & args) +{ + return loadModule(KCModuleInfo(module), report, false, parent, name, args); +} + +void KCModuleLoader::unloadModule(const KCModuleInfo &mod) +{ + // get the library loader instance + KLibLoader *loader = KLibLoader::self(); + + // try to unload the library + QString libname("libkcm_%1"); + loader->unloadLibrary(QFile::encodeName(libname.arg(mod.library()))); + + libname = "kcm_%1"; + loader->unloadLibrary(QFile::encodeName(libname.arg(mod.library()))); +} + +void KCModuleLoader::showLastLoaderError(QWidget *parent) +{ + KMessageBox::detailedError(parent, + i18n("There was an error loading the module."),i18n("<qt><p>The diagnostics is:<br>%1" + "<p>Possible reasons:</p><ul><li>An error occurred during your last " + "KDE upgrade leaving an orphaned control module<li>You have old third party " + "modules lying around.</ul><p>Check these points carefully and try to remove " + "the module mentioned in the error message. If this fails, consider contacting " + "your distributor or packager.</p></qt>") + .arg(KLibLoader::self()->lastErrorMessage())); + +} + +bool KCModuleLoader::testModule( const QString& module ) +{ + return testModule( KCModuleInfo( module ) ); +} + +bool KCModuleLoader::testModule( const KCModuleInfo& module ) +{ + if (!module.service()) + { + kdDebug(1208) << "Module '" << module.fileName() << "' not found." << endl; + return true; + } + + bool doLoad = module.service()->property( "X-KDE-Test-Module", QVariant::Bool ).toBool(); + if( !doLoad ) + { + return true; + } + else + { + /** + * If something fails we return true - we can't risk functionality becoming + * unavailable because of a buggy test. Furthermore, the error needs to + * show so it is discovered. KCModuleProxy will detect the error and load + * a corresponding KCMError. + * */ + KLibLoader* loader = KLibLoader::self(); + KLibrary* library = loader->library( QFile::encodeName((QString("kcm_%1").arg(module.library()))) ); + if( library ) + { + void *test_func = library->symbol( QString("test_%1").arg(module.factoryName()).utf8() ); + if( test_func ) + { + bool (*func)() = (bool(*)())test_func; + if( func() ) + { + return true; + } + else + { + return false; + } + } + else + { + kdDebug(1208) << "The test function for module '" << module.fileName() << "' could not be found." << endl; + return true; + } + } + kdDebug(1208) << "The library '" << module.library() << "' could not be found." << endl; + return true; + } +} + +KCModule* KCModuleLoader::reportError( ErrorReporting report, const QString & text, + QString details, QWidget * parent ) +{ + if( details.isNull() ) + details = i18n("<qt><p>The diagnostics is:<br>%1" + "<p>Possible reasons:</p><ul><li>An error occurred during your last " + "KDE upgrade leaving an orphaned control module<li>You have old third party " + "modules lying around.</ul><p>Check these points carefully and try to remove " + "the module mentioned in the error message. If this fails, consider contacting " + "your distributor or packager.</p></qt>").arg(KLibLoader::self()->lastErrorMessage()); + if( report & Dialog ) + KMessageBox::detailedError( parent, text, details ); + if( report & Inline ) + return new KCMError( text, details, parent ); + return 0; +} + +// vim: ts=2 sw=2 et + diff --git a/kutils/kcmoduleloader.h b/kutils/kcmoduleloader.h new file mode 100644 index 000000000..8aa6aac69 --- /dev/null +++ b/kutils/kcmoduleloader.h @@ -0,0 +1,220 @@ +/* + Copyright (c) 1999 Matthias Hoelzer-Kluepfel <hoelzer@kde.org> + Copyright (c) 2002-2003 Daniel Molkentin <molkentin@kde.org> + + This file is part of the KDE project + + 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. +*/ +#ifndef MODLOADER_H +#define MODLOADER_H + +#include <kcmodule.h> +#include <kcmoduleinfo.h> + +class QWidget; +class KLibLoader; + +/** + * @ingroup kcmodule + * @brief Loads a KControl Module. + * + * KCModuleLoader tries in several ways + * to locate and load a KCModule. If loading fails a + * zero pointer is returned. \n + * It is very unlikely KCModuleLoader is what you want + * and @ref KCModuleProxy suits your needs. + * + * @author Matthias Hoelzer-Kluepfel <mhk@kde.org> + * @author Frans Englich <frans.englich@telia.com> + * @since 3.2 + * @internal +**/ +class KUTILS_EXPORT KCModuleLoader +{ + public: + + /** + * Loads a @ref KCModule. If loading fails a zero pointer is returned. + * @param module what module to load + * @param withFallback if true and loading failed a separate window + * with the module may appear and a zero pointer is a returned + * @param parent The parent widget + * @param name The widget's name + * @param args A list of arguments for the module to load + * + * @return a pointer to the loaded @ref KCModule + * + * @deprecated use the function which explicitly states the error reporting + * method + */ + static KCModule *loadModule(const KCModuleInfo &module, bool withFallback=true, + QWidget * parent = 0, const char * name = 0, + const QStringList & args = QStringList() ) KDE_DEPRECATED; + + /** + * Loads a @ref KCModule. If loading fails a zero pointer is returned. + * @param module what module to load + * with the module may appear and a zero pointer is a returned + * @param parent The parent widget + * @param name The widget's name + * @param args A list of arguments for the module to load + * + * @deprecated use the function which explicitly states the error reporting + * method + */ + static KCModule *loadModule(const QString &module, QWidget *parent = 0, + const char *name = 0, const QStringList & args = QStringList()) KDE_DEPRECATED; + + /** + * Determines the way errors are reported + */ + enum ErrorReporting { + /** + * no error reporting is done + * */ + None = 0, + /** + * the error report is shown instead of the + * KCModule that should have * been loaded + */ + Inline = 1, + /** + * shows a dialog with the error report + */ + Dialog = 2, + /** + * does both Inline and Dialog + */ + Both = 3 + }; + + /** + * Loads a @ref KCModule. If loading fails a zero pointer is returned. + * @param module what module to load + * @param report see ErrorReporting + * @param withFallback if true and loading failed a separate window + * with the module may appear and a zero pointer is a returned + * @param parent The parent widget + * @param name The widget's name + * @param args A list of arguments for the module to load + * + * @return a pointer to the loaded @ref KCModule + * @since 3.4 + */ + static KCModule *loadModule(const KCModuleInfo &module, ErrorReporting + report, bool withFallback=true, QWidget * parent = 0, + const char * name = 0, const QStringList & args = QStringList() ); + + /** + * Loads a @ref KCModule. If loading fails a zero pointer is returned. + * @param module what module to load + * @param report see ErrorReporting + * with the module may appear and a zero pointer is a returned + * @param parent The parent widget + * @param name The widget's name + * @param args A list of arguments for the module to load + * + * @return a pointer to the loaded @ref KCModule + * @since 3.4 + */ + static KCModule *loadModule(const QString &module, ErrorReporting + report, QWidget *parent = 0, const char *name = 0, + const QStringList & args = QStringList()); + + /** + * Unloads the module's library + * @param mod What module to unload for + */ + static void unloadModule(const KCModuleInfo &mod); + + /** + * Display a message box explaining an error occured and possible + * reasons to why. + * + * @deprecated Use a constructor with ErrorReporting set to Dialog to show a + * message box like this function did. + */ + static void showLastLoaderError(QWidget *parent) KDE_DEPRECATED; + + + /** + * Checks whether an KCModule should be shown by running its + * test function. If it is unsure whether a module should be shown, it should + * be made available, leaving the decision to the user. + * If false is returned, the module should not be loaded in any interface. + * + * A module declares it needs to be tested by having "X-KDE-Test-Module=true" in + * its desktop file. When that line exists, the following code must be available + * in the module's library: + * + * \code + * extern "C" + * { + * bool test_moduleName() + * { + * // Code testing for hardware/software presence. + * return true; // and the modue will be loaded. + * } + * + * } + * \endcode + * + * where moduleName is the library name for the module. + * + * @param module the module to check + * @returns true if the module should be loaded + * @since 3.4 + */ + static bool testModule( const QString& module ); + + /** + * Convenience function, essentially the same as above. + * + * @param module the module to check + * @returns true if the module should be loaded + * @since 3.4 + */ + static bool testModule( const KCModuleInfo& module ); + + /** + * Returns a KCModule containing the messages @p report and @p text. + * + * @param report the type of error reporting, see ErrorReporting + * @param text the main message + * @param details any additional details + * @param parent The parent widget + * + * @since 3.4 + * @internal + */ + static KCModule* reportError( ErrorReporting report, const QString & text, + QString details, QWidget * parent ); + + private: + + /** + * Internal loader called by the public loaders. + * @internal + */ + static KCModule* load(const KCModuleInfo &mod, const QString &libname, + KLibLoader *loader, ErrorReporting report, QWidget * parent = 0, + const char * name = 0, const QStringList & args = QStringList() ); + +}; + +// vim: ts=2 sw=2 et +#endif // MODLOADER_H + diff --git a/kutils/kcmoduleproxy.cpp b/kutils/kcmoduleproxy.cpp new file mode 100644 index 000000000..3822db841 --- /dev/null +++ b/kutils/kcmoduleproxy.cpp @@ -0,0 +1,649 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Frans Englich <frans.englich@telia.com> + Copyright (C) 2003 Matthias Kretz <kretz@kde.org> + + 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 <qapplication.h> +#include <qcursor.h> +#include <qdatastream.h> +#include <qevent.h> +#include <qfileinfo.h> +#include <qframe.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpoint.h> +#include <qscrollview.h> +#include <qtextstream.h> +#include <qvbox.h> +#include <qwhatsthis.h> +#include <qwidget.h> + +#include <dcopclient.h> +#include <qxembed.h> + +#include <kapplication.h> +#include <kaboutdata.h> +#include <kcmodule.h> +#include <kcmoduleinfo.h> +#include <kcmoduleloader.h> +#include <kdebug.h> +#include <kdialog.h> +#include <klocale.h> +#include <kprocess.h> +#include <kservice.h> +#include <kstandarddirs.h> +#include <kuser.h> + +#include <X11/Xlib.h> + +#include "kcmoduleproxy.h" +#include "kcmoduleproxyIface.h" +#include "kcmoduleproxyIfaceImpl.h" + +/***************************************************************/ +class KCModuleProxy::KCModuleProxyPrivate +{ + public: + KCModuleProxyPrivate( const KCModuleInfo & info ) + : args( 0 ) + , kcm( 0 ) + //, view( 0 ) + , embedWidget( 0 ) + , rootProcess ( 0 ) + , embedFrame ( 0 ) + , dcopObject( 0 ) + , dcopClient( 0 ) + , topLayout( 0 ) + , rootCommunicator( 0 ) + , rootInfo( 0 ) + , modInfo( info ) + , withFallback( false ) + , changed( false ) + , rootMode( false ) + , bogusOccupier( false ) + , isInitialized( false ) + {} + + ~KCModuleProxyPrivate() + { + delete rootInfo; // Delete before embedWidget! + delete embedWidget; // Delete before embedFrame! + delete embedFrame; + delete dcopClient; + delete dcopObject; + delete rootCommunicator; + delete rootProcess; + delete kcm; + } + + QStringList args; + KCModule *kcm; + QXEmbed *embedWidget; + KProcess *rootProcess; + QVBox *embedFrame; + KCModuleProxyIfaceImpl *dcopObject; + DCOPClient *dcopClient; + QVBoxLayout *topLayout; /* Contains QScrollView view, and root stuff */ + KCModuleProxyRootCommunicatorImpl *rootCommunicator; + QLabel *rootInfo; + QCString dcopName; + KCModuleInfo modInfo; + bool withFallback; + bool changed; + bool rootMode; + bool bogusOccupier; + bool isInitialized; +}; +/***************************************************************/ + + + +/* + TODO: + + - How KCModuleProxy behaves wrt memory leaks and behavior, when exiting + from root mode is not tested, because no code make use of it. It needs + work, if it should be used. + + - Should write a document which outlines test cases, to avoid + regressions. This class is a hazard. + + - Two Layout problems in runAsRoot: + * lblBusy doesn't show + * d->kcm/d->rootInfo doesn't get it right when the user + presses cancel in the kdesu dialog + + - Resizing horizontally is contrained; minimum size is set somewhere. + It appears to be somehow derived from the module's size. + + - Prettify: set icon in KCMultiDialog. + + - Perhaps it's possible to link against kdesu such that + the dialog is in process? + + */ +/***************************************************************/ +KCModule * KCModuleProxy::realModule() const +{ + + /* + * Note, don't call any function that calls realModule() since + * that leads to an infinite loop. + */ + + kdDebug(711) << k_funcinfo << endl; + + /* Already loaded */ + if( d->kcm ) + return d->kcm; + + /* /We/ have no kcm, but kcmshell running with root prevs does.. */ + if( d->rootMode ) + return 0; + + QApplication::setOverrideCursor( Qt::WaitCursor ); + + KCModuleProxy * that = const_cast<KCModuleProxy*>( this ); + + if( !d->isInitialized ) + { + d->dcopName = moduleInfo().handle().prepend("KCModuleProxy-").utf8(); + d->topLayout = new QVBoxLayout( that, 0, 0, "topLayout" ); + + d->isInitialized = true; + } + + if( !d->dcopClient ) + d->dcopClient = new DCOPClient(); + + if( !d->dcopClient->isRegistered() ) + d->dcopClient->registerAs( d->dcopName, false ); + + d->dcopClient->setAcceptCalls( true ); + + if( d->dcopClient->appId() == d->dcopName || d->bogusOccupier ) + { /* We got the name we requested, because no one was before us, + * or, it was an random application which had picked that name */ + kdDebug(711) << "Module not already loaded, loading module" << endl; + + d->dcopObject = new KCModuleProxyIfaceImpl( d->dcopName, that ); + + d->kcm = KCModuleLoader::loadModule( moduleInfo(), KCModuleLoader::Inline, d->withFallback, + that, name(), d->args ); + + connect( d->kcm, SIGNAL( changed( bool ) ), + SLOT(moduleChanged(bool)) ); + connect( d->kcm, SIGNAL( destroyed() ), + SLOT( moduleDestroyed() ) ); + connect( d->kcm, SIGNAL(quickHelpChanged()), + SIGNAL(quickHelpChanged())); + QWhatsThis::add( that, d->kcm->quickHelp() ); + + d->topLayout->addWidget( d->kcm ); + + if ( !d->rootInfo && /* If it's already done */ + moduleInfo().needsRootPrivileges() /* root, anyone? */ && + !KUser().isSuperUser() ) /* Not necessary if we're root */ + { + + d->rootInfo = new QLabel( that, "rootInfo" ); + d->topLayout->insertWidget( 0, d->rootInfo ); + + d->rootInfo->setFrameShape(QFrame::Box); + d->rootInfo->setFrameShadow(QFrame::Raised); + + const QString msg = d->kcm->rootOnlyMsg(); + if( msg.isEmpty() ) + d->rootInfo->setText(i18n( + "<b>Changes in this section requires root access.</b><br />" + "Click the \"Administrator Mode\" button to " + "allow modifications.")); + else + d->rootInfo->setText(msg); + + QWhatsThis::add( d->rootInfo, i18n( + "This section requires special permissions, probably " + "for system-wide changes; therefore, it is " + "required that you provide the root password to be " + "able to change the module's properties. If " + "you do not provide the password, the module will be " + "disabled.")); + } + } + else + { + kdDebug(711) << "Module already loaded, loading KCMError" << endl; + + d->dcopClient->detach(); + /* Re-register as anonymous */ + d->dcopClient->attach(); + + d->dcopClient->setNotifications( true ); + connect( d->dcopClient, SIGNAL( applicationRemoved( const QCString& )), + SLOT( applicationRemoved( const QCString& ))); + + /* Figure out the name of where the module is already loaded */ + QByteArray replyData, data; + QCString replyType; + QString result; + QDataStream arg, stream( replyData, IO_ReadOnly ); + + if( d->dcopClient->call( d->dcopName, d->dcopName, "applicationName()", + data, replyType, replyData )) + { + stream >> result; + + d->kcm = KCModuleLoader::reportError( KCModuleLoader::Inline, + i18n( "Argument is application name", "This configuration section is " + "already opened in %1" ).arg( result ), " ", that ); + + d->topLayout->addWidget( d->kcm ); + } + else + { + kdDebug(711) << "Calling KCModuleProxy's DCOP interface for fetching the name failed." << endl; + d->bogusOccupier = true; + QApplication::restoreOverrideCursor(); + return realModule(); + } + } + + QApplication::restoreOverrideCursor(); + + return d->kcm; +} + +void KCModuleProxy::applicationRemoved( const QCString& app ) +{ + if( app == d->dcopName ) + { + /* Violence: Get rid of KCMError & CO, so that + * realModule() attempts to reload the module */ + delete d->kcm; + d->kcm = 0; + d->dcopClient->setNotifications( false ); + realModule(); + d->kcm->show(); + } +} + +void KCModuleProxy::showEvent( QShowEvent * ev ) +{ + + kdDebug(711) << k_funcinfo << endl; + ( void )realModule(); + + /* We have no kcm, if we're in root mode */ + if( d->kcm ) + d->kcm->show(); + + QWidget::showEvent( ev ); + +} + +void KCModuleProxy::runAsRoot() +{ + if ( !moduleInfo().needsRootPrivileges() ) + return; + + QApplication::setOverrideCursor( Qt::WaitCursor ); + + delete d->rootProcess; + delete d->embedWidget; + delete d->embedFrame; + + d->embedFrame = new QVBox( this, "embedFrame" ); + d->embedFrame->setFrameStyle( QFrame::Box | QFrame::Raised ); + + QPalette pal( red ); + pal.setColor( QColorGroup::Background, + colorGroup().background() ); + d->embedFrame->setPalette( pal ); + d->embedFrame->setLineWidth( 2 ); + d->embedFrame->setMidLineWidth( 2 ); + d->topLayout->addWidget(d->embedFrame,1); + + d->embedWidget = new QXEmbed( d->embedFrame, "embedWidget" ); + + d->embedFrame->show(); + + QLabel *lblBusy = new QLabel(i18n("<big>Loading...</big>"), d->embedWidget, "lblBusy" ); + lblBusy->setTextFormat(RichText); + lblBusy->setAlignment(AlignCenter); + lblBusy->setGeometry(0,0, d->kcm->width(), d->kcm->height()); + lblBusy->show(); + + deleteClient(); + /* The DCOP registration is now gone, and it will occur again when kcmshell soon + * registers. Here's a race condition in other words, but how likely is that? + * + * - It's a user initiated action, which means the user have to do weird stuff, very + * quick. + * - If the user _do_ manage to fsck up, the code will recover gracefully, see realModule(). + * + * So no worry. At the end of this function, communication with + * the DCOP object is established. + */ + + /* Prepare the process to run the kcmshell */ + QString cmd = moduleInfo().service()->exec().stripWhiteSpace(); + if (cmd.left(5) == "kdesu") + { + cmd = cmd.remove(0,5).stripWhiteSpace(); + + /* Remove all kdesu switches */ + while( cmd.length() > 1 && cmd[ 0 ] == '-' ) + cmd = cmd.remove( 0, cmd.find( ' ' ) ).stripWhiteSpace(); + } + + if (cmd.left(8) == "kcmshell") + cmd = cmd.remove(0,8).stripWhiteSpace(); + + /* Run the process */ + QString kdesu = KStandardDirs::findExe("kdesu"); + if (!kdesu.isEmpty()) + { + + d->rootProcess = new KProcess; + + *d->rootProcess << kdesu; + *d->rootProcess << "--nonewdcop" << "-n" << "-d" << QString( "-i%1" ).arg(moduleInfo().icon()); + + *d->rootProcess << QString("%1 %2 --embed-proxy %3 --lang %4").arg(locate("exe", "kcmshell")) + .arg(cmd).arg(d->embedWidget->winId()).arg(KGlobal::locale()->language()); + + connect(d->rootProcess, SIGNAL(processExited(KProcess*)), SLOT(rootExited())); + + if ( !d->rootProcess->start( KProcess::NotifyOnExit )) + { + d->rootMode = false; + rootExited(); + } + else + { + d->rootMode = true; + kapp->dcopClient(); + d->rootCommunicator = new KCModuleProxyRootCommunicatorImpl( d->dcopName + "-RootCommunicator", this ); + } + + delete lblBusy; + QApplication::restoreOverrideCursor(); + return; + } + + /* Clean up in case of failure */ + delete d->embedWidget; + d->embedWidget = 0; + delete d->embedFrame; + d->embedFrame = 0; + + QApplication::restoreOverrideCursor(); +} + +void KCModuleProxy::rootExited() +{ + kdDebug(711) << k_funcinfo << endl; + + if ( d->embedWidget->embeddedWinId() ) + XDestroyWindow(qt_xdisplay(), d->embedWidget->embeddedWinId()); + + delete d->embedWidget; + d->embedWidget = 0; + + delete d->rootProcess; + d->rootProcess = 0; + + delete d->embedFrame; + d->embedFrame=0; + + delete d->rootCommunicator; + d->rootCommunicator = 0; + + /* Such that the "ordinary" module loads again */ + d->rootMode = false; + + d->topLayout->invalidate(); + + QShowEvent ev; + showEvent( &ev ); + + moduleChanged( false ); + emit childClosed(); +} + +KCModuleProxy::~KCModuleProxy() +{ + deleteClient(); + KCModuleLoader::unloadModule(moduleInfo()); + + delete d; +} + +void KCModuleProxy::deleteClient() +{ + if( d->embedWidget ) + XKillClient(qt_xdisplay(), d->embedWidget->embeddedWinId()); + + + delete d->kcm; + d->kcm = 0; + + delete d->dcopObject; + d->dcopObject = 0; + + if( d->dcopClient && !d->dcopClient->detach() ) + kdDebug(711) << "Unregistering from DCOP failed." << endl; + + delete d->dcopClient; + d->dcopClient = 0; + + kapp->syncX(); + +} + +void KCModuleProxy::moduleChanged( bool c ) +{ + if( d->changed == c ) + return; + + d->changed = c; + emit changed( c ); + emit changed( this ); +} + +void KCModuleProxy::moduleDestroyed() +{ + d->kcm = 0; +} + +KCModuleProxy::KCModuleProxy( const KService::Ptr & service, bool withFallback, + QWidget * parent, const char * name, const QStringList & args) + : QWidget( parent, name ) +{ + init( KCModuleInfo( service )); + d->args = args; + d->withFallback = withFallback; +} + +KCModuleProxy::KCModuleProxy( const KCModuleInfo & info, bool withFallback, + QWidget * parent, const char * name, const QStringList & args ) + : QWidget( parent, name ) +{ + init( info ); + d->args = args; + d->withFallback = withFallback; +} + +KCModuleProxy::KCModuleProxy( const QString& serviceName, bool withFallback, + QWidget * parent, const char * name, + const QStringList & args) + : QWidget( parent, name ) +{ + init( KCModuleInfo( serviceName )); + d->args = args; + d->withFallback = withFallback; +} + +void KCModuleProxy::init( const KCModuleInfo& info ) +{ + kdDebug(711) << k_funcinfo << endl; + + d = new KCModuleProxyPrivate( info ); + + /* This is all we do for now; all the heavy work is + * done in realModule(). It's called when the module + * _actually_ is needed, in for example showEvent(). + * The module is loaded "on demand" -- lazy loading. + */ + +} + +void KCModuleProxy::load() +{ + + if( d->rootMode ) + callRootModule( "load()" ); + else if( realModule() ) + { + d->kcm->load(); + moduleChanged( false ); + } +} + +void KCModuleProxy::save() +{ + if( d->rootMode ) + callRootModule( "save()" ); + else if( d->changed && realModule() ) + { + d->kcm->save(); + moduleChanged( false ); + } +} + +void KCModuleProxy::callRootModule( const QCString& function ) +{ + QByteArray sendData, replyData; + QCString replyType; + + /* Note, we don't use d->dcopClient here, because it's used for + * the loaded module(and it's not "us" when this function is called) */ + if( !kapp->dcopClient()->call( d->dcopName, d->dcopName, function, sendData, + replyType, replyData, true, -1 )) + kdDebug(711) << "Calling function '" << function << "' failed." << endl; + +} + +void KCModuleProxy::defaults() +{ + if( d->rootMode ) + callRootModule( "defaults()" ); + if( realModule() ) + d->kcm->defaults(); +} + +QString KCModuleProxy::quickHelp() const +{ + + if( !d->rootMode ) + return realModule() ? realModule()->quickHelp() : QString::null; + else + { + QByteArray data, replyData; + QCString replyType; + + if (kapp->dcopClient()->call(d->dcopName, d->dcopName, "quickHelp()", + data, replyType, replyData)) + kdDebug(711) << "Calling DCOP function bool changed() failed." << endl; + else + { + QDataStream reply(replyData, IO_ReadOnly); + if (replyType == "QString") + { + QString result; + reply >> result; + return result; + } + else + kdDebug(711) << "DCOP function changed() returned mumbo jumbo." << endl; + } + return QString::null; + } +} + +const KAboutData * KCModuleProxy::aboutData() const +{ + if( !d->rootMode ) + return realModule() ? realModule()->aboutData() : 0; + else + /* This needs fixing, perhaps cache a KAboutData copy + * while in root mode? */ + return 0; + + +} + +int KCModuleProxy::buttons() const +{ + return realModule() ? realModule()->buttons() : + KCModule::Help | KCModule::Default | KCModule::Apply ; +} + +QString KCModuleProxy::rootOnlyMsg() const +{ + return realModule() ? realModule()->rootOnlyMsg() : QString::null; +} + +bool KCModuleProxy::useRootOnlyMsg() const +{ + return realModule() ? realModule()->useRootOnlyMsg() : true; +} + +KInstance * KCModuleProxy::instance() const +{ + return realModule() ? realModule()->instance() : 0; +} + +bool KCModuleProxy::changed() const +{ + return d->changed; +} + +const KCModuleInfo& KCModuleProxy::moduleInfo() const +{ + return d->modInfo; +} + +bool KCModuleProxy::rootMode() const +{ + return d->rootMode; +} + +QCString KCModuleProxy::dcopName() const +{ + return d->dcopName; +} + +void KCModuleProxy::emitQuickHelpChanged() +{ + emit quickHelpChanged(); +} + +/***************************************************************/ +#include "kcmoduleproxy.moc" + +// vim: sw=4 ts=4 noet diff --git a/kutils/kcmoduleproxy.h b/kutils/kcmoduleproxy.h new file mode 100644 index 000000000..b785bbaca --- /dev/null +++ b/kutils/kcmoduleproxy.h @@ -0,0 +1,357 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Matthias Kretz <kretz@kde.org> + Copyright (C) 2004 Frans Englich <frans.englich@telia.com> + + 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. + +*/ + +#ifndef KCMODULEPROXY_H +#define KCMODULEPROXY_H + +#include <qwidget.h> +#include <qstringlist.h> + +#include <kservice.h> +#include <kdelibs_export.h> + +class KAboutData; +class KCModule; +class KCModuleInfo; +class KInstance; +class KProcess; + +/** + * @ingroup kcmodule + * + * @brief Encapsulates a KCModule for embedding. + * + * KCModuleProxy is a wrapper for KCModule intended for cases where + * modules are to be displayed. It ensures layout is consistent, handles + * root/administrator modules and in general takes care of the details + * needed for making a module available in an interface. A KCModuleProxy + * can be treated as a QWidget, without worrying about the details specific + * for modules such as library loading. KCModuleProxy is not a sub class of KCModule + * but its API closely resembles KCModule's.\n + * Usually, an instance is created by passing one of the constructors a KService::Ptr, + * KCModuleInfo or simply the name of the module and then added to the layout as any + * other widget. \n + * When the user have changed the module, changed( bool ) as well as changed ( KCModuleProxy * ) + * is emitted. KCModuleProxy does not take care of prompting for saving - if the object is deleted while + * changes is not saved the changes will be lost. changed() returns true if changes are unsaved. \n + * \n + * KCModuleProxy does not take care of authorization of KCModules. \n + * KCModuleProxy do lazy loading, meaning the library will not be loaded or + * any other initialization done before its show() function is called. This means + * modules will only be loaded when they are actually needed as well as it is possible to + * load many KCModuleProxy without any speed penalty. + * + * KCModuleProxy should be used in all cases where modules are embedded in order to + * promote code efficiency and usability consistency. + * + * @author Frans Englich <frans.englich@telia.com> + * @author Matthias Kretz <kretz@kde.org> + * + */ +class KUTILS_EXPORT KCModuleProxy : public QWidget +{ +Q_OBJECT + + friend class KCModuleProxyRootCommunicatorImpl; + +public: + + /** + * Constructs a KCModuleProxy from a KCModuleInfo class. + * + * @param info The KCModuleInfo to construct the module from. + * @param withFallback If set to true and loading of the module fails, + * a alternative will be tried, resulting in the module appearing in its + * own window, if at all. + * The embedded module will be load()ed. + * @param parent the parent QWidget. + * @param name the module's name. + * @param args This is used in the implementation and is internal. + * Use the default. + */ + KCModuleProxy( const KCModuleInfo & info, bool withFallback = true, + QWidget * parent = 0, const char * name = 0, + const QStringList & args = QStringList() ); + + /** + * Constructs a KCModuleProxy from a module's service name, which is + * equivalent to the desktop file for the kcm without the ".desktop" part. + * Otherwise equal to the one above. + * + * @param serviceName The module's service name to construct from. + * @param withFallback If set to true and loading of the module fails, + * a alternative will be tried, resulting in the module appearing in its + * own window, if at all. + * The embedded module will be load()ed. + * @param parent the parent QWidget. + * @param name the module's name. + * @param args This is used in the implementation and is internal. + * Use the default. + */ + KCModuleProxy( const QString& serviceName, bool withFallback = true, + QWidget * parent = 0, const char * name = 0, + const QStringList & args = QStringList() ); + + /** + * Constructs a KCModuleProxy from KService. Otherwise equal to the one above. + * + * @param service The KService to construct from. + * @param withFallback If set to true and loading of the module fails, + * a alternative will be tried, resulting in the module appearing in its + * own window, if at all. + * The embedded module will be load()ed. + * @param parent the parent QWidget. + * @param name the module's name. + * @param args This is used in the implementation and is internal. + * Use the default. + */ + KCModuleProxy( const KService::Ptr& service, bool withFallback = true, + QWidget * parent = 0, const char * name = 0, + const QStringList & args = QStringList() ); + + /** + * Default destructor + */ + ~KCModuleProxy(); + + /** + * Calling it will cause the contained module to + * run its load() routine. + */ + void load(); + + /** + * Calling it will cause the contained module to + * run its save() routine. + * + * If the module was not modified, it will not be asked + * to save. + */ + void save(); + + /** + * @return the module's quickHelp(); + */ + QString quickHelp() const; + + /** + * @return the module's aboutData() + */ + const KAboutData * aboutData() const; + + /** + * @return what buttons the module + * needs + */ + int buttons() const; + + /** + * @return The module's custom root + * message, if it has one + * @deprecated + */ + QString rootOnlyMsg() const; + //KDE4 remove. There's a limit for convenience functions, + // this one's available via moduleInfo()-> and realModule()-> + + /** + * @return If the module is a root module. + * @deprecated + */ + bool useRootOnlyMsg() const; + //KDE4 remove. There's a limit for convenience functions, + // this one's available via moduleInfo()-> and realModule()-> + + /** + * Returns the embedded KCModule's KInstance. + * @return The module's KInstance. + * @deprecated + */ + KInstance * instance() const; + //KDE4 remove. There's a limit for convenience functions, + // this one's available via realModule() + + /** + * @return true if the module is modified + * and needs to be saved. + */ + bool changed() const; + + /** + * Returns whether the module is running in root mode. A module is in root mode + * when runAsRoot() has been called. A session under root user will never reach + * root mode. + * + * @note realModule() will return null when the module is running in root mode. + * + * @return true if the module is running with root privileges + * @since 3.4 + */ + bool rootMode() const; + + /** + * Access to the actual module. However, if the module is + * running in root mode, see rootMode(), this function returns + * a NULL pointer, since the module is in another process. It may also + * return NULL if anything goes wrong. + * + * @return the encapsulated module. + */ + KCModule* realModule() const; + + /** + * @return a KCModuleInfo for the encapsulated + * module + */ + const KCModuleInfo& moduleInfo() const; + + /** + * Returns the DCOP the module's DCOPClient + * and DCOPObject has(they are identical). + * + * @since 3.4 + */ + QCString dcopName() const; + +public slots: + + /** + * Calling this will cause the module to be run in + * "administrator mode". + * + * @since 3.4 + */ + void runAsRoot(); + + /** + * Calling it will cause the contained module to + * load its default values. + */ + void defaults(); + + /** + * Calling this, results in deleting the contained + * module, and unregistering from DCOP. A similar result is achieved + * by deleting the KCModuleProxy itself. + * + * @since 3.4 + */ + void deleteClient(); + +signals: + + /* + * This signal is emitted when the contained module is changed. + */ + void changed( bool state ); + + /** + * This is emitted in the same situations as in the one above. Practical + * when several KCModuleProxys are loaded. + * + * @since 3.4 + */ + void changed( KCModuleProxy* mod ); + + /** + * When a module running with root privileges and exits, returns to normal mode, the + * childClosed() signal is emitted. + * + * @since 3.4 + */ + void childClosed(); + + /* + * This signal is relayed from the encapsulated module, and + * is equivalent to the module's own quickHelpChanged() signal. + * + * @since 3.4 + */ + void quickHelpChanged(); + +protected: + + /** + * Reimplemented for internal purposes. Makes sure the encapsulated + * module is loaded before the show event is taken care of. + */ + void showEvent( QShowEvent * ); + + /** + * Internal intialization function, called by the constructors. + * + * @internal + * @since 3.4 + */ + void init( const KCModuleInfo& info ); + + + /** + * Emits the quickHelpChanged signal. + * @since 3.4 + */ + void emitQuickHelpChanged(); + +private slots: + + /** + * Calls the function @p function of the root module's KCModuleProxy + * DCOP interface. + * + * @param function the function signature of the function to call. + * @since 3.4 + */ + void callRootModule( const QCString& function ); + + /** + * This is called when the module exits from root mode. It zeroes + * pointers, deletes the embed window, and so forth. + * + * @since 3.4 + */ + void rootExited(); + + /** + * Makes sure the proper variables is set and signals are emitted. + */ + void moduleChanged( bool ); + + /** + * Zeroes d->kcm + */ + void moduleDestroyed(); + + /** + * Gets called by DCOP when an application closes. + * Is used to (try to) reload a KCM which previously + * was loaded. + * + * @since 3.4 + */ + void applicationRemoved( const QCString& app ); + +private: + + class KCModuleProxyPrivate; + KCModuleProxyPrivate * d; +}; + +#endif // KCMODULEPROXY_H +// vim: sw=4 ts=4 noet diff --git a/kutils/kcmoduleproxyIface.h b/kutils/kcmoduleproxyIface.h new file mode 100644 index 000000000..8159a843f --- /dev/null +++ b/kutils/kcmoduleproxyIface.h @@ -0,0 +1,121 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004 Frans Englich <frans.englich@telia.com> + + 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 __KCMODULEPROXYIFACE_H__ +#define __KCMODULEPROXYIFACE_H__ + +#include <dcopobject.h> + +/** + * @ingroup kcmodule + * @brief DCOP Interface for KCModule. + * + * A module (which is loaded via KCModuleProxy) does always have + * this DCOP interface, whether it's in root mode or not. + * + * @since 3.4 + * @internal + * @author Frans Englich <frans.englich@telia.com> + */ +class KCModuleProxyIface : virtual public DCOPObject +{ + K_DCOP + +k_dcop: + + /** + * Return the caption of the host application which the module + * is part in. This is the application name, or similar. + * + * @returns the host's name + */ + virtual QString applicationName() = 0; + + /** + * Save settings. + */ + virtual void save() = 0; + + /** + * Load settings. + */ + virtual void load() = 0; + + /** + * Load defaults. + */ + virtual void defaults() = 0; + + /** + * Returns the module's quick help. + */ + virtual QString quickHelp() = 0; + + /** + * @returns true if the module has unsaved + * data, typically. + */ + virtual bool changed() = 0; + +k_dcop_signals: + + /** + * Emitted when the state of the module changes. @p c + * is true when the content is changed, otherwise false. + * + * @param c true if the module is modified, false if its not. + * @param module a string identifying the module which was changed. This + * is typically "KCModuleProx-X" where X is the module's name. + */ + virtual void changed( bool c ); + + virtual void quickHelpChanged(); + +}; + +/** + * @ingroup kcmodule + * @brief DCOP interface for communicating with the real module running in root mode. + * + * When a KCModuleProxy tells kcmshell to load "itself" and embed into the + * KCModuleProxy, this DCOP interface is used to communicate to the real KCModuleProxy, since + * the KCModuleProxy which told kcmshell to load itself, is nothing but a shell. + * + * Currently is only the changed signal routed, but it's possible to proxy + * the rest of the KCModuleProxy API, if it turns out necessary. + * + * @since 3.4 + * @internal + * @author Frans Englich <frans.englich@telia.com> + */ +class KCModuleProxyRootDispatcher : virtual public DCOPObject +{ + K_DCOP + +k_dcop: + + /** + * KCModuleProxyIface::changed() gets connected to this. + */ + virtual void changed( bool c ) = 0; + + virtual void quickHelpChanged() = 0; +}; + +#endif // __KCMODULEPROXYIFACE_H__ diff --git a/kutils/kcmoduleproxyIfaceImpl.cpp b/kutils/kcmoduleproxyIfaceImpl.cpp new file mode 100644 index 000000000..5d1ae15fe --- /dev/null +++ b/kutils/kcmoduleproxyIfaceImpl.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2004 Frans Englich <frans.englich@telia.com> + * + * 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 <qcstring.h> +#include <qdatastream.h> + +#include <dcopclient.h> + +#include <kapplication.h> +#include <kcmoduleproxy.h> +#include <kdebug.h> + +#include "kcmoduleproxyIfaceImpl.h" + + +#include <qmessagebox.h> + +KCModuleProxyIfaceImpl::KCModuleProxyIfaceImpl( const QCString& name, + KCModuleProxy* const client ) + : DCOPObject( name ), QObject( 0, name ), + p( const_cast<KCModuleProxy *>( client )) +{ + connect( p, SIGNAL( changed(bool)), + SLOT( changedRelay(bool))); + connect( p, SIGNAL( quickHelpChanged()), + SLOT( quickHelpRelay())); +} + +void KCModuleProxyIfaceImpl::save() +{ + kdDebug(711) << k_funcinfo << endl; + p->save(); +} + +void KCModuleProxyIfaceImpl::load() +{ + kdDebug(711) << k_funcinfo << endl; + p->load(); +} + +void KCModuleProxyIfaceImpl::defaults() +{ + kdDebug(711) << k_funcinfo << endl; + p->defaults(); +} + +QString KCModuleProxyIfaceImpl::applicationName() +{ + return kapp->caption(); +} + +QString KCModuleProxyIfaceImpl::quickHelp() +{ + return p->quickHelp(); +} + +bool KCModuleProxyIfaceImpl::changed() +{ + return p->changed(); +} + +void KCModuleProxyIfaceImpl::changedRelay( bool c ) +{ + QByteArray data; + QDataStream stream(data, IO_WriteOnly); + stream << c; + emitDCOPSignal( "changed(bool)", data ); +} + +void KCModuleProxyIfaceImpl::quickHelpRelay() +{ + QByteArray data; + emitDCOPSignal( "quickHelpChanged()", data ); +} + +/***************************************************************/ + + + + +/***************************************************************/ +KCModuleProxyRootCommunicatorImpl::KCModuleProxyRootCommunicatorImpl + ( const QCString& name, KCModuleProxy* const client ) + : DCOPObject( name ), QObject( 0, name ), + p( const_cast<KCModuleProxy *>( client )) +{ + /* + * Connect kcmshell's KCModuleProxy's change signal + * to us, such that we act as a proxy for + * KCModuleProxy's API. + */ + + /* Note, we don't use KCModuleProxy::d->dcopClient */ + kapp->dcopClient()->connectDCOPSignal( 0, p->dcopName(), + "changed(bool)", objId(), "changed(bool)", false ); + + kapp->dcopClient()->connectDCOPSignal( 0, p->dcopName(), + "quickHelpChanged()", objId(), "quickHelpChanged()", false ); +} + +/* Reimplementations of DCOP members */ +void KCModuleProxyRootCommunicatorImpl::changed( bool c ) +{ + kdDebug(711) << k_funcinfo << endl; + p->moduleChanged( c ); +} + +void KCModuleProxyRootCommunicatorImpl::quickHelpChanged() +{ + kdDebug(711) << k_funcinfo << endl; + p->emitQuickHelpChanged(); +} + +#include "kcmoduleproxyIfaceImpl.moc" diff --git a/kutils/kcmoduleproxyIfaceImpl.h b/kutils/kcmoduleproxyIfaceImpl.h new file mode 100644 index 000000000..435c856ab --- /dev/null +++ b/kutils/kcmoduleproxyIfaceImpl.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2004 Frans Englich <frans.englich@telia.com> + * + * 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. + */ + +#ifndef __KCMODULEPROXYIFACEIMPL_H__ +#define __KCMODULEPROXYIFACEIMPL_H__ + +#include "kcmoduleproxyIface.h" + +class KCModuleProxy; + +/***************************************************************/ +/** @internal + @ingroup internal */ +class KCModuleProxyIfaceImpl: public QObject, virtual public KCModuleProxyIface +{ + /* KDE4 Merge KCModuleProxyIfaceImpl with KCModuleProxy(MI) + * if it doesn't break what DCOPClient it binds to. + * Update: This is probably not possible, since we don't want the DCOPObject when + * we're running in root mode. */ + + Q_OBJECT + +public: + + /* Reimplementations of DCOP members */ + KCModuleProxyIfaceImpl( const QCString& name, KCModuleProxy* const client ); + + virtual void save(); + + virtual void load(); + + virtual void defaults(); + + virtual QString applicationName(); + + virtual QString quickHelp(); + + virtual bool changed(); +public slots: + + /** + * Emits the changed(bool) DCOP signal. + */ + void changedRelay( bool c ); + + /** + * Simply relays KCModuleProxy's signal with the same name. + */ + void quickHelpRelay(); + + +private: + + KCModuleProxy* p; +}; +/***************************************************************/ + + + + +/***************************************************************/ +/** @internal + @ingroup internal */ +class KCModuleProxyRootCommunicatorImpl: public QObject, + virtual public KCModuleProxyRootDispatcher +{ + Q_OBJECT + +public: + KCModuleProxyRootCommunicatorImpl( const QCString& name, KCModuleProxy* const client ); + + /* Reimplementations of DCOP members */ + virtual void changed( bool c ); + + virtual void quickHelpChanged(); + +KCModuleProxy* p; +}; +/***************************************************************/ + +#endif // __KCMODULEPROXYIFACEIMPL_H__ + diff --git a/kutils/kcmultidialog.cpp b/kutils/kcmultidialog.cpp new file mode 100644 index 000000000..f64a9e139 --- /dev/null +++ b/kutils/kcmultidialog.cpp @@ -0,0 +1,411 @@ +/* + Copyright (c) 2000 Matthias Elter <elter@kde.org> + Copyright (c) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (c) 2003 Matthias Kretz <kretz@kde.org> + Copyright (c) 2004 Frans Englich <frans.erglich.com> + + 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 <qcursor.h> +#include <qhbox.h> +#include <qlayout.h> +#include <qpushbutton.h> + +#include <kaboutdata.h> +#include <kapplication.h> +#include <kdebug.h> +#include <kiconloader.h> +#include <klibloader.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kprocess.h> +#include <krun.h> +#include <kstdguiitem.h> +#include <kuser.h> + +#include "kcmoduleloader.h" +#include "kcmoduleproxy.h" +#include "kcmultidialog.h" +#include "kcmultidialog.moc" + +class KCMultiDialog::KCMultiDialogPrivate +{ + public: + KCMultiDialogPrivate() + : hasRootKCM( false ), currentModule( 0 ) + {} + + bool hasRootKCM; + KCModuleProxy* currentModule; +}; + + +KCMultiDialog::KCMultiDialog(QWidget *parent, const char *name, bool modal) + : KDialogBase(IconList, i18n("Configure"), Help | Default |Cancel | Apply | + Ok | User1 | User2, Ok, parent, name, modal, true, + KStdGuiItem::reset(), KStdGuiItem::adminMode()) + , dialogface( IconList ), d( new KCMultiDialogPrivate ) +{ + init(); +} + +KCMultiDialog::KCMultiDialog( int dialogFace, const QString & caption, QWidget * parent, const char * name, bool modal ) + : KDialogBase( dialogFace, caption, Help | Default | Cancel | Apply | Ok | + User1 | User2, Ok, parent, name, modal, true, + KStdGuiItem::reset(), KStdGuiItem::adminMode()) + , dialogface( dialogFace ), d( new KCMultiDialogPrivate ) +{ + init(); +} + +KCMultiDialog::KCMultiDialog( int dialogFace, const KGuiItem &user2, + const KGuiItem &user3, int buttonMask, const QString &caption, + QWidget *parent, const char *name, bool modal ) + : KDialogBase( dialogFace, caption, buttonMask | Help | Default | Cancel | + Apply | Ok | User1, Ok, parent, name, modal, true, + KStdGuiItem::reset(), user2, user3 ) + , dialogface( dialogFace ), d( new KCMultiDialogPrivate ) +{ + kdDebug( 710 ) << "Root modules will not work with this constructor. See the API documentation." << endl; + init(); + if ( buttonMask & User2 ) + showButton( User2, true ); +} + +inline void KCMultiDialog::init() +{ + connect( this, SIGNAL( finished()), SLOT( dialogClosed())); + showButton( User1, false ); + showButton( User2, false ); + enableButton(Apply, false); + connect(this, SIGNAL(aboutToShowPage(QWidget *)), this, SLOT(slotAboutToShow(QWidget *))); + setInitialSize(QSize(640,480)); + moduleParentComponents.setAutoDelete( true ); + +} + +KCMultiDialog::~KCMultiDialog() +{ + OrphanMap::Iterator end2 = m_orphanModules.end(); + for( OrphanMap::Iterator it = m_orphanModules.begin(); it != end2; ++it ) + delete ( *it ); + delete d; +} + +void KCMultiDialog::slotDefault() +{ + int curPageIndex = activePageIndex(); + + ModuleList::Iterator end = m_modules.end(); + for( ModuleList::Iterator it = m_modules.begin(); it != end; ++it ) + if( pageIndex( ( QWidget * )( *it ).kcm->parent() ) == curPageIndex ) + { + ( *it ).kcm->defaults(); + clientChanged( true ); + return; + } +} + +void KCMultiDialog::slotUser1() +{ + int curPageIndex = activePageIndex(); + + ModuleList::Iterator end = m_modules.end(); + for( ModuleList::Iterator it = m_modules.begin(); it != end; ++it ) + if( pageIndex( ( QWidget * )( *it ).kcm->parent() ) == curPageIndex ) + { + ( *it ).kcm->load(); + clientChanged( false ); + return; + } +} + +void KCMultiDialog::apply() +{ + QStringList updatedModules; + ModuleList::Iterator end = m_modules.end(); + for( ModuleList::Iterator it = m_modules.begin(); it != end; ++it ) + { + KCModuleProxy * m = ( *it ).kcm; + if( m->changed() ) + { + m->save(); + QStringList * names = moduleParentComponents[ m ]; + kdDebug(710) << k_funcinfo << *names << " saved and added to the list" << endl; + for( QStringList::ConstIterator it = names->begin(); it != names->end(); ++it ) + if( updatedModules.find( *it ) == updatedModules.end() ) + updatedModules.append( *it ); + } + } + for( QStringList::const_iterator it = updatedModules.begin(); it != updatedModules.end(); ++it ) + { + kdDebug(710) << k_funcinfo << *it << " " << ( *it ).latin1() << endl; + emit configCommitted( ( *it ).latin1() ); + } + emit configCommitted(); +} + +void KCMultiDialog::slotApply() +{ + QPushButton *button = actionButton(Apply); + if (button) + button->setFocus(); + emit applyClicked(); + apply(); +} + + +void KCMultiDialog::slotOk() +{ + QPushButton *button = actionButton(Ok); + if (button) + button->setFocus(); + emit okClicked(); + apply(); + accept(); +} + +void KCMultiDialog::slotHelp() +{ + QString docPath; + + int curPageIndex = activePageIndex(); + ModuleList::Iterator end = m_modules.end(); + for( ModuleList::Iterator it = m_modules.begin(); it != end; ++it ) + if( pageIndex( ( QWidget * )( *it ).kcm->parent() ) == curPageIndex ) + { + docPath = ( *it ).kcm->moduleInfo().docPath(); + break; + } + + KURL url( KURL("help:/"), docPath ); + + if (url.protocol() == "help" || url.protocol() == "man" || url.protocol() == "info") { + KProcess process; + process << "khelpcenter" + << url.url(); + process.start(KProcess::DontCare); + process.detach(); + } else { + new KRun(url); + } +} + +void KCMultiDialog::clientChanged(bool state) +{ + kdDebug( 710 ) << k_funcinfo << state << endl; + ModuleList::Iterator end = m_modules.end(); + for( ModuleList::Iterator it = m_modules.begin(); it != end; ++it ) + if( ( *it ).kcm->changed() ) + { + enableButton( Apply, true ); + return; + } + enableButton( Apply, false ); +} + +void KCMultiDialog::addModule(const QString& path, bool withfallback) +{ + QString complete = path; + + if( !path.endsWith( ".desktop" )) + complete += ".desktop"; + + KService::Ptr service = KService::serviceByStorageId( complete ); + + addModule( KCModuleInfo( service ), QStringList(), withfallback); +} + +void KCMultiDialog::addModule(const KCModuleInfo& moduleinfo, + QStringList parentmodulenames, bool withfallback) +{ + kdDebug(710) << "KCMultiDialog::addModule " + << moduleinfo.moduleName() << endl; + + if( !moduleinfo.service() ) + return; + + if ( !kapp->authorizeControlModule( moduleinfo.service()->menuId() )) + return; + + if( !KCModuleLoader::testModule( moduleinfo )) + return; + + QFrame* page = 0; + if (!moduleinfo.service()->noDisplay()) + switch( dialogface ) + { + case TreeList: + parentmodulenames += moduleinfo.moduleName(); + page = addHBoxPage( parentmodulenames, moduleinfo.comment(), + SmallIcon( moduleinfo.icon(), + IconSize( KIcon::Small ) ) ); + break; + case IconList: + page = addHBoxPage( moduleinfo.moduleName(), + moduleinfo.comment(), DesktopIcon( moduleinfo.icon(), + KIcon::SizeMedium ) ); + break; + case Plain: + page = plainPage(); + ( new QHBoxLayout( page ) )->setAutoAdd( true ); + break; + default: + kdError( 710 ) << "unsupported dialog face for KCMultiDialog" + << endl; + break; + } + if(!page) { + KCModuleLoader::unloadModule(moduleinfo); + return; + } + KCModuleProxy * module; + if( m_orphanModules.contains( moduleinfo.service() ) ) + { + // the KCModule already exists - it was removed from the dialog in + // removeAllModules + module = m_orphanModules[ moduleinfo.service() ]; + m_orphanModules.remove( moduleinfo.service() ); + kdDebug( 710 ) << "Use KCModule from the list of orphans for " << + moduleinfo.moduleName() << ": " << module << endl; + + module->reparent( page, 0, QPoint( 0, 0 ), true ); + + if( module->changed() ) + clientChanged( true ); + + if( activePageIndex() == -1 ) + showPage( pageIndex( page ) ); + } + else + { + module = new KCModuleProxy( moduleinfo, withfallback, page ); + QStringList parentComponents = moduleinfo.service()->property( + "X-KDE-ParentComponents" ).toStringList(); + moduleParentComponents.insert( module, + new QStringList( parentComponents ) ); + + connect(module, SIGNAL(changed(bool)), this, SLOT(clientChanged(bool))); + + if( m_modules.count() == 0 ) + aboutToShowPage( page ); + } + CreatedModule cm; + cm.kcm = module; + cm.service = moduleinfo.service(); + m_modules.append( cm ); + if ( moduleinfo.needsRootPrivileges() && + !d->hasRootKCM && + !KUser().isSuperUser() ) /* If we're embedded, it's true */ + { + d->hasRootKCM = true; + showButton( User2, true ); + if( plainPage() ) // returns 0 if we're not a Plain dialog + slotAboutToShow( page ); // Won't be called otherwise, necessary for adminMode button + } +} + +void KCMultiDialog::removeAllModules() +{ + kdDebug( 710 ) << k_funcinfo << endl; + ModuleList::Iterator end = m_modules.end(); + for( ModuleList::Iterator it = m_modules.begin(); it != end; ++it ) + { + kdDebug( 710 ) << "remove 2" << endl; + KCModuleProxy * kcm = ( *it ).kcm; + QObject * page = kcm->parent(); + kcm->hide(); + if( page ) + { + // I hate this + kcm->reparent( 0, QPoint( 0, 0 ), false ); + delete page; + } + m_orphanModules[ ( *it ).service ] = kcm; + kdDebug( 710 ) << "added KCModule to the list of orphans: " << + kcm << endl; + } + m_modules.clear(); + // all modules are gone, none can be changed + clientChanged( false ); +} + +void KCMultiDialog::show() +{ /* KDE 4 Remove..? */ + KDialogBase::show(); +} + +void KCMultiDialog::slotAboutToShow(QWidget *page) +{ + kdDebug(710) << k_funcinfo << endl; + + QObject * obj = page->child( 0, "KCModuleProxy" ); + if( ! obj ) + return; + + KCModuleProxy * module = ( KCModuleProxy* )obj->qt_cast( + "KCModuleProxy" ); + if( ! module ) + return; + d->currentModule = module; + + enableButton( KDialogBase::Help, + d->currentModule->buttons() & KCModule::Help ); + enableButton( KDialogBase::Default, + d->currentModule->buttons() & KCModule::Default ); + + disconnect( this, SIGNAL(user2Clicked()), 0, 0 ); + + if (d->currentModule->moduleInfo().needsRootPrivileges()) + { + if ( !d->currentModule->rootMode() ) + { /* Enable the Admin Mode button */ + enableButton( User2, true ); + connect( this, SIGNAL(user2Clicked()), d->currentModule, SLOT( runAsRoot() )); + connect( this, SIGNAL(user2Clicked()), SLOT( disableRModeButton() )); + } + else + enableButton( User2, false); + } +} + +void KCMultiDialog::rootExit() +{ + enableButton( User2, true); +} + +void KCMultiDialog::disableRModeButton() +{ + enableButton( User2, false ); + connect ( d->currentModule, SIGNAL( childClosed() ), SLOT( rootExit() )); +} + +void KCMultiDialog::dialogClosed() +{ + kdDebug(710) << k_funcinfo << endl; + + /* If we don't delete them, the DCOP registration stays, and trying to load the KCMs + * in other situations will lead to "module already loaded in Foo," while to the user + * doesn't appear so(the dialog is hidden) */ + ModuleList::Iterator end = m_modules.end(); + for( ModuleList::Iterator it = m_modules.begin(); it != end; ++it ) + ( *it ).kcm->deleteClient(); +} + + +// vim: sw=4 et sts=4 diff --git a/kutils/kcmultidialog.h b/kutils/kcmultidialog.h new file mode 100644 index 000000000..8461c5643 --- /dev/null +++ b/kutils/kcmultidialog.h @@ -0,0 +1,284 @@ +/* + Copyright (c) 2000 Matthias Elter <elter@kde.org> + Copyright (c) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (c) 2003 Matthias Kretz <kretz@kde.org> + + 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 KCMULTIDIALOG_H +#define KCMULTIDIALOG_H + +#include <qptrdict.h> + +#include <kdialogbase.h> +#include <klocale.h> +#include <kservice.h> + +class KCModuleProxy; +class KCModuleInfo; + +/** + * @ingroup kcmodule + * @short A method that offers a KDialogBase containing arbitrary + * KControl Modules. + * + * @author Matthias Elter <elter@kde.org>, Daniel Molkentin <molkentin@kde.org> + * @since 3.2 + */ +class KUTILS_EXPORT KCMultiDialog : public KDialogBase +{ + Q_OBJECT + +public: + /** + * Constructs a new KCMultiDialog + * + * @param parent The parent widget + * @param name The widget name + * @param modal If you pass true here, the dialog will be modal + **/ + KCMultiDialog( QWidget *parent=0, const char *name=0, bool modal=false ); + + /** + * Construct a personalized KCMultiDialog. + * + * @param dialogFace You can use TreeList, Tabbed, Plain, Swallow or + * IconList. + * @param caption The dialog caption. Do not specify the application name + * here. The class will take care of that. + * @param parent Parent of the dialog. + * @param name Dialog name (for internal use only). + * @param modal Controls dialog modality. If @p false, the rest of the + * program interface (example: other dialogs) is accessible while + * the dialog is open. + */ + KCMultiDialog( int dialogFace, const QString & caption, QWidget * parent = 0, + const char * name = 0, bool modal = false ); + + + /** + * Constructor for the predefined layout mode where you specify the + * kind of layout (face) and also add buttons. Note that the User1 button + * of KDialogBase is already used to provide a "Reset" button so only + * two more buttons are available to users of KCMultiDialog. When clicked + * they trigger slotUser2() and slotUser3(). + * + * @note If any root modules are added to the dialog when this constructor is + * used, it will not be able to run them with root privileges. Since that will + * render them useless, it is a good idea to use another constructor. In KDE 4 + * the argument @p user3 will be removed. + * + * @param dialogFace You can use TreeList, Tabbed, Plain, Swallow or + * IconList. + * @param user2 User button2 text item. + * @param user3 User button3 text item. + * @param buttonMask Specifies which buttons will be visible. If zero + * (0) no extra buttons will be added. You can only use the User2 and + * User3 buttons. The User1 button is already used internally. See + * KDialogBase for more information on this. + * @param caption The dialog caption. Do not specify the application name + * here. The class will take care of that. + * @param parent Parent of the dialog. + * @param name Dialog name (for internal use only). + * @param modal Controls dialog modality. If @p false, the rest of the + * program interface (example: other dialogs) is accessible while + * the dialog is open. + */ + KCMultiDialog( int dialogFace, const KGuiItem &user2, + const KGuiItem &user3=KGuiItem(), int buttonMask=User2, + const QString &caption=i18n("Configure"), QWidget *parent=0, + const char *name=0, bool modal=false ) KDE_DEPRECATED; + // KDE4 remove the user3 argument, and instead initialize it to KStdGuiItem::adminMode. + + /** + * Destructor + **/ + virtual ~KCMultiDialog(); + + /** + * Add a module. + * + * @param module Specify the name of the module that is to be added + * to the list of modules the dialog will show. + * + * @param withfallback Try harder to load the module. Might result + * in the module appearing outside the dialog. + **/ + void addModule(const QString& module, bool withfallback=true); + + /** + * Add a module. + * + * @param moduleinfo Pass a KCModuleInfo object which will be + * used for creating the module. It will be added + * to the list of modules the dialog will show. + * + * @param parentmodulenames The names of the modules that should appear as + * parents in the TreeList. Look at the + * KDialogBase::addPage documentation for more info + * on this. + * + * @param withfallback Try harder to load the module. Might result + * in the module appearing outside the dialog. + **/ + void addModule(const KCModuleInfo& moduleinfo, QStringList + parentmodulenames = QStringList(), bool withfallback=false); + + /** + * Remove all modules from the dialog. + */ + void removeAllModules(); + + /** + * @internal + * Re-implemented for internal reasons. + */ + void show(); + +signals: + /** + * Emitted after all KCModules have been told to save their configuration. + * + * The applyClicked and okClicked signals are emitted before the + * configuration is saved. + */ + void configCommitted(); + + /** + * Emitted after the KCModules have been told to save their configuration. + * It is emitted once for every instance the KCMs that were changed belong + * to. + * + * You can make use of this if you have more than one component in your + * application. instanceName tells you the instance that has to reload its + * configuration. + * + * The applyClicked and okClicked signals are emitted before the + * configuration is saved. + * + * @param instanceName The name of the instance that needs to reload its + * configuration. + */ + void configCommitted( const QCString & instanceName ); + +protected slots: + /** + * This slot is called when the user presses the "Default" Button. + * You can reimplement it if needed. + * + * @note Make sure you call the original implementation. + **/ + virtual void slotDefault(); + + /** + * This slot is called when the user presses the "Reset" Button. + * You can reimplement it if needed. + * + * @note Make sure you call the original implementation. + */ + virtual void slotUser1(); + + /** + * This slot is called when the user presses the "Apply" Button. + * You can reimplement it if needed. + * + * @note Make sure you call the original implementation. + **/ + virtual void slotApply(); + + /** + * This slot is called when the user presses the "OK" Button. + * You can reimplement it if needed. + * + * @note Make sure you call the original implementation. + **/ + virtual void slotOk(); + + /** + * This slot is called when the user presses the "Help" Button. + * It reads the DocPath field of the currently selected KControl + * module's .desktop file to find the path to the documentation, + * which it then attempts to load. + * + * You can reimplement this slot if needed. + * + * @note Make sure you call the original implementation. + **/ + virtual void slotHelp(); + +private slots: + + void slotAboutToShow(QWidget *); + + void clientChanged(bool state); + + /** + * Called when entering root mode, and disables + * the Admin Mode button such that the user doesn't do it + * twice. + * + * @since 3.4 + */ + void disableRModeButton(); + + /** + * Called when the current module exits from root + * mode. Enables the Administrator Mode button, again. + * + * @since 3.4 + */ + void rootExit(); + + /** + * + * Called when the dialog is hidden. It unregisters the modules, + * such that they don't hinder the same modules to be opened in + * another application. + * + * @since 3.4 + */ + void dialogClosed(); + +private: + + void init(); + void apply(); + + struct CreatedModule + { + KCModuleProxy * kcm; + KService::Ptr service; + /* KDE 4 Move to Private class */ + }; + typedef QValueList<CreatedModule> ModuleList; + ModuleList m_modules; + + typedef QMap<KService::Ptr, KCModuleProxy*> OrphanMap; + OrphanMap m_orphanModules; + + QPtrDict<QStringList> moduleParentComponents; + QString _docPath; + int dialogface; + + class KCMultiDialogPrivate; + KCMultiDialogPrivate *d; +}; + +#endif //KCMULTIDIALOG_H + +// vim: sw=4 sts=4 et diff --git a/kutils/kdeglobals.kcfg b/kutils/kdeglobals.kcfg new file mode 100644 index 000000000..0941cebb7 --- /dev/null +++ b/kutils/kdeglobals.kcfg @@ -0,0 +1,564 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 + http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" > + <kcfgfile name="kdeglobals" /> + <group name="DesktopIcons" > + <entry key="GridXSpacing" type="Int" > + <label>Distance between desktop icons</label> + <whatsthis>The distance between icons specified in pixels.</whatsthis> + <default>50</default> + </entry> + </group> + <group name="General" > + <entry key="alternateBackground" type="String" /> + <entry key="background" type="String" /> + <entry key="buttonBackground" type="String" /> + <entry key="buttonForeground" type="String" /> + <entry key="foreground" type="String" /> + <entry key="selectBackground" type="String" /> + <entry key="selectForeground" type="String" /> + <entry key="widgetStyle" type="String" > + <label>Widget style to use</label> + <whatsthis>The name of the widget style, for example "keramik" or "plastik". Without quotes.</whatsthis> + <default>keramik</default> + </entry> + <entry key="windowBackground" type="String" /> + <entry key="windowForeground" type="String" /> + <entry key="UseSystemBell" type="Bool" > + <label>Use the PC speaker</label> + <whatsthis>If the ordinary PC speaker should be used instead of KDE's own notifications system.</whatsthis> + <default>false</default> + </entry> + <entry key="TerminalApplication" type="String" > + <label>What terminal application to use</label> + <whatsthis>Whenever a terminal application is launched this terminal emulator program will be used. +</whatsthis> + <default>konsole</default> + </entry> + <entry key="fixed" type="Font" > + <label>Fixed width font</label> + <whatsthis>This font is used when a fixed font is needed. A fixed font has a constant width. +</whatsthis> + </entry> + <entry key="font" type="Font" > + <label>System wide font</label> + </entry> + <entry key="menuFont" type="Font" > + <label>Font for menus</label> + <whatsthis>What font to use for menus in applications.</whatsthis> + </entry> + <entry key="linkColor" type="Color" > + <label>Color for links</label> + <whatsthis>What color links which are yet not clicked on should have.</whatsthis> + </entry> + <entry key="visitedLinkColor" type="Color" > + <label>Color for visited links</label> + </entry> + <entry key="taskbarFont" type="Font" > + <label>Font for the taskbar</label> + <whatsthis>What font to use for the panel at the bottom of the screen, where the currently running applications are.</whatsthis> + </entry> + <entry key="toolBarFont" type="Font" > + <label>Fonts for toolbars</label> + </entry> + </group> + <group name="Global Shortcuts" > + <entry key="Activate Window Demanding Attention" type="String" name="Activate Window Demanding Attention" > + <default>Alt+Ctrl+A</default> + </entry> + + <entry key="Desktop Screenshot" type="String" name="Desktop Screenshot" > + <label>Shortcut for taking screenshot</label> + <default>Ctrl+Print</default> + </entry> + + <entry key="Enable/Disable Clipboard Actions" type="String" name="Enable/Disable Clipboard Actions" > + <label>Shortcut for toggling Clipboard Actions on and off</label> + <default>Alt+Ctrl+X</default> + </entry> + + <entry key="Halt without Confirmation" type="String" name="Halt without Confirmation" > + <label>Shortcut for shutting down the computer without confirmation</label> + <default>Alt+Ctrl+Shift+PageDown</default> + </entry> + + <entry key="Kill Window" type="String" name="Kill Window" > + <default>Alt+Ctrl+Escape</default> + </entry> + + <entry key="Lock Screen" type="String" > + <default>Alt+Ctrl+L</default> + </entry> + <entry key="Logout" type="String" > + <default>Alt+Ctrl+Delete</default> + </entry> + <entry key="Logout without Confirmation" type="String" > + <default>Alt+Ctrl+Shift+Delete</default> + </entry> + <entry key="Manually Invoke Action on Current Clipboard" type="String" > + <default>Alt+Ctrl+R</default> + </entry> + <entry key="Mouse Emulation" type="String" > + <default>Alt+F12</default> + </entry> + <entry key="Popup Launch Menu" type="String" > + <default>Alt+F1</default> + </entry> + <entry key="Reboot without Confirmation" type="String" name="Reboot without Confirmation" > + <default>Alt+Ctrl+Shift+PageUp</default> + </entry> + <entry key="Run Command" type="String" name="Run Command"> + <default>Alt+F2</default> + </entry> + <entry key="Show Klipper Popup-Menu" type="String" > + <default>Alt+Ctrl+V</default> + </entry> + <entry key="Show Taskmanager" type="String" > + <default>Ctrl+Escape</default> + </entry> + <entry key="Show Window List" type="String" > + <default>Alt+F5</default> + </entry> + <entry key="Switch One Desktop Down" type="String" > + <default></default> + </entry> + <entry key="Switch One Desktop Up" type="String" > + <default></default> + </entry> + <entry key="Switch One Desktop to the Left" type="String" > + <default></default> + </entry> + <entry key="Switch One Desktop to the Right" type="String" > + <default></default> + </entry> + <entry key="Switch to Desktop 1" type="String" > + <default>Ctrl+F1</default> + </entry> + <entry key="Switch to Desktop 10" type="String" > + <default>Ctrl+F10</default> + </entry> + <entry key="Switch to Desktop 11" type="String" > + <default>Ctrl+F11</default> + </entry> + <entry key="Switch to Desktop 12" type="String" > + <default>Ctrl+F12</default> + </entry> + <entry key="Switch to Desktop 13" type="String" > + <default>Ctrl+Shift+F1</default> + </entry> + <entry key="Switch to Desktop 14" type="String" > + <default>Ctrl+Shift+F2</default> + </entry> + <entry key="Switch to Desktop 15" type="String" > + <default>Ctrl+Shift+F3</default> + </entry> + <entry key="Switch to Desktop 16" type="String" > + <default>Ctrl+Shift+F4</default> + </entry> + <entry key="Switch to Desktop 2" type="String" > + <default>Ctrl+F2</default> + </entry> + <entry key="Switch to Desktop 3" type="String" > + <default>Ctrl+F3</default> + </entry> + <entry key="Switch to Desktop 4" type="String" > + <default>Ctrl+F4</default> + </entry> + <entry key="Switch to Desktop 5" type="String" > + <default>Ctrl+F5</default> + </entry> + <entry key="Switch to Desktop 6" type="String" > + <default>Ctrl+F6</default> + </entry> + <entry key="Switch to Desktop 7" type="String" > + <default>Ctrl+F7</default> + </entry> + <entry key="Switch to Desktop 8" type="String" > + <default>Ctrl+F8</default> + </entry> + <entry key="Switch to Desktop 9" type="String" > + <default>Ctrl+F9</default> + </entry> + <entry key="Switch to Next Desktop" type="String" > + <default></default> + </entry> + <entry key="Switch to Next Keyboard Layout" type="String" > + <default>Ctrl+Alt+K</default> + </entry> + <entry key="Switch to Previous Desktop" type="String" > + <default></default> + </entry> + <entry key="Toggle Showing Desktop" type="String" > + <default>Alt+Ctrl+D</default> + </entry> + <entry key="Toggle Window Raise/Lower" type="String" > + <default></default> + </entry> + <entry key="Walk Through Desktop List" type="String" > + <default>Ctrl+Tab</default> + </entry> + <entry key="Walk Through Desktop List (Reverse)" type="String" > + <default>Ctrl+Shift+Tab</default> + </entry> + <entry key="Walk Through Desktops" type="String" > + <default></default> + </entry> + <entry key="Walk Through Desktops (Reverse)" type="String" > + <default></default> + </entry> + <entry key="Walk Through Windows" type="String" > + <default>Alt+Tab</default> + </entry> + <entry key="Walk Through Windows (Reverse)" type="String" > + <default>Alt+Shift+Tab</default> + </entry> + <entry key="Window Above Other Windows" type="String" > + <default></default> + </entry> + <entry key="Window Below Other Windows" type="String" > + <default></default> + </entry> + <entry key="Window Close" type="String" > + <default>Alt+F4</default> + </entry> + <entry key="Window Fullscreen" type="String" > + <default></default> + </entry> + <entry key="Window Grow Horizontal" type="String" > + <default></default> + </entry> + <entry key="Window Grow Vertical" type="String" > + <default></default> + </entry> + <entry key="Window Lower" type="String" > + <default></default> + </entry> + <entry key="Window Maximize" type="String" > + <default></default> + </entry> + <entry key="Window Maximize Horizontal" type="String" > + <default></default> + </entry> + <entry key="Window Maximize Vertical" type="String" > + <default></default> + </entry> + <entry key="Window Minimize" type="String" > + <default></default> + </entry> + <entry key="Window Move" type="String" > + <default></default> + </entry> + <entry key="Window No Border" type="String" > + <default></default> + </entry> + <entry key="Window On All Desktops" type="String" > + <default></default> + </entry> + <entry key="Window Operations Menu" type="String" > + <default>Alt+F3</default> + </entry> + <entry key="Window Pack Down" type="String" > + <default></default> + </entry> + <entry key="Window Pack Left" type="String" > + <default></default> + </entry> + <entry key="Window Pack Right" type="String" > + <default></default> + </entry> + <entry key="Window Pack Up" type="String" > + <default></default> + </entry> + <entry key="Window Raise" type="String" > + <default></default> + </entry> + <entry key="Window Resize" type="String" > + <default></default> + </entry> + <entry key="Window Screenshot" type="String" > + <default>Alt+Print</default> + </entry> + <entry key="Window Shade" type="String" > + <default></default> + </entry> + <entry key="Window Shrink Horizontal" type="String" > + <default></default> + </entry> + <entry key="Window Shrink Vertical" type="String" > + <default></default> + </entry> + <entry key="Window to Desktop 1" type="String" > + <default></default> + </entry> + <entry key="Window to Desktop 10" type="String" > + <default></default> + </entry> + <entry key="Window to Desktop 11" type="String" > + <default></default> + </entry> + <entry key="Window to Desktop 12" type="String" > + <default></default> + </entry> + <entry key="Window to Desktop 13" type="String" > + <default></default> + </entry> + <entry key="Window to Desktop 14" type="String" > + <default></default> + </entry> + <entry key="Window to Desktop 15" type="String" > + <default></default> + </entry> + <entry key="Window to Desktop 16" type="String" > + <default></default> + </entry> + <entry key="Window to Desktop 2" type="String" > + <default></default> + </entry> + <entry key="Window to Desktop 3" type="String" > + <default></default> + </entry> + <entry key="Window to Desktop 4" type="String" > + <default></default> + </entry> + <entry key="Window to Desktop 5" type="String" > + <default></default> + </entry> + <entry key="Window to Desktop 6" type="String" > + <default></default> + </entry> + <entry key="Window to Desktop 7" type="String" > + <default></default> + </entry> + <entry key="Window to Desktop 8" type="String" > + <default></default> + </entry> + <entry key="Window to Desktop 9" type="String" > + <default></default> + </entry> + <entry key="Window to Next Desktop" type="String" > + <default></default> + </entry> + + <entry key="Window to Previous Desktop" type="String" > + <default></default> + </entry> + + </group> + <group name="KDE" > + <entry key="AutoSelectDelay" type="String" /> + <entry key="DoubleClickInterval" type="String" /> + <entry key="EffectAnimateCombo" type="String" /> + <entry key="EffectAnimateMenu" type="String" /> + <entry key="EffectAnimateTooltip" type="String" /> + <entry key="EffectFadeMenu" type="String" /> + <entry key="EffectFadeTooltip" type="String" /> + <entry key="EffectNoTooltip" type="String" /> + <entry key="EffectsEnabled" type="String" /> + <entry key="InsertTearOffHandle" type="String" /> + <entry key="ShowIconsOnPushButtons" type="String" /> + <entry key="SingleClick" type="String" /> + <entry key="StartDragDist" type="String" /> + <entry key="StartDragTime" type="String" /> + <entry key="VisualActivate" type="String" /> + <entry key="WheelScrollLines" type="String" /> + <entry key="colorScheme" type="String" /> + <entry key="contrast" type="String" /> + </group> + <group name="KFileDialog Settings" > + <entry key="Automatically select filename extension" type="String" name="Automatically select filename extension"> + <default>true</default> + </entry> + <entry key="Height 768" type="String" name="Height 768" /> + <entry key="LocationCombo Completionmode" type="String" name="LocationCombo Completionmode" /> + <entry key="PathCombo Completionmode" type="String" name="PathCombo Completionmode" /> + + <entry key="Separate Directories" type="Bool" name="Separate Directories" > + <default>false</default> + </entry> + + <entry key="Sort by" type="String" name="Sort by" /> + + <entry key="Sort case insensitively" type="Bool" name="Sort case insensitively"> + <default>true</default> + </entry> + + <entry key="Sort directories first" type="Bool" name="Sort directories first" > + <label>Show directories first</label> + <whatsthis>If directories should be placed at the top when displaying files</whatsthis> + <default>true</default> + </entry> + + <entry key="View Style" type="String" name="View Style" /> + <entry key="Width 1024" type="String" name="Width 1024" /> + + <entry key="Recent URLs" type="Path" name="Recent URLs" > + <label>The recent URLs recently visited</label> + <whatsthis>Used for auto completion in file dialogs for example.</whatsthis> + </entry> + + <entry key="Show Preview" type="Bool" name="Show Preview" > + <label>Show file preview in file dialog</label> + <default>false</default> + </entry> + + <entry key="Show hidden files" type="Bool" name="Show hidden files" > + <label>Show hidden files</label> + <whatsthis>Determines if files starting with a dot(convention for hidden files) should be shown</whatsthis> + <default>false</default> + </entry> + + <entry key="Show Speedbar" type="Bool" name="Show Speedbar" > + <label>Show speedbar</label> + <whatsthis>Whether the shortcut icons to the left in the file dialog should be shown</whatsthis> + <default>true</default> + </entry> + + <entry key="Sort reversed" type="Bool" name="Sort reversed" > + <default>false</default> + </entry> + + </group> + <group name="KSpell" > + <entry key="KSpell_Client" type="String" /> + <entry key="KSpell_DictFromList" type="String" /> + <entry key="KSpell_Dictionary" type="String" /> + <entry key="KSpell_Encoding" type="String" /> + <entry key="KSpell_NoRootAffix" type="String" /> + <entry key="KSpell_RunTogether" type="String" /> + </group> + <group name="Locale" > + <entry key="Country" type="String" > + <label>What country</label> + <whatsthis>Used to determine how to display numbers, currency and time/date for example</whatsthis> + <default>C</default> + </entry> + <entry key="Language" type="String" > + <label>What language to display text in</label> + </entry> + <entry key="PositiveSign" type="String" > + <label>Character used for indicating positive numbers</label> + <whatsthis>Most countries have no character for this</whatsthis> + </entry> + </group> + <group name="Paths" > + <entry key="Trash" type="Path" > + <label>Path for the trash can</label> + <default>$HOME/Desktop/Trash</default> + </entry> + <entry key="Autostart" type="Path" > + <label>Path to the autostart directory</label> + <whatsthis>Path to the directory containing executables to be run on session login</whatsthis> + <default>$KDEHOME/Autostart</default> + </entry> + <entry key="Desktop" type="Path" > + <label>Path to the desktop directory</label> + <whatsthis>In this directory the files on the dekstop are stored</whatsthis> + <default>$HOME/Desktop</default> + </entry> + <entry key="Documents" type="Path" > + <label>Path to documents folder</label> + <whatsthis></whatsthis> + <default>$HOME</default> + </entry> + </group> + <group name="PreviewSettings" > + <entry key="BoostSize" type="Bool" /> + <entry key="MaximumSize" type="Bool" /> + <entry key="UseFileThumbnails" type="Bool" /> + <entry key="ar" type="Bool" /> + <entry key="audiocd" type="Bool" /> + <entry key="devices" type="Bool" /> + <entry key="file" type="Bool" /> + <entry key="fish" type="Bool" /> + <entry key="floppy" type="Bool" /> + <entry key="fonts" type="Bool" /> + <entry key="ftp" type="Bool" /> + <entry key="imap" type="Bool" /> + <entry key="imaps" type="Bool" /> + <entry key="kdeapi" type="Bool" /> + <entry key="lan" type="Bool" /> + <entry key="mac" type="Bool" /> + <entry key="man" type="Bool" /> + <entry key="nfs" type="Bool" /> + <entry key="nntp" type="Bool" /> + <entry key="perldoc" type="Bool" /> + <entry key="pop3" type="Bool" /> + <entry key="pop3s" type="Bool" /> + <entry key="print" type="Bool" /> + <entry key="printdb" type="Bool" /> + <entry key="programs" type="Bool" /> + <entry key="pydoc" type="Bool" /> + <entry key="rlan" type="Bool" /> + <entry key="settings" type="Bool" /> + <entry key="sftp" type="Bool" /> + <entry key="sieve" type="Bool" /> + <entry key="smb" type="Bool" /> + <entry key="smtp" type="Bool" /> + <entry key="smtps" type="Bool" /> + <entry key="system" type="Bool" /> + <entry key="tar" type="Bool" /> + <entry key="webdav" type="Bool" /> + <entry key="webdavs" type="Bool" /> + <entry key="zip" type="Bool" /> + </group> + <group name="Socks" > + <entry key="SOCKS_enable" type="Bool" > + <label>Enable SOCKS support</label> + <whatsthis>Whether SOCKS version 4 and 5 should be enabled in KDE's sub systems</whatsthis> + <default>false</default> + </entry> + <entry key="SOCKS_lib" type="Path" > + <label>Path to custom SOCKS library</label> + </entry> + <entry key="SOCKS_method" type="String" /> + </group> + <group name="Toolbar style" > + <entry key="Highlighting" type="Bool"> + <label>Highlight toolbar buttons on mouse over</label> + <default>true</default> + </entry> + <entry key="IconText" type="Bool"> + <label>Show text on toolbar icons </label> + <whatsthis>Whether text should be shown in addition to icons on toolbar icons</whatsthis> + <default>false</default> + </entry> + <entry key="TransparentMoving" type="Bool" > + <label>Transparent toolbars when moved</label> + <whatsthis>Whether toolbars should be visible when moved</whatsthis> + <default>false</default> + </entry> + </group> + <group name="WM" > + <entry key="activeBackground" type="String" /> + <entry key="activeBlend" type="String" /> + <entry key="activeFont" type="String" /> + <entry key="activeForeground" type="String" /> + <entry key="activeTitleBtnBg" type="String" /> + <entry key="frame" type="String" /> + <entry key="handle" type="String" /> + <entry key="inactiveBackground" type="String" /> + <entry key="inactiveBlend" type="String" /> + <entry key="inactiveForeground" type="String" /> + <entry key="inactiveFrame" type="String" /> + <entry key="inactiveHandle" type="String" /> + <entry key="inactiveTitleBtnBg" type="String" /> + </group> + <group name="Passwords"> + <entry name="EchoMode" type="Enum"> + <label>Password echo type</label> + <choices> + <choice name="OneStar"/> + <choice name="ThreeStars"/> + <choice name="NoEcho"/> + </choices> + <default>OneStar</default> + </entry> + </group> + <group name="KKeyDialog Settings"> + <entry name="Dialog Size" type="IntList"><!-- Should this be taken care of globally, for dialogs? --> + <label>The size of the dialog</label> + </entry> + </group> +</kcfg> diff --git a/kutils/kdeglobals.kcfgc b/kutils/kdeglobals.kcfgc new file mode 100644 index 000000000..fee74fc45 --- /dev/null +++ b/kutils/kdeglobals.kcfgc @@ -0,0 +1,5 @@ +File=kdeglobals.kcfg +ClassName=KDEGlobals +Singleton=true +Mutators=true + diff --git a/kutils/kfind.cpp b/kutils/kfind.cpp new file mode 100644 index 000000000..a6eb8dd46 --- /dev/null +++ b/kutils/kfind.cpp @@ -0,0 +1,710 @@ +/* + Copyright (C) 2001, S.R.Haque <srhaque@iee.org>. + Copyright (C) 2002, David Faure <david@mandrakesoft.com> + Copyright (C) 2004, Arend van Beelen jr. <arend@auton.nl> + This file is part of the KDE project + + 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 "kfind.h" +#include "kfinddialog.h" +#include <kapplication.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <qlabel.h> +#include <qregexp.h> +#include <qstylesheet.h> +#include <qguardedptr.h> +#include <qptrvector.h> +#include <kdebug.h> + +//#define DEBUG_FIND + +#define INDEX_NOMATCH -1 + +class KFindNextDialog : public KDialogBase +{ +public: + KFindNextDialog(const QString &pattern, QWidget *parent); +}; + +// Create the dialog. +KFindNextDialog::KFindNextDialog(const QString &pattern, QWidget *parent) : + KDialogBase(parent, 0, false, // non-modal! + i18n("Find Next"), + User1 | Close, + User1, + false, + KStdGuiItem::find()) +{ + setMainWidget( new QLabel( i18n("<qt>Find next occurrence of '<b>%1</b>'?</qt>").arg(pattern), this ) ); +} + +//// + +struct KFind::Private +{ + Private() : + findDialog(0), + patternChanged(false), + matchedPattern(""), + incrementalPath(29, true), + emptyMatch(0), + currentId(0), + customIds(false) + { + incrementalPath.setAutoDelete(true); + data.setAutoDelete(true); + } + + ~Private() + { + delete emptyMatch; + emptyMatch = 0; + } + + struct Match + { + Match(int dataId, int index, int matchedLength) : + dataId(dataId), + index(index), + matchedLength(matchedLength) + { } + + int dataId; + int index; + int matchedLength; + }; + + struct Data + { + Data() : id(-1), dirty(false) { } + Data(int id, const QString &text, bool dirty = false) : + id(id), + text(text), + dirty(dirty) + { } + + int id; + QString text; + bool dirty; + }; + + QGuardedPtr<QWidget> findDialog; + bool patternChanged; + QString matchedPattern; + QDict<Match> incrementalPath; + Match * emptyMatch; + QPtrVector<Data> data; + int currentId; + bool customIds; +}; + +//// + +KFind::KFind( const QString &pattern, long options, QWidget *parent ) + : QObject( parent ) +{ + d = new KFind::Private; + m_options = options; + init( pattern ); +} + +KFind::KFind( const QString &pattern, long options, QWidget *parent, QWidget *findDialog ) + : QObject( parent ) +{ + d = new KFind::Private; + d->findDialog = findDialog; + m_options = options; + init( pattern ); +} + +void KFind::init( const QString& pattern ) +{ + m_matches = 0; + m_pattern = pattern; + m_dialog = 0; + m_dialogClosed = false; + m_index = INDEX_NOMATCH; + m_lastResult = NoMatch; + if (m_options & KFindDialog::RegularExpression) + m_regExp = new QRegExp(pattern, m_options & KFindDialog::CaseSensitive); + else { + m_regExp = 0; + } +} + +KFind::~KFind() +{ + delete m_dialog; + delete d; +} + +bool KFind::needData() const +{ + // always true when m_text is empty. + if (m_options & KFindDialog::FindBackwards) + // m_index==-1 and m_lastResult==Match means we haven't answered nomatch yet + // This is important in the "replace with a prompt" case. + return ( m_index < 0 && m_lastResult != Match ); + else + // "index over length" test removed: we want to get a nomatch before we set data again + // This is important in the "replace with a prompt" case. + return m_index == INDEX_NOMATCH; +} + +void KFind::setData( const QString& data, int startPos ) +{ + setData( -1, data, startPos ); +} + +void KFind::setData( int id, const QString& data, int startPos ) +{ + // cache the data for incremental find + if ( m_options & KFindDialog::FindIncremental ) + { + if ( id != -1 ) + d->customIds = true; + else + id = d->currentId + 1; + + if ( id >= (int) d->data.size() ) + d->data.resize( id + 100 ); + + d->data.insert( id, new Private::Data(id, data, true) ); + } + + if ( !(m_options & KFindDialog::FindIncremental) || needData() ) + { + m_text = data; + + if ( startPos != -1 ) + m_index = startPos; + else if (m_options & KFindDialog::FindBackwards) + m_index = m_text.length(); + else + m_index = 0; +#ifdef DEBUG_FIND + kdDebug() << "setData: '" << m_text << "' m_index=" << m_index << endl; +#endif + Q_ASSERT( m_index != INDEX_NOMATCH ); + m_lastResult = NoMatch; + + d->currentId = id; + } +} + +KDialogBase* KFind::findNextDialog( bool create ) +{ + if ( !m_dialog && create ) + { + m_dialog = new KFindNextDialog( m_pattern, parentWidget() ); + connect( m_dialog, SIGNAL( user1Clicked() ), this, SLOT( slotFindNext() ) ); + connect( m_dialog, SIGNAL( finished() ), this, SLOT( slotDialogClosed() ) ); + } + return m_dialog; +} + +KFind::Result KFind::find() +{ + Q_ASSERT( m_index != INDEX_NOMATCH || d->patternChanged ); + + if ( m_lastResult == Match && !d->patternChanged ) + { + // Move on before looking for the next match, _if_ we just found a match + if (m_options & KFindDialog::FindBackwards) { + m_index--; + if ( m_index == -1 ) // don't call KFind::find with -1, it has a special meaning + { + m_lastResult = NoMatch; + return NoMatch; + } + } else + m_index++; + } + d->patternChanged = false; + + if ( m_options & KFindDialog::FindIncremental ) + { + // if the current pattern is shorter than the matchedPattern we can + // probably look up the match in the incrementalPath + if ( m_pattern.length() < d->matchedPattern.length() ) + { + Private::Match *match = m_pattern.isEmpty() ? d->emptyMatch : d->incrementalPath[m_pattern]; + QString previousPattern = d->matchedPattern; + d->matchedPattern = m_pattern; + if ( match != 0 ) + { + bool clean = true; + + // find the first result backwards on the path that isn't dirty + while ( d->data[match->dataId]->dirty == true && + !m_pattern.isEmpty() ) + { + m_pattern.truncate( m_pattern.length() - 1 ); + + match = d->incrementalPath[m_pattern]; + + clean = false; + } + + // remove all matches that lie after the current match + while ( m_pattern.length() < previousPattern.length() ) + { + d->incrementalPath.remove(previousPattern); + previousPattern.truncate(previousPattern.length() - 1); + } + + // set the current text, index, etc. to the found match + m_text = d->data[match->dataId]->text; + m_index = match->index; + m_matchedLength = match->matchedLength; + d->currentId = match->dataId; + + // if the result is clean we can return it now + if ( clean ) + { + if ( d->customIds ) + emit highlight(d->currentId, m_index, m_matchedLength); + else + emit highlight(m_text, m_index, m_matchedLength); + + m_lastResult = Match; + d->matchedPattern = m_pattern; + return Match; + } + } + // if we couldn't look up the match, the new pattern isn't a + // substring of the matchedPattern, so we start a new search + else + { + startNewIncrementalSearch(); + } + } + // if the new pattern is longer than the matchedPattern we might be + // able to proceed from the last search + else if ( m_pattern.length() > d->matchedPattern.length() ) + { + // continue from the previous pattern + if ( m_pattern.startsWith(d->matchedPattern) ) + { + // we can't proceed from the previous position if the previous + // position already failed + if ( m_index == INDEX_NOMATCH ) + return NoMatch; + + QString temp = m_pattern; + m_pattern.truncate(d->matchedPattern.length() + 1); + d->matchedPattern = temp; + } + // start a new search + else + { + startNewIncrementalSearch(); + } + } + // if the new pattern is as long as the matchedPattern, we reset if + // they are not equal + else if ( m_pattern != d->matchedPattern ) + { + startNewIncrementalSearch(); + } + } + +#ifdef DEBUG_FIND + kdDebug() << k_funcinfo << "m_index=" << m_index << endl; +#endif + do + { + // if we have multiple data blocks in our cache, walk through these + // blocks till we either searched all blocks or we find a match + do + { + // Find the next candidate match. + if ( m_options & KFindDialog::RegularExpression ) + m_index = KFind::find(m_text, *m_regExp, m_index, m_options, &m_matchedLength); + else + m_index = KFind::find(m_text, m_pattern, m_index, m_options, &m_matchedLength); + + if ( m_options & KFindDialog::FindIncremental ) + d->data[d->currentId]->dirty = false; + + if ( m_index == -1 && d->currentId < (int) d->data.count() - 1 ) + { + m_text = d->data[++d->currentId]->text; + + if ( m_options & KFindDialog::FindBackwards ) + m_index = m_text.length(); + else + m_index = 0; + } + else + break; + } while ( !(m_options & KFindDialog::RegularExpression) ); + + if ( m_index != -1 ) + { + // Flexibility: the app can add more rules to validate a possible match + if ( validateMatch( m_text, m_index, m_matchedLength ) ) + { + bool done = true; + + if ( m_options & KFindDialog::FindIncremental ) + { + if ( m_pattern.isEmpty() ) { + delete d->emptyMatch; + d->emptyMatch = new Private::Match( d->currentId, m_index, m_matchedLength ); + } else + d->incrementalPath.replace(m_pattern, new Private::Match(d->currentId, m_index, m_matchedLength)); + + if ( m_pattern.length() < d->matchedPattern.length() ) + { + m_pattern += d->matchedPattern.mid(m_pattern.length(), 1); + done = false; + } + } + + if ( done ) + { + m_matches++; + // Tell the world about the match we found, in case someone wants to + // highlight it. + if ( d->customIds ) + emit highlight(d->currentId, m_index, m_matchedLength); + else + emit highlight(m_text, m_index, m_matchedLength); + + if ( !m_dialogClosed ) + findNextDialog(true)->show(); + +#ifdef DEBUG_FIND + kdDebug() << k_funcinfo << "Match. Next m_index=" << m_index << endl; +#endif + m_lastResult = Match; + return Match; + } + } + else // Skip match + { + if (m_options & KFindDialog::FindBackwards) + m_index--; + else + m_index++; + } + } + else + { + if ( m_options & KFindDialog::FindIncremental ) + { + QString temp = m_pattern; + temp.truncate(temp.length() - 1); + m_pattern = d->matchedPattern; + d->matchedPattern = temp; + } + + m_index = INDEX_NOMATCH; + } + } + while (m_index != INDEX_NOMATCH); + +#ifdef DEBUG_FIND + kdDebug() << k_funcinfo << "NoMatch. m_index=" << m_index << endl; +#endif + m_lastResult = NoMatch; + return NoMatch; +} + +void KFind::startNewIncrementalSearch() +{ + Private::Match *match = d->emptyMatch; + if(match == 0) + { + m_text = QString::null; + m_index = 0; + d->currentId = 0; + } + else + { + m_text = d->data[match->dataId]->text; + m_index = match->index; + d->currentId = match->dataId; + } + m_matchedLength = 0; + d->incrementalPath.clear(); + delete d->emptyMatch; + d->emptyMatch = 0; + d->matchedPattern = m_pattern; + m_pattern = QString::null; +} + +// static +int KFind::find(const QString &text, const QString &pattern, int index, long options, int *matchedLength) +{ + // Handle regular expressions in the appropriate way. + if (options & KFindDialog::RegularExpression) + { + QRegExp regExp(pattern, options & KFindDialog::CaseSensitive); + + return find(text, regExp, index, options, matchedLength); + } + + bool caseSensitive = (options & KFindDialog::CaseSensitive); + + if (options & KFindDialog::WholeWordsOnly) + { + if (options & KFindDialog::FindBackwards) + { + // Backward search, until the beginning of the line... + while (index >= 0) + { + // ...find the next match. + index = text.findRev(pattern, index, caseSensitive); + if (index == -1) + break; + + // Is the match delimited correctly? + *matchedLength = pattern.length(); + if (isWholeWords(text, index, *matchedLength)) + break; + index--; + } + } + else + { + // Forward search, until the end of the line... + while (index < (int)text.length()) + { + // ...find the next match. + index = text.find(pattern, index, caseSensitive); + if (index == -1) + break; + + // Is the match delimited correctly? + *matchedLength = pattern.length(); + if (isWholeWords(text, index, *matchedLength)) + break; + index++; + } + if (index >= (int)text.length()) // end of line + index = -1; // not found + } + } + else + { + // Non-whole-word search. + if (options & KFindDialog::FindBackwards) + { + index = text.findRev(pattern, index, caseSensitive); + } + else + { + index = text.find(pattern, index, caseSensitive); + } + if (index != -1) + { + *matchedLength = pattern.length(); + } + } + return index; +} + +// static +int KFind::find(const QString &text, const QRegExp &pattern, int index, long options, int *matchedLength) +{ + if (options & KFindDialog::WholeWordsOnly) + { + if (options & KFindDialog::FindBackwards) + { + // Backward search, until the beginning of the line... + while (index >= 0) + { + // ...find the next match. + index = text.findRev(pattern, index); + if (index == -1) + break; + + // Is the match delimited correctly? + //pattern.match(text, index, matchedLength, false); + /*int pos =*/ pattern.search( text.mid(index) ); + *matchedLength = pattern.matchedLength(); + if (isWholeWords(text, index, *matchedLength)) + break; + index--; + } + } + else + { + // Forward search, until the end of the line... + while (index < (int)text.length()) + { + // ...find the next match. + index = text.find(pattern, index); + if (index == -1) + break; + + // Is the match delimited correctly? + //pattern.match(text, index, matchedLength, false); + /*int pos =*/ pattern.search( text.mid(index) ); + *matchedLength = pattern.matchedLength(); + if (isWholeWords(text, index, *matchedLength)) + break; + index++; + } + if (index >= (int)text.length()) // end of line + index = -1; // not found + } + } + else + { + // Non-whole-word search. + if (options & KFindDialog::FindBackwards) + { + index = text.findRev(pattern, index); + } + else + { + index = text.find(pattern, index); + } + if (index != -1) + { + //pattern.match(text, index, matchedLength, false); + /*int pos =*/ pattern.search( text.mid(index) ); + *matchedLength = pattern.matchedLength(); + } + } + return index; +} + +bool KFind::isInWord(QChar ch) +{ + return ch.isLetter() || ch.isDigit() || ch == '_'; +} + +bool KFind::isWholeWords(const QString &text, int starts, int matchedLength) +{ + if ((starts == 0) || (!isInWord(text[starts - 1]))) + { + int ends = starts + matchedLength; + + if ((ends == (int)text.length()) || (!isInWord(text[ends]))) + return true; + } + return false; +} + +void KFind::slotFindNext() +{ + emit findNext(); +} + +void KFind::slotDialogClosed() +{ + emit dialogClosed(); + m_dialogClosed = true; +} + +void KFind::displayFinalDialog() const +{ + QString message; + if ( numMatches() ) + message = i18n( "1 match found.", "%n matches found.", numMatches() ); + else + message = i18n("<qt>No matches found for '<b>%1</b>'.</qt>").arg(QStyleSheet::escape(m_pattern)); + KMessageBox::information(dialogsParent(), message); +} + +bool KFind::shouldRestart( bool forceAsking, bool showNumMatches ) const +{ + // Only ask if we did a "find from cursor", otherwise it's pointless. + // Well, unless the user can modify the document during a search operation, + // hence the force boolean. + if ( !forceAsking && (m_options & KFindDialog::FromCursor) == 0 ) + { + displayFinalDialog(); + return false; + } + QString message; + if ( showNumMatches ) + { + if ( numMatches() ) + message = i18n( "1 match found.", "%n matches found.", numMatches() ); + else + message = i18n("No matches found for '<b>%1</b>'.").arg(QStyleSheet::escape(m_pattern)); + } + else + { + if ( m_options & KFindDialog::FindBackwards ) + message = i18n( "Beginning of document reached." ); + else + message = i18n( "End of document reached." ); + } + + message += "<br><br>"; // can't be in the i18n() of the first if() because of the plural form. + // Hope this word puzzle is ok, it's a different sentence + message += + ( m_options & KFindDialog::FindBackwards ) ? + i18n("Continue from the end?") + : i18n("Continue from the beginning?"); + + int ret = KMessageBox::questionYesNo( dialogsParent(), QString("<qt>")+message+QString("</qt>"), + QString::null, KStdGuiItem::cont(), KStdGuiItem::stop() ); + bool yes = ( ret == KMessageBox::Yes ); + if ( yes ) + const_cast<KFind*>(this)->m_options &= ~KFindDialog::FromCursor; // clear FromCursor option + return yes; +} + +void KFind::setOptions( long options ) +{ + m_options = options; + + delete m_regExp; + if (m_options & KFindDialog::RegularExpression) + m_regExp = new QRegExp(m_pattern, m_options & KFindDialog::CaseSensitive); + else + m_regExp = 0; +} + +void KFind::closeFindNextDialog() +{ + delete m_dialog; + m_dialog = 0L; + m_dialogClosed = true; +} + +int KFind::index() const +{ + return m_index; +} + +void KFind::setPattern( const QString& pattern ) +{ + if ( m_options & KFindDialog::FindIncremental && m_pattern != pattern ) + d->patternChanged = true; + + m_pattern = pattern; + setOptions( options() ); // rebuild m_regExp if necessary +} + +QWidget* KFind::dialogsParent() const +{ + // If the find dialog is still up, it should get the focus when closing a message box + // Otherwise, maybe the "find next?" dialog is up + // Otherwise, the "view" is the parent. + return d->findDialog ? (QWidget*)d->findDialog : ( m_dialog ? m_dialog : parentWidget() ); +} + +#include "kfind.moc" diff --git a/kutils/kfind.h b/kutils/kfind.h new file mode 100644 index 000000000..a2159f6b1 --- /dev/null +++ b/kutils/kfind.h @@ -0,0 +1,426 @@ +/* + Copyright (C) 2001, S.R.Haque <srhaque@iee.org>. + Copyright (C) 2002, David Faure <david@mandrakesoft.com> + This file is part of the KDE project + + 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. +*/ + +#ifndef KFIND_H +#define KFIND_H + +#include <kdialogbase.h> +#include <qrect.h> + +/** + * @ingroup main + * @ingroup findreplace + * @brief A generic implementation of the "find" function. + * + * @author S.R.Haque <srhaque@iee.org>, David Faure <faure@kde.org>, + * Arend van Beelen jr. <arend@auton.nl> + * + * \b Detail: + * + * This class includes prompt handling etc. Also provides some + * static functions which can be used to create custom behavior + * instead of using the class directly. + * + * \b Example: + * + * To use the class to implement a complete find feature: + * + * In the slot connected to the find action, after using KFindDialog: + * \code + * + * // This creates a find-next-prompt dialog if needed. + * m_find = new KFind(pattern, options, this); + * + * // Connect highlight signal to code which handles highlighting + * // of found text. + * connect( m_find, SIGNAL( highlight( const QString &, int, int ) ), + * this, SLOT( slotHighlight( const QString &, int, int ) ) ); + * // Connect findNext signal - called when pressing the button in the dialog + * connect( m_find, SIGNAL( findNext() ), + * this, SLOT( slotFindNext() ) ); + * \endcode + * + * If you are using a non-modal find dialog (the recommended new way + * in KDE-3.2), you should call right away m_find->closeFindNextDialog(). + * + * Then initialize the variables determining the "current position" + * (to the cursor, if the option FromCursor is set, + * to the beginning of the selection if the option SelectedText is set, + * and to the beginning of the document otherwise). + * Initialize the "end of search" variables as well (end of doc or end of selection). + * Swap begin and end if FindBackwards. + * Finally, call slotFindNext(); + * + * \code + * void slotFindNext() + * { + * KFind::Result res = KFind::NoMatch; + * while ( res == KFind::NoMatch && <position not at end> ) { + * if ( m_find->needData() ) + * m_find->setData( <current text fragment> ); + * + * // Let KFind inspect the text fragment, and display a dialog if a match is found + * res = m_find->find(); + * + * if ( res == KFind::NoMatch ) { + * <Move to the next text fragment, honoring the FindBackwards setting for the direction> + * } + * } + * + * if ( res == KFind::NoMatch ) // i.e. at end + * <Call either m_find->displayFinalDialog(); delete m_find; m_find = 0L; + * or if ( m_find->shouldRestart() ) { reinit (w/o FromCursor) and call slotFindNext(); } + * else { m_find->closeFindNextDialog(); }> + * } + * \endcode + * + * Don't forget to delete m_find in the destructor of your class, + * unless you gave it a parent widget on construction. + * + * This implementation allows to have a "Find Next" action, which resumes the + * search, even if the user closed the "Find Next" dialog. + * + * A "Find Previous" action can simply switch temporarily the value of + * FindBackwards and call slotFindNext() - and reset the value afterwards. + */ +class KUTILS_EXPORT KFind : + public QObject +{ + Q_OBJECT + +public: + + /** + * Only use this constructor if you don't use KFindDialog, or if + * you use it as a modal dialog. + * @param pattern The pattern to look for. + * @param options Options for the find dialog. @see KFindDialog. + * @param parent The parent widget. + */ + KFind(const QString &pattern, long options, QWidget *parent); + + /** + * This is the recommended constructor if you also use KFindDialog (non-modal). + * You should pass the pointer to it here, so that when a message box + * appears it has the right parent. Don't worry about deletion, KFind + * will notice if the find dialog is closed. + * @param pattern The pattern to look for. + * @param options Options for the find dialog. @see KFindDialog. + * @param parent The parent widget. + * @param findDialog A pointer to the KFindDialog object. + */ + KFind(const QString &pattern, long options, QWidget *parent, QWidget* findDialog); + + /** + * Destructor. + */ + virtual ~KFind(); + + /** + * Result enum. Holds information if the find was successful. + */ + enum Result { + NoMatch, ///< No match was found. + Match ///< A match was found. + }; + + /** + * @return @c true if the application must supply a new text fragment + * It also means the last call returned "NoMatch". But by storing this here + * the application doesn't have to store it in a member variable (between + * calls to slotFindNext()). + */ + bool needData() const; + + /** + * Call this when needData returns @c true, before calling find(). + * @param data the text fragment (line) + * @param startPos if set, the index at which the search should start. + * This is only necessary for the very first call to setData usually, + * for the 'find in selection' feature. A value of -1 (the default value) + * means "process all the data", i.e. either 0 or data.length()-1 depending + * on FindBackwards. + */ + void setData( const QString& data, int startPos = -1 ); + + /** + * Call this when needData returns @c true, before calling find(). The use of + * ID's is especially useful if you're using the FindIncremental option. + * @param id the id of the text fragment + * @param data the text fragment (line) + * @param startPos if set, the index at which the search should start. + * This is only necessary for the very first call to setData usually, + * for the 'find in selection' feature. A value of -1 (the default value) + * means "process all the data", i.e. either 0 or data.length()-1 depending + * on FindBackwards. + * + * @since 3.3 + */ + void setData( int id, const QString& data, int startPos = -1 ); + + /** + * Walk the text fragment (e.g. text-processor line, kspread cell) looking for matches. + * For each match, emits the highlight() signal and displays the find-again dialog + * proceeding. + * @return Whether or not there has been a match. + */ + Result find(); + + /** + * Return the current options. + * + * Warning: this is usually the same value as the one passed to the constructor, + * but options might change _during_ the replace operation: + * e.g. the "All" button resets the PromptOnReplace flag. + * + * @return The current options. @see KFindDialog. + */ + long options() const { return m_options; } + + /** + * Set new options. Usually this is used for setting or clearing the + * FindBackwards options. + * + * @see KFindDialog. + */ + virtual void setOptions( long options ); + + /** + * @return the pattern we're currently looking for + */ + QString pattern() const { return m_pattern; } + + /** + * Change the pattern we're looking for + * @param pattern The new pattern. + */ + void setPattern( const QString& pattern ); + + /** + * Return the number of matches found (i.e. the number of times + * the highlight signal was emitted). + * If 0, can be used in a dialog box to tell the user "no match was found". + * The final dialog does so already, unless you used setDisplayFinalDialog(false). + * @return The number of matches. + */ + int numMatches() const { return m_matches; } + + /** + * Call this to reset the numMatches count + * (and the numReplacements count for a KReplace). + * Can be useful if reusing the same KReplace for different operations, + * or when restarting from the beginning of the document. + */ + virtual void resetCounts() { m_matches = 0; } + + /** + * Virtual method, which allows applications to add extra checks for + * validating a candidate match. It's only necessary to reimplement this + * if the find dialog extension has been used to provide additional + * criterias. + * + * @param text The current text fragment + * @param index The starting index where the candidate match was found + * @param matchedlength The length of the candidate match + */ + virtual bool validateMatch( const QString & text, int index, int matchedlength ) { + Q_UNUSED(text); Q_UNUSED(index); Q_UNUSED(matchedlength); return true; } + + /** + * Returns @c true if we should restart the search from scratch. + * Can ask the user, or return @c false (if we already searched the whole document). + * + * @param forceAsking set to @c true if the user modified the document during the + * search. In that case it makes sense to restart the search again. + * + * @param showNumMatches set to @c true if the dialog should show the number of + * matches. Set to @c false if the application provides a "find previous" action, + * in which case the match count will be erroneous when hitting the end, + * and we could even be hitting the beginning of the document (so not all + * matches have even been seen). + * + * @return @c true, if the search should be restarted. + */ + virtual bool shouldRestart( bool forceAsking = false, bool showNumMatches = true ) const; + + /** + * Search the given string, and returns whether a match was found. If one is, + * the length of the string matched is also returned. + * + * A performance optimised version of the function is provided for use + * with regular expressions. + * + * @param text The string to search. + * @param pattern The pattern to look for. + * @param index The starting index into the string. + * @param options The options to use. + * @param matchedlength The length of the string that was matched + * @return The index at which a match was found, or -1 if no match was found. + */ + static int find( const QString &text, const QString &pattern, int index, long options, int *matchedlength ); + + /** + * Search the given regular expression, and returns whether a match was found. If one is, + * the length of the matched string is also returned. + * + * Another version of the function is provided for use with strings. + * + * @param text The string to search. + * @param pattern The regular expression pattern to look for. + * @param index The starting index into the string. + * @param options The options to use. + * @param matchedlength The length of the string that was matched + * @return The index at which a match was found, or -1 if no match was found. + */ + static int find( const QString &text, const QRegExp &pattern, int index, long options, int *matchedlength ); + + /** + * Displays the final dialog saying "no match was found", if that was the case. + * Call either this or shouldRestart(). + */ + virtual void displayFinalDialog() const; + + /** + * Return (or create) the dialog that shows the "find next?" prompt. + * Usually you don't need to call this. + * One case where it can be useful, is when the user selects the "Find" + * menu item while a find operation is under way. In that case, the + * program may want to call setActiveWindow() on that dialog. + * @return The find next dialog. + */ + KDialogBase* findNextDialog( bool create = false ); + + /** + * Close the "find next?" dialog. The application should do this when + * the last match was hit. If the application deletes the KFind, then + * "find previous" won't be possible anymore. + * + * IMPORTANT: you should also call this if you are using a non-modal + * find dialog, to tell KFind not to pop up its own dialog. + */ + void closeFindNextDialog(); + + /** + * @return the current matching index ( or -1 ). + * Same as the matchingIndex parameter passed to highlight. + * You usually don't need to use this, except maybe when updating the current data, + * so you need to call setData( newData, index() ). + * @since 3.2 + */ + int index() const; + +signals: + + /** + * Connect to this signal to implement highlighting of found text during the find + * operation. + * + * If you've set data with setData(id, text), use the signal highlight(id, + * matchingIndex, matchedLength) + * + * @warning If you're using the FindIncremental option, the text argument + * passed by this signal is not necessarily the data last set through + * setData(), but can also be an earlier set data block. + * + * @param text The found text. + * @param matchingIndex The index of the found text's occurrence. + * @param matchedLength The length of the matched text. + * @see setData() + */ + void highlight(const QString &text, int matchingIndex, int matchedLength); + + /** + * Connect to this signal to implement highlighting of found text during the find + * operation. + * + * Use this signal if you've set your data with setData(id, text), otherwise + * use the signal with highlight(text, matchingIndex, matchedLength). + * + * @warning If you're using the FindIncremental option, the id argument + * passed by this signal is not necessarily the id of the data last set + * through setData(), but can also be of an earlier set data block. + * + * @param id The ID of the text fragment, as used in setData(). + * @param matchingIndex The index of the found text's occurrence. + * @param matchedLength The length of the matched text. + * @see setData() + * + * @since 3.3 + */ + void highlight(int id, int matchingIndex, int matchedLength); + + // ## TODO docu + // findprevious will also emit findNext, after temporarily switching the value + // of FindBackwards + void findNext(); + + /** + * Emitted when the options have changed. + * This can happen e.g. with "Replace All", or if our 'find next' dialog + * gets a "find previous" one day. + */ + void optionsChanged(); + + /** + * Emitted when the 'find next' dialog is being closed. + * Some apps might want to remove the highlighted text when this happens. + * Apps without support for "Find Next" can also do m_find->deleteLater() + * to terminate the find operation. + */ + void dialogClosed(); + +protected: + + QWidget* parentWidget() const { return (QWidget *)parent(); } + QWidget* dialogsParent() const; + +protected slots: + + void slotFindNext(); + void slotDialogClosed(); + +private: + void init( const QString& pattern ); + void startNewIncrementalSearch(); + + static bool isInWord( QChar ch ); + static bool isWholeWords( const QString &text, int starts, int matchedLength ); + + friend class KReplace; + + + QString m_pattern; + QRegExp *m_regExp; + KDialogBase* m_dialog; + long m_options; + unsigned m_matches; + + QString m_text; // the text set by setData + int m_index; + int m_matchedLength; + bool m_dialogClosed; + bool m_lastResult; + + // Binary compatible extensibility. + struct Private; + Private *d; +}; + +#endif diff --git a/kutils/kfinddialog.cpp b/kutils/kfinddialog.cpp new file mode 100644 index 000000000..7a18111a3 --- /dev/null +++ b/kutils/kfinddialog.cpp @@ -0,0 +1,544 @@ +/* + Copyright (C) 2001, S.R.Haque <srhaque@iee.org>. + Copyright (C) 2002, David Faure <david@mandrakesoft.com> + This file is part of the KDE project + + 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 "kfinddialog.h" +#include <qcheckbox.h> +#include <qcursor.h> +#include <qgroupbox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpopupmenu.h> +#include <qpushbutton.h> +#include <qregexp.h> +#include <kcombobox.h> +#include <kdebug.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <assert.h> +#include <qwhatsthis.h> + +#include <kregexpeditorinterface.h> +#include <kparts/componentfactory.h> + +class KFindDialog::KFindDialogPrivate +{ +public: + KFindDialogPrivate() : m_regexpDialog(0), + m_regexpDialogQueryDone(false), + m_enabled(WholeWordsOnly | FromCursor | SelectedText | CaseSensitive | FindBackwards | RegularExpression), m_initialShowDone(false) {} + QDialog* m_regexpDialog; + bool m_regexpDialogQueryDone; + long m_enabled; // uses Options to define which search options are enabled + bool m_initialShowDone; + QStringList findStrings; + QString pattern; +}; + +KFindDialog::KFindDialog(QWidget *parent, const char *name, long options, const QStringList &findStrings, bool hasSelection) : + KDialogBase(parent, name, true, i18n("Find Text"), Ok | Cancel, Ok), + m_findExtension (0), + m_replaceExtension (0) +{ + d = new KFindDialogPrivate; + init(false, findStrings, hasSelection); + setOptions(options); + setButtonCancel( KStdGuiItem::close() ); +} + +KFindDialog::KFindDialog(bool modal, QWidget *parent, const char *name, long options, const QStringList &findStrings, bool hasSelection) : + KDialogBase(parent, name, modal, i18n("Find Text"), Ok | Cancel, Ok), + m_findExtension (0), + m_replaceExtension (0) +{ + d = new KFindDialogPrivate; + init(false, findStrings, hasSelection); + setOptions(options); + setButtonCancel( KStdGuiItem::close() ); +} + +KFindDialog::KFindDialog(QWidget *parent, const char *name, bool /*forReplace*/) : + KDialogBase(parent, name, true, i18n("Replace Text"), Ok | Cancel, Ok), + m_findExtension (0), + m_replaceExtension (0) +{ + d = new KFindDialogPrivate; + setButtonCancel( KStdGuiItem::close() ); +} + +KFindDialog::~KFindDialog() +{ + delete d; +} + +QWidget *KFindDialog::findExtension() +{ + if (!m_findExtension) + { + m_findExtension = new QWidget(m_findGrp); + m_findLayout->addMultiCellWidget(m_findExtension, 3, 3, 0, 1); + } + + return m_findExtension; +} + +QStringList KFindDialog::findHistory() const +{ + return m_find->historyItems(); +} + +void KFindDialog::init(bool forReplace, const QStringList &findStrings, bool hasSelection) +{ + QVBoxLayout *topLayout; + QGridLayout *optionsLayout; + + // Create common parts of dialog. + QWidget *page = new QWidget(this); + setMainWidget(page); + + topLayout = new QVBoxLayout(page); + topLayout->setSpacing( KDialog::spacingHint() ); + topLayout->setMargin( 0 ); + + m_findGrp = new QGroupBox(0, Qt::Vertical, i18n("Find"), page); + m_findGrp->layout()->setSpacing( KDialog::spacingHint() ); + // m_findGrp->layout()->setMargin( KDialog::marginHint() ); + m_findLayout = new QGridLayout(m_findGrp->layout()); + m_findLayout->setSpacing( KDialog::spacingHint() ); + // m_findLayout->setMargin( KDialog::marginHint() ); + + m_findLabel = new QLabel(i18n("&Text to find:"), m_findGrp); + m_find = new KHistoryCombo(true, m_findGrp); + m_find->setMaxCount(10); + m_find->setDuplicatesEnabled(false); + m_regExp = new QCheckBox(i18n("Regular e&xpression"), m_findGrp); + m_regExpItem = new QPushButton(i18n("&Edit..."), m_findGrp); + m_regExpItem->setEnabled(false); + + m_findLayout->addWidget(m_findLabel, 0, 0); + m_findLayout->addMultiCellWidget(m_find, 1, 1, 0, 1); + m_findLayout->addWidget(m_regExp, 2, 0); + m_findLayout->addWidget(m_regExpItem, 2, 1); + topLayout->addWidget(m_findGrp); + + m_replaceGrp = new QGroupBox(0, Qt::Vertical, i18n("Replace With"), page); + m_replaceGrp->layout()->setSpacing( KDialog::spacingHint() ); + // m_replaceGrp->layout()->setMargin( KDialog::marginHint() ); + m_replaceLayout = new QGridLayout(m_replaceGrp->layout()); + m_replaceLayout->setSpacing( KDialog::spacingHint() ); +// m_replaceLayout->setMargin( KDialog::marginHint() ); + + m_replaceLabel = new QLabel(i18n("Replace&ment text:"), m_replaceGrp); + m_replace = new KHistoryCombo(true, m_replaceGrp); + m_replace->setMaxCount(10); + m_replace->setDuplicatesEnabled(false); + m_backRef = new QCheckBox(i18n("Use p&laceholders"), m_replaceGrp); + m_backRefItem = new QPushButton(i18n("Insert Place&holder"), m_replaceGrp); + m_backRefItem->setEnabled(false); + + m_replaceLayout->addWidget(m_replaceLabel, 0, 0); + m_replaceLayout->addMultiCellWidget(m_replace, 1, 1, 0, 1); + m_replaceLayout->addWidget(m_backRef, 2, 0); + m_replaceLayout->addWidget(m_backRefItem, 2, 1); + topLayout->addWidget(m_replaceGrp); + + m_optionGrp = new QGroupBox(0, Qt::Vertical, i18n("Options"), page); + m_optionGrp->layout()->setSpacing(KDialog::spacingHint()); + // m_optionGrp->layout()->setMargin(KDialog::marginHint()); + optionsLayout = new QGridLayout(m_optionGrp->layout()); + optionsLayout->setSpacing( KDialog::spacingHint() ); + // optionsLayout->setMargin( KDialog::marginHint() ); + + m_caseSensitive = new QCheckBox(i18n("C&ase sensitive"), m_optionGrp); + m_wholeWordsOnly = new QCheckBox(i18n("&Whole words only"), m_optionGrp); + m_fromCursor = new QCheckBox(i18n("From c&ursor"), m_optionGrp); + m_findBackwards = new QCheckBox(i18n("Find &backwards"), m_optionGrp); + m_selectedText = new QCheckBox(i18n("&Selected text"), m_optionGrp); + setHasSelection( hasSelection ); + // If we have a selection, we make 'find in selection' default + // and if we don't, then the option has to be unchecked, obviously. + m_selectedText->setChecked( hasSelection ); + slotSelectedTextToggled( hasSelection ); + + m_promptOnReplace = new QCheckBox(i18n("&Prompt on replace"), m_optionGrp); + m_promptOnReplace->setChecked( true ); + + optionsLayout->addWidget(m_caseSensitive, 0, 0); + optionsLayout->addWidget(m_wholeWordsOnly, 1, 0); + optionsLayout->addWidget(m_fromCursor, 2, 0); + optionsLayout->addWidget(m_findBackwards, 0, 1); + optionsLayout->addWidget(m_selectedText, 1, 1); + optionsLayout->addWidget(m_promptOnReplace, 2, 1); + topLayout->addWidget(m_optionGrp); + + // We delay creation of these until needed. + m_patterns = 0L; + m_placeholders = 0L; + + // signals and slots connections + connect(m_selectedText, SIGNAL(toggled(bool)), this, SLOT(slotSelectedTextToggled(bool))); + connect(m_regExp, SIGNAL(toggled(bool)), m_regExpItem, SLOT(setEnabled(bool))); + connect(m_backRef, SIGNAL(toggled(bool)), m_backRefItem, SLOT(setEnabled(bool))); + connect(m_regExpItem, SIGNAL(clicked()), this, SLOT(showPatterns())); + connect(m_backRefItem, SIGNAL(clicked()), this, SLOT(showPlaceholders())); + + connect(m_find, SIGNAL(textChanged ( const QString & )),this, SLOT(textSearchChanged( const QString & ))); + + // tab order + setTabOrder(m_find, m_regExp); + setTabOrder(m_regExp, m_regExpItem); + setTabOrder(m_regExpItem, m_replace); + setTabOrder(m_replace, m_backRef); + setTabOrder(m_backRef, m_backRefItem); + setTabOrder(m_backRefItem, m_caseSensitive); + setTabOrder(m_caseSensitive, m_wholeWordsOnly); + setTabOrder(m_wholeWordsOnly, m_fromCursor); + setTabOrder(m_fromCursor, m_findBackwards); + setTabOrder(m_findBackwards, m_selectedText); + setTabOrder(m_selectedText, m_promptOnReplace); + + // buddies + m_findLabel->setBuddy(m_find); + m_replaceLabel->setBuddy(m_replace); + + if (!forReplace) + { + m_promptOnReplace->hide(); + m_replaceGrp->hide(); + } + + d->findStrings = findStrings; + m_find->setFocus(); + enableButtonOK( !pattern().isEmpty() ); + if (forReplace) + { + setButtonOK(KGuiItem( i18n("&Replace"), QString::null, + i18n("Start replace"), + i18n("<qt>If you press the <b>Replace</b> button, the text you entered " + "above is searched for within the document and any occurrence is " + "replaced with the replacement text.</qt>"))); + } + else + { + setButtonOK(KGuiItem( i18n("&Find"), "find", + i18n("Start searching"), + i18n("<qt>If you press the <b>Find</b> button, the text you entered " + "above is searched for within the document.</qt>"))); + } + + // QWhatsthis texts + QWhatsThis::add ( m_find, i18n( + "Enter a pattern to search for, or select a previous pattern from " + "the list.") ); + QWhatsThis::add ( m_regExp, i18n( + "If enabled, search for a regular expression.") ); + QWhatsThis::add ( m_regExpItem, i18n( + "Click here to edit your regular expression using a graphical editor.") ); + QWhatsThis::add ( m_replace, i18n( + "Enter a replacement string, or select a previous one from the list.") ); + QWhatsThis::add( m_backRef, i18n( + "<qt>If enabled, any occurrence of <code><b>\\N</b></code>, where " + "<code><b>N</b></code> is a integer number, will be replaced with " + "the corresponding capture (\"parenthesized substring\") from the " + "pattern.<p>To include (a literal <code><b>\\N</b></code> in your " + "replacement, put an extra backslash in front of it, like " + "<code><b>\\\\N</b></code>.</qt>") ); + QWhatsThis::add ( m_backRefItem, i18n( + "Click for a menu of available captures.") ); + QWhatsThis::add ( m_wholeWordsOnly, i18n( + "Require word boundaries in both ends of a match to succeed.") ); + QWhatsThis::add ( m_fromCursor, i18n( + "Start searching at the current cursor location rather than at the top.") ); + QWhatsThis::add ( m_selectedText, i18n( + "Only search within the current selection.") ); + QWhatsThis::add ( m_caseSensitive, i18n( + "Perform a case sensitive search: entering the pattern " + "'Joe' will not match 'joe' or 'JOE', only 'Joe'.") ); + QWhatsThis::add ( m_findBackwards, i18n( + "Search backwards.") ); + QWhatsThis::add ( m_promptOnReplace, i18n( + "Ask before replacing each match found.") ); +} + +void KFindDialog::textSearchChanged( const QString & text) +{ + enableButtonOK( !text.isEmpty() ); +} + +void KFindDialog::showEvent( QShowEvent *e ) +{ + if ( !d->m_initialShowDone ) + { + d->m_initialShowDone = true; // only once + kdDebug() << "showEvent\n"; + if (!d->findStrings.isEmpty()) + setFindHistory(d->findStrings); + d->findStrings = QStringList(); + if (!d->pattern.isEmpty()) { + m_find->lineEdit()->setText( d->pattern ); + m_find->lineEdit()->selectAll(); + d->pattern = QString::null; + } + } + KDialogBase::showEvent(e); +} + +long KFindDialog::options() const +{ + long options = 0; + + if (m_caseSensitive->isChecked()) + options |= CaseSensitive; + if (m_wholeWordsOnly->isChecked()) + options |= WholeWordsOnly; + if (m_fromCursor->isChecked()) + options |= FromCursor; + if (m_findBackwards->isChecked()) + options |= FindBackwards; + if (m_selectedText->isChecked()) + options |= SelectedText; + if (m_regExp->isChecked()) + options |= RegularExpression; + return options; +} + +QString KFindDialog::pattern() const +{ + return m_find->currentText(); +} + +void KFindDialog::setPattern (const QString &pattern) +{ + m_find->lineEdit()->setText( pattern ); + m_find->lineEdit()->selectAll(); + d->pattern = pattern; + kdDebug() << "setPattern " << pattern<<endl; +} + +void KFindDialog::setFindHistory(const QStringList &strings) +{ + if (strings.count() > 0) + { + m_find->setHistoryItems(strings, true); + m_find->lineEdit()->setText( strings.first() ); + m_find->lineEdit()->selectAll(); + } + else + m_find->clearHistory(); +} + +void KFindDialog::setHasSelection(bool hasSelection) +{ + if (hasSelection) d->m_enabled |= SelectedText; + else d->m_enabled &= ~SelectedText; + m_selectedText->setEnabled( hasSelection ); + if ( !hasSelection ) + { + m_selectedText->setChecked( false ); + slotSelectedTextToggled( hasSelection ); + } +} + +void KFindDialog::slotSelectedTextToggled(bool selec) +{ + // From cursor doesn't make sense if we have a selection + m_fromCursor->setEnabled( !selec && (d->m_enabled & FromCursor) ); + if ( selec ) // uncheck if disabled + m_fromCursor->setChecked( false ); +} + +void KFindDialog::setHasCursor(bool hasCursor) +{ + if (hasCursor) d->m_enabled |= FromCursor; + else d->m_enabled &= ~FromCursor; + m_fromCursor->setEnabled( hasCursor ); + m_fromCursor->setChecked( hasCursor && (options() & FromCursor) ); +} + +void KFindDialog::setSupportsBackwardsFind( bool supports ) +{ + // ########## Shouldn't this hide the checkbox instead? + if (supports) d->m_enabled |= FindBackwards; + else d->m_enabled &= ~FindBackwards; + m_findBackwards->setEnabled( supports ); + m_findBackwards->setChecked( supports && (options() & FindBackwards) ); +} + +void KFindDialog::setSupportsCaseSensitiveFind( bool supports ) +{ + // ########## This should hide the checkbox instead + if (supports) d->m_enabled |= CaseSensitive; + else d->m_enabled &= ~CaseSensitive; + m_caseSensitive->setEnabled( supports ); + m_caseSensitive->setChecked( supports && (options() & CaseSensitive) ); +} + +void KFindDialog::setSupportsWholeWordsFind( bool supports ) +{ + // ########## This should hide the checkbox instead + if (supports) d->m_enabled |= WholeWordsOnly; + else d->m_enabled &= ~WholeWordsOnly; + m_wholeWordsOnly->setEnabled( supports ); + m_wholeWordsOnly->setChecked( supports && (options() & WholeWordsOnly) ); +} + +void KFindDialog::setSupportsRegularExpressionFind( bool supports ) +{ + // ########## This should hide the checkbox instead + if (supports) d->m_enabled |= RegularExpression; + else d->m_enabled &= ~RegularExpression; + m_regExp->setEnabled( supports ); + m_regExp->setChecked( supports && (options() & RegularExpression) ); +} + +void KFindDialog::setOptions(long options) +{ + m_caseSensitive->setChecked((d->m_enabled & CaseSensitive) && (options & CaseSensitive)); + m_wholeWordsOnly->setChecked((d->m_enabled & WholeWordsOnly) && (options & WholeWordsOnly)); + m_fromCursor->setChecked((d->m_enabled & FromCursor) && (options & FromCursor)); + m_findBackwards->setChecked((d->m_enabled & FindBackwards) && (options & FindBackwards)); + m_selectedText->setChecked((d->m_enabled & SelectedText) && (options & SelectedText)); + m_regExp->setChecked((d->m_enabled & RegularExpression) && (options & RegularExpression)); +} + +// Create a popup menu with a list of regular expression terms, to help the user +// compose a regular expression search pattern. +void KFindDialog::showPatterns() +{ + if ( !d->m_regexpDialogQueryDone ) + { + d->m_regexpDialog = KParts::ComponentFactory::createInstanceFromQuery<QDialog>( "KRegExpEditor/KRegExpEditor", QString::null, this ); + d->m_regexpDialogQueryDone = true; + } + + if ( d->m_regexpDialog ) + { + KRegExpEditorInterface *iface = static_cast<KRegExpEditorInterface *>( d->m_regexpDialog->qt_cast( "KRegExpEditorInterface" ) ); + assert( iface ); + + iface->setRegExp( pattern() ); + if ( d->m_regexpDialog->exec() == QDialog::Accepted ) + setPattern( iface->regExp() ); + } + else // No complete regexp-editor available, bring up the old popupmenu + { + typedef struct + { + const char *description; + const char *regExp; + int cursorAdjustment; + } term; + static const term items[] = + { + { I18N_NOOP("Any Character"), ".", 0 }, + { I18N_NOOP("Start of Line"), "^", 0 }, + { I18N_NOOP("End of Line"), "$", 0 }, + { I18N_NOOP("Set of Characters"), "[]", -1 }, + { I18N_NOOP("Repeats, Zero or More Times"), "*", 0 }, + { I18N_NOOP("Repeats, One or More Times"), "+", 0 }, + { I18N_NOOP("Optional"), "?", 0 }, + { I18N_NOOP("Escape"), "\\", 0 }, + { I18N_NOOP("TAB"), "\\t", 0 }, + { I18N_NOOP("Newline"), "\\n", 0 }, + { I18N_NOOP("Carriage Return"), "\\r", 0 }, + { I18N_NOOP("White Space"), "\\s", 0 }, + { I18N_NOOP("Digit"), "\\d", 0 }, + }; + int i; + + // Populate the popup menu. + if (!m_patterns) + { + m_patterns = new QPopupMenu(this); + for (i = 0; (unsigned)i < sizeof(items) / sizeof(items[0]); i++) + { + m_patterns->insertItem(i18n(items[i].description), i, i); + } + } + + // Insert the selection into the edit control. + i = m_patterns->exec(m_regExpItem->mapToGlobal(m_regExpItem->rect().bottomLeft())); + if (i != -1) + { + QLineEdit *editor = m_find->lineEdit(); + + editor->insert(items[i].regExp); + editor->setCursorPosition(editor->cursorPosition() + items[i].cursorAdjustment); + } + } +} + +// Create a popup menu with a list of backreference terms, to help the user +// compose a regular expression replacement pattern. +void KFindDialog::showPlaceholders() +{ + // Populate the popup menu. + if (!m_placeholders) + { + m_placeholders = new QPopupMenu(this); + connect( m_placeholders, SIGNAL(aboutToShow()), this, SLOT(slotPlaceholdersAboutToShow()) ); + } + + // Insert the selection into the edit control. + int i = m_placeholders->exec(m_backRefItem->mapToGlobal(m_backRefItem->rect().bottomLeft())); + if (i != -1) + { + QLineEdit *editor = m_replace->lineEdit(); + editor->insert( QString("\\%1").arg( i ) ); + } +} + +void KFindDialog::slotPlaceholdersAboutToShow() +{ + m_placeholders->clear(); + m_placeholders->insertItem( i18n("Complete Match"), 0 ); + + QRegExp r( pattern() ); + uint n = r.numCaptures(); + for ( uint i=0; i < n; i++ ) + m_placeholders->insertItem( i18n("Captured Text (%1)").arg( i+1 ), i+1 ); +} + +void KFindDialog::slotOk() +{ + // Nothing to find? + if (pattern().isEmpty()) + { + KMessageBox::error(this, i18n("You must enter some text to search for.")); + return; + } + + if (m_regExp->isChecked()) + { + // Check for a valid regular expression. + QRegExp regExp(pattern()); + + if (!regExp.isValid()) + { + KMessageBox::error(this, i18n("Invalid regular expression.")); + return; + } + } + m_find->addToHistory(pattern()); + emit okClicked(); + if ( testWFlags( WShowModal ) ) + accept(); +} +// kate: space-indent on; indent-width 4; replace-tabs on; +#include "kfinddialog.moc" diff --git a/kutils/kfinddialog.h b/kutils/kfinddialog.h new file mode 100644 index 000000000..ed63d5551 --- /dev/null +++ b/kutils/kfinddialog.h @@ -0,0 +1,315 @@ +/* + Copyright (C) 2001, S.R.Haque <srhaque@iee.org>. + Copyright (C) 2002, David Faure <david@mandrakesoft.com> + This file is part of the KDE project + + 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. +*/ + +#ifndef KFINDDIALOG_H +#define KFINDDIALOG_H + +#include <kdialogbase.h> +class KHistoryCombo; +class QPushButton; +class QPopupMenu; +class QGridLayout; +class QLabel; +class QGroupBox; +class QCheckBox; + +/** + * @ingroup main + * @ingroup findreplace + * @brief A generic "find" dialog. + * + * @author S.R.Haque <srhaque@iee.org> + * + * \b Detail: + * + * This widget inherits from KDialogBase and implements + * the following additional functionalities: a find string + * object and an area for a user-defined widget to extend the dialog. + * + * \b Example: + * + * To use the basic modal find dialog, and then run the search: + * + * \code + * KFindDialog dlg(....) + * if ( dlg.exec() != QDialog::Accepted ) + * return; + * + * // proceed with KFind from here + * \endcode + * + * To create a non-modal find dialog: + * \code + * if ( m_findDia ) + * KWin::setActiveWindow( m_findDia->winId() ); + * else + * { + * m_findDia = new KFindDialog(false,...); + * connect( m_findDia, SIGNAL(okClicked()), this, SLOT(findTextNext()) ); + * } + * \endcode + * Don't forget to delete and reset m_findDia when closed. + * (But do NOT delete your KFind object at that point, it's needed for "Find Next") + * + * To use your own extensions: see findExtension(). + * + */ +class KUTILS_EXPORT KFindDialog: + public KDialogBase +{ + Q_OBJECT + +public: + + // Options. + + // KDE4: move to KFind + /** + * Options for the search + */ + enum Options + { + WholeWordsOnly = 1, ///< Match whole words only. + FromCursor = 2, ///< Start from current cursor position. + SelectedText = 4, ///< Only search selected area. + CaseSensitive = 8, ///< Consider case when matching. + FindBackwards = 16, ///< Go backwards. + RegularExpression = 32, ///< Interpret the pattern as a regular expression. + FindIncremental = 64, ///< Find incremental. + // Note that KReplaceDialog uses 256 and 512 + // User extensions can use boolean options above this value. + MinimumUserOption = 65536 ///< The first flag which can be used by extensions. + }; + + /** + * Construct a modal find dialog + * + * @param parent The parent object of this widget. + * @param name The name of this widget. + * @param options A bitfield of the Options to be checked. + * @param findStrings The find history, see findHistory() + * @param hasSelection Whether a selection exists + */ + KFindDialog( QWidget *parent = 0, const char *name = 0, long options = 0, + const QStringList &findStrings = QStringList(), bool hasSelection = false ); + // KDE4: fix ambiguity with private constructor + // Maybe remove options (there's setOptions) and findStrings (setFindHistory) and hasSelection (setHasSelection) + + /** + * Construct a non-modal find dialog + * + * @param modal set to @c false to get a non-modal dialog + * @param parent The parent object of this widget. + * @param name The name of this widget. + * @param options A bitfield of the Options to be checked. + * @param findStrings The find history, see findHistory() + * @param hasSelection Whether a selection exists + */ + KFindDialog( bool modal, QWidget *parent = 0, const char *name = 0, long options = 0, + const QStringList &findStrings = QStringList(), bool hasSelection = false ); + // KDE4: consider simplifying + + /** + * Destructor. + */ + virtual ~KFindDialog(); + + /** + * Provide the list of @p strings to be displayed as the history + * of find strings. @p strings might get truncated if it is + * too long. + * + * @param history The find history. + * @see findHistory + */ + void setFindHistory( const QStringList &history ); + + /** + * Returns the list of history items. + * + * @return The find history. + * @see setFindHistory + */ + QStringList findHistory() const; + + /** + * Enable/disable the 'search in selection' option, depending + * on whether there actually is a selection. + * + * @param hasSelection @c true if a selection exists + */ + void setHasSelection( bool hasSelection ); + + /** + * Hide/show the 'from cursor' option, depending + * on whether the application implements a cursor. + * + * @param hasCursor @c true if the application features a cursor + * This is assumed to be the case by default. + */ + void setHasCursor( bool hasCursor ); + + /** + * Enable/disable the 'Find backwards' option, depending + * on whether the application supports it. + * + * @param supports @c true if the application supports backwards find + * This is assumed to be the case by default. + * @since 3.4 + */ + void setSupportsBackwardsFind( bool supports ); + + /** + * Enable/disable the 'Case sensitive' option, depending + * on whether the application supports it. + * + * @param supports @c true if the application supports case sensitive find + * This is assumed to be the case by default. + * @since 3.4 + */ + void setSupportsCaseSensitiveFind( bool supports ); + + /** + * Enable/disable the 'Whole words only' option, depending + * on whether the application supports it. + * + * @param supports @c true if the application supports whole words only find + * This is assumed to be the case by default. + * @since 3.4 + */ + void setSupportsWholeWordsFind( bool supports ); + + /** + * Enable/disable the 'Regular expression' option, depending + * on whether the application supports it. + * + * @param supports @c true if the application supports regular expression find + * This is assumed to be the case by default. + * @since 3.4 + */ + void setSupportsRegularExpressionFind( bool supports ); + + /** + * Set the options which are checked. + * + * @param options The setting of the Options. + * @see Options + */ + void setOptions( long options ); + + /** + * Returns the state of the options. Disabled options may be returned in + * an indeterminate state. + * + * @return The options. + * @see Options, setOptions + */ + long options() const; + + /** + * Returns the pattern to find. + * @return The search text. + */ + QString pattern() const; + + /** + * Sets the pattern to find. + * @param pattern The new search pattern. + */ + void setPattern ( const QString &pattern ); + + /** + * Returns an empty widget which the user may fill with additional UI + * elements as required. The widget occupies the width of the dialog, + * and is positioned immediately below the regular expression support + * widgets for the pattern string. + * @return An extensible QWidget. + */ + QWidget *findExtension(); + +protected slots: + + void slotOk(); + void slotSelectedTextToggled(bool); + void showPatterns(); + void showPlaceholders(); + void textSearchChanged( const QString &); + +protected: + virtual void showEvent ( QShowEvent * ); + +private slots: + /** + * connected to the aboutToShow of the placeholders menu, + * updates it according to the text in the pattern. + */ + void slotPlaceholdersAboutToShow(); + +private: + + QGroupBox *m_findGrp; + QLabel *m_findLabel; + KHistoryCombo *m_find; + QCheckBox *m_regExp; + QPushButton *m_regExpItem; + QGridLayout *m_findLayout; + QWidget *m_findExtension; + + QGroupBox *m_optionGrp; + QCheckBox *m_wholeWordsOnly; + QCheckBox *m_fromCursor; + QCheckBox *m_selectedText; + QCheckBox *m_caseSensitive; + QCheckBox *m_findBackwards; + + QPopupMenu *m_patterns; + + // Our dirty little secret is that we also implement the "replace" dialog. But we + // keep that fact hidden from all but our friends. + + friend class KReplaceDialog; + + /** + * Construct a find dialog with a parent object and a name. This version of the + * constructor is for use by friends only! + * + * @param forReplace Is this a replace dialog? + */ + KFindDialog( QWidget *parent, const char *name, bool forReplace ); + void init( bool forReplace, const QStringList &findStrings, bool hasSelection ); + + QGroupBox *m_replaceGrp; + QLabel *m_replaceLabel; + KHistoryCombo *m_replace; + QCheckBox* m_backRef; + QPushButton* m_backRefItem; + QGridLayout *m_replaceLayout; + QWidget *m_replaceExtension; + + QCheckBox* m_promptOnReplace; + + QPopupMenu *m_placeholders; + + // Binary compatible extensibility. + class KFindDialogPrivate; + KFindDialogPrivate *d; +}; + +#endif // KFINDDIALOG_H diff --git a/kutils/kmultitabbar.cpp b/kutils/kmultitabbar.cpp new file mode 100644 index 000000000..18a123717 --- /dev/null +++ b/kutils/kmultitabbar.cpp @@ -0,0 +1,997 @@ +/*************************************************************************** + kmultitabbar.cpp - description + ------------------- + begin : 2001 + copyright : (C) 2001,2002,2003 by Joseph Wenninger <jowenn@kde.org> + ***************************************************************************/ + +/*************************************************************************** + 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 "kmultitabbar.h" +#include "kmultitabbar.moc" +#include "kmultitabbar_p.h" +#include "kmultitabbar_p.moc" +#include <qbutton.h> +#include <qpopupmenu.h> +#include <qlayout.h> +#include <qpainter.h> +#include <qtooltip.h> +#include <qfontmetrics.h> +#include <qstyle.h> + +#include <kiconloader.h> +#include <kdebug.h> +#include <qapplication.h> +#include <math.h> + +#define NEARBYINT(i) ((int(float(i) + 0.5))) + +class KMultiTabBarTabPrivate { +public: + QPixmap pix; +}; + + +KMultiTabBarInternal::KMultiTabBarInternal(QWidget *parent, KMultiTabBar::KMultiTabBarMode bm):QScrollView(parent) +{ + m_expandedTabSize=-1; + m_showActiveTabTexts=false; + m_tabs.setAutoDelete(true); + m_barMode=bm; + setHScrollBarMode(AlwaysOff); + setVScrollBarMode(AlwaysOff); + if (bm==KMultiTabBar::Vertical) + { + box=new QWidget(viewport()); + mainLayout=new QVBoxLayout(box); + mainLayout->setAutoAdd(true); + box->setFixedWidth(24); + setFixedWidth(24); + } + else + { + box=new QWidget(viewport()); + mainLayout=new QHBoxLayout(box); + mainLayout->setAutoAdd(true); + box->setFixedHeight(24); + setFixedHeight(24); + } + addChild(box); + setFrameStyle(NoFrame); + viewport()->setBackgroundMode(Qt::PaletteBackground); +/* box->setPaletteBackgroundColor(Qt::red); + setPaletteBackgroundColor(Qt::green);*/ +} + +void KMultiTabBarInternal::setStyle(enum KMultiTabBar::KMultiTabBarStyle style) +{ + m_style=style; + for (uint i=0;i<m_tabs.count();i++) + m_tabs.at(i)->setStyle(m_style); + + if ( (m_style==KMultiTabBar::KDEV3) || + (m_style==KMultiTabBar::KDEV3ICON ) ) { + delete mainLayout; + mainLayout=0; + resizeEvent(0); + } else if (mainLayout==0) { + if (m_barMode==KMultiTabBar::Vertical) + { + box=new QWidget(viewport()); + mainLayout=new QVBoxLayout(box); + box->setFixedWidth(24); + setFixedWidth(24); + } + else + { + box=new QWidget(viewport()); + mainLayout=new QHBoxLayout(box); + box->setFixedHeight(24); + setFixedHeight(24); + } + addChild(box); + for (uint i=0;i<m_tabs.count();i++) + mainLayout->add(m_tabs.at(i)); + mainLayout->setAutoAdd(true); + + } + viewport()->repaint(); +} + +void KMultiTabBarInternal::drawContents ( QPainter * paint, int clipx, int clipy, int clipw, int cliph ) +{ + QScrollView::drawContents (paint , clipx, clipy, clipw, cliph ); + + if (m_position==KMultiTabBar::Right) + { + + paint->setPen(colorGroup().shadow()); + paint->drawLine(0,0,0,viewport()->height()); + paint->setPen(colorGroup().background().dark(120)); + paint->drawLine(1,0,1,viewport()->height()); + + + } + else + if (m_position==KMultiTabBar::Left) + { + paint->setPen(colorGroup().light()); + paint->drawLine(23,0,23,viewport()->height()); + paint->drawLine(22,0,22,viewport()->height()); + + paint->setPen(colorGroup().shadow()); + paint->drawLine(0,0,0,viewport()->height()); + } + else + if (m_position==KMultiTabBar::Bottom) + { + paint->setPen(colorGroup().shadow()); + paint->drawLine(0,0,viewport()->width(),0); + paint->setPen(colorGroup().background().dark(120)); + paint->drawLine(0,1,viewport()->width(),1); + } + else + { + paint->setPen(colorGroup().light()); + paint->drawLine(0,23,viewport()->width(),23); + paint->drawLine(0,22,viewport()->width(),22); + +/* paint->setPen(colorGroup().shadow()); + paint->drawLine(0,0,0,viewport()->height());*/ + + } + + +} + +void KMultiTabBarInternal::contentsMousePressEvent(QMouseEvent *ev) +{ + ev->ignore(); +} + +void KMultiTabBarInternal::mousePressEvent(QMouseEvent *ev) +{ + ev->ignore(); +} + + +#define CALCDIFF(m_tabs,diff,i) if (m_lines>(int)lines) {\ + /*kdDebug()<<"i="<<i<<" tabCount="<<tabCount<<" space="<<space<<endl;*/ \ + uint ulen=0;\ + diff=0; \ + for (uint i2=i;i2<tabCount;i2++) {\ + uint l1=m_tabs.at(i2)->neededSize();\ + if ((ulen+l1)>space){\ + if (ulen==0) diff=0;\ + else diff=((float)(space-ulen))/(i2-i);\ + break;\ + }\ + ulen+=l1;\ + }\ + } else {diff=0; } + + +void KMultiTabBarInternal::resizeEvent(QResizeEvent *ev) { +/* kdDebug()<<"KMultiTabBarInternal::resizeEvent"<<endl; + kdDebug()<<"KMultiTabBarInternal::resizeEvent - box geometry"<<box->geometry()<<endl; + kdDebug()<<"KMultiTabBarInternal::resizeEvent - geometry"<<geometry()<<endl;*/ + if (ev) QScrollView::resizeEvent(ev); + + if ( (m_style==KMultiTabBar::KDEV3) || + (m_style==KMultiTabBar::KDEV3ICON) ){ + box->setGeometry(0,0,width(),height()); + int lines=1; + uint space; + float tmp=0; + if ((m_position==KMultiTabBar::Bottom) || (m_position==KMultiTabBar::Top)) + space=width(); + else + space=height(); + + int cnt=0; +//CALCULATE LINES + const uint tabCount=m_tabs.count(); + for (uint i=0;i<tabCount;i++) { + cnt++; + tmp+=m_tabs.at(i)->neededSize(); + if (tmp>space) { + if (cnt>1)i--; + else if (i==(tabCount-1)) break; + cnt=0; + tmp=0; + lines++; + } + } +//SET SIZE & PLACE + float diff=0; + cnt=0; + + if ((m_position==KMultiTabBar::Bottom) || (m_position==KMultiTabBar::Top)) { + + setFixedHeight(lines*24); + box->setFixedHeight(lines*24); + m_lines=height()/24-1; + lines=0; + CALCDIFF(m_tabs,diff,0) + tmp=-diff; + + //kdDebug()<<"m_lines recalculated="<<m_lines<<endl; + for (uint i=0;i<tabCount;i++) { + KMultiTabBarTab *tab=m_tabs.at(i); + cnt++; + tmp+=tab->neededSize()+diff; + if (tmp>space) { + //kdDebug()<<"about to start new line"<<endl; + if (cnt>1) { + CALCDIFF(m_tabs,diff,i) + i--; + } + else { + //kdDebug()<<"placing line on old line"<<endl; + kdDebug()<<"diff="<<diff<<endl; + tab->removeEventFilter(this); + tab->move(NEARBYINT(tmp-tab->neededSize()),lines*24); +// tab->setFixedWidth(tab->neededSize()+diff); + tab->setFixedWidth(NEARBYINT(tmp+diff)-tab->x());; + tab->installEventFilter(this); + CALCDIFF(m_tabs,diff,(i+1)) + + } + tmp=-diff; + cnt=0; + lines++; + //kdDebug()<<"starting new line:"<<lines<<endl; + + } else { + //kdDebug()<<"Placing line on line:"<<lines<<" pos: (x/y)=("<<tmp-m_tabs.at(i)->neededSize()<<"/"<<lines*24<<")"<<endl; + //kdDebug()<<"diff="<<diff<<endl; + tab->removeEventFilter(this); + tab->move(NEARBYINT(tmp-tab->neededSize()),lines*24); + tab->setFixedWidth(NEARBYINT(tmp+diff)-tab->x());; + + //tab->setFixedWidth(tab->neededSize()+diff); + tab->installEventFilter(this); + + } + } + } + else { + setFixedWidth(lines*24); + box->setFixedWidth(lines*24); + m_lines=lines=width()/24; + lines=0; + CALCDIFF(m_tabs,diff,0) + tmp=-diff; + + for (uint i=0;i<tabCount;i++) { + KMultiTabBarTab *tab=m_tabs.at(i); + cnt++; + tmp+=tab->neededSize()+diff; + if (tmp>space) { + if (cnt>1) { + CALCDIFF(m_tabs,diff,i); + tmp=-diff; + i--; + } + else { + tab->removeEventFilter(this); + tab->move(lines*24,NEARBYINT(tmp-tab->neededSize())); + tab->setFixedHeight(NEARBYINT(tmp+diff)-tab->y());; + tab->installEventFilter(this); + } + cnt=0; + tmp=-diff; + lines++; + } else { + tab->removeEventFilter(this); + tab->move(lines*24,NEARBYINT(tmp-tab->neededSize())); + tab->setFixedHeight(NEARBYINT(tmp+diff)-tab->y());; + tab->installEventFilter(this); + } + } + } + + + //kdDebug()<<"needed lines:"<<m_lines<<endl; + } else { + int size=0; /*move the calculation into another function and call it only on add tab and tab click events*/ + for (int i=0;i<(int)m_tabs.count();i++) + size+=(m_barMode==KMultiTabBar::Vertical?m_tabs.at(i)->height():m_tabs.at(i)->width()); + if ((m_position==KMultiTabBar::Bottom) || (m_position==KMultiTabBar::Top)) + box->setGeometry(0,0,size,height()); + else box->setGeometry(0,0,width(),size); + + } +} + + +void KMultiTabBarInternal::showActiveTabTexts(bool show) +{ + m_showActiveTabTexts=show; +} + + +KMultiTabBarTab* KMultiTabBarInternal::tab(int id) const +{ + for (QPtrListIterator<KMultiTabBarTab> it(m_tabs);it.current();++it){ + if (it.current()->id()==id) return it.current(); + } + return 0; +} + +bool KMultiTabBarInternal::eventFilter(QObject *, QEvent *e) { + if (e->type()==QEvent::Resize) resizeEvent(0); + return false; +} + +int KMultiTabBarInternal::appendTab(const QPixmap &pic ,int id,const QString& text) +{ + KMultiTabBarTab *tab; + m_tabs.append(tab= new KMultiTabBarTab(pic,text,id,box,m_position,m_style)); + tab->installEventFilter(this); + tab->showActiveTabText(m_showActiveTabTexts); + + if (m_style==KMultiTabBar::KONQSBC) + { + if (m_expandedTabSize<tab->neededSize()) { + m_expandedTabSize=tab->neededSize(); + for (uint i=0;i<m_tabs.count();i++) + m_tabs.at(i)->setSize(m_expandedTabSize); + + } else tab->setSize(m_expandedTabSize); + } else tab->updateState(); + tab->show(); + resizeEvent(0); + return 0; +} + +void KMultiTabBarInternal::removeTab(int id) +{ + for (uint pos=0;pos<m_tabs.count();pos++) + { + if (m_tabs.at(pos)->id()==id) + { + m_tabs.remove(pos); + resizeEvent(0); + break; + } + } +} + +void KMultiTabBarInternal::setPosition(enum KMultiTabBar::KMultiTabBarPosition pos) +{ + m_position=pos; + for (uint i=0;i<m_tabs.count();i++) + m_tabs.at(i)->setTabsPosition(m_position); + viewport()->repaint(); +} + +KMultiTabBarButton::KMultiTabBarButton(const QPixmap& pic,const QString& text, QPopupMenu *popup, + int id,QWidget *parent,KMultiTabBar::KMultiTabBarPosition pos,KMultiTabBar::KMultiTabBarStyle style) + :QPushButton(QIconSet(),text,parent),m_style(style) +{ + setIconSet(pic); + setText(text); + m_position=pos; + if (popup) setPopup(popup); + setFlat(true); + setFixedHeight(24); + setFixedWidth(24); + m_id=id; + QToolTip::add(this,text); + connect(this,SIGNAL(clicked()),this,SLOT(slotClicked())); +} + +KMultiTabBarButton::KMultiTabBarButton(const QString& text, QPopupMenu *popup, + int id,QWidget *parent,KMultiTabBar::KMultiTabBarPosition pos,KMultiTabBar::KMultiTabBarStyle style) + :QPushButton(QIconSet(),text,parent),m_style(style) +{ + setText(text); + m_position=pos; + if (popup) setPopup(popup); + setFlat(true); + setFixedHeight(24); + setFixedWidth(24); + m_id=id; + QToolTip::add(this,text); + connect(this,SIGNAL(clicked()),this,SLOT(slotClicked())); +} + +KMultiTabBarButton::~KMultiTabBarButton() { +} + +int KMultiTabBarButton::id() const{ + return m_id; +} + +void KMultiTabBarButton::setText(const QString& text) +{ + QPushButton::setText(text); + m_text=text; + QToolTip::add(this,text); +} + +void KMultiTabBarButton::slotClicked() +{ + emit clicked(m_id); +} + +void KMultiTabBarButton::setPosition(KMultiTabBar::KMultiTabBarPosition pos) +{ + m_position=pos; + repaint(); +} + +void KMultiTabBarButton::setStyle(KMultiTabBar::KMultiTabBarStyle style) +{ + m_style=style; + repaint(); +} + +void KMultiTabBarButton::hideEvent( QHideEvent* he) { + QPushButton::hideEvent(he); + KMultiTabBar *tb=dynamic_cast<KMultiTabBar*>(parentWidget()); + if (tb) tb->updateSeparator(); +} + +void KMultiTabBarButton::showEvent( QShowEvent* he) { + QPushButton::showEvent(he); + KMultiTabBar *tb=dynamic_cast<KMultiTabBar*>(parentWidget()); + if (tb) tb->updateSeparator(); +} + + +QSize KMultiTabBarButton::sizeHint() const +{ + constPolish(); + + int w = 0, h = 0; + + // calculate contents size... +#ifndef QT_NO_ICONSET + if ( iconSet() && !iconSet()->isNull() ) { + int iw = iconSet()->pixmap( QIconSet::Small, QIconSet::Normal ).width() + 4; + int ih = iconSet()->pixmap( QIconSet::Small, QIconSet::Normal ).height(); + w += iw; + h = QMAX( h, ih ); + } +#endif + if ( isMenuButton() ) + w += style().pixelMetric(QStyle::PM_MenuButtonIndicator, this); + + if ( pixmap() ) { + QPixmap *pm = (QPixmap *)pixmap(); + w += pm->width(); + h += pm->height(); + } else { + QString s( text() ); + bool empty = s.isEmpty(); + if ( empty ) + s = QString::fromLatin1("XXXX"); + QFontMetrics fm = fontMetrics(); + QSize sz = fm.size( ShowPrefix, s ); + if(!empty || !w) + w += sz.width(); + if(!empty || !h) + h = QMAX(h, sz.height()); + } + + return (style().sizeFromContents(QStyle::CT_ToolButton, this, QSize(w, h)). + expandedTo(QApplication::globalStrut())); +} + + +KMultiTabBarTab::KMultiTabBarTab(const QPixmap& pic, const QString& text, + int id,QWidget *parent,KMultiTabBar::KMultiTabBarPosition pos, + KMultiTabBar::KMultiTabBarStyle style) + :KMultiTabBarButton(text,0,id,parent,pos,style), + m_showActiveTabText(false) +{ + d=new KMultiTabBarTabPrivate(); + setIcon(pic); + m_expandedSize=24; + setToggleButton(true); +} + +KMultiTabBarTab::~KMultiTabBarTab() { + delete d; +} + + +void KMultiTabBarTab::setTabsPosition(KMultiTabBar::KMultiTabBarPosition pos) +{ + if ((pos!=m_position) && ((pos==KMultiTabBar::Left) || (pos==KMultiTabBar::Right))) { + if (!d->pix.isNull()) { + QWMatrix temp;// (1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F); + temp.rotate(180); + d->pix=d->pix.xForm(temp); + setIconSet(d->pix); + } + } + + setPosition(pos); +// repaint(); +} + +void KMultiTabBarTab::setIcon(const QString& icon) +{ + QPixmap pic=SmallIcon(icon); + setIcon(pic); +} + +void KMultiTabBarTab::setIcon(const QPixmap& icon) +{ + + if (m_style!=KMultiTabBar::KDEV3) { + if ((m_position==KMultiTabBar::Left) || (m_position==KMultiTabBar::Right)) { + QWMatrix rotateMatrix; + if (m_position==KMultiTabBar::Left) + rotateMatrix.rotate(90); + else + rotateMatrix.rotate(-90); + QPixmap pic=icon.xForm(rotateMatrix); //TODO FIX THIS, THIS SHOWS WINDOW + d->pix=pic; + setIconSet(pic); + } else setIconSet(icon); + } +} + +void KMultiTabBarTab::slotClicked() +{ + updateState(); + KMultiTabBarButton::slotClicked(); +} + +void KMultiTabBarTab::setState(bool b) +{ + setOn(b); + updateState(); +} + +void KMultiTabBarTab::updateState() +{ + + if (m_style!=KMultiTabBar::KONQSBC) { + if ((m_style==KMultiTabBar::KDEV3) || (m_style==KMultiTabBar::KDEV3ICON) || (isOn())) { + QPushButton::setText(m_text); + } else { + kdDebug()<<"KMultiTabBarTab::updateState(): setting text to an empty QString***************"<<endl; + QPushButton::setText(QString::null); + } + + if ((m_position==KMultiTabBar::Right || m_position==KMultiTabBar::Left)) { + setFixedWidth(24); + if ((m_style==KMultiTabBar::KDEV3) || (m_style==KMultiTabBar::KDEV3ICON) || (isOn())) { + setFixedHeight(KMultiTabBarButton::sizeHint().width()); + } else setFixedHeight(36); + } else { + setFixedHeight(24); + if ((m_style==KMultiTabBar::KDEV3) || (m_style==KMultiTabBar::KDEV3ICON) || (isOn())) { + setFixedWidth(KMultiTabBarButton::sizeHint().width()); + } else setFixedWidth(36); + } + } else { + if ((!isOn()) || (!m_showActiveTabText)) + { + setFixedWidth(24); + setFixedHeight(24); + return; + } + if ((m_position==KMultiTabBar::Right || m_position==KMultiTabBar::Left)) + setFixedHeight(m_expandedSize); + else + setFixedWidth(m_expandedSize); + } + QApplication::sendPostedEvents(0,QEvent::Paint | QEvent::Move | QEvent::Resize | QEvent::LayoutHint); + QApplication::flush(); +} + +int KMultiTabBarTab::neededSize() +{ + return (((m_style!=KMultiTabBar::KDEV3)?24:0)+QFontMetrics(QFont()).width(m_text)+6); +} + +void KMultiTabBarTab::setSize(int size) +{ + m_expandedSize=size; + updateState(); +} + +void KMultiTabBarTab::showActiveTabText(bool show) +{ + m_showActiveTabText=show; +} + +void KMultiTabBarTab::drawButtonLabel(QPainter *p) { + drawButton(p); +} +void KMultiTabBarTab::drawButton(QPainter *paint) +{ + if (m_style!=KMultiTabBar::KONQSBC) drawButtonStyled(paint); + else drawButtonClassic(paint); +} + +void KMultiTabBarTab::drawButtonStyled(QPainter *paint) { + + QSize sh; + const int width = 36; // rotated + const int height = 24; + if ((m_style==KMultiTabBar::KDEV3) || (m_style==KMultiTabBar::KDEV3ICON) || (isOn())) { + if ((m_position==KMultiTabBar::Left) || (m_position==KMultiTabBar::Right)) + sh=QSize(this->height(),this->width());//KMultiTabBarButton::sizeHint(); + else sh=QSize(this->width(),this->height()); + } + else + sh=QSize(width,height); + + QPixmap pixmap( sh.width(),height); ///,sh.height()); + pixmap.fill(eraseColor()); + QPainter painter(&pixmap); + + + QStyle::SFlags st=QStyle::Style_Default; + + st|=QStyle::Style_Enabled; + + if (isOn()) st|=QStyle::Style_On; + + style().drawControl(QStyle::CE_PushButton,&painter,this, QRect(0,0,pixmap.width(),pixmap.height()), colorGroup(),st); + style().drawControl(QStyle::CE_PushButtonLabel,&painter,this, QRect(0,0,pixmap.width(),pixmap.height()), colorGroup(),st); + + switch (m_position) { + case KMultiTabBar::Left: + paint->rotate(-90); + paint->drawPixmap(1-pixmap.width(),0,pixmap); + break; + case KMultiTabBar::Right: + paint->rotate(90); + paint->drawPixmap(0,1-pixmap.height(),pixmap); + break; + + default: + paint->drawPixmap(0,0,pixmap); + break; + } +// style().drawControl(QStyle::CE_PushButtonLabel,painter,this, QRect(0,0,pixmap.width(),pixmap.height()), +// colorGroup(),QStyle::Style_Enabled); + + +} + +void KMultiTabBarTab::drawButtonClassic(QPainter *paint) +{ + QPixmap pixmap; + if ( iconSet()) + pixmap = iconSet()->pixmap( QIconSet::Small, QIconSet::Normal ); + paint->fillRect(0, 0, 24, 24, colorGroup().background()); + + if (!isOn()) + { + + if (m_position==KMultiTabBar::Right) + { + paint->fillRect(0,0,21,21,QBrush(colorGroup().background())); + + paint->setPen(colorGroup().background().dark(150)); + paint->drawLine(0,22,23,22); + + paint->drawPixmap(12-pixmap.width()/2,12-pixmap.height()/2,pixmap); + + paint->setPen(colorGroup().shadow()); + paint->drawLine(0,0,0,23); + paint->setPen(colorGroup().background().dark(120)); + paint->drawLine(1,0,1,23); + + } + else + if ((m_position==KMultiTabBar::Bottom) || (m_position==KMultiTabBar::Top)) + { + paint->fillRect(0,1,23,22,QBrush(colorGroup().background())); + + paint->drawPixmap(12-pixmap.width()/2,12-pixmap.height()/2,pixmap); + + paint->setPen(colorGroup().background().dark(120)); + paint->drawLine(23,0,23,23); + + + paint->setPen(colorGroup().light()); + paint->drawLine(0,22,23,22); + paint->drawLine(0,23,23,23); + paint->setPen(colorGroup().shadow()); + paint->drawLine(0,0,23,0); + paint->setPen(colorGroup().background().dark(120)); + paint->drawLine(0,1,23,1); + + } + else + { + paint->setPen(colorGroup().background().dark(120)); + paint->drawLine(0,23,23,23); + paint->fillRect(0,0,23,21,QBrush(colorGroup().background())); + paint->drawPixmap(12-pixmap.width()/2,12-pixmap.height()/2,pixmap); + + paint->setPen(colorGroup().light()); + paint->drawLine(23,0,23,23); + paint->drawLine(22,0,22,23); + + paint->setPen(colorGroup().shadow()); + paint->drawLine(0,0,0,23); + + } + + + } + else + { + if (m_position==KMultiTabBar::Right) + { + paint->setPen(colorGroup().shadow()); + paint->drawLine(0,height()-1,23,height()-1); + paint->drawLine(0,height()-2,23,height()-2); + paint->drawLine(23,0,23,height()-1); + paint->drawLine(22,0,22,height()-1); + paint->fillRect(0,0,21,height()-3,QBrush(colorGroup().light())); + paint->drawPixmap(10-pixmap.width()/2,10-pixmap.height()/2,pixmap); + + if (m_showActiveTabText) + { + if (height()<25+4) return; + + QPixmap tpixmap(height()-25-3, width()-2); + QPainter painter(&tpixmap); + + painter.fillRect(0,0,tpixmap.width(),tpixmap.height(),QBrush(colorGroup().light())); + + painter.setPen(colorGroup().text()); + painter.drawText(0,+width()/2+QFontMetrics(QFont()).height()/2,m_text); + + paint->rotate(90); + kdDebug()<<"tpixmap.width:"<<tpixmap.width()<<endl; + paint->drawPixmap(25,-tpixmap.height()+1,tpixmap); + } + + } + else + if (m_position==KMultiTabBar::Top) + { + paint->fillRect(0,0,width()-1,23,QBrush(colorGroup().light())); + paint->drawPixmap(10-pixmap.width()/2,10-pixmap.height()/2,pixmap); + if (m_showActiveTabText) + { + paint->setPen(colorGroup().text()); + paint->drawText(25,height()/2+QFontMetrics(QFont()).height()/2,m_text); + } + } + else + if (m_position==KMultiTabBar::Bottom) + { + paint->setPen(colorGroup().shadow()); + paint->drawLine(0,23,width()-1,23); + paint->drawLine(0,22,width()-1,22); + paint->fillRect(0,0,width()-1,21,QBrush(colorGroup().light())); + paint->drawPixmap(10-pixmap.width()/2,10-pixmap.height()/2,pixmap); + if (m_showActiveTabText) + { + paint->setPen(colorGroup().text()); + paint->drawText(25,height()/2+QFontMetrics(QFont()).height()/2,m_text); + } + + } + else + { + + + paint->setPen(colorGroup().shadow()); + paint->drawLine(0,height()-1,23,height()-1); + paint->drawLine(0,height()-2,23,height()-2); + paint->fillRect(0,0,23,height()-3,QBrush(colorGroup().light())); + paint->drawPixmap(10-pixmap.width()/2,10-pixmap.height()/2,pixmap); + if (m_showActiveTabText) + { + + if (height()<25+4) return; + + QPixmap tpixmap(height()-25-3, width()-2); + QPainter painter(&tpixmap); + + painter.fillRect(0,0,tpixmap.width(),tpixmap.height(),QBrush(colorGroup().light())); + + painter.setPen(colorGroup().text()); + painter.drawText(tpixmap.width()-QFontMetrics(QFont()).width(m_text),+width()/2+QFontMetrics(QFont()).height()/2,m_text); + + paint->rotate(-90); + kdDebug()<<"tpixmap.width:"<<tpixmap.width()<<endl; + + paint->drawPixmap(-24-tpixmap.width(),2,tpixmap); + + } + + } + + } +} + + + + + + + +KMultiTabBar::KMultiTabBar(KMultiTabBarMode bm, QWidget *parent,const char *name):QWidget(parent,name) +{ + m_buttons.setAutoDelete(false); + if (bm==Vertical) + { + m_l=new QVBoxLayout(this); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding, true); +// setFixedWidth(24); + } + else + { + m_l=new QHBoxLayout(this); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed, true); +// setFixedHeight(24); + } + m_l->setMargin(0); + m_l->setAutoAdd(false); + + m_internal=new KMultiTabBarInternal(this,bm); + setPosition((bm==KMultiTabBar::Vertical)?KMultiTabBar::Right:KMultiTabBar::Bottom); + setStyle(VSNET); + // setStyle(KDEV3); + //setStyle(KONQSBC); + m_l->insertWidget(0,m_internal); + m_l->insertWidget(0,m_btnTabSep=new QFrame(this)); + m_btnTabSep->setFixedHeight(4); + m_btnTabSep->setFrameStyle(QFrame::Panel | QFrame::Sunken); + m_btnTabSep->setLineWidth(2); + m_btnTabSep->hide(); + + updateGeometry(); +} + +KMultiTabBar::~KMultiTabBar() { +} + +/*int KMultiTabBar::insertButton(QPixmap pic,int id ,const QString&) +{ + (new KToolbarButton(pic,id,m_internal))->show(); + return 0; +}*/ + +int KMultiTabBar::appendButton(const QPixmap &pic ,int id,QPopupMenu *popup,const QString&) +{ + KMultiTabBarButton *btn; + m_buttons.append(btn= new KMultiTabBarButton(pic,QString::null, + popup,id,this,m_position,m_internal->m_style)); + m_l->insertWidget(0,btn); + btn->show(); + m_btnTabSep->show(); + return 0; +} + +void KMultiTabBar::updateSeparator() { + bool hideSep=true; + for (QPtrListIterator<KMultiTabBarButton> it(m_buttons);it.current();++it){ + if (it.current()->isVisibleTo(this)) { + hideSep=false; + break; + } + } + if (hideSep) m_btnTabSep->hide(); + else m_btnTabSep->show(); + +} + +int KMultiTabBar::appendTab(const QPixmap &pic ,int id ,const QString& text) +{ + m_internal->appendTab(pic,id,text); + return 0; +} + +KMultiTabBarButton* KMultiTabBar::button(int id) const +{ + for (QPtrListIterator<KMultiTabBarButton> it(m_buttons);it.current();++it){ + if (it.current()->id()==id) return it.current(); + } + return 0; +} + +KMultiTabBarTab* KMultiTabBar::tab(int id) const +{ + return m_internal->tab(id); +} + + + +void KMultiTabBar::removeButton(int id) +{ + for (uint pos=0;pos<m_buttons.count();pos++) + { + if (m_buttons.at(pos)->id()==id) + { + m_buttons.take(pos)->deleteLater(); + break; + } + } + if (m_buttons.count()==0) m_btnTabSep->hide(); +} + +void KMultiTabBar::removeTab(int id) +{ + m_internal->removeTab(id); +} + +void KMultiTabBar::setTab(int id,bool state) +{ + KMultiTabBarTab *ttab=tab(id); + if (ttab) + { + ttab->setState(state); + } +} + +bool KMultiTabBar::isTabRaised(int id) const +{ + KMultiTabBarTab *ttab=tab(id); + if (ttab) + { + return ttab->isOn(); + } + + return false; +} + + +void KMultiTabBar::showActiveTabTexts(bool show) +{ + m_internal->showActiveTabTexts(show); +} + +void KMultiTabBar::setStyle(KMultiTabBarStyle style) +{ + m_internal->setStyle(style); +} + +KMultiTabBar::KMultiTabBarStyle KMultiTabBar::tabStyle() const +{ + return m_internal->m_style; +} + +void KMultiTabBar::setPosition(KMultiTabBarPosition pos) +{ + m_position=pos; + m_internal->setPosition(pos); + for (uint i=0;i<m_buttons.count();i++) + m_buttons.at(i)->setPosition(pos); +} + +KMultiTabBar::KMultiTabBarPosition KMultiTabBar::position() const +{ + return m_position; +} +void KMultiTabBar::fontChange(const QFont& /* oldFont */) +{ + for (uint i=0;i<tabs()->count();i++) + tabs()->at(i)->resize(); + repaint(); +} + +QPtrList<KMultiTabBarTab>* KMultiTabBar::tabs() {return m_internal->tabs();} +QPtrList<KMultiTabBarButton>* KMultiTabBar::buttons() {return &m_buttons;} + diff --git a/kutils/kmultitabbar.h b/kutils/kmultitabbar.h new file mode 100644 index 000000000..8d9a95449 --- /dev/null +++ b/kutils/kmultitabbar.h @@ -0,0 +1,316 @@ +/*************************************************************************** + kmultitabbar.h - description + ------------------- + begin : 2001 + copyright : (C) 2001,2002,2003 by Joseph Wenninger <jowenn@kde.org> + ***************************************************************************/ + +/*************************************************************************** + 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 _KMultitabbar_h_ +#define _KMultitabbar_h_ + +#include <qscrollview.h> +#include <qvbox.h> +#include <qhbox.h> +#include <qlayout.h> +#include <qstring.h> +#include <qptrlist.h> +#include <qpushbutton.h> + +#include <kdelibs_export.h> + +class QPixmap; +class QPainter; +class QFrame; + +class KMultiTabBarPrivate; +class KMultiTabBarTabPrivate; +class KMultiTabBarButtonPrivate; +class KMultiTabBarInternal; + +/** + * @ingroup main + * @ingroup multitabbar + * A Widget for horizontal and vertical tabs. + * It is possible to add normal buttons to the top/left + * The handling if only one tab at a time or multiple tabs + * should be raisable is left to the "user". + *@author Joseph Wenninger + */ +class KUTILS_EXPORT KMultiTabBar: public QWidget +{ + Q_OBJECT +public: + /** + * The tab bar's orientation. Also constraints the bar's position. + */ + enum KMultiTabBarMode { + Horizontal, ///< Horizontal orientation (i.e. on top or bottom) + Vertical ///< Vertical orientation (i.e. on the left or right hand side) + }; + + /** + * The tab bar's position + */ + enum KMultiTabBarPosition { + Left, ///< Left hand side + Right, ///< Right hand side + Top, ///< On top + Bottom ///< On bottom + }; + + /** + * The list of available styles for KMultiTabBar + */ + enum KMultiTabBarStyle { + VSNET=0, ///< Visual Studio .Net like (only show the text of active tabs) + KDEV3=1, ///< KDevelop 3 like (always show the text) + KONQSBC=2, ///< Konqueror's classic sidebar style (unthemed) (currently disabled) + KDEV3ICON=3, ///< KDevelop 3 like with icons + STYLELAST=0xffff ///< Last style + }; + + /** + * Constructor. + * @param bm The tab bar's orientation + * @param parent The parent widget + * @param name The widget's name + */ + KMultiTabBar(KMultiTabBarMode bm,QWidget *parent=0,const char *name=0); + + /** + * Destructor. + */ + virtual ~KMultiTabBar(); + + /** + * append a new button to the button area. The button can later on be accessed with button(ID) + * eg for connecting signals to it + * @param pic a pixmap for the button + * @param id an arbitraty ID value. It will be emitted in the clicked signal for identifying the button + * if more than one button is connected to a signals. + * @param popup A popup menu which should be displayed if the button is clicked + * @param not_used_yet will be used for a popup text in the future + */ + int appendButton(const QPixmap &pic,int id=-1,QPopupMenu* popup=0,const QString& not_used_yet=QString::null); + /** + * remove a button with the given ID + */ + void removeButton(int id); + /** + * append a new tab to the tab area. It can be accessed lateron with tabb(id); + * @param pic a bitmap for the tab + * @param id an arbitrary ID which can be used later on to identify the tab + * @param text if a mode with text is used it will be the tab text, otherwise a mouse over hint + * @return Always zero. Can be safely ignored. + */ + int appendTab(const QPixmap &pic,int id=-1,const QString& text=QString::null); + /** + * remove a tab with a given ID + * @param id The ID of the tab to remove + */ + void removeTab(int id); + /** + * set a tab to "raised" + * @param id The ID of the tab to manipulate + * @param state true == activated/raised, false == not active + */ + void setTab(int id ,bool state); + /** + * return the state of a tab, identified by it's ID + * @param id The ID of the tab to raise + */ + bool isTabRaised(int id) const; + /** + * get a pointer to a button within the button area identified by its ID + * @param id The id of the tab + */ + class KMultiTabBarButton *button(int id) const; + + /** + * get a pointer to a tab within the tab area, identified by its ID + */ + class KMultiTabBarTab *tab(int id) const; + /** + * set the real position of the widget. + * @param pos if the mode is horizontal, only use top, bottom, if it is vertical use left or right + */ + void setPosition(KMultiTabBarPosition pos); + /** + * get the tabbar position. + * @return The tab bar's position + */ + KMultiTabBarPosition position() const; + /** + * set the display style of the tabs + * @param style The new display style + */ + void setStyle(KMultiTabBarStyle style); + /** + * get the display style of the tabs + * @return display style + */ + KMultiTabBarStyle tabStyle() const; + /** + * Returns the list of pointers to the tabs of type KMultiTabBarTab. + * @return The list of tabs. + * @warning be careful, don't delete tabs yourself and don't delete the list itself + */ + QPtrList<KMultiTabBarTab>* tabs(); + /** + * Returns the list of pointers to the tab buttons of type KMultiTabBarButton. + * @return The list of tab buttons. + * @warning be careful, don't delete buttons yourself and don't delete the list itself + */ + QPtrList<KMultiTabBarButton>* buttons(); + + /** + * might vanish, not sure yet + */ + void showActiveTabTexts(bool show=true); +protected: + friend class KMultiTabBarButton; + virtual void fontChange( const QFont& ); + void updateSeparator(); +private: + class KMultiTabBarInternal *m_internal; + QBoxLayout *m_l; + QFrame *m_btnTabSep; + QPtrList<KMultiTabBarButton> m_buttons; + KMultiTabBarPosition m_position; + KMultiTabBarPrivate *d; +}; + +/** + * @ingroup multitabbar + * This class represents a tab bar button in a KMultiTabBarWidget. + * This class should never be created except with the appendButton call of KMultiTabBar + */ +class KUTILS_EXPORT KMultiTabBarButton: public QPushButton +{ + Q_OBJECT +public: + /** @internal */ + KMultiTabBarButton(const QPixmap& pic,const QString&, QPopupMenu *popup, + int id,QWidget *parent, KMultiTabBar::KMultiTabBarPosition pos, KMultiTabBar::KMultiTabBarStyle style); + /** @internal */ + KMultiTabBarButton(const QString&, QPopupMenu *popup, + int id,QWidget *parent, KMultiTabBar::KMultiTabBarPosition pos, KMultiTabBar::KMultiTabBarStyle style); + /** + * Destructor + */ + virtual ~KMultiTabBarButton(); + /** + * Returns the tab's ID + * @return The tab's ID + */ + int id() const; + +public slots: + /** + * this is used internaly, but can be used by the user, if (s)he wants to + * It the according call of KMultiTabBar is invoked though this modifications will be overwritten + */ + void setPosition(KMultiTabBar::KMultiTabBarPosition); + /** + * this is used internaly, but can be used by the user, if (s)he wants to + * It the according call of KMultiTabBar is invoked though this modifications will be overwritten + */ + void setStyle(KMultiTabBar::KMultiTabBarStyle); + + /** + * modify the text of the button + */ + void setText(const QString &); + + QSize sizeHint() const; + +protected: + KMultiTabBar::KMultiTabBarPosition m_position; + KMultiTabBar::KMultiTabBarStyle m_style; + QString m_text; + virtual void hideEvent( class QHideEvent*); + virtual void showEvent( class QShowEvent*); +private: + int m_id; + KMultiTabBarButtonPrivate *d; +signals: + /** + * this is emitted if the button is clicked + * @param id the ID identifying the button + */ + void clicked(int id); +protected slots: + virtual void slotClicked(); +}; + +/** + * @ingroup multitabbar + * This class represents a tab bar's tab in a KMultiTabBarWidget. + * This class should never be created except with the appendTab call of KMultiTabBar + */ +class KUTILS_EXPORT KMultiTabBarTab: public KMultiTabBarButton +{ + Q_OBJECT +public: + /** @internal */ + KMultiTabBarTab(const QPixmap& pic,const QString&,int id,QWidget *parent, + KMultiTabBar::KMultiTabBarPosition pos,KMultiTabBar::KMultiTabBarStyle style); + /** + * Destructor. + */ + virtual ~KMultiTabBarTab(); + /** + * set the active state of the tab + * @param state @c true if the tab should become active, @c false otherwise + */ + void setState(bool state); + /** + * choose if the text should always be displayed + * this is only used in classic mode if at all + * @param show Whether or not to show the text + */ + void showActiveTabText(bool show); + /** + * Resized the tab to the needed size. + */ + void resize(){ setSize( neededSize() ); } +private: + bool m_showActiveTabText; + int m_expandedSize; + KMultiTabBarTabPrivate *d; +protected: + friend class KMultiTabBarInternal; + void setSize(int); + int neededSize(); + void updateState(); + virtual void drawButton(QPainter *); + virtual void drawButtonLabel(QPainter *); + void drawButtonStyled(QPainter *); + void drawButtonClassic(QPainter *); +protected slots: + virtual void slotClicked(); + void setTabsPosition(KMultiTabBar::KMultiTabBarPosition); + +public slots: + virtual void setIcon(const QString&); + virtual void setIcon(const QPixmap&); +}; + +#endif diff --git a/kutils/kmultitabbar_p.h b/kutils/kmultitabbar_p.h new file mode 100644 index 000000000..f47cc5385 --- /dev/null +++ b/kutils/kmultitabbar_p.h @@ -0,0 +1,67 @@ +/*************************************************************************** + kmultitabbar_p.h - description + ------------------- + begin : 2003 + copyright : (C) 2003 by Joseph Wenninger <jowenn@kde.org> + ***************************************************************************/ + +/*************************************************************************** + 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 K_MULTI_TAB_BAR_P_H +#define K_MULTI_TAB_BAR_P_H +#include <qscrollview.h> +#include <kmultitabbar.h> + +class KMultiTabBarInternal: public QScrollView +{ + Q_OBJECT +public: + KMultiTabBarInternal(QWidget *parent,KMultiTabBar::KMultiTabBarMode bm); + int appendTab(const QPixmap &,int=-1,const QString& =QString::null); + KMultiTabBarTab *tab(int) const; + void removeTab(int); + void setPosition(enum KMultiTabBar::KMultiTabBarPosition pos); + void setStyle(enum KMultiTabBar::KMultiTabBarStyle style); + void showActiveTabTexts(bool show); + QPtrList<KMultiTabBarTab>* tabs(){return &m_tabs;} +private: + friend class KMultiTabBar; + QWidget *box; + QBoxLayout *mainLayout; + QPtrList<KMultiTabBarTab> m_tabs; + enum KMultiTabBar::KMultiTabBarPosition m_position; + bool m_showActiveTabTexts; + enum KMultiTabBar::KMultiTabBarStyle m_style; + int m_expandedTabSize; + int m_lines; + KMultiTabBar::KMultiTabBarMode m_barMode; +protected: + virtual bool eventFilter(QObject *,QEvent*); + virtual void drawContents ( QPainter *, int, int, int, int); + + /** + * [contentsM|m]ousePressEvent are reimplemented from QScrollView + * in order to ignore all mouseEvents on the viewport, so that the + * parent can handle them. + */ + virtual void contentsMousePressEvent(QMouseEvent *); + virtual void mousePressEvent(QMouseEvent *); + virtual void resizeEvent(QResizeEvent *); +}; +#endif + diff --git a/kutils/kplugininfo.cpp b/kutils/kplugininfo.cpp new file mode 100644 index 000000000..4b80aa776 --- /dev/null +++ b/kutils/kplugininfo.cpp @@ -0,0 +1,359 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Matthias Kretz <kretz@kde.org> + + 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 "kplugininfo.h" +#include <ksimpleconfig.h> +#include <ktrader.h> +#include <kdebug.h> +#include <kconfigbase.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <kdesktopfile.h> +#include <kservice.h> + +class KPluginInfo::KPluginInfoPrivate +{ + public: + KPluginInfoPrivate() + : hidden( false ) + , enabledbydefault( false ) + , pluginenabled( false ) + , config( 0 ) + , kcmservicesCached( false ) + {} + + ~KPluginInfoPrivate() + { + delete config; + } + + QString specfile; // the filename of the file containing all the info + QString name; + QString comment; + QString icon; + QString author; + QString email; + QString pluginName; // the name attribute in the .rc file + QString version; + QString website; // URL to the website of the plugin/author + QString category; + QString license; + QStringList dependencies; + + bool hidden; + bool enabledbydefault; + bool pluginenabled; + + KConfig * config; + QString configgroup; + KService::Ptr service; + QValueList<KService::Ptr> kcmservices; + bool kcmservicesCached; +}; + +KPluginInfo::KPluginInfo( const QString & filename, const char* resource ) +: d( new KPluginInfoPrivate ) +{ + KDesktopFile file( filename, true, resource ); + + d->specfile = filename; + + if( filename.endsWith( QString::fromAscii( ".desktop" ) ) ) + { + file.setDesktopGroup(); + d->hidden = file.readBoolEntry( "Hidden", false ); + if( d->hidden ) + return; + + d->name = file.readName(); + d->comment = file.readComment(); + d->icon = file.readEntry( "Icon" ); + d->author = file.readEntry( "X-KDE-PluginInfo-Author" ); + d->email = file.readEntry( "X-KDE-PluginInfo-Email" ); + d->pluginName = file.readEntry( "X-KDE-PluginInfo-Name" ); + d->version = file.readEntry( "X-KDE-PluginInfo-Version" ); + d->website = file.readEntry( "X-KDE-PluginInfo-Website" ); + d->category = file.readEntry( "X-KDE-PluginInfo-Category" ); + d->license = file.readEntry( "X-KDE-PluginInfo-License" ); + d->dependencies = file.readListEntry( "X-KDE-PluginInfo-Depends" ); + d->enabledbydefault = file.readBoolEntry( + "X-KDE-PluginInfo-EnabledByDefault", false ); + } + else if( filename.endsWith( QString::fromAscii( ".plugin" ) ) ) + { // provided for noatun style .plugin files compatibility + + d->name = file.readName(); + d->comment = file.readComment(); + d->icon = file.readEntry( "Icon" ); + d->author = file.readEntry( "Author" ); + d->email = file.readEntry( "Email" ); + d->pluginName = file.readPathEntry( "Filename" ); + // no version + d->website = file.readEntry( "Site" ); + d->category = file.readEntry( "Type" ); + d->license = file.readEntry( "License" ); + d->dependencies = file.readListEntry( "Require" ); + } +} + +KPluginInfo::KPluginInfo( const KService::Ptr service ) +: d( new KPluginInfoPrivate ) +{ + d->service = service; + d->specfile = service->desktopEntryPath(); + + if ( service->isDeleted() ) + { + d->hidden = true; + return; + } + + d->name = service->name(); + d->comment = service->comment(); + d->icon = service->icon(); + d->author = service->property( "X-KDE-PluginInfo-Author" ).toString(); + d->email = service->property( "X-KDE-PluginInfo-Email" ).toString(); + d->pluginName = service->property( "X-KDE-PluginInfo-Name" ).toString(); + d->version = service->property( "X-KDE-PluginInfo-Version" ).toString(); + d->website = service->property( "X-KDE-PluginInfo-Website" ).toString(); + d->category = service->property( "X-KDE-PluginInfo-Category" ).toString(); + d->license = service->property( "X-KDE-PluginInfo-License" ).toString(); + d->dependencies = + service->property( "X-KDE-PluginInfo-Depends" ).toStringList(); + QVariant tmp = service->property( "X-KDE-PluginInfo-EnabledByDefault" ); + d->enabledbydefault = tmp.isValid() ? tmp.toBool() : false; +} + +//X KPluginInfo::KPluginInfo() +//X : d( new KPluginInfoPrivate ) +//X { +//X d->hidden = true; +//X } + +KPluginInfo::~KPluginInfo() +{ + delete d; +} + +QValueList<KPluginInfo*> KPluginInfo::fromServices( const KService::List & services, KConfig * config, const QString & group ) +{ + QValueList<KPluginInfo*> infolist; + KPluginInfo * info; + for( KService::List::ConstIterator it = services.begin(); + it != services.end(); ++it ) + { + info = new KPluginInfo( *it ); + info->setConfig( config, group ); + infolist += info; + } + return infolist; +} + +QValueList<KPluginInfo*> KPluginInfo::fromFiles( const QStringList & files, KConfig * config, const QString & group ) +{ + QValueList<KPluginInfo*> infolist; + for( QStringList::ConstIterator it = files.begin(); it != files.end(); ++it ) + { + KPluginInfo * info = new KPluginInfo( *it ); + info->setConfig( config, group ); + infolist += info; + } + return infolist; +} + +QValueList<KPluginInfo*> KPluginInfo::fromKPartsInstanceName( const QString & name, KConfig * config, const QString & group ) +{ + QStringList files = KGlobal::dirs()->findAllResources( "data", name + + "/kpartplugins/*.desktop", true, false ); + return fromFiles( files, config, group ); +} + +bool KPluginInfo::isHidden() const +{ + return d->hidden; +} + +void KPluginInfo::setPluginEnabled( bool enabled ) +{ + kdDebug( 703 ) << k_funcinfo << endl; + d->pluginenabled = enabled; +} + +bool KPluginInfo::isPluginEnabled() const +{ + kdDebug( 703 ) << k_funcinfo << endl; + return d->pluginenabled; +} + +bool KPluginInfo::isPluginEnabledByDefault() const +{ + kdDebug( 703 ) << k_funcinfo << endl; + return d->enabledbydefault; +} + +const QString & KPluginInfo::name() const +{ + return d->name; +} + +const QString & KPluginInfo::comment() const +{ + return d->comment; +} + +const QString & KPluginInfo::icon() const +{ + return d->icon; +} + +const QString & KPluginInfo::specfile() const +{ + return d->specfile; +} + +const QString & KPluginInfo::author() const +{ + return d->author; +} + +const QString & KPluginInfo::email() const +{ + return d->email; +} + +const QString & KPluginInfo::category() const +{ + return d->category; +} + +const QString & KPluginInfo::pluginName() const +{ + return d->pluginName; +} + +const QString & KPluginInfo::version() const +{ + return d->version; +} + +const QString & KPluginInfo::website() const +{ + return d->website; +} + +const QString & KPluginInfo::license() const +{ + return d->license; +} + +const QStringList & KPluginInfo::dependencies() const +{ + return d->dependencies; +} + +KService::Ptr KPluginInfo::service() const +{ + return d->service; +} + +const QValueList<KService::Ptr> & KPluginInfo::kcmServices() const +{ + if ( !d->kcmservicesCached ) + { + d->kcmservices = KTrader::self()->query( "KCModule", "'" + d->pluginName + + "' in [X-KDE-ParentComponents]" ); + kdDebug( 703 ) << "found " << d->kcmservices.count() << " offers for " << + d->pluginName << endl; + + d->kcmservicesCached = true; + } + + return d->kcmservices; +} + +void KPluginInfo::setConfig( KConfig * config, const QString & group ) +{ + d->config = config; + d->configgroup = group; +} + +KConfig * KPluginInfo::config() const +{ + return d->config; +} + +const QString & KPluginInfo::configgroup() const +{ + return d->configgroup; +} + +QVariant KPluginInfo::property( const QString & key ) const +{ + if( d->service ) + return d->service->property( key ); + else + return QVariant(); +} + +QVariant KPluginInfo::operator[]( const QString & key ) const +{ + return property( key ); +} + +void KPluginInfo::save( KConfigGroup * config ) +{ + kdDebug( 703 ) << k_funcinfo << endl; + if( 0 == config ) + { + if( 0 == d->config ) + { + kdWarning( 703 ) << "no KConfigGroup, cannot save" << endl; + return; + } + d->config->setGroup( d->configgroup ); + d->config->writeEntry( d->pluginName + "Enabled", isPluginEnabled() ); + } + else + config->writeEntry( d->pluginName + "Enabled", isPluginEnabled() ); +} + +void KPluginInfo::load( KConfigGroup * config ) +{ + kdDebug( 703 ) << k_funcinfo << endl; + if( 0 == config ) + { + if( 0 == d->config ) + { + kdWarning( 703 ) << "no KConfigGroup, cannot load" << endl; + return; + } + d->config->setGroup( d->configgroup ); + setPluginEnabled( d->config->readBoolEntry( d->pluginName + "Enabled", isPluginEnabledByDefault() ) ); + } + else + setPluginEnabled( config->readBoolEntry( d->pluginName + "Enabled", isPluginEnabledByDefault() ) ); +} + +void KPluginInfo::defaults() +{ + kdDebug( 703 ) << k_funcinfo << endl; + setPluginEnabled( isPluginEnabledByDefault() ); +} + +// vim: sw=4 sts=4 et diff --git a/kutils/kplugininfo.desktop b/kutils/kplugininfo.desktop new file mode 100644 index 000000000..2521bedeb --- /dev/null +++ b/kutils/kplugininfo.desktop @@ -0,0 +1,107 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=KPluginInfo +Name=KDE Plugin Information +Name[af]=KDE Inprop module Informasie +Name[ar]=معلومات ملحقات كيدي +Name[az]=KDE Əlavə Mə'lumatı +Name[be]=Звесткі аб дадатках KDE +Name[bg]=Информация за приставките в KDE +Name[bn]=কে.ডি.ই. প্লাগ-ইন তথ্য +Name[br]=Titouroù al lugent KDE +Name[bs]=KDE informacije o dodatku +Name[ca]=Connector informatiu de KDE +Name[cs]=Informace o pluginu KDE +Name[csb]=Wëdowiédzô ò pluginsach KDE +Name[cy]=Gwybodaeth Ategynnau KDE +Name[da]=KDE's plugin-information +Name[de]=KDE-Modul-Information +Name[el]=Πληροφορίες πρόσθετου KDE +Name[eo]=KDEa kromaĵinformo +Name[es]=Información de los plugin de KDE +Name[et]=KDE plugina info +Name[eu]=KDEren plugin-en informazioa +Name[fa]=اطلاعات وصلۀ KDE +Name[fi]=KDE:n lisäosatiedot +Name[fr]=Information sur le module KDE +Name[fy]=KDE Plugin ynformaasje +Name[ga]=Eolas Bhreiseán KDE +Name[gl]=Información de Plugin de KDE +Name[he]=מידע על תוספים של KDE +Name[hi]=केडीई प्लगिन जानकारी +Name[hr]=Podaci o KDE dodatku +Name[hu]=A KDE bővítőmodulok áttekintése +Name[id]=Informasi Plugin KDE +Name[is]=KDE íforritsupplýsingar +Name[it]=Informazioni plugin KDE +Name[ja]=KDE プラグイン情報 +Name[ka]=KDE მოდულის ინფორმაცია +Name[kk]=KDE модулі туралы мәлімет +Name[km]=ព័ត៌មានកម្មវិធីជំនួយខាងក្នុង KDE +Name[ko]=KDE 플러그인 정보 +Name[lb]=KDE-Plugininformatioun +Name[lt]=KDE priedų informacija +Name[lv]=KDE spraudņa informācija +Name[mk]=KDE Информација за приклучок +Name[mn]=КДЭ плугин мэдээлэл +Name[ms]=Maklumat Plugmasuk KDE +Name[nb]=KDEs informasjon om programtillegg +Name[nds]=Plugin-Informatschoon +Name[ne]=KDE प्लगइन सूचना +Name[nl]=KDE-plugininformatie +Name[nn]=Informasjon om KDE-programtillegg +Name[pa]=KDE ਪਲੱਗਿੰਨ ਜਾਣਕਾਰੀ +Name[pl]=Informacja o wtyczkach KDE +Name[pt]=Informação do 'Plugin' do KDE +Name[pt_BR]=Informações do Plug-in do KDE +Name[ro]=Informaţii modul KDE +Name[ru]=Сведения о модуле KDE +Name[rw]=Ibisobanuro by'Icomeka KDE +Name[se]=Dieđut KDE-moduvlla birra +Name[sk]=Informácia o module KDE +Name[sl]=Informacija o vstavkih v KDE +Name[sq]=Informata për Shtojca të KDE +Name[sr]=Информација о KDE dodatku +Name[sr@Latn]=Informacija o KDE dodatku +Name[sv]=KDE-insticksinformation +Name[ta]=KDE சொருகுப்பொருள் தகவல் +Name[te]=కెడిఈ ప్లగిన్ సమాచారం +Name[tg]=KDE барномаҳои чоп кунӣ +Name[th]=ข้อมูลปลั้กอินของ KDE +Name[tr]=KDE Eklenti Bilgisi +Name[tt]=KDE Östämäse Turında +Name[uk]=Інформація про втулок KDE +Name[uz]=KDE plagin haqida maʼlumot +Name[uz@cyrillic]=KDE плагин ҳақида маълумот +Name[vi]=Thông tin bộ cầm phít KDE +Name[wa]=Informåcion sol tchôke-divins di KDE +Name[zh_CN]=KDE 打印信息 +Name[zh_HK]=KDE 外掛資訊 +Name[zh_TW]=KDE 外掛資訊 + +[PropertyDef::X-KDE-PluginInfo-Author] +Type=QString + +[PropertyDef::X-KDE-PluginInfo-Email] +Type=QString + +[PropertyDef::X-KDE-PluginInfo-Name] +Type=QString + +[PropertyDef::X-KDE-PluginInfo-Version] +Type=QString + +[PropertyDef::X-KDE-PluginInfo-Website] +Type=QString + +[PropertyDef::X-KDE-PluginInfo-Category] +Type=QString + +[PropertyDef::X-KDE-PluginInfo-Depends] +Type=QStringList + +[PropertyDef::X-KDE-PluginInfo-License] +Type=QString + +[PropertyDef::X-KDE-PluginInfo-EnabledByDefault] +Type=bool diff --git a/kutils/kplugininfo.h b/kutils/kplugininfo.h new file mode 100644 index 000000000..32ee5b5a7 --- /dev/null +++ b/kutils/kplugininfo.h @@ -0,0 +1,319 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Matthias Kretz <kretz@kde.org> + + 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. + +*/ + +#ifndef KPLUGININFO_H +#define KPLUGININFO_H + +#include <qstring.h> +#include <qmap.h> +#include <qstringlist.h> +#include <qvaluelist.h> +#include <kservice.h> + +class KConfigGroup; + +/** + * @ingroup main + * @ingroup plugin + * Information about a plugin. + * + * This holds all the information about a plugin there is. It's used for the + * user to decide whether he wants to use this plugin or not. + * + * @author Matthias Kretz <kretz@kde.org> + * @since 3.2 + */ +class KUTILS_EXPORT KPluginInfo +{ + public: + typedef QValueList<KPluginInfo*> List; + + /** + * Read plugin info from @p filename. + * + * The file should be of the following form: + * \verbatim + [Desktop Entry] + Name=User Visible Name + Comment=Description of what the plugin does + + [X-KDE Plugin Info] + Author=Author's Name + Email=author@foo.bar + PluginName=internalname + Version=1.1 + Website=http://www.plugin.org/ + Category=playlist + Depends=plugin1,plugin3 + License=GPL + EnabledByDefault=true + \endverbatim + * The first two entries in the "Desktop Entry" group always need to + * be present. + * + * The "X-KDE-PluginInfo" keys you may add further entries which + * will be available using property(). The Website,Category,Require + * keys are optional. + * For EnabledByDefault look at isPluginEnabledByDefault. + * + * @param filename The filename of the .desktop file. + * @param resource If filename is relative, you need to specify a resource type + * (e.g. "service", "apps"... KStandardDirs). Otherwise, + * resource isn't used. + */ + KPluginInfo( const QString & filename, const char* resource = 0 ); + + /** + * Read plugin info from a KService object. + * + * The .desktop file should look like this: + * \verbatim + [Desktop Entry] + Encoding=UTF-8 + Icon=mypluginicon + Type=Service + ServiceTypes=KPluginInfo + + X-KDE-PluginInfo-Author=Author's Name + X-KDE-PluginInfo-Email=author@foo.bar + X-KDE-PluginInfo-Name=internalname + X-KDE-PluginInfo-Version=1.1 + X-KDE-PluginInfo-Website=http://www.plugin.org/ + X-KDE-PluginInfo-Category=playlist + X-KDE-PluginInfo-Depends=plugin1,plugin3 + X-KDE-PluginInfo-License=GPL + X-KDE-PluginInfo-EnabledByDefault=true + + Name=User Visible Name + Comment=Description of what the plugin does + \endverbatim + * In the first three entries the Icon entry is optional. + */ + KPluginInfo( const KService::Ptr service ); + +//X /** +//X * Create an empty hidden plugin. +//X * @internal +//X */ +//X KPluginInfo(); + + virtual ~KPluginInfo(); + + /** + * @return A list of KPluginInfo objects constructed from a list of + * KService objects. If you get a trader offer of the plugins you want + * to use you can just pass them to this function. + */ + static KPluginInfo::List fromServices( const KService::List & services, KConfig * config = 0, const QString & group = QString::null ); + + /** + * @return A list of KPluginInfo objects constructed from a list of + * filenames. If you make a lookup using, for example, + * KStandardDirs::findAllResources() you pass the list of files to this + * function. + */ + static KPluginInfo::List fromFiles( const QStringList & files, KConfig * config = 0, const QString & group = QString::null ); + + /** + * @return A list of KPluginInfo objects for the KParts plugins of an + * instance. You only need the name of the instance not a pointer to the + * KInstance object. + */ + static KPluginInfo::List fromKPartsInstanceName( const QString &, KConfig * config = 0, const QString & group = QString::null ); + + /** + * @return Whether the plugin should be hidden. + */ + bool isHidden() const; + + /** + * Set whether the plugin is currently loaded. + * + * You might need to reimplement this method for special needs. + * + * @see isPluginEnabled() + * @see save() + */ + virtual void setPluginEnabled( bool enabled ); + + /** + * @return Whether the plugin is currently loaded. + * + * You might need to reimplement this method for special needs. + * + * @see setPluginEnabled() + * @see load() + */ + virtual bool isPluginEnabled() const; + + /** + * @return The default value whether the plugin is enabled or not. + * Defaults to the value set in the desktop file, or if that isn't set + * to false. + */ + bool isPluginEnabledByDefault() const; + + /** + * @return The value associated the the @p key. You can use it if you + * want to read custom values. To do this you need to define + * your own servicetype and add it to the ServiceTypes keys. + * + * @see operator[] + */ + QVariant property( const QString & key ) const; + + /** + * This is the same as property(). It is provided for convenience. + * + * @return The value associated with the @p key. + * + * @see property() + */ + QVariant operator[]( const QString & key ) const; + + /** + * @return The user visible name of the plugin. + */ + const QString & name() const; + + /** + * @return A comment describing the plugin. + */ + const QString & comment() const; + + /** + * @return The iconname for this plugin + */ + const QString & icon() const; + + /** + * @return The file containing the information about the plugin. + */ + const QString & specfile() const; + + /** + * @return The author of this plugin. + */ + const QString & author() const; + + /** + * @return The email address of the author. + */ + const QString & email() const; + + /** + * @return The category of this plugin (e.g. playlist/skin). + */ + const QString & category() const; + + /** + * @return The internal name of the plugin (for KParts Plugins this is + * the same name as set in the .rc file). + */ + const QString & pluginName() const; + + /** + * @return The version of the plugin. + */ + const QString & version() const; + + /** + * @return The website of the plugin/author. + */ + const QString & website() const; + + + /** + * @return The license of this plugin. + */ + const QString & license() const; + + /** + * @return A list of plugins required for this plugin to be enabled. Use + * the pluginName in this list. + */ + const QStringList & dependencies() const; + + /** + * @return The KService object for this plugin. You might need it if you + * want to read custom values. To do this you need to define + * your own servicetype and add it to the ServiceTypes keys. + * Then you can use the KService::property() method to read your + * keys. + * + * @see property() + */ + KService::Ptr service() const; + + /** + * @return A list of Service pointers if the plugin installs one or more + * KCModule + */ + const QValueList<KService::Ptr> & kcmServices() const; + + /** + * Set the KConfigGroup to use for load()ing and save()ing the + * configuration. This will be overridden by the KConfigGroup passed to + * save() or load() (if one is passed). + */ + void setConfig( KConfig * config, const QString & group ); + + /** + * @return If the KPluginInfo object has a KConfig object set return + * it, else return 0. + */ + KConfig * config() const; + + /** + * @return The groupname used in the KConfig object for load()ing and + * save()ing whether the plugin is enabled. + */ + const QString & configgroup() const; + + /** + * Save state of the plugin - enabled or not. This function is provided + * for reimplementation if you need to save somewhere else. + * @param config The KConfigGroup holding the information whether + * plugin is enabled. + */ + virtual void save( KConfigGroup * config = 0 ); + + /** + * Load the state of the plugin - enabled or not. This function is provided + * for reimplementation if you need to save somewhere else. + * @param config The KConfigGroup holding the information whether + * plugin is enabled. + */ + virtual void load( KConfigGroup * config = 0 ); + + /** + * Restore defaults (enabled or not). + */ + virtual void defaults(); + + private: + KPluginInfo( const KPluginInfo & ); + const KPluginInfo & operator=( const KPluginInfo & ); + + class KPluginInfoPrivate; + KPluginInfoPrivate * d; +}; + +// vim: sw=4 sts=4 et tw=80 +#endif // KPLUGININFO_H diff --git a/kutils/kpluginselector.cpp b/kutils/kpluginselector.cpp new file mode 100644 index 000000000..4bcc166d1 --- /dev/null +++ b/kutils/kpluginselector.cpp @@ -0,0 +1,727 @@ +/* This file is part of the KDE project + Copyright (C) 2002-2003 Matthias Kretz <kretz@kde.org> + + 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 "kpluginselector.h" +#include "kpluginselector_p.h" + +#include <qtooltip.h> +#include <qvbox.h> +#include <qlabel.h> +#include <qstrlist.h> +#include <qfile.h> +#include <qstring.h> +#include <qlayout.h> +#include <qptrlist.h> +#include <qwidgetstack.h> +#include <qcursor.h> +#include <qapplication.h> +#include <qobjectlist.h> +#include <qcstring.h> + +#include <kdebug.h> +#include <klocale.h> +#include <klistview.h> +#include <ksimpleconfig.h> +#include <kdialog.h> +#include <kglobal.h> +#include <kglobalsettings.h> +#include <kstandarddirs.h> +#include <ktabctl.h> +#include <kcmoduleinfo.h> +#include <qvaluelist.h> +#include <kservice.h> +#include <ktrader.h> +#include <ktabwidget.h> +#include <kiconloader.h> +#include <kcmodule.h> +#include "kcmoduleinfo.h" +#include "kcmoduleloader.h" +#include <qsplitter.h> +#include <qframe.h> +#include "kplugininfo.h" +#include <kinstance.h> +#include <qptrdict.h> +#include <qstringlist.h> +#include "kcmoduleproxy.h" + +/* + QCheckListViewItem that holds a pointer to the KPluginInfo object. + Used in the tooltip code to access additional fields +*/ +class KPluginInfoLVI : public QCheckListItem +{ +public: + KPluginInfoLVI( KPluginInfo *pluginInfo, KListView *parent ) + : QCheckListItem( parent, pluginInfo->name(), QCheckListItem::CheckBox ), m_pluginInfo( pluginInfo ) + { + } + + KPluginInfo * pluginInfo() { return m_pluginInfo; } + +private: + KPluginInfo *m_pluginInfo; +}; + +/* + Custom QToolTip for the list view. + The decision whether or not to show tooltips is taken in + maybeTip(). See also the QListView sources from Qt itself. +*/ +class KPluginListViewToolTip : public QToolTip +{ +public: + KPluginListViewToolTip( QWidget *parent, KListView *lv ); + + void maybeTip( const QPoint &pos ); + +private: + KListView *m_listView; +}; + +KPluginListViewToolTip::KPluginListViewToolTip( QWidget *parent, KListView *lv ) +: QToolTip( parent ), m_listView( lv ) +{ +} + +void KPluginListViewToolTip::maybeTip( const QPoint &pos ) +{ + if ( !parentWidget() || !m_listView ) + return; + + KPluginInfoLVI *item = dynamic_cast<KPluginInfoLVI *>( m_listView->itemAt( pos ) ); + if ( !item ) + return; + + QString toolTip = i18n( "<qt><table>" + "<tr><td><b>Description:</b></td><td>%1</td></tr>" + "<tr><td><b>Author:</b></td><td>%2</td></tr>" + "<tr><td><b>Version:</b></td><td>%3</td></tr>" + "<tr><td><b>License:</b></td><td>%4</td></tr></table></qt>" ).arg( item->pluginInfo()->comment(), + item->pluginInfo()->author(), item->pluginInfo()->version(), item->pluginInfo()->license() ); + + //kdDebug( 702 ) << k_funcinfo << "Adding tooltip: itemRect: " << itemRect << ", tooltip: " << toolTip << endl; + tip( m_listView->itemRect( item ), toolTip ); +} + +struct KPluginSelectionWidget::KPluginSelectionWidgetPrivate +{ + KPluginSelectionWidgetPrivate( KPluginSelector * _kps, + const QString & _cat, + KConfigGroup * _config ) + : widgetstack( 0 ) + , kps( _kps ) + , config( _config ) + , tooltip( 0 ) + , catname( _cat ) + , currentplugininfo( 0 ) + , visible( true ) + , currentchecked( false ) + , changed( 0 ) + { + moduleParentComponents.setAutoDelete( true ); + } + + ~KPluginSelectionWidgetPrivate() + { + delete config; + } + + QMap<QCheckListItem*, KPluginInfo*> pluginInfoMap; + + QWidgetStack * widgetstack; + KPluginSelector * kps; + KConfigGroup * config; + KPluginListViewToolTip *tooltip; + + QDict<KCModuleInfo> pluginconfigmodules; + QMap<QString, int> widgetIDs; + QMap<KPluginInfo*, bool> plugincheckedchanged; + QString catname; + QValueList<KCModuleProxy*> modulelist; + QPtrDict<QStringList> moduleParentComponents; + + KPluginInfo * currentplugininfo; + bool visible; + bool currentchecked; + int changed; +}; + +KPluginSelectionWidget::KPluginSelectionWidget( + const QValueList<KPluginInfo*> & plugininfos, KPluginSelector * kps, + QWidget * parent, const QString & catname, const QString & category, + KConfigGroup * config, const char * name ) + : QWidget( parent, name ) + , d( new KPluginSelectionWidgetPrivate( kps, catname, config ) ) +{ + init( plugininfos, category ); +} + +inline QString KPluginSelectionWidget::catName() const +{ + return d->catname; +} + +void KPluginSelectionWidget::init( const QValueList<KPluginInfo*> & plugininfos, + const QString & category ) +{ + // setup Widgets + ( new QVBoxLayout( this, 0, KDialog::spacingHint() ) )->setAutoAdd( true ); + KListView * listview = new KListView( this ); + d->tooltip = new KPluginListViewToolTip( listview->viewport(), listview ); + connect( listview, SIGNAL( pressed( QListViewItem * ) ), this, + SLOT( executed( QListViewItem * ) ) ); + connect( listview, SIGNAL( spacePressed( QListViewItem * ) ), this, + SLOT( executed( QListViewItem * ) ) ); + connect( listview, SIGNAL( returnPressed( QListViewItem * ) ), this, + SLOT( executed( QListViewItem * ) ) ); + connect( listview, SIGNAL( selectionChanged( QListViewItem * ) ), this, + SLOT( executed( QListViewItem * ) ) ); + listview->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred ); + listview->setAcceptDrops( false ); + listview->setFullWidth( true ); + listview->setSelectionModeExt( KListView::Single ); + listview->setAllColumnsShowFocus( true ); + listview->addColumn( i18n( "Name" ) ); + for( QValueList<KPluginInfo*>::ConstIterator it = plugininfos.begin(); + it != plugininfos.end(); ++it ) + { + d->plugincheckedchanged[ *it ] = false; + if( !( *it )->isHidden() && + ( category.isNull() || ( *it )->category() == category ) ) + { + QCheckListItem * item = new KPluginInfoLVI( *it, listview ); + if( ! ( *it )->icon().isEmpty() ) + item->setPixmap( 0, SmallIcon( ( *it )->icon(), IconSize( KIcon::Small ) ) ); + item->setOn( ( *it )->isPluginEnabled() ); + d->pluginInfoMap.insert( item, *it ); + } + } + + // widgetstack + d->widgetstack = d->kps->widgetStack(); + load(); + // select and highlight the first item in the plugin list + if( listview->firstChild() ) + listview->setSelected( listview->firstChild(), true ); +} + +KPluginSelectionWidget::~KPluginSelectionWidget() +{ + delete d->tooltip; + delete d; +} + +bool KPluginSelectionWidget::pluginIsLoaded( const QString & pluginName ) const +{ + for( QMap<QCheckListItem*, KPluginInfo*>::ConstIterator it = + d->pluginInfoMap.begin(); it != d->pluginInfoMap.end(); ++it ) + if( it.data()->pluginName() == pluginName ) + return it.data()->isPluginEnabled(); + return false; +} + + +QWidget * KPluginSelectionWidget::insertKCM( QWidget * parent, + const KCModuleInfo & moduleinfo ) +{ + KCModuleProxy * module = new KCModuleProxy( moduleinfo, false, + parent ); + if( !module->realModule() ) + { + //FIXME: not very verbose + QLabel * label = new QLabel( i18n( "Error" ), parent ); + label->setAlignment( Qt::AlignCenter ); + + return label; + } + // add the KCM to the list so that we can call load/save/defaults on it + d->modulelist.append( module ); + QStringList * parentComponents = new QStringList( + moduleinfo.service()->property( + "X-KDE-ParentComponents" ).toStringList() ); + d->moduleParentComponents.insert( module, parentComponents ); + connect( module, SIGNAL( changed( bool ) ), SLOT( clientChanged( bool ) ) ); + return module; +} + +void KPluginSelectionWidget::embeddPluginKCMs( KPluginInfo * plugininfo, bool checked ) +{ + //if we have Services for the plugin we should be able to + //create KCM(s) + QApplication::setOverrideCursor( Qt::WaitCursor ); + if( plugininfo->kcmServices().size() > 1 ) + { + // we need a tabwidget + KTabWidget * tabwidget = new KTabWidget( d->widgetstack ); + tabwidget->setEnabled( checked ); + + int id = d->widgetstack->addWidget( tabwidget ); + d->kps->configPage( id ); + d->widgetIDs[ plugininfo->pluginName() ] = id; + + for( QValueList<KService::Ptr>::ConstIterator it = + plugininfo->kcmServices().begin(); + it != plugininfo->kcmServices().end(); ++it ) + { + if( !( *it )->noDisplay() ) + { + KCModuleInfo moduleinfo( *it ); + QWidget * module = insertKCM( tabwidget, moduleinfo ); + tabwidget->addTab( module, moduleinfo.moduleName() ); + } + } + } + else + { + if( !plugininfo->kcmServices().front()->noDisplay() ) + { + KCModuleInfo moduleinfo( + plugininfo->kcmServices().front() ); + QWidget * module = insertKCM( d->widgetstack, moduleinfo ); + module->setEnabled( checked ); + + int id = d->widgetstack->addWidget( module ); + d->kps->configPage( id ); + d->widgetIDs[ plugininfo->pluginName() ] = id; + } + } + QApplication::restoreOverrideCursor(); +} + +inline void KPluginSelectionWidget::updateConfigPage() +{ + updateConfigPage( d->currentplugininfo, d->currentchecked ); +} + +void KPluginSelectionWidget::updateConfigPage( KPluginInfo * plugininfo, + bool checked ) +{ + //kdDebug( 702 ) << k_funcinfo << endl; + d->currentplugininfo = plugininfo; + d->currentchecked = checked; + + // if this widget is not currently visible (meaning that it's in a tabwidget + // and another tab is currently opened) it's not allowed to change the + // widgetstack + if( ! d->visible ) + return; + + if( 0 == plugininfo ) + { + d->kps->configPage( 1 ); + return; + } + + if( plugininfo->kcmServices().empty() ) + d->kps->configPage( 1 ); + else + { + if( !d->widgetIDs.contains( plugininfo->pluginName() ) ) + // if no widget exists for the plugin create it + embeddPluginKCMs( plugininfo, checked ); + else + { + // the page already exists + int id = d->widgetIDs[ plugininfo->pluginName() ]; + d->kps->configPage( id ); + d->widgetstack->widget( id )->setEnabled( checked ); + } + } +} + +void KPluginSelectionWidget::clientChanged( bool didchange ) +{ + kdDebug( 702 ) << k_funcinfo << endl; + d->changed += didchange ? 1 : -1; + if( d->changed == 1 ) + emit changed( true ); + else if( d->changed == 0 ) + emit changed( false ); + else if( d->changed < 0 ) + kdError( 702 ) << "negative changed value: " << d->changed << endl; +} + +void KPluginSelectionWidget::tabWidgetChanged( QWidget * widget ) +{ + if( widget == this ) + { + d->visible = true; + updateConfigPage(); + } + else + d->visible = false; +} + +void KPluginSelectionWidget::executed( QListViewItem * item ) +{ + kdDebug( 702 ) << k_funcinfo << endl; + if( item == 0 ) + return; + + // Why not a dynamic_cast? - Martijn + // because this is what the Qt API suggests; and since gcc 3.x I don't + // trust dynamic_cast anymore - mkretz + if( item->rtti() != 1 ) //check for a QCheckListItem + return; + + QCheckListItem * citem = static_cast<QCheckListItem *>( item ); + bool checked = citem->isOn(); + //kdDebug( 702 ) << "it's a " << ( checked ? "checked" : "unchecked" ) + // << " QCheckListItem" << endl; + + KPluginInfo * info = d->pluginInfoMap[ citem ]; + Q_ASSERT( !info->isHidden() ); + + if ( info->isPluginEnabled() != checked ) + { + kdDebug( 702 ) << "Item changed state, emitting changed()" << endl; + + if( ! d->plugincheckedchanged[ info ] ) + { + ++d->changed; + if ( d->changed == 1 ) + emit changed( true ); + } + d->plugincheckedchanged[ info ] = true; + + checkDependencies( info ); + } + else + { + if( d->plugincheckedchanged[ info ] ) + { + --d->changed; + if ( d->changed == 0 ) + emit changed( false ); + } + d->plugincheckedchanged[ info ] = false; + // FIXME: plugins that depend on this plugin need to be disabled, too + } + + updateConfigPage( info, checked ); +} + +void KPluginSelectionWidget::load() +{ + //kdDebug( 702 ) << k_funcinfo << endl; + + for( QMap<QCheckListItem*, KPluginInfo*>::Iterator it = + d->pluginInfoMap.begin(); it != d->pluginInfoMap.end(); ++it ) + { + KPluginInfo * info = it.data(); + info->load( d->config ); + it.key()->setOn( info->isPluginEnabled() ); + if( d->visible && info == d->currentplugininfo ) + d->currentchecked = info->isPluginEnabled(); + } + + for( QValueList<KCModuleProxy*>::Iterator it = d->modulelist.begin(); + it != d->modulelist.end(); ++it ) + if( ( *it )->changed() ) + ( *it )->load(); + + updateConfigPage(); + // TODO: update changed state +} + +void KPluginSelectionWidget::save() +{ + kdDebug( 702 ) << k_funcinfo << endl; + + for( QMap<QCheckListItem*, KPluginInfo*>::Iterator it = + d->pluginInfoMap.begin(); it != d->pluginInfoMap.end(); ++it ) + { + KPluginInfo * info = it.data(); + bool checked = it.key()->isOn(); + info->setPluginEnabled( checked ); + info->save( d->config ); + d->plugincheckedchanged[ info ] = false; + } + QStringList updatedModules; + for( QValueList<KCModuleProxy*>::Iterator it = d->modulelist.begin(); + it != d->modulelist.end(); ++it ) + if( ( *it )->changed() ) + { + ( *it )->save(); + QStringList * names = d->moduleParentComponents[ *it ]; + if( names->size() == 0 ) + names->append( QString::null ); + for( QStringList::ConstIterator nameit = names->begin(); + nameit != names->end(); ++nameit ) + if( updatedModules.find( *nameit ) == updatedModules.end() ) + updatedModules.append( *nameit ); + } + for( QStringList::ConstIterator it = updatedModules.begin(); it != updatedModules.end(); ++it ) + emit configCommitted( ( *it ).latin1() ); + + updateConfigPage(); + kdDebug( 702 ) << "syncing config file" << endl; + d->config->sync(); + d->changed = 0; + emit changed( false ); +} + +void KPluginSelectionWidget::checkDependencies( const KPluginInfo * info ) +{ + if( info->dependencies().isEmpty() ) + return; + + for( QStringList::ConstIterator it = info->dependencies().begin(); + it != info->dependencies().end(); ++it ) + for( QMap<QCheckListItem*, + KPluginInfo*>::Iterator infoIt = d->pluginInfoMap.begin(); + infoIt != d->pluginInfoMap.end(); ++infoIt ) + if( infoIt.data()->pluginName() == *it ) + { + if( !infoIt.key()->isOn() ) + { + infoIt.key()->setOn( true ); + checkDependencies( infoIt.data() ); + } + continue; + } +} + +class KPluginSelector::KPluginSelectorPrivate +{ + public: + KPluginSelectorPrivate() + : frame( 0 ) + , tabwidget( 0 ) + , widgetstack( 0 ) + , hideconfigpage( false ) + { + } + + QFrame * frame; + KTabWidget * tabwidget; + QWidgetStack * widgetstack; + QValueList<KPluginSelectionWidget *> pswidgets; + bool hideconfigpage; +}; + +KPluginSelector::KPluginSelector( QWidget * parent, const char * name ) +: QWidget( parent, name ) +, d( new KPluginSelectorPrivate ) +{ + QBoxLayout * hbox = new QHBoxLayout( this, 0, KDialog::spacingHint() ); + hbox->setAutoAdd( true ); + + QSplitter* splitter = new QSplitter( QSplitter::Horizontal, this ); + d->frame = new QFrame( splitter, "KPluginSelector left frame" ); + d->frame->setFrameStyle( QFrame::NoFrame ); + ( new QVBoxLayout( d->frame, 0, KDialog::spacingHint() ) )->setAutoAdd( true ); + + // widgetstack + d->widgetstack = new QWidgetStack( splitter, "KPluginSelector Config Pages" ); + d->widgetstack->setFrameStyle( QFrame::Panel | QFrame::Sunken ); + d->widgetstack->setMinimumSize( 200, 200 ); + + QLabel * label = new QLabel( i18n( "(This plugin is not configurable)" ), + d->widgetstack ); + ( new QVBoxLayout( label, 0, KDialog::spacingHint() ) )->setAutoAdd( true ); + label->setAlignment( Qt::AlignCenter ); + label->setMinimumSize( 200, 200 ); + + d->widgetstack->addWidget( label, 1 ); + + configPage( 1 ); +} + +KPluginSelector::~KPluginSelector() +{ + delete d; +} + +void KPluginSelector::checkNeedForTabWidget() +{ + kdDebug( 702 ) << k_funcinfo << endl; + if( ! d->tabwidget && d->pswidgets.size() == 1 ) + { + kdDebug( 702 ) << "no TabWidget and one KPluginSelectionWidget" << endl; + // there's only one KPluginSelectionWidget yet, we need a TabWidget + KPluginSelectionWidget * w = d->pswidgets.first(); + if( w ) + { + kdDebug( 702 ) << "create TabWidget" << endl; + d->tabwidget = new KTabWidget( d->frame, + "KPluginSelector TabWidget" ); + w->reparent( d->tabwidget, QPoint( 0, 0 ) ); + d->tabwidget->addTab( w, w->catName() ); + connect( d->tabwidget, SIGNAL( currentChanged( QWidget * ) ), w, + SLOT( tabWidgetChanged( QWidget * ) ) ); + } + } +} + +static QValueList<KPluginInfo*> kpartsPluginInfos( const QString& instanceName ) +{ + if( instanceName.isNull() ) + return QValueList<KPluginInfo*>(); //nothing + + const QStringList desktopfilenames = KGlobal::dirs()->findAllResources( "data", + instanceName + "/kpartplugins/*.desktop", true, false ); + return KPluginInfo::fromFiles( desktopfilenames ); +} + +void KPluginSelector::addPlugins( const QString & instanceName, + const QString & catname, const QString & category, KConfig * config ) +{ + const QValueList<KPluginInfo*> plugininfos = kpartsPluginInfos( instanceName ); + if ( plugininfos.isEmpty() ) + return; + checkNeedForTabWidget(); + Q_ASSERT( config ); // please set config, or use addPlugins( instance, ... ) which takes care of it + if ( !config ) // KDE4: ensure that config is always set; make it second in the arg list? + config = new KSimpleConfig( instanceName ); // memleak! + KConfigGroup * cfgGroup = new KConfigGroup( config, "KParts Plugins" ); + kdDebug( 702 ) << k_funcinfo << "cfgGroup = " << cfgGroup << endl; + addPluginsInternal( plugininfos, catname, category, cfgGroup ); +} + +void KPluginSelector::addPluginsInternal( const QValueList<KPluginInfo*> plugininfos, + const QString & catname, const QString & category, + KConfigGroup* cfgGroup ) +{ + KPluginSelectionWidget * w; + if( d->tabwidget ) + { + w = new KPluginSelectionWidget( plugininfos, this, + d->tabwidget, catname, category, cfgGroup ); + d->tabwidget->addTab( w, catname ); + connect( d->tabwidget, SIGNAL( currentChanged( QWidget * ) ), w, + SLOT( tabWidgetChanged( QWidget * ) ) ); + } + else + w = new KPluginSelectionWidget( plugininfos, this, d->frame, + catname, category, cfgGroup ); + w->setMinimumSize( 200, 200 ); + connect( w, SIGNAL( changed( bool ) ), this, SIGNAL( changed( bool ) ) ); + connect( w, SIGNAL( configCommitted( const QCString & ) ), this, + SIGNAL( configCommitted( const QCString & ) ) ); + d->pswidgets += w; +} + +void KPluginSelector::addPlugins( const KInstance * instance, const QString & + catname, const QString & category, KConfig * config ) +{ + if ( !config ) + config = instance->config(); + addPlugins( instance->instanceName(), catname, category, config ); +} + +void KPluginSelector::addPlugins( const QValueList<KPluginInfo*> & plugininfos, + const QString & catname, const QString & category, KConfig * config ) +{ + checkNeedForTabWidget(); + // the KConfigGroup becomes owned by KPluginSelectionWidget + KConfigGroup * cfgGroup = new KConfigGroup( config ? config : KGlobal::config(), "Plugins" ); + kdDebug( 702 ) << k_funcinfo << "cfgGroup = " << cfgGroup << endl; + addPluginsInternal( plugininfos, catname, category, cfgGroup ); +} + +QWidgetStack * KPluginSelector::widgetStack() +{ + return d->widgetstack; +} + +inline void KPluginSelector::configPage( int id ) +{ + if( id == 1 ) + { + // no config page + if( d->hideconfigpage ) + { + d->widgetstack->hide(); + return; + } + } + else + d->widgetstack->show(); + + d->widgetstack->raiseWidget( id ); +} + +void KPluginSelector::setShowEmptyConfigPage( bool show ) +{ + d->hideconfigpage = !show; + if( d->hideconfigpage ) + if( d->widgetstack->id( d->widgetstack->visibleWidget() ) == 1 ) + d->widgetstack->hide(); +} + +void KPluginSelector::load() +{ + for( QValueList<KPluginSelectionWidget *>::Iterator it = + d->pswidgets.begin(); it != d->pswidgets.end(); ++it ) + { + ( *it )->load(); + } +} + +void KPluginSelector::save() +{ + for( QValueList<KPluginSelectionWidget *>::Iterator it = + d->pswidgets.begin(); it != d->pswidgets.end(); ++it ) + { + ( *it )->save(); + } +} + +void KPluginSelector::defaults() +{ + kdDebug( 702 ) << k_funcinfo << endl; + + // what should defaults do? here's what I think: + // Pressing a button in the dialog should not change any widgets that are + // not visible for the user. Therefor we may only change the currently + // visible plugin's KCM. Restoring the default plugin selections is therefor + // not possible. (if the plugin has multiple KCMs they will be shown in a + // tabwidget - defaults() will be called for all of them) + + QWidget * pluginconfig = d->widgetstack->visibleWidget(); + KCModuleProxy * kcm = ( KCModuleProxy* )pluginconfig->qt_cast( + "KCModuleProxy" ); + if( kcm ) + { + kdDebug( 702 ) << "call KCModule::defaults() for the plugins KCM" + << endl; + kcm->defaults(); + return; + } + + // if we get here the visible Widget must be a tabwidget holding more than + // one KCM + QObjectList * kcms = pluginconfig->queryList( "KCModuleProxy", + 0, false, false ); + QObjectListIt it( *kcms ); + QObject * obj; + while( ( obj = it.current() ) != 0 ) + { + ++it; + ( ( KCModule* )obj )->defaults(); + } + delete kcms; + // FIXME: update changed state +} + +// vim: sw=4 sts=4 et + +#include "kpluginselector.moc" +#include "kpluginselector_p.moc" diff --git a/kutils/kpluginselector.h b/kutils/kpluginselector.h new file mode 100644 index 000000000..b21e41b3e --- /dev/null +++ b/kutils/kpluginselector.h @@ -0,0 +1,218 @@ +/* This file is part of the KDE project + Copyright (C) 2002-2003 Matthias Kretz <kretz@kde.org> + + 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. + +*/ + +#ifndef KPLUGINSELECTOR_H +#define KPLUGINSELECTOR_H + +#include <qwidget.h> +#include <qstring.h> + +#include <kdelibs_export.h> + +class KInstance; +class KPluginInfo; +class QWidgetStack; +class KConfig; +class KConfigGroup; + +/** + * @ingroup main + * @ingroup plugin + * @short A widget to select what plugins to load and configure the plugins. + * + * It shows the list of available plugins on top (if there's more than one + * category this is a TabWidget) and the configuration of the selected plugin + * below that. + * + * Since the user needs a way to know what a specific plugin does every plugin + * sould install a desktop file containing a name, comment and category field. + * The category is usefull for applications that can use different kinds of + * plugins like a playlist, skin or visualization. + * + * The location of these desktop files is the + * share/apps/<instancename>/<plugindir> directory. But if you need + * you may use a different directory. + * + * Often a program has more than one kind of plugin. In that case you want to + * make a visible distinction between those plugins. All you have to do is to + * create a KPluginSelectionWidget for every category and then add them all + * to the KPluginSelector. + * + * @author Matthias Kretz <kretz@kde.org> + * @since 3.2 + */ +class KUTILS_EXPORT KPluginSelector : public QWidget +{ + friend class KPluginSelectionWidget; + + Q_OBJECT + public: + /** + * Create a new KPluginSelector. + */ + KPluginSelector( QWidget * parent, const char * name = 0 ); + ~KPluginSelector(); + + /** + * Add a list of KParts plugins + * + * If you want to support non-KParts plugins use the following + * function. + * + * The information about the plugins will be loaded from the + * share/apps/<instancename>/kpartplugins directory. + * + * @param instanceName The name of the KInstance of the plugin's parent. + * @param catname The translated name of the category. This is the + * name that is shown in the TabWidget if there is + * more than one category. + * @param category When you have different categories of KParts + * plugins you distinguish between the plugins using + * the Category key in the .desktop file. Use this + * parameter to select only those KParts plugins + * with the Category key == @p category. If @p + * category is not set the Category key is ignored + * and all plugins are shown. + * @param config The KConfig object that holds the state of the + * plugins being enabled or not. By default it should + * be instance->config(). It is recommended to + * always pass a KConfig object if you use + * KSettings::PluginPage since you never know from where the + * page will be called (think global config app). + * For example KViewCanvas passes KSimpleConfig( + * "kviewcanvas" ). + */ + void addPlugins( const QString & instanceName, + const QString & catname = QString::null, + const QString & category = QString::null, + KConfig * config = 0 ); + + /** + * Add a list of KParts plugins. Convenience method for the one above. + * If not set explicitely, @p config is set to instance->config(). + */ + void addPlugins( const KInstance * instance, + const QString & catname = QString::null, + const QString & category = QString::null, + KConfig * config = 0 ); + + /** + * Add a list of non-KParts plugins + * + * @param plugininfos A list of KPluginInfo objects containing the + * necessary information for the plugins you want to + * add to the list. + * @param catname The translated name of the category. This is the + * name that is shown in the TabWidget if there is + * more than one category. + * @param category When you have different categories of KParts + * plugins you distinguish between the plugins using + * the Category key in the .desktop file. Use this + * parameter to select only those KParts plugins + * with the Category key == @p category. If @p + * category is not set the Category key is ignored + * and all plugins are shown. + * @param config The KConfig object that holds the state of the + * plugins being enabled or not. By default it will + * use KGlobal::config(). It is recommended to + * always pass a KConfig object if you use + * KSettings::PluginPage since you never know from where the + * page will be called (think global config app). + * For example KViewCanvas passes KSimpleConfig( + * "kviewcanvas" ). + */ + void addPlugins( const QValueList<KPluginInfo*> & plugininfos, + const QString & catname = QString::null, + const QString & category = QString::null, + KConfig * config = 0 ); + + /** + * Set whether the area for showing the KCMs of the plugins should be + * hidden if the plugin doesn't have a KCM or whether the layout should + * rather stay static and only an message should be shown. + * + * By default the config page is not hidden. + */ + void setShowEmptyConfigPage( bool ); + + /** + * Load the state of the plugins (selected or not) from the KPluginInfo + * objects. For KParts plugins everything should work automatically. For + * your own type of plugins you might need to reimplement the + * KPluginInfo::isPluginEnabled() method. If that doesn't fit your needs + * you can also reimplement this method. + */ + void load(); + + /** + * Save the configuration + */ + void save(); + + /** + * Change to applications defaults + */ + void defaults(); + + signals: + /** + * Tells you whether the configuration is changed or not. + */ + void changed( bool ); + + /** + * Emitted after the config of an embedded KCM has been saved. The + * argument is the name of the parent component that needs to reload + * its config + */ + void configCommitted( const QCString & instanceName ); + + private: + /** + * return the KCM widgetstack + * + * @internal + */ + QWidgetStack * widgetStack(); + + /** + * Show an info page in the widgetstack. + * + * @internal + */ + void configPage( int id ); + + /** + * @internal + */ + void checkNeedForTabWidget(); + + /** + * @internal + */ + void addPluginsInternal( const QValueList<KPluginInfo*> plugininfos, + const QString & catname, const QString & category, + KConfigGroup* cfgGroup ); + + class KPluginSelectorPrivate; + KPluginSelectorPrivate * d; +}; + +// vim: sw=4 sts=4 et tw=80 +#endif // KPLUGINSELECTOR_H diff --git a/kutils/kpluginselector_p.h b/kutils/kpluginselector_p.h new file mode 100644 index 000000000..c9b968c0f --- /dev/null +++ b/kutils/kpluginselector_p.h @@ -0,0 +1,188 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Matthias Kretz <kretz@kde.org> + + 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. + +*/ + +#ifndef KPLUGINSELECTOR_P_H +#define KPLUGINSELECTOR_P_H + +#include <qwidget.h> + +#include <kdelibs_export.h> + +class KConfigGroup; +class QListViewItem; +class KPluginInfo; +class KCModuleInfo; + +/** + * This is a widget to configure what Plugins should be loaded. This widget is + * used by KPluginSelector and has no direct use. + * + * @internal + * @see KPluginSelector + * + * @author Matthias Kretz <kretz@kde.org> + * @since 3.2 + */ +class KPluginSelectionWidget : public QWidget +{ + Q_OBJECT + public: + /** + * Create a new Plugin Selector widget. + * + * @param plugininfos A list of KPluginInfo objects containing the + * necessary information for the plugins you want to + * add the selector's list. + * @param kps A KPluginSelector object. + * @param parent The parent widget. + * @param catname The translated name of the category. + * @param category The unstranslated category key name. + * @param config Set the KConfigGroup object that holds the + * state of the plugins being enabled or not. + * @param name The name of the widget (passed to QWidget) + * + * @internal + */ + KPluginSelectionWidget( const QValueList<KPluginInfo*> & plugininfos, + KPluginSelector * kps, QWidget * parent, const QString & catname, + const QString & category, KConfigGroup * config = 0, + const char * name = 0 ); + + virtual ~KPluginSelectionWidget(); + + + /** + * Returns the translated category name + * + * @internal + */ + QString catName() const; + + /** + * Tell the KPluginInfo objects to load their state (enabled/disabled). + */ + virtual void load(); + + /** + * It tells the KPluginInfo objects to save their current state + * (enabled/disabled). + */ + virtual void save(); + + /** + * @return whether the plugin is enabled in the ListView or not. + */ + bool pluginChecked( const QString & pluginName ) const; + + signals: + /** + * Emits true when at least one embedded KCM is changed, or the plugin + * selection was changed. + * Emits false when the configuration is back to what it was. + */ + void changed( bool ); + + /** + * Emitted after the config of an embedded KCM has been saved. The + * argument is the name of the parent component that needs to reload + * its config + */ + void configCommitted( const QCString & instanceName ); + + protected: + /** + * Reimplement in your subclass if you have special needs: The standard + * implementation looks at the KPluginInfo objects to find the + * needed information. But if, for some reason, your program doesn't + * work with that here's your chance to get it working. + * + * @return Whether the plugin is loaded. + */ + virtual bool pluginIsLoaded( const QString & pluginName ) const; + + private slots: + /** + * Called when a QCheckListItem is checked or unchecked. It calls + * checkDependencies on the Plugin and then updateConfigPage. + * + * @internal + */ + void executed( QListViewItem * ); + + /** + * Called whenever the visible config page should change (plugin + * selection changed, plugin checked changed) + * + * First it checks for a widget for the plugin - if there is one, great: + * show it. + * If there is none, check whether there should be one and try to load + * the KCM. If there are more than one KCM create a TabWidget and put + * all of them inside, else just use the "bare" KCM. If there is no KCM + * us the infoPage( NoKCM ). If there should be one but it can't be + * loaded us the infoPage( LoadError ). + * Depending on whether the currently selected Plugin is checked or not + * disable or enable the "page" (which is the TabWidget or the KCM). + * + * @internal + */ + void updateConfigPage( KPluginInfo * plugininfo, bool checked ); + void updateConfigPage(); + + /** + * Whenever an embedded KCM emits the changed signal we count the number + * of changed KCMs. If it becomes one we emit changed( true ), if it + * becomes zero we emit changed( false ). + * + * @internal + */ + void clientChanged( bool ); + + /** + * Called whenever the tabWidget changes. It checks whether this + * KPluginSelectionWidget is shown and sets the currentplugininfo + * accordingly. + */ + void tabWidgetChanged( QWidget * ); + + private: + /** + * Load a KCM from a KCModuleInfo. If successfull connect changed + * signal and return the module. If not, create a label showing "Error", + * show the loaderError and return the label. + * + * @internal + */ + QWidget * insertKCM( QWidget * parent, const KCModuleInfo & ); + + /** + * Embed the KCMs for the plugin into the widgetstack + * + * @internal + */ + void embeddPluginKCMs( KPluginInfo *, bool ); + + void init( const QValueList<KPluginInfo*> & plugininfos, const QString & ); + void checkDependencies( const KPluginInfo * ); + + struct KPluginSelectionWidgetPrivate; + KPluginSelectionWidgetPrivate * d; +}; + +// vim: sw=4 sts=4 et +#endif // KPLUGINSELECTOR_P_H diff --git a/kutils/kreplace.cpp b/kutils/kreplace.cpp new file mode 100644 index 000000000..6740c297a --- /dev/null +++ b/kutils/kreplace.cpp @@ -0,0 +1,328 @@ +/* + Copyright (C) 2001, S.R.Haque <srhaque@iee.org>. + Copyright (C) 2002, David Faure <david@mandrakesoft.com> + This file is part of the KDE project + + 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 <qlabel.h> +#include <kapplication.h> +#include <kdebug.h> + +#include <klocale.h> +#include <kmessagebox.h> +#include "kreplace.h" +#include "kreplacedialog.h" +#include <qregexp.h> + +//#define DEBUG_REPLACE +#define INDEX_NOMATCH -1 + +class KReplaceNextDialog : public KDialogBase +{ +public: + KReplaceNextDialog( QWidget *parent ); + void setLabel( const QString& pattern, const QString& replacement ); +private: + QLabel* m_mainLabel; +}; + +KReplaceNextDialog::KReplaceNextDialog(QWidget *parent) : + KDialogBase(parent, 0, false, // non-modal! + i18n("Replace"), + User3 | User2 | User1 | Close, + User3, + false, + i18n("&All"), i18n("&Skip"), i18n("Replace")) +{ + m_mainLabel = new QLabel( this ); + setMainWidget( m_mainLabel ); + resize(minimumSize()); +} + +void KReplaceNextDialog::setLabel( const QString& pattern, const QString& replacement ) +{ + m_mainLabel->setText( i18n("Replace '%1' with '%2'?").arg(pattern).arg(replacement) ); +} + +//// + +KReplace::KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent) : + KFind( pattern, options, parent ) +{ + m_replacements = 0; + m_replacement = replacement; +} + +KReplace::KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent, QWidget *dlg) : + KFind( pattern, options, parent, dlg ) +{ + m_replacements = 0; + m_replacement = replacement; +} + +KReplace::~KReplace() +{ + // KFind::~KFind will delete m_dialog +} + +KDialogBase* KReplace::replaceNextDialog( bool create ) +{ + if ( m_dialog || create ) + return dialog(); + return 0L; +} + +KReplaceNextDialog* KReplace::dialog() +{ + if ( !m_dialog ) + { + m_dialog = new KReplaceNextDialog( parentWidget() ); + connect( m_dialog, SIGNAL( user1Clicked() ), this, SLOT( slotReplaceAll() ) ); + connect( m_dialog, SIGNAL( user2Clicked() ), this, SLOT( slotSkip() ) ); + connect( m_dialog, SIGNAL( user3Clicked() ), this, SLOT( slotReplace() ) ); + connect( m_dialog, SIGNAL( finished() ), this, SLOT( slotDialogClosed() ) ); + } + return static_cast<KReplaceNextDialog *>(m_dialog); +} + +void KReplace::displayFinalDialog() const +{ + if ( !m_replacements ) + KMessageBox::information(parentWidget(), i18n("No text was replaced.")); + else + KMessageBox::information(parentWidget(), i18n("1 replacement done.", "%n replacements done.", m_replacements ) ); +} + +KFind::Result KReplace::replace() +{ +#ifdef DEBUG_REPLACE + kdDebug() << k_funcinfo << "m_index=" << m_index << endl; +#endif + if ( m_index == INDEX_NOMATCH && m_lastResult == Match ) + { + m_lastResult = NoMatch; + return NoMatch; + } + + do // this loop is only because validateMatch can fail + { +#ifdef DEBUG_REPLACE + kdDebug() << k_funcinfo << "beginning of loop: m_index=" << m_index << endl; +#endif + // Find the next match. + if ( m_options & KReplaceDialog::RegularExpression ) + m_index = KFind::find(m_text, *m_regExp, m_index, m_options, &m_matchedLength); + else + m_index = KFind::find(m_text, m_pattern, m_index, m_options, &m_matchedLength); +#ifdef DEBUG_REPLACE + kdDebug() << k_funcinfo << "KFind::find returned m_index=" << m_index << endl; +#endif + if ( m_index != -1 ) + { + // Flexibility: the app can add more rules to validate a possible match + if ( validateMatch( m_text, m_index, m_matchedLength ) ) + { + if ( m_options & KReplaceDialog::PromptOnReplace ) + { +#ifdef DEBUG_REPLACE + kdDebug() << k_funcinfo << "PromptOnReplace" << endl; +#endif + // Display accurate initial string and replacement string, they can vary + QString matchedText = m_text.mid( m_index, m_matchedLength ); + QString rep = matchedText; + KReplace::replace(rep, m_replacement, 0, m_options, m_matchedLength); + dialog()->setLabel( matchedText, rep ); + dialog()->show(); + + // Tell the world about the match we found, in case someone wants to + // highlight it. + emit highlight(m_text, m_index, m_matchedLength); + + m_lastResult = Match; + return Match; + } + else + { + doReplace(); // this moves on too + } + } + else + { + // not validated -> move on + if (m_options & KFindDialog::FindBackwards) + m_index--; + else + m_index++; + } + } else + m_index = INDEX_NOMATCH; // will exit the loop + } + while (m_index != INDEX_NOMATCH); + + m_lastResult = NoMatch; + return NoMatch; +} + +int KReplace::replace(QString &text, const QString &pattern, const QString &replacement, int index, long options, int *replacedLength) +{ + int matchedLength; + + index = KFind::find(text, pattern, index, options, &matchedLength); + if (index != -1) + { + *replacedLength = replace(text, replacement, index, options, matchedLength); + if (options & KReplaceDialog::FindBackwards) + index--; + else + index += *replacedLength; + } + return index; +} + +int KReplace::replace(QString &text, const QRegExp &pattern, const QString &replacement, int index, long options, int *replacedLength) +{ + int matchedLength; + + index = KFind::find(text, pattern, index, options, &matchedLength); + if (index != -1) + { + *replacedLength = replace(text, replacement, index, options, matchedLength); + if (options & KReplaceDialog::FindBackwards) + index--; + else + index += *replacedLength; + } + return index; +} + +int KReplace::replace(QString &text, const QString &replacement, int index, long options, int length) +{ + QString rep = replacement; + // Backreferences: replace \0 with the right portion of 'text' + if ( options & KReplaceDialog::BackReference ) + rep.replace( "\\0", text.mid( index, length ) ); + // Then replace rep into the text + text.replace(index, length, rep); + return rep.length(); +} + +void KReplace::slotReplaceAll() +{ + doReplace(); + m_options &= ~KReplaceDialog::PromptOnReplace; + emit optionsChanged(); + emit findNext(); +} + +void KReplace::slotSkip() +{ + if (m_options & KReplaceDialog::FindBackwards) + m_index--; + else + m_index++; + if ( m_dialogClosed ) { + delete m_dialog; // hide it again + m_dialog = 0L; + } else + emit findNext(); +} + +void KReplace::slotReplace() +{ + doReplace(); + if ( m_dialogClosed ) { + delete m_dialog; // hide it again + m_dialog = 0L; + } else + emit findNext(); +} + +void KReplace::doReplace() +{ + int replacedLength = KReplace::replace(m_text, m_replacement, m_index, m_options, m_matchedLength); + + // Tell the world about the replacement we made, in case someone wants to + // highlight it. + emit replace(m_text, m_index, replacedLength, m_matchedLength); +#ifdef DEBUG_REPLACE + kdDebug() << k_funcinfo << "after replace() signal: m_index=" << m_index << " replacedLength=" << replacedLength << endl; +#endif + m_replacements++; + if (m_options & KReplaceDialog::FindBackwards) + m_index--; + else { + m_index += replacedLength; + // when replacing the empty pattern, move on. See also kjs/regexp.cpp for how this should be done for regexps. + if ( m_pattern.isEmpty() ) + ++m_index; + } +#ifdef DEBUG_REPLACE + kdDebug() << k_funcinfo << "after adjustement: m_index=" << m_index << endl; +#endif +} + +void KReplace::resetCounts() +{ + KFind::resetCounts(); + m_replacements = 0; +} + +bool KReplace::shouldRestart( bool forceAsking, bool showNumMatches ) const +{ + // Only ask if we did a "find from cursor", otherwise it's pointless. + // ... Or if the prompt-on-replace option was set. + // Well, unless the user can modify the document during a search operation, + // hence the force boolean. + if ( !forceAsking && (m_options & KFindDialog::FromCursor) == 0 + && (m_options & KReplaceDialog::PromptOnReplace) == 0 ) + { + displayFinalDialog(); + return false; + } + QString message; + if ( showNumMatches ) + { + if ( !m_replacements ) + message = i18n("No text was replaced."); + else + message = i18n("1 replacement done.", "%n replacements done.", m_replacements ); + } + else + { + if ( m_options & KFindDialog::FindBackwards ) + message = i18n( "Beginning of document reached." ); + else + message = i18n( "End of document reached." ); + } + + message += "\n"; + // Hope this word puzzle is ok, it's a different sentence + message += + ( m_options & KFindDialog::FindBackwards ) ? + i18n("Do you want to restart search from the end?") + : i18n("Do you want to restart search at the beginning?"); + + int ret = KMessageBox::questionYesNo( parentWidget(), message, QString::null, i18n("Restart"), i18n("Stop") ); + return( ret == KMessageBox::Yes ); +} + +void KReplace::closeReplaceNextDialog() +{ + closeFindNextDialog(); +} + +#include "kreplace.moc" diff --git a/kutils/kreplace.h b/kutils/kreplace.h new file mode 100644 index 000000000..2a174af23 --- /dev/null +++ b/kutils/kreplace.h @@ -0,0 +1,269 @@ +/* + Copyright (C) 2001, S.R.Haque <srhaque@iee.org>. + Copyright (C) 2002, David Faure <david@mandrakesoft.com> + This file is part of the KDE project + + 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. +*/ + +#ifndef KREPLACE_H +#define KREPLACE_H + +#include "kfind.h" + +class KReplaceNextDialog; + +/** + * @ingroup main + * @ingroup findreplace + * @brief A generic implementation of the "replace" function. + * + * @author S.R.Haque <srhaque@iee.org>, David Faure <faure@kde.org> + * + * \b Detail: + * + * This class includes prompt handling etc. Also provides some + * static functions which can be used to create custom behavior + * instead of using the class directly. + * + * \b Example: + * + * To use the class to implement a complete replace feature: + * + * In the slot connect to the replace action, after using KReplaceDialog: + * \code + * + * // This creates a replace-on-prompt dialog if needed. + * m_replace = new KReplace(pattern, replacement, options, this); + * + * // Connect signals to code which handles highlighting + * // of found text, and on-the-fly replacement. + * connect( m_replace, SIGNAL( highlight( const QString &, int, int ) ), + * this, SLOT( slotHighlight( const QString &, int, int ) ) ); + * // Connect findNext signal - called when pressing the button in the dialog + * connect( m_replace, SIGNAL( findNext() ), + * this, SLOT( slotReplaceNext() ) ); + * // Connect replace signal - called when doing a replacement + * connect( m_replace, SIGNAL( replace(const QString &, int, int, int) ), + * this, SLOT( slotReplace(const QString &, int, int, int) ) ); + * \endcode + * Then initialize the variables determining the "current position" + * (to the cursor, if the option FromCursor is set, + * to the beginning of the selection if the option SelectedText is set, + * and to the beginning of the document otherwise). + * Initialize the "end of search" variables as well (end of doc or end of selection). + * Swap begin and end if FindBackwards. + * Finally, call slotReplaceNext(); + * + * \code + * void slotReplaceNext() + * { + * KFind::Result res = KFind::NoMatch; + * while ( res == KFind::NoMatch && <position not at end> ) { + * if ( m_replace->needData() ) + * m_replace->setData( <current text fragment> ); + * + * // Let KReplace inspect the text fragment, and display a dialog if a match is found + * res = m_replace->replace(); + * + * if ( res == KFind::NoMatch ) { + * <Move to the next text fragment, honoring the FindBackwards setting for the direction> + * } + * } + * + * if ( res == KFind::NoMatch ) // i.e. at end + * <Call either m_replace->displayFinalDialog(); delete m_replace; m_replace = 0L; + * or if ( m_replace->shouldRestart() ) { reinit (w/o FromCursor) and call slotReplaceNext(); } + * else { m_replace->closeReplaceNextDialog(); }> + * } + * \endcode + * + * Don't forget delete m_find in the destructor of your class, + * unless you gave it a parent widget on construction. + * + */ +class KUTILS_EXPORT KReplace : + public KFind +{ + Q_OBJECT + +public: + + /** + * Only use this constructor if you don't use KFindDialog, or if + * you use it as a modal dialog. + * @param pattern The pattern to look for. + * @param replacement The replacement string. + * @param options Options for the find dialog. @see KFindDialog and KReplaceDialog. + * @param parent The parent widget. + */ + KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent = 0); + /** + * This is the recommended constructor if you also use KReplaceDialog (non-modal). + * You should pass the pointer to it here, so that when a message box + * appears it has the right parent. Don't worry about deletion, KReplace + * will notice if the find dialog is closed. + * @param pattern The pattern to look for. + * @param replacement The replacement string. + * @param options Options for the find dialog. @see KFindDialog and KReplaceDialog. + * @param parent The parent widget. + * @param replaceDialog A pointer to the KReplaceDialog object. + */ + KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent, QWidget* replaceDialog); + + /** + * Destructor. + */ + virtual ~KReplace(); + + /** + * Return the number of replacements made (i.e. the number of times + * the replace signal was emitted). + * Can be used in a dialog box to tell the user how many replacements were made. + * The final dialog does so already, unless you used setDisplayFinalDialog(false). + * @return The number of replacements. + */ + int numReplacements() const { return m_replacements; } + + /** + * Call this to reset the numMatches & numReplacements counts. + * Can be useful if reusing the same KReplace for different operations, + * or when restarting from the beginning of the document. + */ + virtual void resetCounts(); + + /** + * Walk the text fragment (e.g. kwrite line, kspread cell) looking for matches. + * For each match, if prompt-on-replace is specified, emits the highlight() signal + * and displays the prompt-for-replace dialog before doing the replace. + * @return Whether or not there has been a match. + */ + Result replace(); + + /** + * Return (or create) the dialog that shows the "find next?" prompt. + * Usually you don't need to call this. + * One case where it can be useful, is when the user selects the "Find" + * menu item while a find operation is under way. In that case, the + * program may want to call setActiveWindow() on that dialog. + * @return The replace next dialog. + */ + KDialogBase* replaceNextDialog( bool create = false ); + + /** + * Close the "replace next?" dialog. The application should do this when + * the last match was hit. If the application deletes the KReplace, then + * "find previous" won't be possible anymore. + */ + void closeReplaceNextDialog(); + + /** + * Searches the given string, replaces with the given replacement string, + * and returns whether a match was found. If one is, + * the replacement string length is also returned. + * + * A performance optimised version of the function is provided for use + * with regular expressions. + * + * @param text The string to search. + * @param pattern The pattern to look for. + * @param replacement The replacement string to insert into the text. + * @param index The starting index into the string. + * @param options The options to use. + * @param replacedLength Output parameter, contains the length of the replaced string. + * Not always the same as replacement.length(), when backreferences are used. + * @return The index at which a match was found, or -1 if no match was found. + */ + static int replace( QString &text, const QString &pattern, const QString &replacement, int index, long options, int *replacedLength ); + + /** + * Searches the given regular expression, replaces with the given replacement string, + * and returns whether a match was found. If one is, + * the replacement string length is also returned. + * + * Another version of the function is provided for use with strings. + * + * @param text The string to search. + * @param pattern The regular expression pattern to look for. + * @param replacement The replacement string to insert into the text. + * @param index The starting index into the string. + * @param options The options to use. + * @param replacedLength Output parameter, contains the length of the replaced string. + * Not always the same as replacement.length(), when backreferences are used. + * @return The index at which a match was found, or -1 if no match was found. + */ + static int replace( QString &text, const QRegExp &pattern, const QString &replacement, int index, long options, int *replacedLength ); + + /** + * Returns @c true if we should restart the search from scratch. + * Can ask the user, or return @c false (if we already searched/replaced the + * whole document without the PromptOnReplace option). + * + * @param forceAsking set to @c true if the user modified the document during the + * search. In that case it makes sense to restart the search again. + * + * @param showNumMatches set to @c true if the dialog should show the number of + * matches. Set to @c false if the application provides a "find previous" action, + * in which case the match count will be erroneous when hitting the end, + * and we could even be hitting the beginning of the document (so not all + * matches have even been seen). + * + * @return @c true, if the search should be restarted. + */ + virtual bool shouldRestart( bool forceAsking = false, bool showNumMatches = true ) const; + + /** + * Displays the final dialog telling the user how many replacements were made. + * Call either this or shouldRestart(). + */ + virtual void displayFinalDialog() const; + +signals: + + /** + * Connect to this slot to implement updating of replaced text during the replace + * operation. + * + * Extra care must be taken to properly implement the "no prompt-on-replace" case. + * For instance highlight isn't emitted in that case (some code might rely on it), + * and for performance reasons one should repaint after replace() ONLY if + * prompt-on-replace was selected. + * + * @param text The text, in which the replacement has already been done. + * @param replacementIndex Starting index of the matched substring + * @param replacedLength Length of the replacement string + * @param matchedLength Length of the matched string + */ + void replace(const QString &text, int replacementIndex, int replacedLength, int matchedLength); + +protected slots: + + void slotSkip(); + void slotReplace(); + void slotReplaceAll(); + +private: + KReplaceNextDialog* dialog(); + void doReplace(); + static int replace( QString &text, const QString &replacement, int index, long options, int length ); + + QString m_replacement; + unsigned m_replacements; + + // Binary compatible extensibility. + class KReplacePrivate; + KReplacePrivate *d; +}; +#endif diff --git a/kutils/kreplacedialog.cpp b/kutils/kreplacedialog.cpp new file mode 100644 index 000000000..80a36e5df --- /dev/null +++ b/kutils/kreplacedialog.cpp @@ -0,0 +1,160 @@ +/* + Copyright (C) 2001, S.R.Haque <srhaque@iee.org>. + Copyright (C) 2002, David Faure <david@mandrakesoft.com> + This file is part of the KDE project + + 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 "kreplacedialog.h" + +#include <qcheckbox.h> +#include <qgroupbox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qregexp.h> +#include <kcombobox.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kdebug.h> + +/** + * we need to insert the strings after the dialog is set + * up, otherwise QComboBox will deliver an aweful big sizeHint + * for long replacement texts. + */ +class KReplaceDialog::KReplaceDialogPrivate { + public: + KReplaceDialogPrivate() : m_initialShowDone(false) {} + QStringList replaceStrings; + bool m_initialShowDone; +}; + +KReplaceDialog::KReplaceDialog(QWidget *parent, const char *name, long options, const QStringList &findStrings, const QStringList &replaceStrings, bool hasSelection) : + KFindDialog(parent, name, true) +{ + d = new KReplaceDialogPrivate; + d->replaceStrings = replaceStrings; + init(true, findStrings, hasSelection); + setOptions(options); +} + +KReplaceDialog::~KReplaceDialog() +{ + delete d; +} + +void KReplaceDialog::showEvent( QShowEvent *e ) +{ + if ( !d->m_initialShowDone ) + { + d->m_initialShowDone = true; // only once + + if (!d->replaceStrings.isEmpty()) + { + setReplacementHistory(d->replaceStrings); + m_replace->lineEdit()->setText( d->replaceStrings[0] ); + } + } + + KFindDialog::showEvent(e); +} + +long KReplaceDialog::options() const +{ + long options = 0; + + options = KFindDialog::options(); + if (m_promptOnReplace->isChecked()) + options |= PromptOnReplace; + if (m_backRef->isChecked()) + options |= BackReference; + return options; +} + +QWidget *KReplaceDialog::replaceExtension() +{ + if (!m_replaceExtension) + { + m_replaceExtension = new QWidget(m_replaceGrp); + m_replaceLayout->addMultiCellWidget(m_replaceExtension, 3, 3, 0, 1); + } + + return m_replaceExtension; +} + +QString KReplaceDialog::replacement() const +{ + return m_replace->currentText(); +} + +QStringList KReplaceDialog::replacementHistory() const +{ + QStringList lst = m_replace->historyItems(); + // historyItems() doesn't tell us about the case of replacing with an empty string + if ( m_replace->lineEdit()->text().isEmpty() ) + lst.prepend( QString::null ); + return lst; +} + +void KReplaceDialog::setOptions(long options) +{ + KFindDialog::setOptions(options); + m_promptOnReplace->setChecked(options & PromptOnReplace); + m_backRef->setChecked(options & BackReference); +} + +void KReplaceDialog::setReplacementHistory(const QStringList &strings) +{ + if (strings.count() > 0) + m_replace->setHistoryItems(strings, true); + else + m_replace->clearHistory(); +} + +void KReplaceDialog::slotOk() +{ + // If regex and backrefs are enabled, do a sanity check. + if ( m_regExp->isChecked() && m_backRef->isChecked() ) + { + QRegExp r ( pattern() ); + int caps = r.numCaptures(); + QRegExp check(QString("((?:\\\\)+)(\\d+)")); + int p = 0; + QString rep = replacement(); + while ( (p = check.search( rep, p ) ) > -1 ) + { + if ( check.cap(1).length()%2 && check.cap(2).toInt() > caps ) + { + KMessageBox::information( this, i18n( + "Your replacement string is referencing a capture greater than '\\%1', ").arg( caps ) + + ( caps ? + i18n("but your pattern only defines 1 capture.", + "but your pattern only defines %n captures.", caps ) : + i18n("but your pattern defines no captures.") ) + + i18n("\nPlease correct.") ); + return; // abort OKing + } + p += check.matchedLength(); + } + + } + + KFindDialog::slotOk(); + m_replace->addToHistory(replacement()); +} + +// kate: space-indent on; indent-width 4; replace-tabs on; +#include "kreplacedialog.moc" diff --git a/kutils/kreplacedialog.h b/kutils/kreplacedialog.h new file mode 100644 index 000000000..459c0c5ae --- /dev/null +++ b/kutils/kreplacedialog.h @@ -0,0 +1,160 @@ +/* + Copyright (C) 2001, S.R.Haque <srhaque@iee.org>. + Copyright (C) 2002, David Faure <david@mandrakesoft.com> + This file is part of the KDE project + + 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. +*/ + +#ifndef KREPLACEDIALOG_H +#define KREPLACEDIALOG_H + +#include "kfinddialog.h" + +class KHistoryCombo; +class QCheckBox; +class QGroupBox; +class QLabel; +class QPopupMenu; +class QPushButton; +class QRect; + +/** + * @ingroup main + * @ingroup findreplace + * @short A generic "replace" dialog. + * + * @author S.R.Haque <srhaque@iee.org> + * + * \b Detail: + * + * This widget inherits from KFindDialog and implements + * the following additional functionalities: a replacement string + * object and an area for a user-defined widget to extend the dialog. + * + * \b Example: + * + * To use the basic replace dialog: + * + * \code + * \endcode + * + * To use your own extensions: + * + * \code + * \endcode + */ +class KUTILS_EXPORT KReplaceDialog: + public KFindDialog +{ + Q_OBJECT + +public: + + /** + * Options. + */ + enum Options + { + PromptOnReplace = 256, ///< Should the user be prompted before the replace operation? + BackReference = 512 + }; + + /** + * Construct a replace dialog.read-only or rather select-only combo box with a + * parent object and a name. + * + * @param parent The parent object of this widget + * @param name The name of this widget + * @param options A bitfield of the Options to be enabled. + * @param findStrings A QStringList to insert in the combo box of text to find + * @param replaceStrings A QStringList to insert in the combo box of text to + * replace with + * @param hasSelection Whether a selection exists + */ + KReplaceDialog( QWidget *parent = 0, const char *name = 0, long options = 0, + const QStringList &findStrings = QStringList(), + const QStringList &replaceStrings = QStringList(), + bool hasSelection = true ); + + /** + * Destructor. + */ + virtual ~KReplaceDialog(); + + /** + * Provide the list of @p strings to be displayed as the history + * of replacement strings. @p strings might get truncated if it is + * too long. + * + * @param history The replacement history. + * @see replacementHistory + */ + void setReplacementHistory( const QStringList &history ); + + /** + * Returns the list of history items. + * + * @return The replacement history. + * @see setReplacementHistory + */ + QStringList replacementHistory() const; + + /** + * Set the options which are enabled. + * + * @param options The setting of the Options. + * @see Options, KFindDialog::Options + */ + void setOptions( long options ); + + /** + * Returns the state of the options. Disabled options may be returned in + * an indeterminate state. + * + * @return The options. + * @see setOptions, Options, KFindDialog::Options + */ + long options() const; + + /** + * Returns the replacement string. + * @return The replacement string. + */ + QString replacement() const; + + /** + * Returns an empty widget which the user may fill with additional UI + * elements as required. The widget occupies the width of the dialog, + * and is positioned immediately the regular expression support widgets + * for the replacement string. + * @return An extensible QWidget. + */ + QWidget *replaceExtension(); + +protected slots: + + void slotOk(); + virtual void showEvent ( QShowEvent * ); + +private: + + // Binary compatible extensibility. + class KReplaceDialogPrivate; + KReplaceDialogPrivate *d; +}; + + +#endif // KREPLACEDIALOG_H diff --git a/kutils/ksettings/Makefile.am b/kutils/ksettings/Makefile.am new file mode 100644 index 000000000..39e7183b2 --- /dev/null +++ b/kutils/ksettings/Makefile.am @@ -0,0 +1,9 @@ +INCLUDES=-I$(srcdir)/.. $(all_includes) +noinst_LTLIBRARIES = libksettings.la + +libksettings_la_SOURCES = dispatcher.cpp dialog.cpp pluginpage.cpp componentsdialog.cpp + +ksettingsincludedir = $(includedir)/ksettings +ksettingsinclude_HEADERS = dispatcher.h dialog.h pluginpage.h componentsdialog.h + +METASOURCES = AUTO diff --git a/kutils/ksettings/README.dox b/kutils/ksettings/README.dox new file mode 100644 index 000000000..0660324c1 --- /dev/null +++ b/kutils/ksettings/README.dox @@ -0,0 +1,276 @@ +/** + +\namespace KSettings + +\short A collection of classes to create configuration dialogs that work over +component boundaries + +<h2>How to use KSettings::Dialog in your application.</h2> + +<hr> +<h3>1. Open the dialog from your app</h3> + +All you need to do is instanciate KSettings::Dialog and show() it. I recommend +the following: + +create the 'Configure MyApp' StdAction like this: +\code +KStdAction::preferences( this, SLOT( showConfigDialog() ), actionCollection ); +\endcode + +and the slot looks like this: +\code +if( m_dlg == 0 ) + m_dlg = new KSettings::Dialog( this ); +m_dlg->show(); +\endcode + +Of course you need to have the 'KSettings::Dialog * m_dlg' member var and +initialize it to 0 in the ctor. + +If your application uses KParts that don't set 'X-KDE-ParentApp=<the instance +name of your application>' then you need to use the second ctor of +KSettings::Dialog: +\code +m_dlg = new KSettings::Dialog( QStringList::split( ';', "component1;component2" ) ); +\endcode + +The KSettings::Dialog object will be destructed automatically by the QObject +mechanisms. + + +<hr> +<h3>2. Create pages for your dialog</h3> + +Every page is a KCM. This is what you need for creating a page: + +\code +class MyAppConfig : public KCModule +{ + Q_OBJECT +public: + MyAppConfig( QWidget *parent, const char *name = 0, const QStringList &args = + QStringList() ); + ~MyAppConfig(); + + void load(); + void save(); + void defaults(); +} +\endcode + +and in the cpp file: + +\code +typedef KGenericFactory<MyAppConfig, QWidget> MyAppConfigFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_myappconfig, MyAppConfigFactory( + "kcm_myappconfig" ) ); + +MyAppConfig::MyAppConfig( QWidget *parent, const char *, const QStringList &args ) + : KCModule( MyAppConfigFactory::instance(), parent, args ) +{ + // create the pages GUI + load(); +} + +// implementations for the other methods +\endcode + +For the KConfig object you can either use +KGlobal::config() (I don't recommend it) or KSimpleConfig( "myapprc" ). +I added a method to KSettings::Dispatcher that gives you the KConfig +object for every registered instance name: \ref KSettings::Dispatcher::configForInstanceName + + +<hr> +<h3>3. The .desktop file for the page</h3> + +The .desktop file holds all the information for the dialog to find the page and +insert it at the right place (with the right icon, name and comment). + +An example file: +\verbatim +[Desktop Entry] +Encoding=UTF-8 +Icon=myapp +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=myappconfig +X-KDE-FactoryName=MyAppConfigFactory +X-KDE-ParentApp=myapp +X-KDE-ParentComponents=myapp +X-KDE-Weight=10 + +Name=General +Comment=General configuration of my app +\endverbatim + + +Some explanation for those keys: +- You just keep 'Encoding', 'Type', 'ServiceTypes' and 'X-KDE-ModuleType' like + in the example. For very special needs you might add another ServiceType to + the list... +- Icon is the icon that will be used in the listview/iconview for your page. +- X-KDE-Library is the name of the library where the page is in. The library + always needs to be prefixed with kcm_ but you don't write the prefix in the + desktop file. For more docu on this look for the KCModule docu. +- X-KDE-FactoryName is either the name of the Factory you used in the + KGenericFactory call or the suffix of the create_ function that you created. + Again for more info look for the KCModule docu. +- X-KDE-ParentApp is the name of the application this config page belongs to. It + is used by the first two \ref KSettings::Dialog constructors. The Dialog will + use all modules that set X-KDE-ParentApp to + KGlobal::instance()->instanceName(). It + should be pretty easy to find out what name that is: look at the first + argument to the KAboutData ctor. +- X-KDE-ParentComponents is a list of the components (plugin/KPart/whatever) + this config page belongs to. Normally there is only one component. + It is used for two things: + -# If you use KSettings::Dispatcher the dispatcher will notify all components + in this list after the save() method of your KCM has been called. The + components then can reload the configuration and apply the changes the user + did to the config. + -# If your component is used by another application (that is not = + X-KDE-ParentApp) then it may add the name of the component to the ctor of + KSettings::Dialog and the dialog will automatically include all config + pages that have the components name in their ParentComponents list. +- X-KDE-Weight sets the order for the modules to be inserted into the dialog. + The higher the number (heavier) the lower the module will appear in the list. + (the default value is 100) +- Name is the string that is shown in the listview/iconview right below the + icon. +- Comment is the string that is shown on top of the config page for a short + description what you can do on this page. + + +<hr> +<h3>4. The .setdlg file for hierarchical (TreeList) page layouts</h3> + +If your config dialog should show a tree of pages in the config dialog you need +to define that hierarchy with a .setdlg file. + +The file should be installed in apps/<appname>/<appname>.setdlg. If third party +plugins need to merge in they will install their file to +apps/<appname>/ksettingsdialog/<pluginname>.setdlg. + +A .setdlg file contains one or more blocks like the following: + +\verbatim +[id] +Name= +Comment= +Icon= +Weight= +Parent= +\endverbatim + +- The group name (id) is the name you use in the .desktop file of the page: + If your page's .desktop file says "X-KDE-CfgDlgHierarchy=id" then it will be + inserted as a child of this entry. +- \p Name: The name of the section. It will appear in the listview. +- \p Comment: A description of what the modules in this section are. It will + appear in the place where the KCMs are placed when the user clicks on the item + in the listview. +- \p Icon: An icon for the item. +- \p Weight: Defines the position in the listview. See X-KDE-Weight above. +- \p Parent: If this group should be a child of another group write the parent's + group id here. + +<hr> +<h3>5. The Pluginselector</h3> + +There are two ways to use the KPluginSelector widget. One is to use the class +directly and the second to use KSettings::PluginPage as baseclass for a config +page that shows the KPluginSelector widget. + +I'll cover the second usage here and the calls to addPlugins are just the same +for the first. + +To create a plugin page you need the following code: + +\code +typedef KGenericFactory<MyAppPluginConfig, QWidget> MyAppPluginConfigFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_myapppluginconfig, MyAppPluginConfigFactory( "kcm_myapppluginconfig" ) ); + +MyAppPluginConfig( QWidget * parent, const char *, const QStringList & args ) + : PluginPage( MyAppPluginConfigFactory::instance(), parent, args ) +{ + pluginSelector()->addPlugins( ... ); + pluginSelector()->addPlugins( ... ); + . + . + . +} +\endcode + +pluginSelector() returns a pointer to the KPluginSelector widget of the page. +There are three addPlugins methods available, two for adding KParts plugins and +one for the rest. + + +<hr> +<h3>6. The .desktop files of plugin config pages</h3> + +this is the entry for the Makefile.am: + +\verbatim +myappconfigpagedir = $(kde_servicesdir)/<appname> +myappconfigpage_DATA = myappconfigpage.desktop +\endverbatim + + +And this is what the .desktop file looks like: + +\verbatim +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Icon=<iconname> +ServiceTypes=KPluginInfo + +Name=MyPlugin +Comment=My plugin is cool and does foo and bar. + +X-KDE-PluginInfo-Name=myplugin + +X-KDE-PluginInfo-Author=<your name> +X-KDE-PluginInfo-Email=<your email> +X-KDE-PluginInfo-Website=http://www.myplugin.org/ +X-KDE-PluginInfo-Category=CoolPlugins +X-KDE-PluginInfo-Version=0.1 +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true +X-KDE-PluginInfo-Depends=myotherplugin +X-KDE-CfgDlgHierarchy=GroupID +\endverbatim + +Explanation: +mandatory entries: +- leave \p Type and \p Encoding like in the example +- \p Name +- \p Comment +- \p X-KDE-PluginInfo-Name is the "internal name" of the plugin. +- You need to have \p KPluginInfo in \p ServiceTypes but of course you may have more + entries in there. + +optional entries: +- \p Icon is the icon used for your plugin (it's shown in the pluginselector if you + set one). +- \p X-KDE-PluginInfo-Author and \p X-KDE-PluginInfo-Email is some information about the author of the plugin. +- \p X-KDE-PluginInfo-Website is the address for a webpage for this plugin. +- \p X-KDE-PluginInfo-Category is used if your application has different categories of plugins. +- \p X-KDE-PluginInfo-Version is the version of this plugin. +- \p X-KDE-PluginInfo-License is the license of this plugin. +- \p X-KDE-PluginInfo-EnabledByDefault tells the program whether the plugin + should be enabled on first startup or not. +- \p X-KDE-PluginInfo-Depends can be used to tell the application that you need to have + myotherplugin enabled for your plugin to work. +- \p X-KDE-CfgDlgHierarchy is used if you use a \p KSettings::Dialog::ConfigurableInline + KSettings::Dialog to put the plugin checkbox into the group with the GroupID + you set here. + +If you have questions contact Matthias Kretz <kretz@kde.org>. +*/ +// vim: tw=80 diff --git a/kutils/ksettings/TODO b/kutils/ksettings/TODO new file mode 100644 index 000000000..9950ddfd9 --- /dev/null +++ b/kutils/ksettings/TODO @@ -0,0 +1,13 @@ +- KPluginSelct.. listview should only show the name and comment, the rest is + shown in a tooltip (I don't know how to get the tooltip working in the + listview.) +- Handle unsaved changes in KCMs that are hidden if the user deselects some + plugin KCMs (in KSettings::Dialog::Configurable mode). Ideas: + - don't allow changes to the plugin selection if there are unsaved changes in + the main dlg ("You have unsaved changes in the configuration dialog, please + save them first before changing the components selection.") + - automatically save changes of hidden modules when the dialog is deleted + - ask as soon as KCMultiDialog can detect that there are unsaved changes to + hidden KCMs (it might even be possible to show the KCMs for that) + +# vim: tw=80 diff --git a/kutils/ksettings/componentsdialog.cpp b/kutils/ksettings/componentsdialog.cpp new file mode 100644 index 000000000..5093af985 --- /dev/null +++ b/kutils/ksettings/componentsdialog.cpp @@ -0,0 +1,180 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Matthias Kretz <kretz@kde.org> + + 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 "ksettings/componentsdialog.h" +#include <klocale.h> +#include <qlayout.h> +#include <klistview.h> +#include <qlabel.h> +#include <qheader.h> +#include <kplugininfo.h> +#include <kiconloader.h> +#include <kdebug.h> +#include <kconfig.h> +#include <kseparator.h> + +namespace KSettings +{ + +class ComponentsDialog::ComponentsDialogPrivate +{ + public: + KListView * listview; + QFrame * infowidget; + QLabel * iconwidget; + QLabel * commentwidget; + QLabel * descriptionwidget; + QMap<QCheckListItem*, KPluginInfo*> plugininfomap; + QValueList<KPluginInfo*> plugininfolist; +}; + +ComponentsDialog::ComponentsDialog( QWidget * parent, const char * name ) + : KDialogBase( parent, name, false, i18n( "Select Components" ) ) +, d( new ComponentsDialogPrivate ) +{ + QWidget * page = new QWidget( this ); + setMainWidget( page ); + ( new QHBoxLayout( page, 0, KDialog::spacingHint() ) )->setAutoAdd( true ); + d->listview = new KListView( page ); + d->listview->setMinimumSize( 200, 200 ); + d->infowidget = new QFrame( page ); + d->infowidget->setMinimumSize( 200, 200 ); + ( new QVBoxLayout( d->infowidget, 0, KDialog::spacingHint() ) )->setAutoAdd( true ); + d->iconwidget = new QLabel( d->infowidget ); + ( void )new KSeparator( d->infowidget ); + d->commentwidget = new QLabel( d->infowidget ); + d->commentwidget->setAlignment( Qt::WordBreak ); + d->descriptionwidget = new QLabel( d->infowidget ); + d->descriptionwidget->setAlignment( Qt::WordBreak ); + + d->listview->addColumn( QString::null ); + d->listview->header()->hide(); + d->listview->setRootIsDecorated( true ); + d->listview->setSorting( -1 ); + d->listview->setAcceptDrops( false ); + d->listview->setSelectionModeExt( KListView::Single ); + d->listview->setAllColumnsShowFocus( true ); + + connect( d->listview, SIGNAL( pressed( QListViewItem * ) ), this, + SLOT( executed( QListViewItem * ) ) ); + connect( d->listview, SIGNAL( spacePressed( QListViewItem * ) ), this, + SLOT( executed( QListViewItem * ) ) ); + connect( d->listview, SIGNAL( returnPressed( QListViewItem * ) ), this, + SLOT( executed( QListViewItem * ) ) ); + connect( d->listview, SIGNAL( selectionChanged( QListViewItem * ) ), this, + SLOT( executed( QListViewItem * ) ) ); +} + +ComponentsDialog::~ComponentsDialog() +{ +} + +void ComponentsDialog::addPluginInfo( KPluginInfo * info ) +{ + d->plugininfolist.append( info ); +} + +void ComponentsDialog::setPluginInfos( const QMap<QString, KPluginInfo*> & + plugininfos ) +{ + for( QMap<QString, KPluginInfo*>::ConstIterator it = plugininfos.begin(); + it != plugininfos.end(); ++it ) + { + d->plugininfolist.append( it.data() ); + } +} + +void ComponentsDialog::setPluginInfos( const QValueList<KPluginInfo *> &plugins ) +{ + d->plugininfolist = plugins; +} + +void ComponentsDialog::show() +{ + // clear the treelist + d->listview->clear(); + d->plugininfomap.clear(); + + // construct the treelist + for( QValueList<KPluginInfo*>::ConstIterator it = d->plugininfolist.begin(); + it != d->plugininfolist.end(); ++it ) + { + ( *it )->load(); + QCheckListItem * item = new QCheckListItem( d->listview, ( *it )->name(), + QCheckListItem::CheckBox ); + if( ! ( *it )->icon().isEmpty() ) + item->setPixmap( 0, SmallIcon( ( *it )->icon(), IconSize( KIcon::Small ) ) ); + item->setOn( ( *it )->isPluginEnabled() ); + d->plugininfomap[ item ] = ( *it ); + } + KDialogBase::show(); +} + +void ComponentsDialog::executed( QListViewItem * item ) +{ + kdDebug( 704 ) << k_funcinfo << endl; + if( item == 0 ) + return; + if( item->rtti() != 1 ) // check for QCheckListItem + return; + + QCheckListItem * citem = static_cast<QCheckListItem *>( item ); + bool checked = citem->isOn(); + + kdDebug( 704 ) << "it's a " << ( checked ? "checked" : "unchecked" ) + << " QCheckListItem" << endl; + + KPluginInfo * info = d->plugininfomap[ citem ]; + info->setPluginEnabled( checked ); + //checkDependencies( info ); + // show info about the component on the right + d->iconwidget->setPixmap( SmallIcon( info->icon(), KIcon::SizeLarge ) ); + d->commentwidget->setText( info->comment() ); + //d->descriptionwidget->setText( info->description() ); +} + +void ComponentsDialog::savePluginInfos() +{ + for( QValueList<KPluginInfo*>::ConstIterator it = d->plugininfolist.begin(); + it != d->plugininfolist.end(); ++it ) + { + if( ( *it )->config() ) + { + ( *it )->save(); + ( *it )->config()->sync(); + } + } +} + +void ComponentsDialog::slotOk() +{ + savePluginInfos(); + KDialogBase::slotOk(); +} + +void ComponentsDialog::slotApply() +{ + savePluginInfos(); + KDialogBase::slotApply(); +} + +} //namespace + +#include "componentsdialog.moc" +// vim: sw=4 sts=4 et diff --git a/kutils/ksettings/componentsdialog.h b/kutils/ksettings/componentsdialog.h new file mode 100644 index 000000000..e5325f707 --- /dev/null +++ b/kutils/ksettings/componentsdialog.h @@ -0,0 +1,88 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Matthias Kretz <kretz@kde.org> + + 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. + +*/ + +#ifndef KSETTINGS_COMPONENTSDIALOG_H +#define KSETTINGS_COMPONENTSDIALOG_H + +#include <kdialogbase.h> + +class QString; +class KPluginInfo; + +namespace KSettings +{ + +/** + @ingroup plugin + @ingroup settings + Dialog for selecting which plugins should be active for an application. Set + the list of available plugins with \ref setPluginInfos. The dialog will save the + configuration on clicking ok or apply to the applications config file. Connect + to the okClicked() and applyClicked() signals to be notified about + configuration changes. +*/ +class KUTILS_EXPORT ComponentsDialog : public KDialogBase +{ + Q_OBJECT + public: + /** + Create Dialog. + + @param parent parent widget + @param name name + */ + ComponentsDialog( QWidget * parent = 0, const char * name = 0 ); + ~ComponentsDialog(); + + /** + Add a plugin that the dialog offers for selection. + */ + void addPluginInfo( KPluginInfo * ); + /** + Set list of plugins the dialog offers for selection. (Overwrites a previous list) + */ + void setPluginInfos( const QMap<QString, KPluginInfo*> & plugininfos ); + /** + Set list of plugins the dialog offers for selection. (Overwrites a previous list) + */ + void setPluginInfos( const QValueList<KPluginInfo *> &plugins ); + + /** + * reimplemented + */ + void show(); + + protected slots: + void slotOk(); + void slotApply(); + + private slots: + void executed( QListViewItem * ); + + private: + void savePluginInfos(); + + class ComponentsDialogPrivate; + ComponentsDialogPrivate * d; +}; + +} + +// vim: sw=4 sts=4 et +#endif // KSETTINGS_COMPONENTSDIALOG_H diff --git a/kutils/ksettings/dialog.cpp b/kutils/ksettings/dialog.cpp new file mode 100644 index 000000000..c98c68872 --- /dev/null +++ b/kutils/ksettings/dialog.cpp @@ -0,0 +1,642 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Matthias Kretz <kretz@kde.org> + + 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 "ksettings/dialog.h" + + +#include <kcmultidialog.h> +#include <klocale.h> +#include <kservicegroup.h> +#include <kdebug.h> +#include <ktrader.h> +#include <kplugininfo.h> +#include "ksettings/dispatcher.h" +#include "ksettings/componentsdialog.h" +#include <ksimpleconfig.h> +#include <kstandarddirs.h> +#include <kiconloader.h> +#include <qvbox.h> +#include <qlabel.h> +#include "kcmoduleinfo.h" + +namespace KSettings +{ + +struct GroupInfo +{ + QString id; + QString name; + QString comment; + QString icon; + int weight; + QString parentid; + QWidget * page; +}; + +// The TreeList can get really complicated. That's why a tree data structure +// is necessary to make it suck less +class PageNode +{ + private: + typedef QValueList<PageNode*> List; + enum Type { KCM, Group, Root }; + union Value + { + KCModuleInfo * kcm; + GroupInfo * group; + }; + Type m_type; + Value m_value; + + Dialog * m_dialog; + List m_children; + PageNode * m_parent; + bool m_visible; + bool m_dirty; + + protected: + PageNode( KCModuleInfo * info, PageNode * parent ) + : m_type( KCM ) + , m_parent( parent ) + , m_visible( true ) + , m_dirty( true ) + { + m_value.kcm = info; + m_dialog = parent->m_dialog; + } + + PageNode( GroupInfo & group, PageNode * parent ) + : m_type( Group ) + , m_parent( parent ) + , m_visible( true ) + , m_dirty( true ) + { + m_value.group = new GroupInfo( group ); + m_value.group->page = 0; + m_dialog = parent->m_dialog; + } + + void bubbleSort( List::Iterator begin, List::Iterator end ) + { + --end; + bool finished; + List::Iterator lastswapped = begin; + List::Iterator i; + List::Iterator j; + while( begin != end ) + { + finished = true; + i = j = end; + do { + --j; + if( **i < **j ) + { + finished = false; + qSwap( *i, *j ); + lastswapped = j; + } + --i; + } while( j != begin ); + if( finished ) + return; + ++lastswapped; + begin = lastswapped; + } + } + + public: + PageNode( Dialog * dialog ) + : m_type( Root ) + , m_dialog( dialog ) + , m_parent( 0 ) + , m_visible( true ) + , m_dirty( true ) + {} + + ~PageNode() + { + if( KCM == m_type ) + delete m_value.kcm; + else if( Group == m_type ) + delete m_value.group; + List::Iterator end = m_children.end(); + for( List::Iterator it = m_children.begin(); it != end; ++it ) + delete ( *it ); + } + + int weight() const + { + int w = ( KCM == m_type ) ? m_value.kcm->weight() + : m_value.group->weight; + kdDebug( 700 ) << k_funcinfo << name() << " " << w << endl; + return w; + } + + bool operator<( const PageNode & rhs ) const + { + return weight() < rhs.weight(); + } + + bool isVisible() + { + if( m_dirty ) + { + if( KCM == m_type ) + m_visible = m_dialog->isPluginForKCMEnabled( m_value.kcm ); + else + { + m_visible = false; + List::Iterator end = m_children.end(); + for( List::Iterator it = m_children.begin(); it != end; + ++it ) + if( ( *it )->isVisible() ) + { + m_visible = true; + break; + } + } + m_dirty = false; + } + kdDebug( 700 ) << k_funcinfo << "returns " << m_visible << endl; + return m_visible; + } + + void makeDirty() + { + m_dirty = true; + List::Iterator end = m_children.end(); + for( List::Iterator it = m_children.begin(); it != end; ++it ) + ( *it )->makeDirty(); + } + + QString name() const + { + if( Root == m_type ) + return QString::fromAscii( "root node" ); + return ( KCM == m_type ) ? m_value.kcm->moduleName() + : m_value.group->name; + } + + QStringList parentNames() const + { + QStringList ret; + PageNode * node = m_parent; + while( node && node->m_type != Root ) + { + ret.prepend( node->name() ); + node = node->m_parent; + } + return ret; + } + + void addToDialog( KCMultiDialog * dlg ) + { + kdDebug( 700 ) << k_funcinfo << "for " << name() << endl; + if( ! isVisible() ) + return; + + if( KCM == m_type ) + { + dlg->addModule( *m_value.kcm, parentNames() ); + return; + } + if( Group == m_type && 0 == m_value.group->page ) + { + QPixmap icon; + if( ! m_value.group->icon.isNull() ) + icon = SmallIcon( m_value.group->icon, + IconSize( KIcon::Small ) ); + QVBox * page = dlg->addVBoxPage( m_value.group->name, + QString::null, icon ); + QLabel * comment = new QLabel( m_value.group->comment, page ); + comment->setTextFormat( Qt::RichText ); + m_value.group->page = page; + } + List::Iterator end = m_children.end(); + for( List::Iterator it = m_children.begin(); it != end; ++it ) + ( *it )->addToDialog( dlg ); + } + + void removeFromDialog( KCMultiDialog * dlg ) + { + kdDebug( 700 ) << k_funcinfo << "for " << name() << endl; + if( KCM == m_type ) + return; + if( Root == m_type ) + dlg->removeAllModules(); + List::Iterator end = m_children.end(); + for( List::Iterator it = m_children.begin(); it != end; ++it ) + ( *it )->removeFromDialog( dlg ); + if( Group == m_type ) + { + delete m_value.group->page; + m_value.group->page = 0; + } + } + + void sort() + { + kdDebug( 700 ) << k_funcinfo << name() << endl; + List::Iterator begin = m_children.begin(); + List::Iterator end = m_children.end(); + bubbleSort( begin, end ); + for( List::Iterator it = begin ; it != end; ++it ) + ( *it )->sort(); + } + + bool insert( GroupInfo & group ) + { + if( group.parentid.isNull() ) + { + if( Root == m_type ) + { + m_children.append( new PageNode( group, this ) ); + return true; + } + else + kdFatal( 700 ) << "wrong PageNode insertion" + << kdBacktrace() << endl; + } + if( Group == m_type && group.parentid == m_value.group->id ) + { + m_children.append( new PageNode( group, this ) ); + return true; + } + List::Iterator end = m_children.end(); + for( List::Iterator it = m_children.begin(); it != end; ++it ) + if( ( *it )->insert( group ) ) + return true; + // no parent with the right parentid + if( Root == m_type ) + { + m_children.append( new PageNode( group, this ) ); + return true; + } + return false; + } + + bool insert( KCModuleInfo * info, const QString & parentid ) + { + if( parentid.isNull() ) + { + if( Root == m_type ) + { + m_children.append( new PageNode( info, this ) ); + return true; + } + else + kdFatal( 700 ) << "wrong PageNode insertion" + << kdBacktrace() << endl; + } + if( Group == m_type && parentid == m_value.group->id ) + { + m_children.append( new PageNode( info, this ) ); + return true; + } + List::Iterator end = m_children.end(); + for( List::Iterator it = m_children.begin(); it != end; ++it ) + if( ( *it )->insert( info, parentid ) ) + return true; + // no parent with the right parentid + if( Root == m_type ) + { + m_children.append( new PageNode( info, this ) ); + return true; + } + return false; + } + + bool needTree() + { + List::ConstIterator end = m_children.end(); + for( List::ConstIterator it = m_children.begin(); it != end; ++it ) + if( ( *it )->m_children.count() > 0 ) + return true; + return false; + } + + bool singleChild() + { + return ( m_children.count() == 1 ); + } +}; + +class Dialog::DialogPrivate +{ + public: + DialogPrivate( Dialog * parent ) + : dlg( 0 ) + , pagetree( parent ) + { + } + + bool staticlistview; + KCMultiDialog * dlg; + PageNode pagetree; + QWidget * parentwidget; + QStringList registeredComponents; + QValueList<KService::Ptr> services; + QMap<QString, KPluginInfo*> plugininfomap; +}; + +Dialog::Dialog( QWidget * parent, const char * name ) + : QObject( parent, name ) + , d( new DialogPrivate( this ) ) +{ + d->parentwidget = parent; + d->staticlistview = true; + d->services = instanceServices(); +} + +Dialog::Dialog( ContentInListView content, + QWidget * parent, const char * name ) + : QObject( parent, name ) + , d( new DialogPrivate( this ) ) +{ + d->parentwidget = parent; + d->staticlistview = ( content == Static ); + d->services = instanceServices(); +} + +Dialog::Dialog( const QStringList & components, + QWidget * parent, const char * name ) + : QObject( parent, name ) + , d( new DialogPrivate( this ) ) +{ + d->parentwidget = parent; + d->staticlistview = true; + d->services = instanceServices() + parentComponentsServices( components ); +} + +Dialog::Dialog( const QStringList & components, + ContentInListView content, QWidget * parent, const char * name ) + : QObject( parent, name ) + , d( new DialogPrivate( this ) ) +{ + d->parentwidget = parent; + d->staticlistview = ( content == Static ); + d->services = instanceServices() + parentComponentsServices( components ); +} + +Dialog::~Dialog() +{ + delete d; +} + +void Dialog::addPluginInfos( const QValueList<KPluginInfo*> & plugininfos ) +{ + for( QValueList<KPluginInfo*>::ConstIterator it = plugininfos.begin(); + it != plugininfos.end(); ++it ) + { + d->registeredComponents.append( ( *it )->pluginName() ); + d->services += ( *it )->kcmServices(); + d->plugininfomap[ ( *it )->pluginName() ] = *it; + } +} + +void Dialog::show() +{ + if( 0 == d->dlg ) + createDialogFromServices(); + Dispatcher::self()->syncConfiguration(); + return d->dlg->show(); +} + +KCMultiDialog * Dialog::dialog() +{ + if( 0 == d->dlg ) + createDialogFromServices(); + return d->dlg; +} + +QValueList<KService::Ptr> Dialog::instanceServices() const +{ + kdDebug( 700 ) << k_funcinfo << endl; + QString instanceName = KGlobal::instance()->instanceName(); + d->registeredComponents.append( instanceName ); + kdDebug( 700 ) << "calling KServiceGroup::childGroup( " << instanceName + << " )" << endl; + KServiceGroup::Ptr service = KServiceGroup::childGroup( instanceName ); + + QValueList<KService::Ptr> ret; + + if( service && service->isValid() ) + { + kdDebug( 700 ) << "call was successfull" << endl; + KServiceGroup::List list = service->entries(); + for( KServiceGroup::List::ConstIterator it = list.begin(); + it != list.end(); ++it ) + { + KSycocaEntry * p = *it; + if( p->isType( KST_KService ) ) + { + kdDebug( 700 ) << "found service" << endl; + ret << static_cast<KService *>( p ); + } + else + kdWarning( 700 ) << "KServiceGroup::childGroup returned" + " something else than a KService (kinda)" << endl; + } + } + + return ret; +} + +QValueList<KService::Ptr> Dialog::parentComponentsServices( + const QStringList & kcdparents ) const +{ + d->registeredComponents += kcdparents; + QString constraint = kcdparents.join( + "' in [X-KDE-ParentComponents]) or ('" ); + constraint = "('" + constraint + "' in [X-KDE-ParentComponents])"; + + kdDebug( 700 ) << "constraint = " << constraint << endl; + return KTrader::self()->query( "KCModule", constraint ); +} + +bool Dialog::isPluginForKCMEnabled( KCModuleInfo * moduleinfo ) const +{ + // if the user of this class requested to hide disabled modules + // we check whether it should be enabled or not + bool enabled = true; + kdDebug( 700 ) << "check whether the " << moduleinfo->moduleName() + << " KCM should be shown" << endl; + // for all parent components + QStringList parentComponents = moduleinfo->service()->property( + "X-KDE-ParentComponents" ).toStringList(); + for( QStringList::ConstIterator pcit = parentComponents.begin(); + pcit != parentComponents.end(); ++pcit ) + { + // if the parentComponent is not registered ignore it + if( d->registeredComponents.find( *pcit ) == + d->registeredComponents.end() ) + continue; + + // we check if the parent component is a plugin + if( ! d->plugininfomap.contains( *pcit ) ) + { + // if not the KCModule must be enabled + enabled = true; + // we're done for this KCModuleInfo + break; + } + // if it is a plugin we check whether the plugin is enabled + KPluginInfo * pinfo = d->plugininfomap[ *pcit ]; + pinfo->load(); + enabled = pinfo->isPluginEnabled(); + kdDebug( 700 ) << "parent " << *pcit << " is " + << ( enabled ? "enabled" : "disabled" ) << endl; + // if it is enabled we're done for this KCModuleInfo + if( enabled ) + break; + } + return enabled; +} + +void Dialog::parseGroupFile( const QString & filename ) +{ + KSimpleConfig file( filename ); + QStringList groups = file.groupList(); + for( QStringList::ConstIterator it = groups.begin(); it != groups.end(); + ++it ) + { + GroupInfo group; + QString id = *it; + file.setGroup( id.utf8() ); + group.id = id; + group.name = file.readEntry( "Name" ); + group.comment = file.readEntry( "Comment" ); + group.weight = file.readNumEntry( "Weight", 100 ); + group.parentid = file.readEntry( "Parent" ); + group.icon = file.readEntry( "Icon" ); + d->pagetree.insert( group ); + } +} + +void Dialog::createDialogFromServices() +{ + // read .setdlg files + QString setdlgpath = locate( "appdata", + KGlobal::instance()->instanceName() + ".setdlg" ); + QStringList setdlgaddon = KGlobal::dirs()->findAllResources( "appdata", + "ksettingsdialog/*.setdlg" ); + if( ! setdlgpath.isNull() ) + parseGroupFile( setdlgpath ); + if( setdlgaddon.size() > 0 ) + for( QStringList::ConstIterator it = setdlgaddon.begin(); + it != setdlgaddon.end(); ++it ) + parseGroupFile( *it ); + + // now we process the KCModule services + for( QValueList<KService::Ptr>::ConstIterator it = d->services.begin(); + it != d->services.end(); ++it ) + { + // we create the KCModuleInfo + KCModuleInfo * info = new KCModuleInfo( *it ); + QString parentid; + QVariant tmp = info->service()->property( "X-KDE-CfgDlgHierarchy", + QVariant::String ); + if( tmp.isValid() ) + parentid = tmp.toString(); + d->pagetree.insert( info, parentid ); + } + + // At this point d->pagetree holds a nice structure of the pages we want + // to show. It's not going to change anymore so we can sort it now. + d->pagetree.sort(); + + int dialogface = KJanusWidget::IconList; + if( d->pagetree.needTree() ) + dialogface = KJanusWidget::TreeList; + else if( d->pagetree.singleChild() ) + dialogface = KJanusWidget::Plain; + + kdDebug( 700 ) << "creating KCMultiDialog" << endl; + d->dlg = new KCMultiDialog( dialogface, i18n( "Configure" ), + d->parentwidget ); + + if( dialogface == KJanusWidget::TreeList ) + d->dlg->setShowIconsInTreeList( true ); + + // TODO: Don't show the reset button until the issue with the + // KPluginSelector::load() method is solved. + // Problem: + // KCMultiDialog::show() call KCModule::load() to reset all KCMs + // (KPluginSelector::load() resets all plugin selections and all plugin + // KCMs). + // The reset button calls KCModule::load(), too but in this case we want the + // KPluginSelector to only reset the current visible plugin KCM and not + // touch the plugin selections. + // I have no idea how to check that in KPluginSelector::load()... + //d->dlg->showButton( KDialogBase::User1, true ); + + if( ! d->staticlistview ) + d->dlg->addButtonBelowList( i18n( "Select Components..." ), this, + SLOT( configureTree() ) ); + + connect( d->dlg, SIGNAL( okClicked() ), Dispatcher::self(), + SLOT( syncConfiguration() ) ); + connect( d->dlg, SIGNAL( applyClicked() ), Dispatcher::self(), + SLOT( syncConfiguration() ) ); + connect( d->dlg, SIGNAL( configCommitted( const QCString & ) ), + Dispatcher::self(), SLOT( reparseConfiguration( const QCString & ) ) ); + + d->pagetree.addToDialog( d->dlg ); + + if( dialogface == KJanusWidget::TreeList ) + d->dlg->unfoldTreeList(); +} + +void Dialog::configureTree() +{ + kdDebug( 700 ) << k_funcinfo << endl; + ComponentsDialog * subdlg = new ComponentsDialog( d->dlg ); + subdlg->setPluginInfos( d->plugininfomap ); + subdlg->show(); + connect( subdlg, SIGNAL( okClicked() ), this, SLOT( updateTreeList() ) ); + connect( subdlg, SIGNAL( applyClicked() ), this, SLOT( updateTreeList() ) ); + connect( subdlg, SIGNAL( okClicked() ), this, + SIGNAL( pluginSelectionChanged() ) ); + connect( subdlg, SIGNAL( applyClicked() ), this, + SIGNAL( pluginSelectionChanged() ) ); + connect( subdlg, SIGNAL( finished() ), subdlg, SLOT( delayedDestruct() ) ); +} + +void Dialog::updateTreeList() +{ + kdDebug( 700 ) << k_funcinfo << endl; + + d->pagetree.makeDirty(); + + // remove all pages from the dialog and then add them again. This is needed + // because KDialogBase/KJanusWidget can only append to the end of the list + // and we need to have a predefined order. + + d->pagetree.removeFromDialog( d->dlg ); + d->pagetree.addToDialog( d->dlg ); + + if( d->pagetree.needTree() ) + d->dlg->unfoldTreeList( true ); +} + +} //namespace + +#include "dialog.moc" + +// vim: sw=4 ts=4 noet diff --git a/kutils/ksettings/dialog.h b/kutils/ksettings/dialog.h new file mode 100644 index 000000000..84f4619be --- /dev/null +++ b/kutils/ksettings/dialog.h @@ -0,0 +1,224 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Matthias Kretz <kretz@kde.org> + + 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. + +*/ + +#ifndef KSETTINGS_DIALOG_H +#define KSETTINGS_DIALOG_H + +#include <qobject.h> +#include <kservice.h> + +template<class T> class QValueList; +class KPluginInfo; +class KCMultiDialog; +class KCModuleInfo; + +namespace KSettings +{ + +/** + * @ingroup main + * @ingroup settings + * @short Generic configuration dialog that even works over component boundaries + * + * For more information see \ref KSettings. + * + * This class aims to standardize the use of configuration dialogs in KDE + * applications. Especially when using KParts and/or Plugins you face problems + * creating a consistent config dialog. + * + * To show a configuration dialog you only have to call the show method and be + * done with it. A code example: + * + * You initialize \p m_cfgdlg with + * \code + * m_cfgdlg = new Dialog( Dialog::Static, this ); + * \endcode + * If you use a KPart that was not especially designed for your app you can use + * the second constructor: + * \code + * QStringList kpartslist; + * for( all my kparts ) + * kpartslist += m_mypart->instance().instanceName(); + * m_cfgdlg = new Dialog( kpartslist, this ); + * \endcode + * and the action for the config dialog is connected to the show slot: + * \code + * KStdAction::preferences( m_cfgdlg, SLOT( show() ), actionCollection() ); + * \endcode + * + * If you need to be informed when the config was changed and applied in the + * dialog you might want to take a look at Dispatcher. + * + * For more information see \ref KSettings. + * + * @author Matthias Kretz <kretz@kde.org> + * @since 3.2 + */ +class KUTILS_EXPORT Dialog : public QObject +{ + friend class PageNode; + Q_OBJECT + public: + /** + * Tells the dialog whether the entries in the listview are all static + * or whether it should add a Configure... button to select which parts + * of the optional functionality should be active or not. + */ + enum ContentInListView + { + /** + * Static listview, while running no entries are added or deleted + */ + Static, + /** + * Configurable listview. The user can select what functionality he + * wants. + */ + Configurable + }; + + /** + * Construct a new Preferences Dialog for the application. It uses all + * KCMs with X-KDE-ParentApp set to KGlobal::instance()->instanceName(). + * + * @param parent The parent is only used as the parent for the + * dialog - centering the dialog over the parent + * widget. + * @param name name + */ + Dialog( QWidget * parent = 0, const char * name = 0 ); + + /** + * Construct a new Preferences Dialog for the application. It uses all + * KCMs with X-KDE-ParentApp set to KGlobal::instance()->instanceName(). + * + * @param content Select whether you want a static or configurable + * config dialog. + * @param parent The parent is only used as the parent for the + * dialog - centering the dialog over the parent + * widget. + * @param name name + */ + Dialog( ContentInListView content = Static, QWidget * parent = 0, + const char * name = 0 ); + + /** + * Construct a new Preferences Dialog with the pages for the selected + * instance names. For example if you want to have the configuration + * pages for the kviewviewer KPart you would pass a + * QStringList consisting of only the name of the part "kviewviewer". + * + * @param components A list of the names of the components that your + * config dialog should merge the config pages in. + * @param parent The parent is only used as the parent for the + * dialog - centering the dialog over the parent + * widget. + * @param name name + */ + Dialog( const QStringList & components, QWidget * parent = 0, + const char * name = 0 ); + + /** + * Construct a new Preferences Dialog with the pages for the selected + * instance names. For example if you want to have the configuration + * pages for the kviewviewer KPart you would pass a + * QStringList consisting of only the name of the part "kviewviewer". + * + * @param components A list of the names of the components that your + * config dialog should merge the config pages in. + * @param content Select whether you want a static or configurable + * config dialog. + * @param parent The parent is only used as the parent for the + * dialog - centering the dialog over the parent + * widget. + * @param name name + */ + Dialog( const QStringList & components, ContentInListView + content, QWidget * parent = 0, const char * name = 0 ); + + ~Dialog(); + + /** + * If you use a Configurable dialog you need to pass KPluginInfo + * objects that the dialog should configure. + */ + void addPluginInfos( const QValueList<KPluginInfo*> & plugininfos ); + + KCMultiDialog * dialog(); + + public slots: + /** + * Show the config dialog. The slot immediatly returns since the dialog + * is non-modal. + */ + void show(); + + signals: + /** + * If you use the dialog in Configurable mode and want to be notified + * when the user changes the plugin selections use this signal. It's + * emitted if the selection has changed and the user pressed Apply or + * Ok. In the slot you would then load and unload the plugins as + * requested. + */ + void pluginSelectionChanged(); + + protected slots: + void configureTree(); + void updateTreeList(); + + private: + /** + * @internal + * Check whether the plugin associated with this KCM is enabled. + */ + bool isPluginForKCMEnabled( KCModuleInfo * ) const; + + QValueList<KService::Ptr> instanceServices() const; + QValueList<KService::Ptr> parentComponentsServices( + const QStringList & ) const; + /** + * @internal + * Read the .setdlg file and add it to the groupmap + */ + void parseGroupFile( const QString & ); + + /** + * @internal + * If this module is put into a TreeList hierarchy this will return a + * list of the names of the parent modules. + */ + QStringList parentModuleNames( KCModuleInfo * ); + + /** + * @internal + * This method is called only once. The KCMultiDialog is not created + * until it's really needed. So if some method needs to access d->dlg it + * checks for 0 and if it's not created this method will do it. + */ + void createDialogFromServices(); + + class DialogPrivate; + DialogPrivate * d; +}; + +} + +// vim: sw=4 sts=4 et +#endif // KSETTINGS_DIALOG_H diff --git a/kutils/ksettings/dispatcher.cpp b/kutils/ksettings/dispatcher.cpp new file mode 100644 index 000000000..4e54538d8 --- /dev/null +++ b/kutils/ksettings/dispatcher.cpp @@ -0,0 +1,157 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Matthias Kretz <kretz@kde.org> + + 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 "ksettings/dispatcher.h" + +#include <qsignal.h> + +#include <kstaticdeleter.h> +#include <kdebug.h> +#include <kconfig.h> +#include <assert.h> + +namespace KSettings +{ + +//class Dispatcher::DispatcherPrivate +//{ +//}; + +static KStaticDeleter<Dispatcher> ksd_kpd; + +Dispatcher * Dispatcher::m_self = 0; + +Dispatcher * Dispatcher::self() +{ + kdDebug( 701 ) << k_funcinfo << endl; + if( m_self == 0 ) + ksd_kpd.setObject( m_self, new Dispatcher() ); + return m_self; +} + +Dispatcher::Dispatcher( QObject * parent, const char * name ) + : QObject( parent, name ) + //, d( 0 ) +{ + kdDebug( 701 ) << k_funcinfo << endl; +} + +Dispatcher::~Dispatcher() +{ + kdDebug( 701 ) << k_funcinfo << endl; + //delete d; +} + +void Dispatcher::registerInstance( KInstance * instance, QObject * recv, const char * slot ) +{ + assert( instance != 0 ); + // keep the KInstance around and call + // instance->config()->reparseConfiguration when the app should reparse + QCString instanceName = instance->instanceName(); + kdDebug( 701 ) << k_funcinfo << instanceName << endl; + m_instanceName[ recv ] = instanceName; + QSignal * sig; + if( m_instanceInfo.contains( instanceName ) ) + { + sig = m_instanceInfo[ instanceName ].signal; + } + else + { + sig = new QSignal( this, "signal dispatcher" ); + m_instanceInfo[ instanceName ].signal = sig; + m_instanceInfo[ instanceName ].instance = instance; + } + sig->connect( recv, slot ); + + ++m_instanceInfo[ instanceName ].count; + connect( recv, SIGNAL( destroyed( QObject * ) ), this, SLOT( unregisterInstance( QObject * ) ) ); +} + +KConfig * Dispatcher::configForInstanceName( const QCString & instanceName ) +{ + kdDebug( 701 ) << k_funcinfo << endl; + if( m_instanceInfo.contains( instanceName ) ) + { + KInstance * inst = m_instanceInfo[ instanceName ].instance; + if( inst ) + return inst->config(); + } + //if( fallback ) + //return new KSimpleConfig( instanceName ); + return 0; +} + +QStrList Dispatcher::instanceNames() const +{ + kdDebug( 701 ) << k_funcinfo << endl; + QStrList names; + for( QMap<QCString, InstanceInfo>::ConstIterator it = m_instanceInfo.begin(); it != m_instanceInfo.end(); ++it ) + if( ( *it ).count > 0 ) + names.append( it.key() ); + return names; +} + +void Dispatcher::reparseConfiguration( const QCString & instanceName ) +{ + kdDebug( 701 ) << k_funcinfo << instanceName << endl; + // check if the instanceName is valid: + if( ! m_instanceInfo.contains( instanceName ) ) + return; + // first we reparse the config of the instance so that the KConfig object + // will be up to date + m_instanceInfo[ instanceName ].instance->config()->reparseConfiguration(); + QSignal * sig = m_instanceInfo[ instanceName ].signal; + if( sig ) + { + kdDebug( 701 ) << "emit signal to instance" << endl; + sig->activate(); + } +} + +void Dispatcher::syncConfiguration() +{ + for( QMap<QCString, InstanceInfo>::ConstIterator it = m_instanceInfo.begin(); it != m_instanceInfo.end(); ++it ) + { + ( *it ).instance->config()->sync(); + } +} + +void Dispatcher::unregisterInstance( QObject * obj ) +{ + kdDebug( 701 ) << k_funcinfo << endl; + QCString name = m_instanceName[ obj ]; + m_instanceName.remove( obj ); //obj will be destroyed when we return, so we better remove this entry + --m_instanceInfo[ name ].count; + if( m_instanceInfo[ name ].count == 0 ) + { + delete m_instanceInfo[ name ].signal; + m_instanceInfo.remove( name ); + } +} + +//X KInstance * Dispatcher::instanceForName( const QCString & instanceName ) +//X { +//X return m_instanceInfo[ instanceName ].instance; +//X } + +} //namespace + +#include "dispatcher.moc" + +// vim: sw=4 sts=4 et diff --git a/kutils/ksettings/dispatcher.h b/kutils/ksettings/dispatcher.h new file mode 100644 index 000000000..5f6270bb5 --- /dev/null +++ b/kutils/ksettings/dispatcher.h @@ -0,0 +1,133 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Matthias Kretz <kretz@kde.org> + + 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. + +*/ + +#ifndef KSETTINGS_DISPATCHER_H +#define KSETTINGS_DISPATCHER_H + +#include <qobject.h> +#include <qmap.h> +#include <kdelibs_export.h> + +class QCString; +class QSignal; +class QStrList; +template<class T> class KStaticDeleter; +class KInstance; +class KConfig; + +namespace KSettings +{ + +/** + * @ingroup settings + * @short Dispatch change notifications from the KCMs to the program. + * + * Since your program does not have direct control over the KCMs that get loaded + * into the KConfigureDialog you need a way to get notified. This is what you + * do: + * \code + * Dispatcher::self()->registerInstance( instance(), this, SLOT( loadSettings() ) ); + * \endcode + * + * @author Matthias Kretz <kretz@kde.org> + * @since 3.2 + */ +class KUTILS_EXPORT Dispatcher : public QObject +{ + friend class KStaticDeleter<Dispatcher>; + + Q_OBJECT + public: + /** + * Get a reference the the Dispatcher object. + */ + static Dispatcher * self(); + + /** + * Register a slot to be called when the configuration for the instance + * has changed. @p instance is the KInstance object + * that is passed to KGenericFactory (if it is used). You can query + * it with KGenericFactory<YourClassName>::instance(). + * instance->instanceName() is also the same name that is put into the + * .desktop file of the KCMs for the X-KDE-ParentComponents. + * + * @param instance The KInstance object + * @param recv The object that should receive the signal + * @param slot The slot to be called: SLOT( slotName() ) + */ + void registerInstance( KInstance * instance, QObject * recv, const char * slot ); + + /** + * @return the KConfig object that belongs to the instanceName + */ + KConfig * configForInstanceName( const QCString & instanceName ); + + /** + * @return a list of all the instance names that are currently + * registered + */ + QStrList instanceNames() const; + +//X /** +//X * @return The KInstance object belonging to the instance name you pass +//X * (only works for registered instances of course). +//X */ +//X KInstance * instanceForName( const QCString & instanceName ); + + public slots: + /** + * Call this slot when the configuration belonging to the associated + * instance name has changed. The registered slot will be called. + * + * @param instanceName The value of X-KDE-ParentComponents. + */ + void reparseConfiguration( const QCString & instanceName ); + + /** + * When this slot is called the KConfig objects of all the registered + * instances are sync()ed. This is usefull when some other KConfig + * objects will read/write from/to the same config file, so that you + * can first write out the current state of the KConfig objects. + */ + void syncConfiguration(); + + private slots: + void unregisterInstance( QObject * ); + + private: + Dispatcher( QObject * parent = 0, const char * name = 0 ); + ~Dispatcher(); + static Dispatcher * m_self; + + struct InstanceInfo { + KInstance * instance; + QSignal * signal; + int count; + }; + QMap<QCString, InstanceInfo> m_instanceInfo; + QMap<QObject *, QCString> m_instanceName; + + class DispatcherPrivate; + DispatcherPrivate * d; +}; + +} + +// vim: sw=4 sts=4 et +#endif // KSETTINGS_DISPATCHER_H diff --git a/kutils/ksettings/pluginpage.cpp b/kutils/ksettings/pluginpage.cpp new file mode 100644 index 000000000..2456ebf86 --- /dev/null +++ b/kutils/ksettings/pluginpage.cpp @@ -0,0 +1,88 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Matthias Kretz <kretz@kde.org> + + 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 "ksettings/pluginpage.h" +#include "kpluginselector.h" +#include <qlayout.h> +#include <kdialog.h> +#include "ksettings/dispatcher.h" + +namespace KSettings +{ + +class PluginPage::PluginPagePrivate +{ + public: + PluginPagePrivate() + : selwid( 0 ) + { + } + + KPluginSelector * selwid; +}; + + PluginPage::PluginPage( QWidget * parent, const char * name, const QStringList & args ) + : KCModule( parent, name, args ) + , d( new PluginPagePrivate ) +{ + ( new QVBoxLayout( this, 0, KDialog::spacingHint() ) )->setAutoAdd( true ); + d->selwid = new KPluginSelector( this ); + connect( d->selwid, SIGNAL( changed( bool ) ), this, SIGNAL( changed( bool ) ) ); +} + + PluginPage::PluginPage( KInstance * instance, QWidget * parent, const QStringList & args ) + : KCModule( instance, parent, args ) + , d( new PluginPagePrivate ) +{ + ( new QVBoxLayout( this, 0, KDialog::spacingHint() ) )->setAutoAdd( true ); + d->selwid = new KPluginSelector( this ); + connect( d->selwid, SIGNAL( changed( bool ) ), this, SIGNAL( changed( bool ) ) ); + connect( d->selwid, SIGNAL( configCommitted( const QCString & ) ), + Dispatcher::self(), SLOT( reparseConfiguration( const QCString & ) ) ); +} + +PluginPage::~PluginPage() +{ + delete d; +} + +KPluginSelector * PluginPage::pluginSelector() +{ + return d->selwid; +} + +void PluginPage::load() +{ + d->selwid->load(); +} + +void PluginPage::save() +{ + d->selwid->save(); +} + +void PluginPage::defaults() +{ + d->selwid->defaults(); +} + +} //namespace + +#include "pluginpage.moc" +// vim: sw=4 sts=4 et diff --git a/kutils/ksettings/pluginpage.h b/kutils/ksettings/pluginpage.h new file mode 100644 index 000000000..fc6120f78 --- /dev/null +++ b/kutils/ksettings/pluginpage.h @@ -0,0 +1,120 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Matthias Kretz <kretz@kde.org> + + 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. + +*/ + +#ifndef KSETTINGS_PLUGINPAGE_H +#define KSETTINGS_PLUGINPAGE_H + +#include <kcmodule.h> +#include <kdelibs_export.h> + +class KPluginSelector; + +namespace KSettings +{ + +/** + * @ingroup settings + * @ingroup plugin + * @short Convenience KCModule for creating a plugins config page. + * + * This class makes it very easy to create a plugins configuration page to your + * program. All you need to do is create a class that is derived from + * PluginPage and add the appropriate plugin infos to the KPluginSelector. + * This is done using the pluginSelector() method: + * \code + * typedef KGenericFactory<MyAppPluginConfig, QWidget> MyAppPluginConfigFactory; + * K_EXPORT_COMPONENT_FACTORY( kcm_myapppluginconfig, MyAppPluginConfigFactory( "kcm_myapppluginconfig" ) ); + * + * MyAppPluginConfig( QWidget * parent, const char *, const QStringList & args ) + * : PluginPage( MyAppPluginConfigFactory::instance(), parent, args ) + * { + * pluginSelector()->addPlugins( KGlobal::instance()->instanceName(), i18n( "General Plugins" ), "General" ); + * pluginSelector()->addPlugins( KGlobal::instance()->instanceName(), i18n( "Effects" ), "Effects" ); + * } + * \endcode + * + * All that remains to be done is to create the appropriate .desktop file + * \verbatim + [Desktop Entry] + Encoding=UTF-8 + Icon=plugin + Type=Service + ServiceTypes=KCModule + + X-KDE-ModuleType=Library + X-KDE-Library=myapppluginconfig + X-KDE-FactoryName=MyAppPluginConfigFactory + X-KDE-ParentApp=myapp + X-KDE-ParentComponents=myapp + + Name=Plugins + Comment=Select and configure your plugins: + \endverbatim + * + * @author Matthias Kretz <kretz@kde.org> + * @since 3.2 + */ +class KUTILS_EXPORT PluginPage : public KCModule +{ + Q_OBJECT + public: + /** + * Standart KCModule constructor. Automatically creates the the + * KPluginSelector widget. + */ + PluginPage( QWidget * parent = 0, const char * name = 0, const QStringList & args = QStringList() ); + + /** + * Standart KCModule constructor. Automatically creates the the + * KPluginSelector widget. + */ + PluginPage( KInstance * instance, QWidget * parent = 0, const QStringList & args = QStringList() ); + + ~PluginPage(); + + /** + * @return a reference to the KPluginSelector. + */ + KPluginSelector * pluginSelector(); + + /** + * Load the state of the plugins (selected or not) from the KPluginInfo + * objects. For KParts plugins everything should work automatically. For + * your own type of plugins you might need to reimplement the + * KPluginInfo::pluginLoaded() method. If that doesn't fit your needs + * you can also reimplement this method. + */ + virtual void load(); + + /** + * Save the state of the plugins to KConfig objects + */ + virtual void save(); + virtual void defaults(); + + private: + class PluginPagePrivate; + PluginPagePrivate * d; +}; + +} + +// vim: sw=4 sts=4 et + +#endif // KSETTINGS_PLUGINPAGE_H diff --git a/kutils/tests/Makefile.am b/kutils/tests/Makefile.am new file mode 100644 index 000000000..3bde7a9a2 --- /dev/null +++ b/kutils/tests/Makefile.am @@ -0,0 +1,16 @@ +INCLUDES = $(all_includes) + +check_PROGRAMS = kfindtest kreplacetest + +TESTS = kfindtest kreplacetest + +kfindtest_SOURCES = kfindtest.cpp +kfindtest_LDADD = ../libkutils.la +kfindtest_LDFLAGS = $(all_libraries) + +kreplacetest_SOURCES = kreplacetest.cpp +kreplacetest_LDADD = ../libkutils.la +kreplacetest_LDFLAGS = $(all_libraries) + +METASOURCES = AUTO + diff --git a/kutils/tests/kfindtest.cpp b/kutils/tests/kfindtest.cpp new file mode 100644 index 000000000..17e8da7ee --- /dev/null +++ b/kutils/tests/kfindtest.cpp @@ -0,0 +1,252 @@ +/* + Copyright (C) 2004, Arend van Beelen jr. <arend@auton.nl> + This file is part of the KDE project + + 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 "../kfind.h" +#include "../kfinddialog.h" +#include "kfindtest.h" + +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <kdebug.h> + +#include <stdlib.h> +#include <assert.h> + +static bool check(QString txt, QString a, QString b) // from kurltest +{ + if (a.isEmpty()) + a = QString::null; + if (b.isEmpty()) + b = QString::null; + if (a == b) { + kdDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'... " << "ok" << endl; + } + else { + kdDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'... " << "KO !" << endl; + exit(1); + } + return true; +} + +void KFindTest::changeText(uint line, const QString &text) +{ + Q_ASSERT(line < m_text.count()); + Q_ASSERT(m_find != 0); + + m_line = line; + m_text[line] = text; + m_find->setData(line, text); +} + +void KFindTest::find(const QString &pattern, long options) +{ + delete m_find; + m_find = new KFind(pattern, options, 0); + + connect(m_find, SIGNAL(highlight(const QString &, int, int)), + SLOT(slotHighlight(const QString &, int, int))); + connect(m_find, SIGNAL(highlight(int, int, int)), + SLOT(slotHighlight(int, int, int))); + + m_line = 0; + KFind::Result result = KFind::NoMatch; + do + { + if(options & KFindDialog::FindIncremental) + m_find->setData(m_line, m_text[m_line]); + else + m_find->setData(m_text[m_line]); + + m_line++; + + result = m_find->find(); + } while(result == KFind::NoMatch && m_line < m_text.count()); +} + +void KFindTest::findNext(const QString &pattern) +{ + Q_ASSERT(m_find != 0); + + if(!pattern.isNull()) + { + m_find->setPattern(pattern); + } + + KFind::Result result = KFind::NoMatch; + do + { + //kdDebug() << "m_line: " << m_line << endl; + + result = m_find->find(); + + if(result == KFind::NoMatch && m_line < m_text.count()) + { + //kdDebug() << "incrementing m_line..." << endl; + if(m_find->options() & KFindDialog::FindIncremental) + m_find->setData(m_line, m_text[m_line]); + else + m_find->setData(m_text[m_line]); + + m_line++; + } + } while(result == KFind::NoMatch && m_line < m_text.count()); + //kdDebug() << "find next completed" << m_line << endl; +} + +void KFindTest::slotHighlight(const QString &text, int index, int matchedLength) +{ + m_hits.append("line: \"" + text + "\", index: " + QString::number(index) + + ", length: " + QString::number(matchedLength) + "\n"); +} + +void KFindTest::slotHighlight(int id, int index, int matchedLength) +{ + m_hits.append("line: \"" + m_text[id] + "\", index: " + QString::number(index) + + ", length: " + QString::number(matchedLength) + "\n"); +} + +int main(int argc, char **argv) +{ + KCmdLineArgs::init(argc, argv, "kfindtest", "KFindTest", 0, 0, false); + KApplication app; + + QString text = "This file is part of the KDE project.\n" + "This library is free software; you can redistribute it and/or\n" + "modify it under the terms of the GNU Library General Public\n" + "License version 2, as published by the Free Software Foundation.\n" + "\n" + " This library is distributed in the hope that it will be useful,\n" + " but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" + " Library General Public License for more details.\n" + "\n" + " You should have received a copy of the GNU Library General Public License\n" + " along with this library; see the file COPYING.LIB. If not, write to\n" + " the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n" + " Boston, MA 02110-1301, USA.\n"; + + QString output1 = "line: \"This file is part of the KDE project.\", index: 0, length: 4\n" + "line: \"This library is free software; you can redistribute it and/or\", index: 0, length: 4\n" + "line: \" This library is distributed in the hope that it will be useful,\", index: 4, length: 4\n" + "line: \" along with this library; see the file COPYING.LIB. If not, write to\", index: 15, length: 4\n"; + + QString output2 = "line: \"This file is part of the KDE project.\", index: 0, length: 0\n" + "line: \"This file is part of the KDE project.\", index: 2, length: 1\n" + "line: \"This file is part of the KDE project.\", index: 2, length: 2\n" + "line: \"This library is free software; you can redistribute it and/or\", index: 42, length: 3\n" + "line: \" This library is distributed in the hope that it will be useful,\", index: 21, length: 3\n" + "line: \" This library is distributed in the hope that it will be useful,\", index: 21, length: 5\n" + "line: \" This library is distributed in the hope that it will be useful,\", index: 21, length: 4\n" + "line: \" This library is distributed in the hope that it will be useful,\", index: 21, length: 3\n" + "line: \"This file is part of the KDE project.\", index: 2, length: 2\n" + "line: \"This library is free software; you can redistribute it and/or\", index: 25, length: 1\n" + "line: \"This library is free software; you can redistribute it and/or\", index: 25, length: 2\n" + "line: \" but WITHOUT ANY WARRANTY; without even the implied warranty of\", index: 20, length: 8\n" + "line: \"This library is free software; you can redistribute it and/or\", index: 16, length: 4\n" + "line: \"License version 2, as published by the Free Software Foundation.\", index: 44, length: 19\n"; + + QString output3 = "line: \"This file is part of the KDE project.\", index: 0, length: 0\n" + "line: \"This file is part of the KDE project.\", index: 2, length: 1\n" + "line: \"This file is part of the KDE project.\", index: 2, length: 2\n" + "line: \"This library is free software; you can redistribute it and/or\", index: 42, length: 3\n" + "line: \"This library is free software; you can redistribute it and/or\", index: 42, length: 4\n" + "line: \" This library is distributed in the hope that it will be useful,\", index: 21, length: 4\n" + "line: \" This library is distributed in the hope that it will be useful,\", index: 21, length: 5\n" + "line: \" This library is distributed in the hope that it will be useful,\", index: 21, length: 4\n" + "line: \" This library is distributed in the hope that it will be useful,\", index: 21, length: 3\n" + "line: \"This file is part of the KDE project.\", index: 2, length: 2\n" + "line: \"This file is part of the KDE project.\", index: 2, length: 1\n" + "line: \"The second line now looks a whole lot different.\", index: 18, length: 1\n" + "line: \"License version 2, as published by the Free Software Foundation.\", index: 48, length: 2\n" + "line: \" but WITHOUT ANY WARRANTY; without even the implied warranty of\", index: 20, length: 8\n" + "line: \" but WITHOUT ANY xxxx; without even the implied warranty of\", index: 51, length: 6\n" + "line: \"License version 2, as published by the Free Software Foundation.\", index: 39, length: 4\n" + "line: \"License version 2, as published by the Free Software Foundation.\", index: 44, length: 19\n"; + + KFindTest *test = new KFindTest(QStringList::split('\n', text, true)); + + kdDebug() << "Plain static search..." << endl; + + // first we do a simple text searching the text and doing a few find nexts + test->find("This", 0); + test->findNext(); + test->findNext(); + test->findNext(); + test->findNext(); + test->findNext(); + + check("result", test->hits().join(""), output1); + test->clearHits(); + kdDebug() << "PASSED" << endl; + + kdDebug() << "FindIncremental with static contents..." << endl; + + // now we'll do some searches using FindIncremental + test->find("", KFindDialog::FindIncremental); + test->findNext("i"); + test->findNext("is"); + test->findNext("ist"); + test->findNext(); + test->findNext("istri"); + test->findNext("istr"); + test->findNext("ist"); + test->findNext("is"); + test->findNext("W"); + test->findNext("WA"); + test->findNext("WARRANTY"); + test->findNext("Free"); + test->findNext("Software Foundation"); + + check("result", test->hits().join(""), output2); + test->clearHits(); + kdDebug() << "PASSED" << endl; + + kdDebug() << "FindIncremental with dynamic contents..." << endl; + + // now do that again but with pages that change between searches + test->find("", KFindDialog::FindIncremental); + test->findNext("i"); + test->findNext("is"); + test->findNext("ist"); + test->findNext("istr"); + test->findNext(); + test->changeText(1, "The second line now looks a whole lot different."); + test->findNext("istri"); + test->findNext("istr"); + test->findNext("ist"); + test->findNext("is"); + test->findNext("i"); + test->findNext("W"); + test->findNext("WA"); + test->findNext("WARRANTY"); + test->changeText(6, " but WITHOUT ANY xxxx; without even the implied warranty of"); + test->findNext("WARRAN"); + test->findNext("Free"); + test->findNext("Software Foundation"); + + check("result", test->hits().join(""), output3); + test->clearHits(); + kdDebug() << "PASSED" << endl; + + //return app.exec(); + delete test; + return 0; +} + +#include "kfindtest.moc" diff --git a/kutils/tests/kfindtest.h b/kutils/tests/kfindtest.h new file mode 100644 index 000000000..2cb60ed76 --- /dev/null +++ b/kutils/tests/kfindtest.h @@ -0,0 +1,59 @@ +/* + Copyright (C) 2004, Arend van Beelen jr. <arend@auton.nl> + This file is part of the KDE project + + 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. +*/ + +#ifndef KFINDTEST_H +#define KFINDTEST_H + +#include <qobject.h> +#include <qstringlist.h> + +class KFind; + +class KFindTest : public QObject +{ + Q_OBJECT + + public: + KFindTest(const QStringList &text) : + QObject(0), + m_find(0), + m_text(text), + m_line(0) + {} + + void find(const QString &pattern, long options = 0); + void findNext(const QString &pattern = QString::null); + + void changeText(uint line, const QString &text); + + const QStringList &hits() const { return m_hits; } + void clearHits() { m_hits.clear(); } + + public slots: + void slotHighlight(const QString &text, int index, int matchedLength); + void slotHighlight(int id, int index, int matchedLengthlength); + + private: + KFind *m_find; + QStringList m_text; + uint m_line; + QStringList m_hits; +}; + +#endif diff --git a/kutils/tests/kreplacetest.cpp b/kutils/tests/kreplacetest.cpp new file mode 100644 index 000000000..9c6f67b3a --- /dev/null +++ b/kutils/tests/kreplacetest.cpp @@ -0,0 +1,342 @@ +/* + Copyright (C) 2002, David Faure <david@mandrakesoft.com> + This file is part of the KDE project + + 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 <assert.h> + +#include <kcmdlineargs.h> +#include <kapplication.h> +#include <qeventloop.h> +#include <kpushbutton.h> +#include "../kreplace.h" +#include "../kreplacedialog.h" + +#include "kreplacetest.h" +#include <kdebug.h> +#include <stdlib.h> + +void KReplaceTest::replace( const QString &pattern, const QString &replacement, long options ) +{ + m_needEventLoop = false; + // This creates a replace-next-prompt dialog if needed. + m_replace = new KReplace(pattern, replacement, options); + + // Connect highlight signal to code which handles highlighting + // of found text. + connect(m_replace, SIGNAL( highlight( const QString &, int, int ) ), + this, SLOT( slotHighlight( const QString &, int, int ) ) ); + // Connect findNext signal - called when pressing the button in the dialog + connect(m_replace, SIGNAL( findNext() ), + this, SLOT( slotReplaceNext() ) ); + // Connect replace signal - called when doing a replacement + connect(m_replace, SIGNAL( replace(const QString &, int, int, int) ), + this, SLOT( slotReplace(const QString &, int, int, int) ) ); + + // Go to initial position + if ( (options & KReplaceDialog::FromCursor) == 0 ) + { + if ( m_replace->options() & KFindDialog::FindBackwards ) + m_currentPos = m_text.fromLast(); + else + m_currentPos = m_text.begin(); + } + + // Launch first replacement + slotReplaceNext(); + + if ( m_needEventLoop ) + qApp->eventLoop()->enterLoop(); +} + +void KReplaceTest::slotHighlight( const QString &str, int matchingIndex, int matchedLength ) +{ + kdDebug() << "slotHighlight Index:" << matchingIndex << " Length:" << matchedLength + << " Substr:" << str.mid(matchingIndex, matchedLength) + << endl; + // Emulate the user saying yes + // animateClick triggers a timer, hence the enterloop/exitloop + // Calling slotReplace directly would lead to infinite loop anyway (Match never returned, + // so slotReplaceNext never returns) + if ( m_replace->options() & KReplaceDialog::PromptOnReplace ) { + m_replace->replaceNextDialog( false )->actionButton( (KDialogBase::ButtonCode)m_button )->animateClick(); + m_needEventLoop = true; + } +} + + +void KReplaceTest::slotReplace(const QString &text, int replacementIndex, int replacedLength, int matchedLength) +{ + kdDebug() << "slotReplace index=" << replacementIndex << " replacedLength=" << replacedLength << " matchedLength=" << matchedLength << " text=" << text.left( 50 ) << endl; + *m_currentPos = text; // KReplace hacked the replacement into 'text' in already. +} + +void KReplaceTest::slotReplaceNext() +{ + //kdDebug() << k_funcinfo << endl; + KFind::Result res = KFind::NoMatch; + while ( res == KFind::NoMatch && m_currentPos != m_text.end() ) { + if ( m_replace->needData() ) + m_replace->setData( *m_currentPos ); + + // Let KReplace inspect the text fragment, and display a dialog if a match is found + res = m_replace->replace(); + + if ( res == KFind::NoMatch ) { + if ( m_replace->options() & KFindDialog::FindBackwards ) + m_currentPos--; + else + m_currentPos++; + } + } + +#if 0 // commented out so that this test doesn't require interaction + if ( res == KFind::NoMatch ) // i.e. at end + if ( m_replace->shouldRestart() ) { + if ( m_replace->options() & KFindDialog::FindBackwards ) + m_currentPos = m_text.fromLast(); + else + m_currentPos = m_text.begin(); + slotReplaceNext(); + } +#endif + if ( res == KFind::NoMatch && m_needEventLoop ) + qApp->eventLoop()->exitLoop(); +} + +void KReplaceTest::print() +{ + QStringList::Iterator it = m_text.begin(); + for ( ; it != m_text.end() ; ++it ) + kdDebug() << *it << endl; +} + +/* button is the button that we emulate pressing, when options includes PromptOnReplace. + Valid possibilities are User1 (replace all) and User3 (replace) */ +static void testReplaceSimple( int options, int button = 0 ) +{ + kdDebug() << "testReplaceSimple: " << options << endl; + KReplaceTest test( QString( "hellohello" ), button ); + test.replace( "hello", "HELLO", options ); + QStringList textLines = test.textLines(); + assert( textLines.count() == 1 ); + if ( textLines[ 0 ] != "HELLOHELLO" ) { + kdError() << "ASSERT FAILED: replaced text is '" << textLines[ 0 ] << "' instead of 'HELLOHELLO'" << endl; + exit(1); + } +} + +// Replacing "a" with "". +// input="aaaaaa", expected output="" +static void testReplaceBlank( int options, int button = 0 ) +{ + kdDebug() << "testReplaceBlank: " << options << endl; + KReplaceTest test( QString( "aaaaaa" ), button ); + test.replace( "a", "", options ); + QStringList textLines = test.textLines(); + assert( textLines.count() == 1 ); + if ( !textLines[ 0 ].isEmpty() ) { + kdError() << "ASSERT FAILED: replaced text is '" << textLines[ 0 ] << "' instead of ''" << endl; + exit(1); + } +} + +// Replacing "" with "foo" +// input="bbbb", expected output="foobfoobfoobfoobfoo" +static void testReplaceBlankSearch( int options, int button = 0 ) +{ + kdDebug() << "testReplaceBlankSearch: " << options << endl; + KReplaceTest test( QString( "bbbb" ), button ); + test.replace( "", "foo", options ); + QStringList textLines = test.textLines(); + assert( textLines.count() == 1 ); + if ( textLines[ 0 ] != "foobfoobfoobfoobfoo" ) { + kdError() << "ASSERT FAILED: replaced text is '" << textLines[ 0 ] << "' instead of 'foobfoobfoobfoobfoo'" << endl; + exit(1); + } +} + +static void testReplaceLonger( int options, int button = 0 ) +{ + kdDebug() << "testReplaceLonger: " << options << endl; + // Standard test of a replacement string longer than the matched string + KReplaceTest test( QString( "aaaa" ), button ); + test.replace( "a", "bb", options ); + QStringList textLines = test.textLines(); + assert( textLines.count() == 1 ); + if ( textLines[ 0 ] != "bbbbbbbb" ) { + kdError() << "ASSERT FAILED: replaced text is '" << textLines[ 0 ] << "' instead of 'bbbbbbbb'" << endl; + exit(1); + } +} + +static void testReplaceLongerInclude( int options, int button = 0 ) +{ + kdDebug() << "testReplaceLongerInclude: " << options << endl; + // Similar test, where the replacement string includes the search string + KReplaceTest test( QString( "a foo b" ), button ); + test.replace( "foo", "foobar", options ); + QStringList textLines = test.textLines(); + assert( textLines.count() == 1 ); + if ( textLines[ 0 ] != "a foobar b" ) { + kdError() << "ASSERT FAILED: replaced text is '" << textLines[ 0 ] << "' instead of 'a foobar b'" << endl; + exit(1); + } +} + +static void testReplaceLongerInclude2( int options, int button = 0 ) +{ + kdDebug() << "testReplaceLongerInclude2: " << options << endl; + // Similar test, but with more chances of matches inside the replacement string + KReplaceTest test( QString( "aaaa" ), button ); + test.replace( "a", "aa", options ); + QStringList textLines = test.textLines(); + assert( textLines.count() == 1 ); + if ( textLines[ 0 ] != "aaaaaaaa" ) { + kdError() << "ASSERT FAILED: replaced text is '" << textLines[ 0 ] << "' instead of 'aaaaaaaa'" << endl; + exit(1); + } +} + +// Test for the \0 backref +static void testReplaceBackRef( int options, int button = 0 ) +{ + kdDebug() << "testReplaceBackRef: " << options << endl; + KReplaceTest test( QString( "abc def" ), button ); + test.replace( "abc", "(\\0)", options ); + QStringList textLines = test.textLines(); + assert( textLines.count() == 1 ); + QString expected = options & KReplaceDialog::BackReference ? "(abc) def" : "(\\0) def"; + if ( textLines[ 0 ] != expected ) { + kdError() << "ASSERT FAILED: replaced text is '" << textLines[ 0 ] << "' instead of '"<< expected << "'" << endl; + exit(1); + } +} + +static void testReplacementHistory( const QStringList& findHistory, const QStringList& replaceHistory ) +{ + KReplaceDialog dlg( 0, 0, 0, findHistory, replaceHistory ); + dlg.show(); + kdDebug() << "testReplacementHistory:" << dlg.replacementHistory() << endl; + assert( dlg.replacementHistory() == replaceHistory ); +} + +static void testReplacementHistory() +{ + QStringList findHistory; + QStringList replaceHistory; + findHistory << "foo" << "bar"; + replaceHistory << "FOO" << "BAR"; + testReplacementHistory( findHistory, replaceHistory ); + + findHistory.clear(); + replaceHistory.clear(); + findHistory << "foo" << "bar"; + replaceHistory << QString::null << "baz"; // #130831 + testReplacementHistory( findHistory, replaceHistory ); +} + +int main( int argc, char **argv ) +{ + KCmdLineArgs::init(argc, argv, "kreplacetest", 0, 0); + KApplication app; + + testReplacementHistory(); // #130831 + + testReplaceBlank( 0 ); + testReplaceBlank( KReplaceDialog::PromptOnReplace, KDialogBase::User3 ); // replace + testReplaceBlank( KReplaceDialog::PromptOnReplace, KDialogBase::User1 ); // replace all + testReplaceBlank( KReplaceDialog::FindBackwards, 0 ); + testReplaceBlank( KReplaceDialog::FindBackwards | KReplaceDialog::PromptOnReplace, KDialogBase::User3 ); // replace + testReplaceBlank( KReplaceDialog::FindBackwards | KReplaceDialog::PromptOnReplace, KDialogBase::User1 ); // replace all + + testReplaceBlankSearch( 0 ); + testReplaceBlankSearch( KReplaceDialog::PromptOnReplace, KDialogBase::User3 ); // replace + testReplaceBlankSearch( KReplaceDialog::PromptOnReplace, KDialogBase::User1 ); // replace all + testReplaceBlankSearch( KReplaceDialog::FindBackwards, 0 ); + testReplaceBlankSearch( KReplaceDialog::FindBackwards | KReplaceDialog::PromptOnReplace, KDialogBase::User3 ); // replace + testReplaceBlankSearch( KReplaceDialog::FindBackwards | KReplaceDialog::PromptOnReplace, KDialogBase::User1 ); // replace all + + testReplaceSimple( 0 ); + testReplaceSimple( KReplaceDialog::PromptOnReplace, KDialogBase::User3 ); // replace + testReplaceSimple( KReplaceDialog::PromptOnReplace, KDialogBase::User1 ); // replace all + testReplaceSimple( KReplaceDialog::FindBackwards, 0 ); + testReplaceSimple( KReplaceDialog::FindBackwards | KReplaceDialog::PromptOnReplace, KDialogBase::User3 ); // replace + testReplaceSimple( KReplaceDialog::FindBackwards | KReplaceDialog::PromptOnReplace, KDialogBase::User1 ); // replace all + + testReplaceLonger( 0 ); + testReplaceLonger( KReplaceDialog::PromptOnReplace, KDialogBase::User3 ); // replace + testReplaceLonger( KReplaceDialog::PromptOnReplace, KDialogBase::User1 ); // replace all + testReplaceLonger( KReplaceDialog::FindBackwards, 0 ); + testReplaceLonger( KReplaceDialog::FindBackwards | KReplaceDialog::PromptOnReplace, KDialogBase::User3 ); // replace + testReplaceLonger( KReplaceDialog::FindBackwards | KReplaceDialog::PromptOnReplace, KDialogBase::User1 ); // replace all + + testReplaceLongerInclude( 0 ); + testReplaceLongerInclude( KReplaceDialog::PromptOnReplace, KDialogBase::User3 ); // replace + testReplaceLongerInclude( KReplaceDialog::PromptOnReplace, KDialogBase::User1 ); // replace all + testReplaceLongerInclude( KReplaceDialog::FindBackwards, 0 ); + testReplaceLongerInclude( KReplaceDialog::FindBackwards | KReplaceDialog::PromptOnReplace, KDialogBase::User3 ); // replace + testReplaceLongerInclude( KReplaceDialog::FindBackwards | KReplaceDialog::PromptOnReplace, KDialogBase::User1 ); // replace all + + testReplaceLongerInclude2( 0 ); + testReplaceLongerInclude2( KReplaceDialog::PromptOnReplace, KDialogBase::User3 ); // replace + testReplaceLongerInclude2( KReplaceDialog::PromptOnReplace, KDialogBase::User1 ); // replace all + testReplaceLongerInclude2( KReplaceDialog::FindBackwards, 0 ); + testReplaceLongerInclude2( KReplaceDialog::FindBackwards | KReplaceDialog::PromptOnReplace, KDialogBase::User3 ); // replace + testReplaceLongerInclude2( KReplaceDialog::FindBackwards | KReplaceDialog::PromptOnReplace, KDialogBase::User1 ); // replace all + + testReplaceBackRef( 0 ); + testReplaceBackRef( KReplaceDialog::PromptOnReplace, KDialogBase::User3 ); // replace + testReplaceBackRef( KReplaceDialog::PromptOnReplace, KDialogBase::User1 ); // replace all + testReplaceBackRef( KReplaceDialog::FindBackwards, 0 ); + testReplaceBackRef( KReplaceDialog::FindBackwards | KReplaceDialog::PromptOnReplace, KDialogBase::User3 ); // replace + testReplaceBackRef( KReplaceDialog::FindBackwards | KReplaceDialog::PromptOnReplace, KDialogBase::User1 ); // replace all + testReplaceBackRef( KReplaceDialog::BackReference | KReplaceDialog::PromptOnReplace, KDialogBase::User3 ); // replace + testReplaceBackRef( KReplaceDialog::BackReference | KReplaceDialog::PromptOnReplace, KDialogBase::User1 ); // replace all + testReplaceBackRef( KReplaceDialog::BackReference | KReplaceDialog::FindBackwards, 0 ); + testReplaceBackRef( KReplaceDialog::BackReference | KReplaceDialog::FindBackwards | KReplaceDialog::PromptOnReplace, KDialogBase::User3 ); // replace + testReplaceBackRef( KReplaceDialog::BackReference | KReplaceDialog::FindBackwards | KReplaceDialog::PromptOnReplace, KDialogBase::User1 ); // replace all + + QString text = "This file is part of the KDE project.\n" + "This library is free software; you can redistribute it and/or\n" + "modify it under the terms of the GNU Library General Public\n" + "License version 2, as published by the Free Software Foundation.\n" + "\n" + " This library is distributed in the hope that it will be useful,\n" + " but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" + " Library General Public License for more details.\n" + "\n" + " You should have received a copy of the GNU Library General Public License\n" + " along with this library; see the file COPYING.LIB. If not, write to\n" + " the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n" + " Boston, MA 02110-1301, USA.\n" + "More tests:\n" + "ThisThis This, This. This\n" + "aGNU\n" + "free"; + KReplaceTest test( QStringList::split( '\n', text, true ), 0 ); + + test.replace( "GNU", "KDE", 0 ); + test.replace( "free", "*free*", 0 ); + test.replace( "This", "THIS*", KFindDialog::FindBackwards ); + + test.print(); + //return app.exec(); + return 0; +} +#include "kreplacetest.moc" diff --git a/kutils/tests/kreplacetest.h b/kutils/tests/kreplacetest.h new file mode 100644 index 000000000..a60563e77 --- /dev/null +++ b/kutils/tests/kreplacetest.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2002, David Faure <david@mandrakesoft.com> + This file is part of the KDE project + + 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. +*/ + +#ifndef KREPLACETEST_H +#define KREPLACETEST_H + +#include <qobject.h> +#include <qstringlist.h> + +class KReplace; + +class KReplaceTest : public QObject +{ + Q_OBJECT +public: + KReplaceTest( const QStringList& text, int button ) + : QObject( 0L ), m_text( text ), m_replace( 0 ), m_button( button ) {} + + void replace( const QString &pattern, const QString &replacement, long options ); + void print(); + const QStringList& textLines() const { return m_text; } + +public slots: + void slotHighlight( const QString &, int, int ); + void slotReplaceNext(); + void slotReplace(const QString &text, int replacementIndex, int replacedLength, int matchedLength); + +private: + QStringList::Iterator m_currentPos; + QStringList m_text; + KReplace* m_replace; + bool m_needEventLoop; + int m_button; +}; + +#endif |