diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
commit | 8362bf63dea22bbf6736609b0f49c152f975eb63 (patch) | |
tree | 0eea3928e39e50fae91d4e68b21b1e6cbae25604 /lib/kofficecore | |
download | koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.tar.gz koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.zip |
Added old abandoned KDE3 version of koffice
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/koffice@1077364 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'lib/kofficecore')
128 files changed, 37174 insertions, 0 deletions
diff --git a/lib/kofficecore/DESIGN b/lib/kofficecore/DESIGN new file mode 100644 index 00000000..c5409b30 --- /dev/null +++ b/lib/kofficecore/DESIGN @@ -0,0 +1,113 @@ +------------------------------------------------------------------------- +| This document answers some questions about how the startup of KOffice +| applications works. This file was put together by +| Eric R. Turner <eric.r.turner@bitbreather.com> +| from the answers given by David Faure <faure@kde.org> +------------------------------------------------------------------------- + +Q: Where is the pointer to the application's instance of KAboutData +data stored? + +A: In a static member of the KCmdLineArgs class. + + + +Q: One of the arguments to KAboutData's constructor is the program +name. Will this be used later to run the program? + +A: No (by the time the KAboutData's constructor is called, the program +is already running). The program name is in fact the "instance name", +which is used to locate the application's own data +(/share/apps/<application name>/...) and many other things. + + + +Q: What is the "+[file]" option passed to KCmdLineArgs? + +A: KCmdLineOptions describe the KCmdLineArgs. "+[file]" means that the +command line arguments can be one or more file names, and that these +arguments are optional. + + + +Q: What is I18N_NOOP()? + +A: I18N_NOOP() marks the options description for translation to other +languages. + + + +Q: What is a KoApplicationInterface used for? + +A: It allows a KOffice application to participate in Inter-Process +Communication and Remote Procedure Calls via DCOP. + + + +Q: What is the .desktop file? + +A: It's responsible for the menu entry, standard KDE stuff. In KOffice +we add a few useful tags to it, in particular the "native mime type" +of the component (e.g. application/x-kword for kword). + + + +Q: Is the .desktop file generated by hand? + +A: Yes. + + + +Q: What's a KoDocumentEntry? I couldn't find it in lib/kofficecore. + +A: KoDocumentEntry describes a KOffice component (i.e. it's a +representation in memory of the data contained in the .desktop +file). It's in kofficecore/koQueryTrader.h. + + + +Q: Is the show() method of KoMainWindow where the application actually +appears to the user? + +A: Yes. + + + +Q: How does KoMainWindow->show() bring up the correct application (how +does it know we want to show one app or another)? + +A: The KoMainWindow is the same for all. But KoDocumentEntry was +created from specific application data, and koQueryTrader klopens the +library containing this component. The very same mechanism is used to +1) embed other components inside the app, 2) create any kind of +koffice document, e.g. in koshell. All the KOffice applications are in +fact very small wrappers that simply dlopen their own compnent - with +the code in kofficecore, which can also dlopen ANY component. + + + +Q: The KoApplication object doesn't store pointers to the KoDocument +nd KoMainWindow objects. Do these objects communicate with each other +later on, and if so, is that done through DCOP? + +A: The KoMainWindow knows about the KoDocument[s] inside it. The +KoMainWindows are in a static linked list, if one really wants to +iterateover all windows in the application, but this is rarely needed. + + +Q: How does KoMainWindow relate to KoDocument and KoView? + +A: KOffice applications reimplement KoDocument and KoView. But the +KoMainWindow is the same class for all, since it doesn't have any +app-specific stuff. It's GUI, i.e. menus and toolbars, are provided by +1) itself (the standard stuff like the File and Help menus), 2) the +document's actions, and 3) the view's actions. + + + +Q: How do KoDocument and KoView relate to each other? + +A: KoDocument can have one or more KoViews, which allows the user to +have multiple views of the same document. In a single KoMainWindow +this might be accomplished using the "split view" feature. KoView[s] +of the same document can be in separate KoMainWindow[s]. diff --git a/lib/kofficecore/KoApplication.cpp b/lib/kofficecore/KoApplication.cpp new file mode 100644 index 00000000..3f2f6b23 --- /dev/null +++ b/lib/kofficecore/KoApplication.cpp @@ -0,0 +1,249 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@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 "KoApplication.h" +#include <config.h> +#include <qfile.h> +#include <qregexp.h> +#include <dcopclient.h> +#include <KoApplicationIface.h> +#include <KoQueryTrader.h> +#include <KoDocument.h> +#include <KoMainWindow.h> +#include <klocale.h> +#include <kcmdlineargs.h> +#include <kdebug.h> +#include <kdesktopfile.h> +#include <kmessagebox.h> +#include <kstandarddirs.h> +#include <stdlib.h> + +void qt_generate_epsf( bool b ); + +static const KCmdLineOptions options[]= +{ + {"print", I18N_NOOP("Only print and exit"),0}, + {"template", I18N_NOOP("Open a new document with a template"), 0}, + {"dpi <dpiX,dpiY>", I18N_NOOP("Override display DPI"), 0}, + KCmdLineLastOption +}; + +bool KoApplication::m_starting = true; + +class KoApplicationPrivate +{ +public: + KoApplicationPrivate() { + m_appIface = 0L; + } + KoApplicationIface *m_appIface; // to avoid a leak +}; + +KoApplication::KoApplication() + : KApplication( initHack() ) +{ + d = new KoApplicationPrivate; + + // Initialize all KOffice directories etc. + KoGlobal::initialize(); + + // Prepare a DCOP interface + d->m_appIface = new KoApplicationIface; + dcopClient()->setDefaultObject( d->m_appIface->objId() ); + + m_starting = true; +} + +// This gets called before entering KApplication::KApplication +bool KoApplication::initHack() +{ + KCmdLineArgs::addCmdLineOptions( options, I18N_NOOP("KOffice"), "koffice", "kde" ); + return true; +} + +// Small helper for start() so that we don't forget to reset m_starting before a return +class KoApplication::ResetStarting +{ +public: + ~ResetStarting() { + KoApplication::m_starting = false; + } +}; + +bool KoApplication::start() +{ + ResetStarting resetStarting; // reset m_starting to false when we're done + Q_UNUSED( resetStarting ); + + // Find the *.desktop file corresponding to the kapp instance name + KoDocumentEntry entry = KoDocumentEntry( KoDocument::readNativeService() ); + if ( entry.isEmpty() ) + { + kdError( 30003 ) << instanceName() << "part.desktop not found." << endl; + kdError( 30003 ) << "Run 'kde-config --path services' to see which directories were searched, assuming kde startup had the same environment as your current shell." << endl; + kdError( 30003 ) << "Check your installation (did you install KOffice in a different prefix than KDE, without adding the prefix to /etc/kderc ?)" << endl; + return false; + } + + // Get the command line arguments which we have to parse + KCmdLineArgs *args= KCmdLineArgs::parsedArgs(); + int argsCount = args->count(); + + KCmdLineArgs *koargs = KCmdLineArgs::parsedArgs("koffice"); + QCString dpiValues = koargs->getOption( "dpi" ); + if ( !dpiValues.isEmpty() ) { + int sep = dpiValues.find( QRegExp( "[x, ]" ) ); + int dpiX; + int dpiY = 0; + bool ok = true; + if ( sep != -1 ) { + dpiY = dpiValues.mid( sep+1 ).toInt( &ok ); + dpiValues.truncate( sep ); + } + if ( ok ) { + dpiX = dpiValues.toInt( &ok ); + if ( ok ) { + if ( !dpiY ) dpiY = dpiX; + KoGlobal::setDPI( dpiX, dpiY ); + } + } + } + + // No argument -> create an empty document + if ( !argsCount ) { + KoDocument* doc = entry.createDoc( 0, "Document" ); + if ( !doc ) + return false; + KoMainWindow *shell = new KoMainWindow( doc->instance() ); + shell->show(); + QObject::connect(doc, SIGNAL(sigProgress(int)), shell, SLOT(slotProgress(int))); + // for initDoc to fill in the recent docs list + // and for KoDocument::slotStarted + doc->addShell( shell ); + + if ( doc->checkAutoSaveFile() ) { + shell->setRootDocument( doc ); + } else { + doc->showStartUpWidget( shell ); + } + + // FIXME This needs to be moved someplace else + QObject::disconnect(doc, SIGNAL(sigProgress(int)), shell, SLOT(slotProgress(int))); + } else { + bool print = koargs->isSet("print"); + bool doTemplate = koargs->isSet("template"); + koargs->clear(); + + // Loop through arguments + + short int n=0; // number of documents open + short int nPrinted = 0; + for(int i=0; i < argsCount; i++ ) + { + // For now create an empty document + KoDocument* doc = entry.createDoc( 0 ); + if ( doc ) + { + // show a shell asap + KoMainWindow *shell = new KoMainWindow( doc->instance() ); + if (!print) + shell->show(); + // are we just trying to open a template? + if ( doTemplate ) { + QStringList paths; + if ( args->url(i).isLocalFile() && QFile::exists(args->url(i).path()) ) + { + paths << QString(args->url(i).path()); + kdDebug(30003) << "using full path..." << endl; + } else { + QString desktopName(args->arg(i)); + QString appName = KGlobal::instance()->instanceName(); + + paths = KGlobal::dirs()->findAllResources("data", appName +"/templates/*/" + desktopName ); + if ( paths.isEmpty()) { + paths = KGlobal::dirs()->findAllResources("data", appName +"/templates/" + desktopName ); + } + if ( paths.isEmpty()) { + KMessageBox::error(0L, i18n("No template found for: %1 ").arg(desktopName) ); + delete shell; + } else if ( paths.count() > 1 ) { + KMessageBox::error(0L, i18n("Too many templates found for: %1").arg(desktopName) ); + delete shell; + } + } + + if ( !paths.isEmpty() ) { + KURL templateBase; + templateBase.setPath(paths[0]); + KDesktopFile templateInfo(paths[0]); + + QString templateName = templateInfo.readURL(); + KURL templateURL; + templateURL.setPath( templateBase.directory() + "/" + templateName ); + if ( shell->openDocument(doc, templateURL )) { + doc->resetURL(); + doc->setEmpty(); + doc->setTitleModified(); + kdDebug(30003) << "Template loaded..." << endl; + n++; + } else { + KMessageBox::error(0L, i18n("Template %1 failed to load.").arg(templateURL.prettyURL()) ); + delete shell; + } + } + // now try to load + } else if ( shell->openDocument( doc, args->url(i) ) ) { + if ( print ) { + shell->print(false /*we want to get the dialog*/); + // delete shell; done by ~KoDocument + nPrinted++; + } else { + // Normal case, success + n++; + } + } else { + // .... if failed + // delete doc; done by openDocument + // delete shell; done by ~KoDocument + } + } + } + if ( print ) + return nPrinted > 0; + if (n == 0) // no doc, e.g. all URLs were malformed + return false; + } + + args->clear(); + // not calling this before since the program will quit there. + return true; +} + +KoApplication::~KoApplication() +{ + delete d->m_appIface; + delete d; +} + +bool KoApplication::isStarting() +{ + return KoApplication::m_starting; +} + +#include <KoApplication.moc> diff --git a/lib/kofficecore/KoApplication.h b/lib/kofficecore/KoApplication.h new file mode 100644 index 00000000..ecd9b943 --- /dev/null +++ b/lib/kofficecore/KoApplication.h @@ -0,0 +1,85 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@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 __ko_app_h__ +#define __ko_app_h__ + +namespace std { } +using namespace std; +#include <kapplication.h> +#include <koffice_export.h> + +class KoApplicationPrivate; + +/** + * @brief Base class for all %KOffice apps + * + * This class handles arguments given on the command line and + * shows a generic about dialog for all KOffice apps. + * + * In addition it adds the standard directories where KOffice applications + * can find their images etc. + * + * If the last mainwindow becomes closed, KoApplication automatically + * calls QApplication::quit. + */ +class KOFFICECORE_EXPORT KoApplication : public KApplication +{ + Q_OBJECT + +public: + /** + * Creates an application object, adds some standard directories and + * initializes kimgio. + */ + KoApplication(); + + /** + * Destructor. + */ + virtual ~KoApplication(); + + // ######### Bad name + /** + * Call this to start the application. + * + * Parses command line arguments and creates the initial shells and docs + * from them (or an empty doc if no cmd-line argument is specified ). + * + * You must call this method directly before calling QApplication::exec. + * + * It is valid behaviour not to call this method at all. In this case you + * have to process your command line parameters by yourself. + */ + virtual bool start(); + + /** + * @return true if the application is starting + */ + static bool isStarting(); + +private: + bool initHack(); + KoApplicationPrivate* d; + static bool m_starting ; ///< is the application starting or not + class ResetStarting; + friend class ResetStarting; +}; + +#endif diff --git a/lib/kofficecore/KoApplicationIface.cc b/lib/kofficecore/KoApplicationIface.cc new file mode 100644 index 00000000..eb6f4d2d --- /dev/null +++ b/lib/kofficecore/KoApplicationIface.cc @@ -0,0 +1,97 @@ +/* This file is part of the KDE project + Copyright (C) 2000 David Faure <faure@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. + + 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 <dcopclient.h> +#include <kdebug.h> +#include <klocale.h> +#include <kmessagebox.h> + +#include "KoApplication.h" +#include "KoApplicationIface.h" +#include "KoDocument.h" +#include "KoDocumentIface.h" +#include "KoMainWindow.h" +#include "KoQueryTrader.h" +#include "KoView.h" + +KoApplicationIface::KoApplicationIface() + : DCOPObject( "KoApplicationIface" ) +{ +} + +KoApplicationIface::~KoApplicationIface() +{ +} + +DCOPRef KoApplicationIface::createDocument( const QString &nativeFormat ) +{ + KoDocumentEntry entry = KoDocumentEntry::queryByMimeType( nativeFormat ); + if ( entry.isEmpty() ) + { + KMessageBox::questionYesNo( 0, i18n( "Unknown KOffice MimeType %s. Check your installation." ).arg( nativeFormat ) ); + return DCOPRef(); + } + KoDocument* doc = entry.createDoc( 0 ); + return DCOPRef( kapp->dcopClient()->appId(), doc->dcopObject()->objId() ); +} + +QValueList<DCOPRef> KoApplicationIface::getDocuments() +{ + QValueList<DCOPRef> lst; + QPtrList<KoDocument> *documents = KoDocument::documentList(); + if ( documents ) + { + QPtrListIterator<KoDocument> it( *documents ); + for (; it.current(); ++it ) + lst.append( DCOPRef( kapp->dcopClient()->appId(), it.current()->dcopObject()->objId() ) ); + } + return lst; +} + +QValueList<DCOPRef> KoApplicationIface::getViews() +{ + QValueList<DCOPRef> lst; + QPtrList<KoDocument> *documents = KoDocument::documentList(); + if ( documents ) + { + QPtrListIterator<KoDocument> it( *documents ); + for (; it.current(); ++it ) + { + QPtrListIterator<KoView> itview( it.current()->views() ); + for ( ; itview.current(); ++itview ) + lst.append( DCOPRef( kapp->dcopClient()->appId(), itview.current()->dcopObject()->objId() ) ); + } + } + return lst; +} + +QValueList<DCOPRef> KoApplicationIface::getWindows() +{ + QValueList<DCOPRef> lst; + QPtrList<KMainWindow> *mainWindows = KMainWindow::memberList; + if ( mainWindows ) + { + QPtrListIterator<KMainWindow> it( *mainWindows ); + for (; it.current(); ++it ) + lst.append( DCOPRef( kapp->dcopClient()->appId(), + static_cast<KoMainWindow *>(it.current())->dcopObject()->objId() ) ); + } + return lst; +} diff --git a/lib/kofficecore/KoApplicationIface.h b/lib/kofficecore/KoApplicationIface.h new file mode 100644 index 00000000..ac72f09a --- /dev/null +++ b/lib/kofficecore/KoApplicationIface.h @@ -0,0 +1,66 @@ +/* This file is part of the KDE project + Copyright (C) 2000 David Faure <faure@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. + + 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 __KoApplicationIface_h__ +#define __KoApplicationIface_h__ + +#include <dcopobject.h> +#include <qvaluelist.h> +#include <dcopref.h> + +/** + * DCOP interface for any KOffice application (entry point) + */ +class KoApplicationIface : public DCOPObject +{ + K_DCOP +public: + + KoApplicationIface(); + ~KoApplicationIface(); + +k_dcop: + /** + * Creates a new document for the given native mimetype + * Use it to create a shell and to load an existing file, if any + */ + DCOPRef createDocument( const QString &nativeFormat ); + + /** + * @return a list of references to all the documents + * (see KoDocumentIface) + */ + QValueList<DCOPRef> getDocuments(); + + /** + * @return a list of references to all the views + * (see KoViewIface) + * Convenience method to avoid iterating over all documents to get all the views. + */ + QValueList<DCOPRef> getViews(); + + /** + * @return a list of references to all the windows + * (see KoMainWindowIface) + */ + QValueList<DCOPRef> getWindows(); +}; + +#endif + diff --git a/lib/kofficecore/KoChild.cpp b/lib/kofficecore/KoChild.cpp new file mode 100644 index 00000000..8207d086 --- /dev/null +++ b/lib/kofficecore/KoChild.cpp @@ -0,0 +1,366 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@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 <KoChild.h> + +#include <qpainter.h> + +#include <kdebug.h> + +class KoChild::KoChildPrivate +{ +public: + KoChildPrivate() + { + m_contentsX = m_contentsY = 0; + } + ~KoChildPrivate() + { + } + + QRect m_geometry; + + double m_rotation; + double m_shearX; + double m_shearY; + QPoint m_rotationPoint; + double m_scaleX; + double m_scaleY; + QWMatrix m_matrix; + bool m_lock; + QPointArray m_old; + bool m_transparent; + int m_contentsX; + int m_contentsY; +}; + +KoChild::KoChild( QObject *parent, const char *name ) +: QObject( parent, name ) +{ + d = new KoChildPrivate; + + d->m_scaleX = d->m_scaleY = 1.0; + d->m_shearX = d->m_shearY = 0.0; + d->m_rotation = 0.0; + d->m_lock = false; + d->m_transparent = false; + + updateMatrix(); +} + +KoChild::~KoChild() +{ + delete d; +} + +void KoChild::setGeometry( const QRect &rect, bool noEmit ) +{ + if ( !d->m_lock ) + d->m_old = framePointArray(); + + d->m_geometry = rect; + + // Embedded objects should have a minimum size of 3, so they can be selected + if( d->m_geometry.width() < 3 ) + d->m_geometry.setWidth( 3 ); + + if( d->m_geometry.height() < 3 ) + d->m_geometry.setHeight( 3 ); + + updateMatrix(); + + if ( !d->m_lock && !noEmit ) + emit changed( this ); +} + +QRect KoChild::geometry() const +{ + return d->m_geometry; +} + +QRegion KoChild::region( const QWMatrix &matrix ) const +{ + return QRegion( pointArray( matrix ) ); +} + +QPointArray KoChild::pointArray( const QWMatrix &matrix ) const +{ + return pointArray( QRect( 0, 0, d->m_geometry.width(), d->m_geometry.height() ), matrix ); +} + +//bool KoChild::contains( const QPoint &point ) const +//{ +// return region().contains( point ); +//} + +QRect KoChild::boundingRect() const +{ + return pointArray().boundingRect(); +} + +bool KoChild::isRectangle() const +{ + return !( d->m_shearX != 0.0 || d->m_shearY != 0.0 || d->m_rotation != 0.0 ); +} + +void KoChild::setClipRegion( QPainter &painter, bool combine ) +{ + painter.setClipping( true ); + if ( combine && !painter.clipRegion().isEmpty() ) + painter.setClipRegion( region( painter.worldMatrix() ).intersect( painter.clipRegion() ) ); + else + painter.setClipRegion( region( painter.worldMatrix() ) ); +} + +void KoChild::setScaling( double x, double y ) +{ + if ( !d->m_lock ) + d->m_old = framePointArray(); + + d->m_scaleX = x; + d->m_scaleY = y; + + // why is that commented out? (Simon) + // This is commented out, because KoChild::transform() scales + // the world matrix explicitly and updateMatrix() doesn't even + // handle scaling (Werner) + //updateMatrix() + + if ( !d->m_lock ) + emit changed( this ); +} + +double KoChild::xScaling() const +{ + return d->m_scaleX; +} + +double KoChild::yScaling() const +{ + return d->m_scaleY; +} + +void KoChild::setShearing( double x, double y ) +{ + if ( !d->m_lock ) + d->m_old = framePointArray(); + + d->m_shearX = x; + d->m_shearY = y; + + updateMatrix(); + + if ( !d->m_lock ) + emit changed( this ); +} + +double KoChild::xShearing() const +{ + return d->m_shearX; +} + +double KoChild::yShearing() const +{ + return d->m_shearY; +} + +void KoChild::setRotation( double rot ) +{ + if ( !d->m_lock ) + d->m_old = framePointArray(); + + d->m_rotation = rot; + updateMatrix(); + + if ( !d->m_lock ) + emit changed( this ); +} + +double KoChild::rotation() const +{ + return d->m_rotation; +} + +void KoChild::setRotationPoint( const QPoint &pos ) +{ + if ( !d->m_lock ) + d->m_old = framePointArray(); + + d->m_rotationPoint = pos; + updateMatrix(); + + if ( !d->m_lock ) + emit changed( this ); +} + +QPoint KoChild::rotationPoint() const +{ + return d->m_rotationPoint; +} + +void KoChild::transform( QPainter &painter ) +{ + setClipRegion( painter, true ); + + QWMatrix m = painter.worldMatrix(); + m = d->m_matrix * m; + m.scale( d->m_scaleX, d->m_scaleY ); + painter.setWorldMatrix( m ); +} + +void KoChild::setContentsPos( int x, int y ) +{ + d->m_contentsX = x; + d->m_contentsY = y; +} + +QRect KoChild::contentRect() const +{ + return QRect( d->m_contentsX, d->m_contentsY, int(d->m_geometry.width() / d->m_scaleX), + int(d->m_geometry.height() / d->m_scaleY) ); +} + +QPointArray KoChild::framePointArray( const QWMatrix &matrix ) const +{ + return pointArray( QRect( -6, -6, d->m_geometry.width() + 12, d->m_geometry.height() + 12 ), matrix ); +} + +QRegion KoChild::frameRegion( const QWMatrix &matrix, bool solid ) const +{ + const QPointArray arr = framePointArray( matrix ); + const QRegion frameReg( arr ); + + if ( solid ) + return frameReg; + + const QRegion reg = region( matrix ); + return frameReg.subtract( reg ); +} + +QPointArray KoChild::pointArray( const QRect &r, const QWMatrix &matrix ) const +{ + QPoint topleft = d->m_matrix.map( QPoint( r.left(), r.top() ) ); + QPoint topright = d->m_matrix.map( QPoint( r.right(), r.top() ) ); + QPoint bottomleft = d->m_matrix.map( QPoint( r.left(), r.bottom() ) ); + QPoint bottomright = d->m_matrix.map( QPoint( r.right(), r.bottom() ) ); + + QPointArray arr( 4 ); + arr.setPoint( 0, topleft ); + arr.setPoint( 1, topright ); + arr.setPoint( 2, bottomright ); + arr.setPoint( 3, bottomleft ); + + for( int i = 0; i < 4; ++i ) + arr.setPoint( i, matrix.map( arr.point( i ) ) ); + + return arr; +} + +void KoChild::updateMatrix() +{ + QWMatrix r; + r.rotate( - d->m_rotation ); + QPoint p = r.map( QPoint( d->m_rotationPoint.x(), + d->m_rotationPoint.y() ) ); + + QWMatrix m; + m.rotate( d->m_rotation ); + m.translate( -d->m_rotationPoint.x() + d->m_geometry.x(), -d->m_rotationPoint.y() + d->m_geometry.y() ); + m.translate( p.x(), p.y() ); + m.shear( d->m_shearX, d->m_shearY ); + + d->m_matrix = m; +} + +QWMatrix KoChild::matrix() const +{ + return d->m_matrix; +} + +void KoChild::lock() +{ + if ( d->m_lock ) + return; + + d->m_old = framePointArray(); + d->m_lock = true; +} + +void KoChild::unlock() +{ + if ( !d->m_lock ) + return; + + d->m_lock = false; + emit changed( this ); +} + +bool KoChild::locked() const +{ + return d->m_lock; +} + +QPointArray KoChild::oldPointArray( const QWMatrix &matrix ) +{ + QPointArray arr = d->m_old; + + for( int i = 0; i < 4; ++i ) + arr.setPoint( i, matrix.map( arr.point( i ) ) ); + + return arr; +} + +void KoChild::setTransparent( bool transparent ) +{ + d->m_transparent = transparent; +} + +bool KoChild::isTransparent() const +{ + return d->m_transparent; +} + +KoChild::Gadget KoChild::gadgetHitTest( const QPoint &p ) +{ + if ( !frameRegion().contains( p ) ) + return NoGadget; + + if ( QRegion( pointArray( QRect( -5, -5, 5, 5 ) ) ).contains( p ) ) + return TopLeft; + if ( QRegion( pointArray( QRect( d->m_geometry.width() / 2 - 3, -5, 5, 5 ) ) ).contains( p ) ) + return TopMid; + if ( QRegion( pointArray( QRect( d->m_geometry.width(), -5, 5, 5 ) ) ).contains( p ) ) + return TopRight; + if ( QRegion( pointArray( QRect( -5, d->m_geometry.height() / 2 - 3, 5, 5 ) ) ).contains( p ) ) + return MidLeft; + if ( QRegion( pointArray( QRect( -5, d->m_geometry.height(), 5, 5 ) ) ).contains( p ) ) + return BottomLeft; + if ( QRegion( pointArray( QRect( d->m_geometry.width() / 2 - 3, + d->m_geometry.height(), 5, 5 ) ) ).contains( p ) ) + return BottomMid; + if ( QRegion( pointArray( QRect( d->m_geometry.width(), d->m_geometry.height(), 5, 5 ) ) ).contains( p ) ) + return BottomRight; + if ( QRegion( pointArray( QRect( d->m_geometry.width(), + d->m_geometry.height() / 2 - 3, 5, 5 ) ) ).contains( p ) ) + return MidRight; + + return Move; +} + +#include <KoChild.moc> diff --git a/lib/kofficecore/KoChild.h b/lib/kofficecore/KoChild.h new file mode 100644 index 00000000..26ad4a08 --- /dev/null +++ b/lib/kofficecore/KoChild.h @@ -0,0 +1,312 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@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 __koChild_h__ +#define __koChild_h__ + +#include <qobject.h> +#include <qwmatrix.h> +#include <koffice_export.h> + +/** + * KoChild is an abstract base class that represents the geometry + * associated with an embedded document. In general it handles its position + * relative to the embedded document's parent. + * + * In detail it handles size, matrix operations and can give you + * a clip region. It can deal with scaling, rotation etc. because it + * makes heavy usage of QWMatrix. + * + * After applying the matrix, viewGeometry() applies zooming, but can be + * reimplemented to also apply e.g. some translation by the application + * (e.g. for centering the page). + * + * @see KoDocumentChild KoViewChild + */ +class KOFFICECORE_EXPORT KoChild : public QObject +{ + Q_OBJECT +public: + + /** + * The gadget generally identifies where a child has been hit (generally + * by the mouse pointer). + * Based on this information different actions can be taken, for example + * moving the child or opening a context menu. NoGadget means that + * this child has not been hit. + * + * @see #gadgetHitTest + */ + enum Gadget { NoGadget, TopLeft, TopMid, TopRight, MidLeft, MidRight, + BottomLeft, BottomMid, BottomRight, Move }; + + KoChild( QObject *parent = 0, const char *name = 0 ); + virtual ~KoChild(); + + /** + * Sets a new geometry for this child document. + * Use noEmit = true if you do not want the 'changed'-signal to be emitted + */ + void setGeometry( const QRect &rect, bool noEmit = false ); + + /** + * @return the rectangle that would be used to display this + * child document if the child is not rotated or + * subject to some other geometric transformation. + * The rectangle is in the coordinate system of the parent, + * using unzoomed coordinates in points. + * + * @see #setGeometry + */ + QRect geometry() const; + + /** + * @return the region of this child part relative to the + * coordinate system of the parent. + * The region is transformed with the passed + * matrix. + */ + virtual QRegion region( const QWMatrix& = QWMatrix() ) const; + + /** + * @return the polygon which surrounds the child part. The points + * are in coordinates of the parent. + * The points are transformed with the + * passed matrix. + */ + virtual QPointArray pointArray( const QWMatrix &matrix = QWMatrix() ) const; + + /** + * Tests whether the part contains a certain point. The point is + * in the coordinate system of the parent. + */ + //virtual bool contains( const QPoint& ) const; + + /** + * @return the effective bounding rect after all transformations. + * The coordinates of the rectangle are in the coordinate system + * of the parent. + */ + QRect boundingRect() const; + + /** + * Scales the content of the child part. However, that does not + * affect the size of the child part. + */ + virtual void setScaling( double x, double y ); + + /** + * @return the x axis scaling of the child part + */ + virtual double xScaling() const; + + /** + * @return the y axis scaling of the child part + */ + virtual double yScaling() const; + + /** + * Shears the content of the child part. + */ + virtual void setShearing( double x, double y ); + + /** + * @return the x axis shearing of the child part + */ + virtual double xShearing() const; + + /** + * @return the y axis shearing of the child part + */ + virtual double yShearing() const; + + /** + * Sets the angle of rotation. + */ + virtual void setRotation( double ); + + /** + * @return the angle of rotation + */ + virtual double rotation() const; + + /** + * Sets the center of the rotation to the point @p pos. + */ + virtual void setRotationPoint( const QPoint& pos ); + + /** + * @return the center of the rotation + */ + virtual QPoint rotationPoint() const; + + /** + * @return true if the child part is an orthogonal rectangle relative + * to its parents coordinate system. + */ + bool isRectangle() const; + + /** + * Sets the clip region of the painter, so that only pixels of the + * child part can be drawn. + * + * @param painter the painter do modify. + * @param combine tells whether the new clip region is an intersection + * of the current region with the childs region or whether only + * the childs region is set. + */ + virtual void setClipRegion( QPainter& painter, bool combine = true ); + + /** + * Transforms the painter (its worldmatrix and the clipping) + * in such a way that the painter can be passed to the child part + * for drawing. + */ + virtual void transform( QPainter& painter ); + + /** + * Sets the position of the content relative to the child frame. + * This can be used to create a border between the frame border + * and the actual content. + */ + virtual void setContentsPos( int x, int y ); + + /** + * @return the contents rectangle that is visible. + * This value depends on the scaling and the geometry. + * This is the value that is passed to KoDocument::paintContent. + * + * @see #xScaling #geometry + */ + virtual QRect contentRect() const; + + /** + * @return the region of the child frame. + * If solid is set to true the complete area of the child region + * is returned, otherwise only the child border is returned. + */ + virtual QRegion frameRegion( const QWMatrix& matrix = QWMatrix(), bool solid = false ) const; + + /** + * @return the frame geometry including a border (6 pixels) as a point + * array with 4 points, one for each corner, transformed by given matrix. + */ + virtual QPointArray framePointArray( const QWMatrix &matrix = QWMatrix() ) const; + + /** + * @return the current transformation of this child as matrix. + * This includes translation, scale, rotation, shearing. + * + * @see #updateMatrix + */ + virtual QWMatrix matrix() const; + + /** + * Locks this child and stores the current transformation. + * A locked child does not emit changed signals. + * + * This is useful if a series of changes are done on this + * child and only the final result is of interest (GUI updating,...). + * + * @see #locked #unlock + */ + void lock(); + + /** + * Unlocks this child and emits a changed signal. + */ + void unlock(); + + /** + * If the child is locked, geometry changes + * (including scaling, rotation, ...) are not backed up. + * + * As long as this child is locked, the backed up + * geometry state can be recovered with oldPointArray. + * + * @return true when this child is locked. + * + * @see #locked #unlock #oldPointArray + */ + bool locked() const; + + /** + * @return the backed up geometry transformed by given matrix. + */ + virtual QPointArray oldPointArray( const QWMatrix &matrix ); + + /** + * Marks this child as either transparent or not. + * @param transparent set this child to transparent (true) + * or opaque (false). + * + * @see #isTransparent + */ + virtual void setTransparent( bool transparent ); + + /** + * It might be interesting for view updates and repainting in general + * whether a child is transparent or not. + * @return true when this child is marked as transparent. + */ + virtual bool isTransparent() const; + + /** + * Different actions are taken depending on where a child frame is + * hit. Two gadgets are known: one for the border (5 pixels) and one + * for the inner area. + * @return the gadget identification for the hit area. + * @param p the hit position. + * + * @see #Gadget + */ + virtual Gadget gadgetHitTest( const QPoint& p ); + +signals: + + /** + * Emitted every time this child changes, but only if this child is not + * locked. + * @see #locked + */ + void changed( KoChild *thisChild ); + +protected: + + /** + * @return point array with the 4 corners of given rectangle, which is + * transformed by given matrix. + * + * @param matrix the transformation of r. + * @param r the rectangle for which the point array should be created. + */ + virtual QPointArray pointArray( const QRect& r, const QWMatrix& matrix = QWMatrix() ) const; + + /** + * Stores the current transformation of this child into a matrix. + * + * @see #matrix + */ + virtual void updateMatrix(); +private: + + class KoChildPrivate; + KoChildPrivate *d; +}; + +#endif diff --git a/lib/kofficecore/KoContainerHandler.cpp b/lib/kofficecore/KoContainerHandler.cpp new file mode 100644 index 00000000..78060592 --- /dev/null +++ b/lib/kofficecore/KoContainerHandler.cpp @@ -0,0 +1,395 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999, 2000 Torben Weis <weis@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 "KoContainerHandler.h" +#include <KoView.h> +#include <math.h> +#include <kcursor.h> +#include <kdebug.h> + +KoEventHandler::KoEventHandler( QObject* target ) +{ + m_target = target; + + m_target->installEventFilter( this ); +} + +KoEventHandler::~KoEventHandler() +{ +} + +QObject* KoEventHandler::target() +{ + return m_target; +} + +// ------------------------------------------------------ + +class KoPartResizeHandlerPrivate { +public: + KoPartResizeHandlerPrivate( const QWMatrix& matrix, KoView *view, KoChild* child, + KoChild::Gadget gadget, const QPoint& point ) : + m_gadget(gadget), m_view(view), m_child(child), m_parentMatrix(matrix) { + + m_geometryStart = child->geometry(); + m_matrix = child->matrix() * matrix; + m_invertParentMatrix = matrix.invert(); + + bool ok = true; + m_invert = m_matrix.invert( &ok ); + Q_ASSERT( ok ); + m_mouseStart = m_invert.map( m_invertParentMatrix.map( point ) ); + } + ~KoPartResizeHandlerPrivate() {} + + KoChild::Gadget m_gadget; + QPoint m_mouseStart; + QRect m_geometryStart; + KoView* m_view; + KoChild* m_child; + QWMatrix m_invert; + QWMatrix m_matrix; + QWMatrix m_parentMatrix; + QWMatrix m_invertParentMatrix; +}; + +KoPartResizeHandler::KoPartResizeHandler( QWidget* widget, const QWMatrix& matrix, KoView* view, KoChild* child, + KoChild::Gadget gadget, const QPoint& point ) + : KoEventHandler( widget ) +{ + child->lock(); + d=new KoPartResizeHandlerPrivate(matrix, view, child, gadget, point); +} + +KoPartResizeHandler::~KoPartResizeHandler() +{ + d->m_child->unlock(); + delete d; + d=0L; +} + +void KoPartResizeHandler::repaint(QRegion &rgn) +{ + rgn = rgn.unite( d->m_child->frameRegion( d->m_parentMatrix, true ) ); + // rgn.translate(- d->m_view->canvasXOffset(), - d->m_view->canvasYOffset()); + ((QWidget*)target())->repaint( rgn ); +} + +bool KoPartResizeHandler::eventFilter( QObject*, QEvent* ev ) +{ + if ( ev->type() == QEvent::MouseButtonRelease ) + { + delete this; + return true; + } + else if ( ev->type() == QEvent::MouseMove ) + { + QMouseEvent* e = (QMouseEvent*)ev; + QPoint p = d->m_invert.map( d->m_invertParentMatrix.map( e->pos() /*+ QPoint(d->m_view->canvasXOffset(), d->m_view->canvasYOffset()) */ ) ); + QRegion rgn( d->m_child->frameRegion( d->m_parentMatrix, true ) ); + + double x1_x, x1_y, x2_x, x2_y; + d->m_matrix.map( double( p.x() ), 0.0, &x1_x, &x1_y ); + d->m_matrix.map( double( d->m_mouseStart.x() ), 0.0, &x2_x, &x2_y ); + double y1_x, y1_y, y2_x, y2_y; + d->m_matrix.map( 0.0, double( p.y() ), &y1_x, &y1_y ); + d->m_matrix.map( 0.0, double( d->m_mouseStart.y() ), &y2_x, &y2_y ); + + double dx = x2_x - x1_x; + double dy = x2_y - x1_y; + int x = int( sqrt( dx * dx + dy * dy ) * ( d->m_mouseStart.x() < p.x() ? 1.0 : -1.0 ) ); + + dx = y2_x - y1_x; + dy = y2_y - y1_y; + int y = int( sqrt( dx * dx + dy * dy ) * ( d->m_mouseStart.y() < p.y() ? 1.0 : -1.0 ) ); + + switch( d->m_gadget ) + { + case KoChild::TopLeft: + { + x = QMIN( d->m_geometryStart.width() - 1, x ); + y = QMIN( d->m_geometryStart.height() - 1, y ); + + d->m_child->setGeometry( QRect( d->m_geometryStart.x() + x, d->m_geometryStart.y() + y, + d->m_geometryStart.width() - x, d->m_geometryStart.height() - y ) ); + repaint(rgn); + } + break; + case KoChild::TopMid: + { + y = QMIN( d->m_geometryStart.height() - 1, y ); + + d->m_child->setGeometry( QRect( d->m_geometryStart.x(), d->m_geometryStart.y() + y, + d->m_geometryStart.width(), d->m_geometryStart.height() - y ) ); + repaint(rgn); + } + break; + case KoChild::TopRight: + { + x = QMAX( -d->m_geometryStart.width() + 1, x ); + y = QMIN( d->m_geometryStart.height() - 1, y ); + + d->m_child->setGeometry( QRect( d->m_geometryStart.x(), d->m_geometryStart.y() + y, + d->m_geometryStart.width() + x, d->m_geometryStart.height() - y ) ); + repaint(rgn); + } + break; + case KoChild::MidLeft: + { + x = QMIN( d->m_geometryStart.width() - 1, x ); + + d->m_child->setGeometry( QRect( d->m_geometryStart.x() + x, d->m_geometryStart.y(), + d->m_geometryStart.width() - x, d->m_geometryStart.height() ) ); + repaint(rgn); + } + break; + case KoChild::MidRight: + { + x = QMAX( -d->m_geometryStart.width() + 1, x ); + + d->m_child->setGeometry( QRect( d->m_geometryStart.x(), d->m_geometryStart.y(), + d->m_geometryStart.width() + x, d->m_geometryStart.height() ) ); + repaint(rgn); + } + break; + case KoChild::BottomLeft: + { + x = QMIN( d->m_geometryStart.width() - 1, x ); + y = QMAX( -d->m_geometryStart.height() + 1, y ); + + d->m_child->setGeometry( QRect( d->m_geometryStart.x() + x, d->m_geometryStart.y(), + d->m_geometryStart.width() - x, d->m_geometryStart.height() + y ) ); + repaint(rgn); + } + break; + case KoChild::BottomMid: + { + y = QMAX( -d->m_geometryStart.height() + 1, y ); + + d->m_child->setGeometry( QRect( d->m_geometryStart.x(), d->m_geometryStart.y(), + d->m_geometryStart.width(), d->m_geometryStart.height() + y ) ); + repaint(rgn); + } + break; + case KoChild::BottomRight: + { + x = QMAX( -d->m_geometryStart.width() + 1, x ); + y = QMAX( -d->m_geometryStart.height() + 1, y ); + + d->m_child->setGeometry( QRect( d->m_geometryStart.x(), d->m_geometryStart.y(), + d->m_geometryStart.width() + x, d->m_geometryStart.height() + y ) ); + repaint(rgn); + } + break; + default: + Q_ASSERT( 0 ); + } + return true; + } + return false; +} + +// -------------------------------------------------------------- + +class KoPartMoveHandlerPrivate { +public: + KoPartMoveHandlerPrivate( const QWMatrix& matrix, KoView* view, KoChild* child, + const QPoint& point) : m_view(view), m_dragChild(child), + m_parentMatrix(matrix) { + m_invertParentMatrix = matrix.invert(); + m_mouseDragStart = m_invertParentMatrix.map( point ); + m_geometryDragStart = m_dragChild->geometry(); + m_rotationDragStart = m_dragChild->rotationPoint(); + } + ~KoPartMoveHandlerPrivate() {} + + KoView* m_view; + KoChild* m_dragChild; + QPoint m_mouseDragStart; + QRect m_geometryDragStart; + QPoint m_rotationDragStart; + QWMatrix m_invertParentMatrix; + QWMatrix m_parentMatrix; +}; + +KoPartMoveHandler::KoPartMoveHandler( QWidget* widget, const QWMatrix& matrix, KoView* view, KoChild* child, + const QPoint& point ) + : KoEventHandler( widget ) +{ + child->lock(); + d=new KoPartMoveHandlerPrivate(matrix, view, child, point); +} + +KoPartMoveHandler::~KoPartMoveHandler() +{ + d->m_dragChild->unlock(); + delete d; + d=0L; +} + +bool KoPartMoveHandler::eventFilter( QObject*, QEvent* ev ) +{ + if ( ev->type() == QEvent::MouseButtonRelease ) + { + delete this; + return true; + } + else if ( ev->type() == QEvent::MouseMove ) + { + QMouseEvent* e = (QMouseEvent*)ev; + + QRegion bound = d->m_dragChild->frameRegion( d->m_parentMatrix, true ); + QPoint pos = d->m_invertParentMatrix.map( e->pos() /* + QPoint(d->m_view->canvasXOffset(), d->m_view->canvasYOffset()) */ ); + d->m_dragChild->setGeometry( QRect( d->m_geometryDragStart.x() + pos.x() - d->m_mouseDragStart.x(), + d->m_geometryDragStart.y() + pos.y() - d->m_mouseDragStart.y(), + d->m_geometryDragStart.width(), d->m_geometryDragStart.height() ) ); + d->m_dragChild->setRotationPoint( QPoint( d->m_rotationDragStart.x() + pos.x() - d->m_mouseDragStart.x(), + d->m_rotationDragStart.y() + pos.y() - d->m_mouseDragStart.y() ) ); + bound = bound.unite( d->m_dragChild->frameRegion( d->m_parentMatrix, false ) ); + // bound.translate(- d->m_view->canvasXOffset(), - d->m_view->canvasYOffset()); + ((QWidget*)target())->repaint( bound ); + + return true; + } + + return false; +} + +// ------------------------------------------------------- + +KoContainerHandler::KoContainerHandler( KoView* view, QWidget* widget ) + : KoEventHandler( widget ) +{ + m_view = view; +} + +KoContainerHandler::~KoContainerHandler() +{ +} + +bool KoContainerHandler::eventFilter( QObject*, QEvent* ev ) +{ + if ( ev->type() == QEvent::KeyPress ) + { + QKeyEvent* keyEvent=(QKeyEvent*)ev; + + KoChild* child=m_view->selectedChild(); + + if (child != 0) + { + if (keyEvent->key()==Qt::Key_Delete) + emit deleteChild(child); + } + } + + if ( ev->type() == QEvent::MouseButtonPress ) + { + KoChild::Gadget gadget; + QPoint pos; + QMouseEvent *e=static_cast<QMouseEvent*>(ev); + KoChild *ch=child(gadget, pos, e); + + if ( e->button() == RightButton && gadget != KoChild::NoGadget ) + { + emit popupMenu( ch, e->globalPos() ); + return true; + } + else if ( e->button() == LeftButton && gadget == KoChild::Move ) + { + (void)new KoPartMoveHandler( static_cast<QWidget*>(target()), m_view->matrix(), m_view, ch, pos ); + return true; + } + else if ( e->button() == LeftButton && gadget != KoChild::NoGadget ) + { + (void)new KoPartResizeHandler( static_cast<QWidget*>(target()), m_view->matrix(), m_view, ch, gadget, pos ); + return true; + } + return false; + } + else if ( ev->type() == QEvent::MouseMove ) + { + QWidget *targetWidget = static_cast<QWidget *>( target() ); + KoChild::Gadget gadget; + QPoint pos; + QMouseEvent *e=static_cast<QMouseEvent*>(ev); + child(gadget, pos, e); + + bool retval = true; + if ( gadget == KoChild::NoGadget ) + retval = false; + + if ( gadget == KoChild::TopLeft || gadget == KoChild::BottomRight ) + targetWidget->setCursor( sizeFDiagCursor ); + else if ( gadget == KoChild::TopRight || gadget == KoChild::BottomLeft ) + targetWidget->setCursor( sizeBDiagCursor ); + else if ( gadget == KoChild::TopMid || gadget == KoChild::BottomMid ) + targetWidget->setCursor( sizeVerCursor ); + else if ( gadget == KoChild::MidLeft || gadget == KoChild::MidRight ) + targetWidget->setCursor( sizeHorCursor ); + else if ( gadget == KoChild::Move ) + targetWidget->setCursor( KCursor::handCursor() ); + else + { +// targetWidget->setCursor( arrowCursor ); + return false; + } + return retval; + } + return false; +} + +KoChild *KoContainerHandler::child(KoChild::Gadget &gadget, QPoint &pos, const QMouseEvent *ev) { + + pos = ev->pos(); //+ QPoint(m_view->canvasXOffset(), m_view->canvasYOffset()); + + pos = m_view->reverseViewTransformations( pos ); + + KoChild *child = 0; + KoDocumentChild* docChild = m_view->selectedChild(); + gadget = KoChild::NoGadget; + if ( docChild ) + { + KoViewChild *viewChild = m_view->child( docChild->document() ); + + if ( viewChild ) + child = viewChild; + else + child = docChild; + + gadget = child->gadgetHitTest( pos ); + } + if ( gadget == KoChild::NoGadget ) + { + docChild = m_view->activeChild(); + if ( docChild ) + { + KoViewChild *viewChild = m_view->child( docChild->document() ); + + if ( viewChild ) + child = viewChild; + else + child = docChild; + + gadget = child->gadgetHitTest( pos ); + } + } + return child; +} + +#include "KoContainerHandler.moc" diff --git a/lib/kofficecore/KoContainerHandler.h b/lib/kofficecore/KoContainerHandler.h new file mode 100644 index 00000000..d34d0500 --- /dev/null +++ b/lib/kofficecore/KoContainerHandler.h @@ -0,0 +1,137 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999, 2000 Torben Weis <weis@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 HANDLER_H +#define HANDLER_H + +#include <qobject.h> +#include <KoDocumentChild.h> + +class QWMatrix; + +class KoView; +class KoPartResizeHandlerPrivate; +class KoPartMoveHandlerPrivate; + +/** + * @brief An abstract base class for event handlers. + * + * The idea of an event handler is that it is created for a + * certain purpose, for example moving or resizing of a part. + * Once that action is finished, the handler will destroy + * itself. + * + * This design pattern helps you to keep your event filters + * and your mousePressEvent, mouseMoveEvent etc. methods clean. + */ +class KOFFICECORE_EXPORT KoEventHandler : public QObject +{ + Q_OBJECT +public: + KoEventHandler( QObject* target ); + ~KoEventHandler(); + + QObject* target(); + +private: + QObject* m_target; +}; + +/** + * Used by @ref KoContainerHandler internally to handle resizing of + * embedded documents. + */ +class KoPartResizeHandler : public KoEventHandler +{ + Q_OBJECT +public: + KoPartResizeHandler( QWidget* widget, const QWMatrix& matrix, KoView* view, KoChild* child, + KoChild::Gadget gadget, const QPoint& point ); + ~KoPartResizeHandler(); + +protected: + void repaint(QRegion &rgn); + bool eventFilter( QObject*, QEvent* ); + +private: + KoPartResizeHandlerPrivate *d; +}; + +/** + * Used by @ref KoContainerHandler internally to handle moving of + * embedded documents. + */ +class KoPartMoveHandler : public KoEventHandler +{ + Q_OBJECT +public: + KoPartMoveHandler( QWidget* widget, const QWMatrix& matrix, KoView* view, KoChild* child, + const QPoint& point ); + ~KoPartMoveHandler(); + +protected: + bool eventFilter( QObject*, QEvent* ); + +private: + KoPartMoveHandlerPrivate *d; +}; + +/** + * This class can handle moving and resizing of embedded + * documents in your class derived from @ref KoView. + * + * Just create one instance per view of this class and parts + * will magically be moved around on your document. + * + * This class acts like an event filter on your view, so the + * mouse events which are used for parts moving and resizing + * will never show up in your view. + * + * @see KoPartMoveHandlerPrivate + * @see KoPartResizeHandlerPrivate + */ +class KOFFICECORE_EXPORT KoContainerHandler : public KoEventHandler +{ + Q_OBJECT +public: + KoContainerHandler( KoView* view, QWidget* widget ); + ~KoContainerHandler(); + +signals: + /** + * Emitted if the user wants to open the popup menu for some + * child object. + */ + void popupMenu( KoChild*, const QPoint& global_pos ); + + /** + * Emitted if the user pressed the delete key whilst a child was selected + */ + void deleteChild( KoChild* ); + +protected: + bool eventFilter( QObject*, QEvent* ); + +private: + /// This is a little helper function to get rid of some duplicated code + KoChild *child(KoChild::Gadget &gadget, QPoint &pos, const QMouseEvent *ev); + KoView* m_view; +}; + +#endif diff --git a/lib/kofficecore/KoDetailsPane.cpp b/lib/kofficecore/KoDetailsPane.cpp new file mode 100644 index 00000000..d94a01f7 --- /dev/null +++ b/lib/kofficecore/KoDetailsPane.cpp @@ -0,0 +1,470 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Peter Simonsson <psn@linux.se> + + 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 "KoDetailsPane.h" + +#include <qcheckbox.h> +#include <qlabel.h> +#include <qfile.h> +#include <qimage.h> +#include <qheader.h> +#include <qrect.h> +#include <qbrush.h> +#include <qpainter.h> +#include <qsimplerichtext.h> +#include <qevent.h> +#include <qsplitter.h> + +#include <kinstance.h> +#include <klocale.h> +#include <klistview.h> +#include <kpushbutton.h> +#include <kconfig.h> +#include <kurl.h> +#include <kfileitem.h> +#include <kio/previewjob.h> +#include <kdebug.h> +#include <ktextbrowser.h> +#include <kapplication.h> + +#include "KoTemplates.h" + +class KoFileListItem : public KListViewItem +{ + public: + KoFileListItem(KListView* listView, QListViewItem* after, const QString& filename, + const QString& fullPath, KFileItem* fileItem) + : KListViewItem(listView, after, filename, fullPath), m_fileItem(fileItem) + { + } + + ~KoFileListItem() + { + delete m_fileItem; + } + + KFileItem* fileItem() const + { + return m_fileItem; + } + + private: + KFileItem* m_fileItem; +}; + +class KoTemplatesPanePrivate +{ + public: + KoTemplatesPanePrivate() + : m_instance(0), m_selected(false) + { + } + + KInstance* m_instance; + bool m_selected; + QString m_alwaysUseTemplate; +}; + + +KoTemplatesPane::KoTemplatesPane(QWidget* parent, KInstance* instance, + KoTemplateGroup *group, KoTemplate* /*defaultTemplate*/) + : KoDetailsPaneBase(parent, "TemplatesPane") +{ + d = new KoTemplatesPanePrivate; + d->m_instance = instance; + m_previewLabel->installEventFilter(this); + m_documentList->installEventFilter(this); + setFocusProxy(m_documentList); +#if KDE_IS_VERSION(3,4,0) + m_documentList->setShadeSortColumn(false); +#endif + + KGuiItem openGItem(i18n("Use This Template")); + m_openButton->setGuiItem(openGItem); + m_documentList->header()->hide(); + KConfigGroup cfgGrp(d->m_instance->config(), "TemplateChooserDialog"); + QString fullTemplateName = cfgGrp.readPathEntry("FullTemplateName"); + d->m_alwaysUseTemplate = cfgGrp.readPathEntry("AlwaysUseTemplate"); + connect(m_alwaysUseCheckBox, SIGNAL(clicked()), this, SLOT(alwaysUseClicked())); + changePalette(); + + if(kapp) { + connect(kapp, SIGNAL(kdisplayPaletteChanged()), this, SLOT(changePalette())); + } + + KListViewItem* selectItem = 0; + + for (KoTemplate* t = group->first(); t != 0L; t = group->next()) { + KListViewItem* item = new KListViewItem(m_documentList, t->name(), t->description(), t->file()); + QImage icon = t->loadPicture(instance).convertToImage(); + icon = icon.smoothScale(64, 64, QImage::ScaleMin); + icon.setAlphaBuffer(true); + icon = icon.copy((icon.width() - 64) / 2, (icon.height() - 64) / 2, 64, 64); + item->setPixmap(0, QPixmap(icon)); + item->setPixmap(2, t->loadPicture(instance)); + + if(d->m_alwaysUseTemplate == t->file()) { + selectItem = item; + } else if(!selectItem && (t->file() == fullTemplateName)) { + selectItem = item; + } + } + + connect(m_documentList, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(selectionChanged(QListViewItem*))); + connect(m_documentList, SIGNAL(doubleClicked(QListViewItem*, const QPoint&, int)), + this, SLOT(openTemplate(QListViewItem*))); + connect(m_documentList, SIGNAL(returnPressed(QListViewItem*)), + this, SLOT(openTemplate(QListViewItem*))); + connect(m_openButton, SIGNAL(clicked()), this, SLOT(openTemplate())); + + if(selectItem) { + m_documentList->setSelected(selectItem, true); + d->m_selected = true; + } else { + m_documentList->setSelected(m_documentList->firstChild(), true); + } +} + +KoTemplatesPane::~KoTemplatesPane() +{ + delete d; +} + +void KoTemplatesPane::selectionChanged(QListViewItem* item) +{ + if(item) { + m_openButton->setEnabled(true); + m_alwaysUseCheckBox->setEnabled(true); + m_titleLabel->setText(item->text(0)); + m_previewLabel->setPixmap(*(item->pixmap(2))); + m_detailsLabel->setText(item->text(1)); + m_alwaysUseCheckBox->setChecked(item->text(2) == d->m_alwaysUseTemplate); + } else { + m_openButton->setEnabled(false); + m_alwaysUseCheckBox->setEnabled(false); + m_alwaysUseCheckBox->setChecked(false); + m_titleLabel->setText(QString::null); + m_previewLabel->setPixmap(QPixmap()); + } +} + +void KoTemplatesPane::openTemplate() +{ + QListViewItem* item = m_documentList->selectedItem(); + openTemplate(item); +} + +void KoTemplatesPane::openTemplate(QListViewItem* item) +{ + if(item) { + KConfigGroup cfgGrp(d->m_instance->config(), "TemplateChooserDialog"); + cfgGrp.writePathEntry("FullTemplateName", item->text(2)); + cfgGrp.writeEntry("LastReturnType", "Template"); + cfgGrp.writeEntry("AlwaysUseTemplate", d->m_alwaysUseTemplate); + emit openTemplate(item->text(2)); + } +} + +void KoTemplatesPane::changePalette() +{ + QPalette p = kapp ? kapp->palette() : palette(); + p.setBrush(QColorGroup::Base, p.brush(QPalette::Normal, QColorGroup::Background)); + p.setColor(QColorGroup::Text, p.color(QPalette::Normal, QColorGroup::Foreground)); + m_detailsLabel->setPalette(p); +} + +bool KoTemplatesPane::isSelected() +{ + return d->m_selected; +} + +void KoTemplatesPane::alwaysUseClicked() +{ + QListViewItem* item = m_documentList->selectedItem(); + + if(!m_alwaysUseCheckBox->isChecked()) { + KConfigGroup cfgGrp(d->m_instance->config(), "TemplateChooserDialog"); + cfgGrp.writeEntry("AlwaysUseTemplate", QString::null); + d->m_alwaysUseTemplate = QString::null; + } else { + d->m_alwaysUseTemplate = item->text(2); + } + + emit alwaysUseChanged(this, d->m_alwaysUseTemplate); +} + +void KoTemplatesPane::changeAlwaysUseTemplate(KoTemplatesPane* sender, const QString& alwaysUse) +{ + if(this == sender) + return; + + QListViewItem* item = m_documentList->selectedItem(); + + // If the old always use template is selected uncheck the checkbox + if(item && (item->text(2) == d->m_alwaysUseTemplate)) { + m_alwaysUseCheckBox->setChecked(false); + } + + d->m_alwaysUseTemplate = alwaysUse; +} + +bool KoTemplatesPane::eventFilter(QObject* watched, QEvent* e) +{ + if(watched == m_previewLabel) { + if(e->type() == QEvent::MouseButtonDblClick) { + openTemplate(); + } + } + + if(watched == m_documentList) { + if((e->type() == QEvent::Resize) && isShown()) { + emit splitterResized(this, m_splitter->sizes()); + } + } + + return false; +} + +void KoTemplatesPane::resizeSplitter(KoDetailsPaneBase* sender, const QValueList<int>& sizes) +{ + if(sender == this) + return; + + m_splitter->setSizes(sizes); +} + + +class KoRecentDocumentsPanePrivate +{ + public: + KoRecentDocumentsPanePrivate() + : m_previewJob(0), m_instance(0) + { + } + + ~KoRecentDocumentsPanePrivate() + { + if(m_previewJob) + m_previewJob->kill(); + } + + KIO::PreviewJob* m_previewJob; + KInstance* m_instance; +}; + +KoRecentDocumentsPane::KoRecentDocumentsPane(QWidget* parent, KInstance* instance) + : KoDetailsPaneBase(parent, "RecentDocsPane") +{ + d = new KoRecentDocumentsPanePrivate; + d->m_instance = instance; + m_previewLabel->installEventFilter(this); + m_documentList->installEventFilter(this); + setFocusProxy(m_documentList); + KGuiItem openGItem(i18n("Open This Document"), "fileopen"); + m_openButton->setGuiItem(openGItem); + m_alwaysUseCheckBox->hide(); + m_documentList->header()->hide(); + m_documentList->setSorting(-1); // Disable sorting + changePalette(); + + if(kapp) { + connect(kapp, SIGNAL(kdisplayPaletteChanged()), this, SLOT(changePalette())); + } + + QString oldGroup = instance->config()->group(); + instance->config()->setGroup("RecentFiles"); + + int i = 0; + QString value; + KFileItemList fileList; + fileList.setAutoDelete(false); + + do { + QString key = QString("File%1").arg(i); + value = instance->config()->readPathEntry(key); + + if(!value.isEmpty()) { + QString path = value; + QString name; + + // Support for kdelibs-3.5's new RecentFiles format: name[url] + if(path.endsWith("]")) { + int pos = path.find("["); + name = path.mid(0, pos - 1); + path = path.mid(pos + 1, path.length() - pos - 2); + } + + KURL url(path); + + if(name.isEmpty()) + name = url.filename(); + + if(!url.isLocalFile() || QFile::exists(url.path())) { + KFileItem* fileItem = new KFileItem(KFileItem::Unknown, KFileItem::Unknown, url); + fileList.append(fileItem); + KoFileListItem* item = new KoFileListItem(m_documentList, + m_documentList->lastItem(), name, url.url(), fileItem); + //center all icons in 64x64 area + QImage icon = fileItem->pixmap(64).convertToImage(); + icon.setAlphaBuffer(true); + icon = icon.copy((icon.width() - 64) / 2, (icon.height() - 64) / 2, 64, 64); + item->setPixmap(0, QPixmap(icon)); + item->setPixmap(2, fileItem->pixmap(128)); + } + } + + i++; + } while ( !value.isEmpty() || i<=10 ); + + instance->config()->setGroup( oldGroup ); + + connect(m_documentList, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(selectionChanged(QListViewItem*))); + connect(m_documentList, SIGNAL(clicked(QListViewItem*)), + this, SLOT(selectionChanged(QListViewItem*))); + connect(m_documentList, SIGNAL(doubleClicked(QListViewItem*, const QPoint&, int)), + this, SLOT(openFile(QListViewItem*))); + connect(m_documentList, SIGNAL(returnPressed(QListViewItem*)), + this, SLOT(openFile(QListViewItem*))); + connect(m_openButton, SIGNAL(clicked()), this, SLOT(openFile())); + + m_documentList->setSelected(m_documentList->firstChild(), true); + + d->m_previewJob = KIO::filePreview(fileList, 200, 200); + + connect(d->m_previewJob, SIGNAL(result(KIO::Job*)), this, SLOT(previewResult(KIO::Job*))); + connect(d->m_previewJob, SIGNAL(gotPreview(const KFileItem*, const QPixmap&)), + this, SLOT(updatePreview(const KFileItem*, const QPixmap&))); +} + +KoRecentDocumentsPane::~KoRecentDocumentsPane() +{ + delete d; +} + +void KoRecentDocumentsPane::selectionChanged(QListViewItem* item) +{ + if(item) { + m_openButton->setEnabled(true); + m_titleLabel->setText(item->text(0)); + m_previewLabel->setPixmap(*(item->pixmap(2))); + + if(static_cast<KoFileListItem*>(item)->fileItem()) { + KFileItem* fileItem = static_cast<KoFileListItem*>(item)->fileItem(); + QString details = "<center><table border=\"0\">"; + details += i18n("File modification date and time. %1 is date time", "<tr><td><b>Modified:</b></td><td>%1</td></tr>") + .arg(fileItem->timeString(KIO::UDS_MODIFICATION_TIME)); + details += i18n("File access date and time. %1 is date time", "<tr><td><b>Accessed:</b></td><td>%1</td></tr>") + .arg(fileItem->timeString(KIO::UDS_ACCESS_TIME)); + details += "</table></center>"; + m_detailsLabel->setText(details); + } else { + m_detailsLabel->setText(QString::null); + } + } else { + m_openButton->setEnabled(false); + m_titleLabel->setText(QString::null); + m_previewLabel->setPixmap(QPixmap()); + m_detailsLabel->setText(QString::null); + } +} + +void KoRecentDocumentsPane::openFile() +{ + QListViewItem* item = m_documentList->selectedItem(); + openFile(item); +} + +void KoRecentDocumentsPane::openFile(QListViewItem* item) +{ + KConfigGroup cfgGrp(d->m_instance->config(), "TemplateChooserDialog"); + cfgGrp.writeEntry("LastReturnType", "File"); + + if(item) + emit openFile(item->text(1)); +} + +void KoRecentDocumentsPane::previewResult(KIO::Job* job) +{ + if(d->m_previewJob == job) + d->m_previewJob = 0; +} + +void KoRecentDocumentsPane::updatePreview(const KFileItem* fileItem, const QPixmap& preview) +{ + if(preview.isNull()) { + return; + } + + QListViewItemIterator it(m_documentList); + + while(it.current()) { + if(it.current()->text(1) == fileItem->url().url()) { + it.current()->setPixmap(2, preview); + QImage icon = preview.convertToImage(); + icon = icon.smoothScale(64, 64, QImage::ScaleMin); + icon.setAlphaBuffer(true); + icon = icon.copy((icon.width() - 64) / 2, (icon.height() - 64) / 2, 64, 64); + it.current()->setPixmap(0, QPixmap(icon)); + + if(it.current()->isSelected()) { + m_previewLabel->setPixmap(preview); + } + + break; + } + + it++; + } +} + +void KoRecentDocumentsPane::changePalette() +{ + QPalette p = kapp ? kapp->palette() : palette(); + p.setBrush(QColorGroup::Base, p.brush(QPalette::Normal, QColorGroup::Background)); + p.setColor(QColorGroup::Text, p.color(QPalette::Normal, QColorGroup::Foreground)); + m_detailsLabel->setPalette(p); +} + +bool KoRecentDocumentsPane::eventFilter(QObject* watched, QEvent* e) +{ + if(watched == m_previewLabel) { + if(e->type() == QEvent::MouseButtonDblClick) { + openFile(); + } + } + + if(watched == m_documentList) { + if((e->type() == QEvent::Resize) && isShown()) { + emit splitterResized(this, m_splitter->sizes()); + } + } + + return false; +} + +void KoRecentDocumentsPane::resizeSplitter(KoDetailsPaneBase* sender, const QValueList<int>& sizes) +{ + if(sender == this) + return; + + m_splitter->setSizes(sizes); +} + +#include "KoDetailsPane.moc" diff --git a/lib/kofficecore/KoDetailsPane.h b/lib/kofficecore/KoDetailsPane.h new file mode 100644 index 00000000..96a92b98 --- /dev/null +++ b/lib/kofficecore/KoDetailsPane.h @@ -0,0 +1,132 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Peter Simonsson <psn@linux.se> + + 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 KODETAILSPANE_H +#define KODETAILSPANE_H + +#include <klistview.h> + +#include "koDetailsPaneBase.h" + +class KoTemplateGroup; +class KoTemplate; +class KInstance; +class QListViewItem; +class KoRecentDocumentsPanePrivate; +class KoRichTextListItemPrivate; +class KFileItem; +class QPixmap; + +namespace KIO { + class Job; +} + +class KoTemplatesPanePrivate; + +/** + * This widget is the right-side part of the template opening widget. + * The parent widget is initial widget in the document space of each KOffice component. + * This widget shows a list of templates and can show their details or open it. + */ +class KoTemplatesPane : public KoDetailsPaneBase +{ + Q_OBJECT + public: + /** + * Constructor. + * @param parent the parent widget + * @param instance the instance object for the app + * @param group the group of templates this widget will show. + * @param defaultTemplate pointer to the default template. Used to select a + * template when none has been selected before. + */ + KoTemplatesPane(QWidget* parent, KInstance* instance, + KoTemplateGroup* group, KoTemplate* defaultTemplate); + ~KoTemplatesPane(); + + /// Returns true if a template in this group was the last one selected + bool isSelected(); + + virtual bool eventFilter(QObject* watched, QEvent* e); + + signals: + void openTemplate(const QString&); + /// Emited when the always use checkbox is selected + void alwaysUseChanged(KoTemplatesPane* sender, const QString& alwaysUse); + + void splitterResized(KoDetailsPaneBase* sender, const QValueList<int>& sizes); + + public slots: + void resizeSplitter(KoDetailsPaneBase* sender, const QValueList<int>& sizes); + + protected slots: + void selectionChanged(QListViewItem* item); + void openTemplate(); + void openTemplate(QListViewItem* item); + void alwaysUseClicked(); + void changeAlwaysUseTemplate(KoTemplatesPane* sender, const QString& alwaysUse); + + void changePalette(); + + private: + KoTemplatesPanePrivate* d; +}; + + +/** + * This widget is the recent doc part of the template opening widget. + * The parent widget is initial widget in the document space of each KOffice component. + * This widget shows a list of recent documents and can show their details or open it. + */ +class KoRecentDocumentsPane : public KoDetailsPaneBase +{ + Q_OBJECT + public: + /** + * Constructor. + * @param parent the parent widget + * @param instance the instance object for the app + */ + KoRecentDocumentsPane(QWidget* parent, KInstance* instance); + ~KoRecentDocumentsPane(); + + virtual bool eventFilter(QObject* watched, QEvent* e); + + signals: + void openFile(const QString&); + + void splitterResized(KoDetailsPaneBase* sender, const QValueList<int>& sizes); + + public slots: + void resizeSplitter(KoDetailsPaneBase* sender, const QValueList<int>& sizes); + + protected slots: + void selectionChanged(QListViewItem* item); + void openFile(); + void openFile(QListViewItem* item); + + void previewResult(KIO::Job* job); + void updatePreview(const KFileItem* fileItem, const QPixmap& preview); + + void changePalette(); + + private: + KoRecentDocumentsPanePrivate* d; +}; + +#endif //KODETAILSPANE_H diff --git a/lib/kofficecore/KoDocInfoPropsFactory.cpp b/lib/kofficecore/KoDocInfoPropsFactory.cpp new file mode 100644 index 00000000..b08deeb2 --- /dev/null +++ b/lib/kofficecore/KoDocInfoPropsFactory.cpp @@ -0,0 +1,27 @@ +/* This file is part of the KDE project + Copyright (c) 2000 Simon Hausmann <hausmann@kde.org> + + $Id: KoDocInfoPropsFactory.cpp 508787 2006-02-12 18:28:12Z ingwa $ + + 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 <KoDocumentInfoDlg.h> + +#include <kgenericfactory.h> + +typedef KGenericFactory<KoDocumentInfoPropsPage, KPropertiesDialog> PropsDlgFactory; +K_EXPORT_COMPONENT_FACTORY( kodocinfopropspage, PropsDlgFactory( "koffice" ) ) diff --git a/lib/kofficecore/KoDocument.cpp b/lib/kofficecore/KoDocument.cpp new file mode 100644 index 00000000..50003627 --- /dev/null +++ b/lib/kofficecore/KoDocument.cpp @@ -0,0 +1,2672 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> + Copyright (C) 2000-2005 David Faure <faure@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 "KoDocument.h" + +#include "KoDocument_p.h" +#include "KoDocumentIface.h" +#include "KoDocumentChild.h" +#include "KoView.h" +#include "KoMainWindow.h" +#include "KoFilterManager.h" +#include "KoDocumentInfo.h" +#include "KoOasisStyles.h" +#include "KoOasisStore.h" +#include "KoXmlNS.h" +#include "KoOpenPane.h" + +#include <KoStoreDevice.h> +#include <KoXmlWriter.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <kdeversion.h> +#include <kfileitem.h> +#include <kiconloader.h> +#include <kio/job.h> +#include <kio/netaccess.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kmimetype.h> +#include <kparts/partmanager.h> +#include <kprinter.h> +#include <ksavefile.h> + +#include <qbuffer.h> +#include <qcursor.h> +#include <qdir.h> +#include <qfile.h> +#include <qfileinfo.h> +#include <qimage.h> +#include <qmap.h> +#include <qpainter.h> +#include <qtimer.h> +#include <qxml.h> +#include <qlayout.h> + +#include <config.h> +#include <assert.h> +#include <locale.h> + + +// Define the protocol used here for embedded documents' URL +// This used to "store" but KURL didn't like it, +// so let's simply make it "tar" ! +#define STORE_PROTOCOL "tar" +// The internal path is a hack to make KURL happy and still pass +// some kind of relative path to KoDocumentChild +#define INTERNAL_PROTOCOL "intern" +#define INTERNAL_PREFIX "intern:/" +// Warning, keep it sync in koStore.cc and koDocumentChild.cc + +QPtrList<KoDocument> *KoDocument::s_documentList=0L; + +using namespace std; +class KoViewWrapperWidget; + +/********************************************************** + * + * KoDocument + * + **********************************************************/ + +const int KoDocument::s_defaultAutoSave = 300; // 5 minutes + +class KoDocument::Private +{ +public: + Private() : + m_dcopObject( 0L ), + filterManager( 0L ), + m_specialOutputFlag( 0 ), // default is native format + m_isImporting( false ), m_isExporting( false ), + m_numOperations( 0 ), + modifiedAfterAutosave( false ), + m_autosaving( false ), + m_shouldCheckAutoSaveFile( true ), + m_autoErrorHandlingEnabled( true ), + m_backupFile( true ), + m_backupPath( QString::null ), + m_doNotSaveExtDoc( false ), + m_current( false ), + m_storeInternal( false ), + m_bLoading( false ), + m_startUpWidget( 0 ) + { + m_confirmNonNativeSave[0] = true; + m_confirmNonNativeSave[1] = true; + if ( KGlobal::locale()->measureSystem() == KLocale::Imperial ) { + m_unit = KoUnit::U_INCH; + } else { + m_unit = KoUnit::U_CM; + } + } + + QPtrList<KoView> m_views; + QPtrList<KoDocumentChild> m_children; + QPtrList<KoMainWindow> m_shells; + QValueList<QDomDocument> m_viewBuildDocuments; + + KoViewWrapperWidget *m_wrapperWidget; + KoDocumentIface * m_dcopObject; + KoDocumentInfo *m_docInfo; + KoView* hitTestView; + + KoUnit::Unit m_unit; + + KoFilterManager * filterManager; // The filter-manager to use when loading/saving [for the options] + + QCString mimeType; // The actual mimetype of the document + QCString outputMimeType; // The mimetype to use when saving + bool m_confirmNonNativeSave [2]; // used to pop up a dialog when saving for the + // first time if the file is in a foreign format + // (Save/Save As, Export) + int m_specialOutputFlag; // See KoFileDialog in koMainWindow.cc + bool m_isImporting, m_isExporting; // File --> Import/Export vs File --> Open/Save + + QTimer m_autoSaveTimer; + QString lastErrorMessage; // see openFile() + int m_autoSaveDelay; // in seconds, 0 to disable. + int m_numOperations; + bool modifiedAfterAutosave; + bool m_bSingleViewMode; + bool m_autosaving; + bool m_shouldCheckAutoSaveFile; // usually true + bool m_autoErrorHandlingEnabled; // usually true + bool m_backupFile; + QString m_backupPath; + bool m_doNotSaveExtDoc; // makes it possible to save only internally stored child documents + bool m_current; + bool m_storeInternal; // Store this doc internally even if url is external + bool m_bLoading; // True while loading (openURL is async) + + KoOpenPane* m_startUpWidget; + QString m_templateType; +}; + +// Used in singleViewMode +class KoViewWrapperWidget : public QWidget +{ +public: + KoViewWrapperWidget( QWidget *parent, const char *name ) + : QWidget( parent, name ) + { + KGlobal::locale()->insertCatalogue("koffice"); + // Tell the iconloader about share/apps/koffice/icons + KGlobal::iconLoader()->addAppDir("koffice"); + m_view = 0L; + // Avoid warning from KParts - we'll have the KoView as focus proxy anyway + setFocusPolicy( ClickFocus ); + } + + virtual ~KoViewWrapperWidget() { + setFocusProxy( 0 ); // to prevent a crash due to clearFocus (#53466) + } + + virtual void resizeEvent( QResizeEvent * ) + { + QObject *wid = child( 0, "QWidget" ); + if ( wid ) + static_cast<QWidget *>(wid)->setGeometry( 0, 0, width(), height() ); + } + + virtual void childEvent( QChildEvent *ev ) + { + if ( ev->type() == QEvent::ChildInserted ) + resizeEvent( 0L ); + } + + // Called by openFile() + void setKoView( KoView * view ) { + m_view = view; + setFocusProxy( m_view ); + } + KoView * koView() const { return m_view; } +private: + KoView* m_view; +}; + +KoBrowserExtension::KoBrowserExtension( KoDocument * doc, const char * name ) + : KParts::BrowserExtension( doc, name ) +{ + emit enableAction( "print", true ); +} + +void KoBrowserExtension::print() +{ + KoDocument * doc = static_cast<KoDocument *>( parent() ); + KoViewWrapperWidget * wrapper = static_cast<KoViewWrapperWidget *>( doc->widget() ); + KoView * view = wrapper->koView(); + // TODO remove code duplication (KoMainWindow), by moving this to KoView + KPrinter printer; + // ### TODO: apply global koffice settings here + view->setupPrinter( printer ); + if ( printer.setup( view ) ) + view->print( printer ); +} + +KoDocument::KoDocument( QWidget * parentWidget, const char *widgetName, QObject* parent, const char* name, bool singleViewMode ) + : KParts::ReadWritePart( parent, name ) +{ + if(s_documentList==0L) + s_documentList=new QPtrList<KoDocument>; + s_documentList->append(this); + + d = new Private; + m_bEmpty = TRUE; + connect( &d->m_autoSaveTimer, SIGNAL( timeout() ), this, SLOT( slotAutoSave() ) ); + setAutoSave( s_defaultAutoSave ); + d->m_bSingleViewMode = singleViewMode; + + + // the parent setting *always* overrides! (Simon) + if ( parent ) + { + if ( parent->inherits( "KoDocument" ) ) + d->m_bSingleViewMode = ((KoDocument *)parent)->isSingleViewMode(); + else if ( parent->inherits( "KParts::Part" ) ) + d->m_bSingleViewMode = true; + } + + if ( singleViewMode ) + { + d->m_wrapperWidget = new KoViewWrapperWidget( parentWidget, widgetName ); + setWidget( d->m_wrapperWidget ); + kdDebug(30003) << "creating KoBrowserExtension" << endl; + (void) new KoBrowserExtension( this ); // ## only if embedded into a browser? + } + + d->m_docInfo = new KoDocumentInfo( this, "document info" ); + + m_pageLayout.ptWidth = 0; + m_pageLayout.ptHeight = 0; + m_pageLayout.ptTop = 0; + m_pageLayout.ptBottom = 0; + m_pageLayout.ptLeft = 0; + m_pageLayout.ptRight = 0; + + // A way to 'fix' the job's window, since we have no widget known to KParts + if ( !singleViewMode ) + connect( this, SIGNAL( started( KIO::Job* ) ), SLOT( slotStarted( KIO::Job* ) ) ); +} + +KoDocument::~KoDocument() +{ + d->m_autoSaveTimer.stop(); + + QPtrListIterator<KoDocumentChild> childIt( d->m_children ); + for (; childIt.current(); ++childIt ) + disconnect( childIt.current(), SIGNAL( destroyed() ), + this, SLOT( slotChildDestroyed() ) ); + + // Tell our views that the document is already destroyed and + // that they shouldn't try to access it. + QPtrListIterator<KoView> vIt( d->m_views ); + for (; vIt.current(); ++vIt ) + vIt.current()->setDocumentDeleted(); + + delete d->m_startUpWidget; + d->m_startUpWidget = 0; + + d->m_children.setAutoDelete( true ); + d->m_children.clear(); + + d->m_shells.setAutoDelete( true ); + d->m_shells.clear(); + + delete d->m_dcopObject; + delete d->filterManager; + delete d; + s_documentList->removeRef(this); + // last one? + if(s_documentList->isEmpty()) { + delete s_documentList; + s_documentList=0; + } +} + +bool KoDocument::isSingleViewMode() const +{ + return d->m_bSingleViewMode; +} + +bool KoDocument::isEmbedded() const +{ + return dynamic_cast<KoDocument *>( parent() ) != 0; +} + +KoView *KoDocument::createView( QWidget *parent, const char *name ) +{ + KoView *view=createViewInstance(parent, name); + addView(view); + return view; +} + +bool KoDocument::exp0rt( const KURL & _url ) +{ + bool ret; + + d->m_isExporting = true; + + // + // Preserve a lot of state here because we need to restore it in order to + // be able to fake a File --> Export. Can't do this in saveFile() because, + // for a start, KParts has already set m_url and m_file and because we need + // to restore the modified flag etc. and don't want to put a load on anyone + // reimplementing saveFile() (Note: import() and export() will remain + // non-virtual). + // + KURL oldURL = m_url; + QString oldFile = m_file; + + bool wasModified = isModified (); + QCString oldMimeType = mimeType (); + + + // save... + ret = saveAs( _url ); + + + // + // This is sooooo hacky :( + // Hopefully we will restore enough state. + // + kdDebug(30003) << "Restoring KoDocument state to before export" << endl; + + // always restore m_url & m_file because KParts has changed them + // (regardless of failure or success) + m_url = oldURL; + m_file = oldFile; + + // on successful export we need to restore modified etc. too + // on failed export, mimetype/modified hasn't changed anyway + if (ret) + { + setModified (wasModified); + d->mimeType = oldMimeType; + } + + + d->m_isExporting = false; + + return ret; +} + +bool KoDocument::saveFile() +{ + kdDebug(30003) << "KoDocument::saveFile() doc='" << url().url() <<"'"<< endl; + + // set local again as it can happen that it gets resetted + // so that all saved numbers have a . and not e.g a , as a + // decimal seperator + setlocale( LC_NUMERIC, "C" ); + + // Save it to be able to restore it after a failed save + const bool wasModified = isModified (); + + // The output format is set by koMainWindow, and by openFile + QCString outputMimeType = d->outputMimeType; + //Q_ASSERT( !outputMimeType.isEmpty() ); // happens when using the DCOP method saveAs + if ( outputMimeType.isEmpty() ) + outputMimeType = d->outputMimeType = nativeFormatMimeType(); + + QApplication::setOverrideCursor( waitCursor ); + + if ( backupFile() ) { + if ( url().isLocalFile() ) + KSaveFile::backupFile( url().path(), d->m_backupPath ); + else { + KIO::UDSEntry entry; + if ( KIO::NetAccess::stat( url(), entry, shells().current() ) ) { // this file exists => backup + emit sigStatusBarMessage( i18n("Making backup...") ); + KURL backup; + if ( d->m_backupPath.isEmpty()) + backup = url(); + else + backup = d->m_backupPath +"/"+url().fileName(); + backup.setPath( backup.path() + QString::fromLatin1("~") ); + KFileItem item( entry, url() ); + Q_ASSERT( item.name() == url().fileName() ); + KIO::NetAccess::file_copy( url(), backup, item.permissions(), true /*overwrite*/, false /*resume*/, shells().current() ); + } + } + } + + emit sigStatusBarMessage( i18n("Saving...") ); + bool ret = false; + bool suppressErrorDialog = false; + if ( !isNativeFormat( outputMimeType ) ) { + kdDebug(30003) << "Saving to format " << outputMimeType << " in " << m_file << endl; + // Not native format : save using export filter + if ( !d->filterManager ) + d->filterManager = new KoFilterManager( this ); + + KoFilter::ConversionStatus status = d->filterManager->exp0rt( m_file, outputMimeType ); + ret = status == KoFilter::OK; + suppressErrorDialog = (status == KoFilter::UserCancelled || status == KoFilter::BadConversionGraph ); + } else { + // Native format => normal save + Q_ASSERT( !m_file.isEmpty() ); + ret = saveNativeFormat( m_file ); + } + + if ( ret ) { + removeAutoSaveFiles(); + // Restart the autosave timer + // (we don't want to autosave again 2 seconds after a real save) + setAutoSave( d->m_autoSaveDelay ); + } + + QApplication::restoreOverrideCursor(); + if ( !ret ) + { + if ( !suppressErrorDialog ) + { + showSavingErrorDialog(); + } + + // couldn't save file so this new URL is invalid + // FIXME: we should restore the current document's true URL instead of + // setting it to nothing otherwise anything that depends on the URL + // being correct will not work (i.e. the document will be called + // "Untitled" which may not be true) + // + // Update: now the URL is restored in KoMainWindow but really, this + // should still be fixed in KoDocument/KParts (ditto for m_file). + // We still resetURL() here since we may or may not have been called + // by KoMainWindow - Clarence + resetURL(); + + // As we did not save, restore the "was modified" status + setModified( wasModified ); + } + + if ( ret ) + { + d->mimeType = outputMimeType; + setConfirmNonNativeSave ( isExporting (), false ); + } + emit sigClearStatusBarMessage(); + + return ret; +} + +QCString KoDocument::mimeType() const +{ + return d->mimeType; +} + +void KoDocument::setMimeType( const QCString & mimeType ) +{ + d->mimeType = mimeType; +} + +void KoDocument::setOutputMimeType( const QCString & mimeType, int specialOutputFlag ) +{ + d->outputMimeType = mimeType; + d->m_specialOutputFlag = specialOutputFlag; +} + +QCString KoDocument::outputMimeType() const +{ + return d->outputMimeType; +} + +int KoDocument::specialOutputFlag() const +{ + return d->m_specialOutputFlag; +} + +bool KoDocument::confirmNonNativeSave( const bool exporting ) const +{ + // "exporting ? 1 : 0" is different from "exporting" because a bool is + // usually implemented like an "int", not "unsigned : 1" + return d->m_confirmNonNativeSave [ exporting ? 1 : 0 ]; +} + +void KoDocument::setConfirmNonNativeSave( const bool exporting, const bool on ) +{ + d->m_confirmNonNativeSave [ exporting ? 1 : 0] = on; +} + +bool KoDocument::wantExportConfirmation() const +{ + return true; +} + +bool KoDocument::isImporting() const +{ + return d->m_isImporting; +} + +bool KoDocument::isExporting() const +{ + return d->m_isExporting; +} + +void KoDocument::setCheckAutoSaveFile( bool b ) +{ + d->m_shouldCheckAutoSaveFile = b; +} + +void KoDocument::setAutoErrorHandlingEnabled( bool b ) +{ + d->m_autoErrorHandlingEnabled = b; +} + +bool KoDocument::isAutoErrorHandlingEnabled() const +{ + return d->m_autoErrorHandlingEnabled; +} + +void KoDocument::slotAutoSave() +{ + if ( isModified() && d->modifiedAfterAutosave && !d->m_bLoading ) + { + connect( this, SIGNAL( sigProgress( int ) ), shells().current(), SLOT( slotProgress( int ) ) ); + emit sigStatusBarMessage( i18n("Autosaving...") ); + d->m_autosaving = true; + bool ret = saveNativeFormat( autoSaveFile( m_file ) ); + setModified( true ); + if ( ret ) { + d->modifiedAfterAutosave = false; + d->m_autoSaveTimer.stop(); // until the next change + } + d->m_autosaving = false; + emit sigClearStatusBarMessage(); + disconnect( this, SIGNAL( sigProgress( int ) ), shells().current(), SLOT( slotProgress( int ) ) ); + if ( !ret ) + emit sigStatusBarMessage( i18n("Error during autosave! Partition full?") ); + } +} + +KAction *KoDocument::action( const QDomElement &element ) const +{ + // First look in the document itself + KAction* act = KParts::ReadWritePart::action( element ); + if ( act ) + return act; + + Q_ASSERT( d->m_bSingleViewMode ); + // Then look in the first view (this is for the single view mode) + if ( !d->m_views.isEmpty() ) + return d->m_views.getFirst()->action( element ); + else + return 0L; +} + +QDomDocument KoDocument::domDocument() const +{ + // When embedded into e.g. konqueror, we want the view's GUI (hopefully a reduced one) + // to be used. + Q_ASSERT( d->m_bSingleViewMode ); + if ( d->m_views.isEmpty() ) + return QDomDocument(); + else + return d->m_views.getFirst()->domDocument(); +} + +void KoDocument::setManager( KParts::PartManager *manager ) +{ + KParts::ReadWritePart::setManager( manager ); + if ( d->m_bSingleViewMode && d->m_views.count() == 1 ) + d->m_views.getFirst()->setPartManager( manager ); + + if ( manager ) + { + QPtrListIterator<KoDocumentChild> it( d->m_children ); + for (; it.current(); ++it ) + if ( it.current()->document() ) + manager->addPart( it.current()->document(), false ); + } +} + +void KoDocument::setReadWrite( bool readwrite ) +{ + KParts::ReadWritePart::setReadWrite( readwrite ); + + QPtrListIterator<KoView> vIt( d->m_views ); + for (; vIt.current(); ++vIt ) + vIt.current()->updateReadWrite( readwrite ); + + QPtrListIterator<KoDocumentChild> dIt( d->m_children ); + for (; dIt.current(); ++dIt ) + if ( dIt.current()->document() ) + dIt.current()->document()->setReadWrite( readwrite ); + + setAutoSave( d->m_autoSaveDelay ); +} + +void KoDocument::setAutoSave( int delay ) +{ + d->m_autoSaveDelay = delay; + if ( isReadWrite() && !isEmbedded() && d->m_autoSaveDelay > 0 ) + d->m_autoSaveTimer.start( d->m_autoSaveDelay * 1000 ); + else + d->m_autoSaveTimer.stop(); +} + +void KoDocument::addView( KoView *view ) +{ + if ( !view ) + return; + + d->m_views.append( view ); + view->updateReadWrite( isReadWrite() ); +} + +void KoDocument::removeView( KoView *view ) +{ + d->m_views.removeRef( view ); +} + +const QPtrList<KoView>& KoDocument::views() const +{ + return d->m_views; +} + +int KoDocument::viewCount() const +{ + return d->m_views.count(); +} + +void KoDocument::insertChild( KoDocumentChild *child ) +{ + setModified( true ); + + d->m_children.append( child ); + + connect( child, SIGNAL( changed( KoChild * ) ), + this, SLOT( slotChildChanged( KoChild * ) ) ); + connect( child, SIGNAL( destroyed() ), + this, SLOT( slotChildDestroyed() ) ); + + // It may be that insertChild is called without the KoDocumentChild + // having a KoDocument attached, yet. This happens for example + // when KPresenter loads a document with embedded objects. For those + // KPresenterChild objects are allocated and insertChild is called. + // Later in loadChildren() KPresenter iterates over the child list + // and calls loadDocument for each child. That's exactly where we + // will try to do what we cannot do now: Register the child document + // at the partmanager (Simon) + if ( manager() && !isSingleViewMode() && child->document() ) + manager()->addPart( child->document(), false ); +} + +void KoDocument::slotChildChanged( KoChild *c ) +{ + assert( c->inherits( "KoDocumentChild" ) ); + emit childChanged( static_cast<KoDocumentChild *>( c ) ); +} + +void KoDocument::slotChildDestroyed() +{ + setModified( true ); + + const KoDocumentChild *child = static_cast<const KoDocumentChild *>( sender() ); + d->m_children.removeRef( child ); +} + +const QPtrList<KoDocumentChild>& KoDocument::children() const +{ + return d->m_children; +} + +KParts::Part *KoDocument::hitTest( QWidget *widget, const QPoint &globalPos ) +{ + QPtrListIterator<KoView> it( d->m_views ); + for (; it.current(); ++it ) + if ( static_cast<QWidget *>(it.current()) == widget ) + { + KoView* view = it.current(); + d->hitTestView = view; // koffice-1.x hack + QPoint canvasPos( view->canvas()->mapFromGlobal( globalPos ) ); + canvasPos.rx() += view->canvasXOffset(); + canvasPos.ry() += view->canvasYOffset(); + + KParts::Part *part = view->hitTest( canvasPos ); + d->hitTestView = 0; + if ( part ) + return part; + } + + return 0L; +} + +KoView* KoDocument::hitTestView() +{ + return d->hitTestView; +} + +KoDocument* KoDocument::hitTest( const QPoint &pos, const QWMatrix &matrix ) +{ + // Call KoDocumentChild::hitTest for any child document + QPtrListIterator<KoDocumentChild> it( d->m_children ); + for (; it.current(); ++it ) + { + KoDocument *doc = it.current()->hitTest( pos, matrix ); + if ( doc ) + return doc; + } + + // Unless we hit an embedded document, the hit is on this document itself. + return this; +} + +KoDocumentChild *KoDocument::child( KoDocument *doc ) +{ + QPtrListIterator<KoDocumentChild> it( d->m_children ); + for (; it.current(); ++it ) + if ( it.current()->document() == doc ) + return it.current(); + + return 0L; +} + +KoDocumentInfo *KoDocument::documentInfo() const +{ + return d->m_docInfo; +} + +void KoDocument::setViewBuildDocument( KoView *view, const QDomDocument &doc ) +{ + if ( d->m_views.find( view ) == -1 ) + return; + + uint viewIdx = d->m_views.at(); + + if ( d->m_viewBuildDocuments.count() == viewIdx ) + d->m_viewBuildDocuments.append( doc ); + else if ( d->m_viewBuildDocuments.count() > viewIdx ) + d->m_viewBuildDocuments[ viewIdx ] = doc; +} + +QDomDocument KoDocument::viewBuildDocument( KoView *view ) +{ + QDomDocument res; + + if ( d->m_views.find( view ) == -1 ) + return res; + + uint viewIdx = d->m_views.at(); + + if ( viewIdx >= d->m_viewBuildDocuments.count() ) + return res; + + res = d->m_viewBuildDocuments[ viewIdx ]; + + // make this entry empty. otherwise we get a segfault in QMap ;-( + d->m_viewBuildDocuments[ viewIdx ] = QDomDocument(); + + return res; +} + +void KoDocument::paintEverything( QPainter &painter, const QRect &rect, bool transparent, KoView *view, double zoomX, double zoomY ) +{ + paintContent( painter, rect, transparent, zoomX, zoomY ); + paintChildren( painter, rect, view, zoomX, zoomY ); +} + +void KoDocument::paintChildren( QPainter &painter, const QRect &/*rect*/, KoView *view, double zoomX, double zoomY ) +{ + QPtrListIterator<KoDocumentChild> it( d->m_children ); + for (; it.current(); ++it ) + { + // #### todo: paint only if child is visible inside rect + painter.save(); + paintChild( it.current(), painter, view, zoomX, zoomY ); + painter.restore(); + } +} + +void KoDocument::paintChild( KoDocumentChild *child, QPainter &painter, KoView *view, double zoomX, double zoomY ) +{ + if ( child->isDeleted() ) + return; + + // QRegion rgn = painter.clipRegion(); + + child->transform( painter ); + child->document()->paintEverything( painter, child->contentRect(), child->isTransparent(), view, zoomX, zoomY ); + + if ( view && view->partManager() ) + { + // ### do we need to apply zoomX and zoomY here ? + KParts::PartManager *manager = view->partManager(); + + painter.scale( 1.0 / child->xScaling(), 1.0 / child->yScaling() ); + + int w = int( (double)child->contentRect().width() * child->xScaling() ); + int h = int( (double)child->contentRect().height() * child->yScaling() ); + if ( ( manager->selectedPart() == (KParts::Part *)child->document() && + manager->selectedWidget() == (QWidget *)view ) || + ( manager->activePart() == (KParts::Part *)child->document() && + manager->activeWidget() == (QWidget *)view ) ) + { + // painter.setClipRegion( rgn ); + painter.setClipping( FALSE ); + + painter.setPen( black ); + painter.fillRect( -5, -5, w + 10, 5, white ); + painter.fillRect( -5, h, w + 10, 5, white ); + painter.fillRect( -5, -5, 5, h + 10, white ); + painter.fillRect( w, -5, 5, h + 10, white ); + painter.fillRect( -5, -5, w + 10, 5, BDiagPattern ); + painter.fillRect( -5, h, w + 10, 5, BDiagPattern ); + painter.fillRect( -5, -5, 5, h + 10, BDiagPattern ); + painter.fillRect( w, -5, 5, h + 10, BDiagPattern ); + + if ( manager->selectedPart() == (KParts::Part *)child->document() && + manager->selectedWidget() == (QWidget *)view ) + { + QColor color; + if ( view->koDocument() == this ) + color = black; + else + color = gray; + painter.fillRect( -5, -5, 5, 5, color ); + painter.fillRect( -5, h, 5, 5, color ); + painter.fillRect( w, h, 5, 5, color ); + painter.fillRect( w, -5, 5, 5, color ); + painter.fillRect( w / 2 - 3, -5, 5, 5, color ); + painter.fillRect( w / 2 - 3, h, 5, 5, color ); + painter.fillRect( -5, h / 2 - 3, 5, 5, color ); + painter.fillRect( w, h / 2 - 3, 5, 5, color ); + } + + painter.setClipping( TRUE ); + } + } +} + +bool KoDocument::isModified() const +{ + if ( KParts::ReadWritePart::isModified() ) + { + //kdDebug(30003)<<k_funcinfo<<" Modified doc='"<<url().url()<<"' extern="<<isStoredExtern()<<endl; + return true; + } + // Then go through internally stored children (considered to be part of this doc) + QPtrListIterator<KoDocumentChild> it = children(); + for (; it.current(); ++it ) + { + KoDocument *doc = it.current()->document(); + if ( doc && !it.current()->isStoredExtern() && !it.current()->isDeleted() && doc->isModified() ) + return true; + } + return false; +} + +bool KoDocument::saveChildren( KoStore* _store ) +{ + //kdDebug(30003)<<k_funcinfo<<" checking children of doc='"<<url().url()<<"'"<<endl; + int i = 0; + QPtrListIterator<KoDocumentChild> it( children() ); + for( ; it.current(); ++it ) { + KoDocument* childDoc = it.current()->document(); + if (childDoc && !it.current()->isDeleted()) + { + if ( !childDoc->isStoredExtern() ) + { + //kdDebug(30003) << "KoDocument::saveChildren internal url: /" << i << endl; + if ( !childDoc->saveToStore( _store, QString::number( i++ ) ) ) + return FALSE; + + if (!isExporting ()) + childDoc->setModified( false ); + } + //else kdDebug(30003)<<k_funcinfo<<" external (don't save) url:" << childDoc->url().url()<<endl; + } + } + return true; +} + +bool KoDocument::saveChildrenOasis( KoStore* store, KoXmlWriter* manifestWriter ) +{ + //kdDebug(30003)<<k_funcinfo<<" checking children of doc='"<<url().url()<<"'"<<endl; + QPtrListIterator<KoDocumentChild> it( children() ); + for( ; it.current(); ++it ) { + KoDocument* childDoc = it.current()->document(); + if ( childDoc && !it.current()->isDeleted() ) + { + if ( !it.current()->saveOasis( store, manifestWriter ) ) + return false; + if ( !childDoc->isStoredExtern() && !isExporting () ) + childDoc->setModified( false ); + } + } + return true; +} + +bool KoDocument::saveExternalChildren() +{ + if ( d->m_doNotSaveExtDoc ) + { + //kdDebug(30003)<<k_funcinfo<<" Don't save external docs in doc='"<<url().url()<<"'"<<endl; + d->m_doNotSaveExtDoc = false; + return true; + } + + //kdDebug(30003)<<k_funcinfo<<" checking children of doc='"<<url().url()<<"'"<<endl; + KoDocumentChild *ch; + QPtrListIterator<KoDocumentChild> it = children(); + for (; (ch = it.current()); ++it ) + { + if ( !ch->isDeleted() ) + { + KoDocument* doc = ch->document(); + if ( doc && doc->isStoredExtern() && doc->isModified() ) + { + kdDebug(30003)<<" save external doc='"<<url().url()<<"'"<<endl; + doc->setDoNotSaveExtDoc(); // Only save doc + it's internal children + if ( !doc->save() ) + return false; // error + } + //kdDebug(30003)<<k_funcinfo<<" not modified doc='"<<url().url()<<"'"<<endl; + // save possible external docs inside doc + if ( doc && !doc->saveExternalChildren() ) + return false; + } + } + return true; +} + +bool KoDocument::saveNativeFormat( const QString & file ) +{ + d->lastErrorMessage = QString::null; + //kdDebug(30003) << "Saving to store" << endl; + + KoStore::Backend backend = KoStore::Auto; +#if 0 + if ( d->m_specialOutputFlag == SaveAsKOffice1dot1 ) + { + kdDebug(30003) << "Saving as KOffice-1.1 format, using a tar.gz" << endl; + backend = KoStore::Tar; // KOffice-1.0/1.1 used tar.gz for the native mimetype + //// TODO more backwards compat stuff (embedded docs etc.) + } + else +#endif + if ( d->m_specialOutputFlag == SaveAsDirectoryStore ) + { + backend = KoStore::Directory; + kdDebug(30003) << "Saving as uncompressed XML, using directory store." << endl; + } + else if ( d->m_specialOutputFlag == SaveAsFlatXML ) + { + kdDebug(30003) << "Saving as a flat XML file." << endl; + QFile f( file ); + if ( f.open( IO_WriteOnly | IO_Translate ) ) + { + bool success = saveToStream( &f ); + f.close(); + return success; + } + else + return false; + } + + kdDebug(30003) << "KoDocument::saveNativeFormat nativeFormatMimeType=" << nativeFormatMimeType() << endl; + // OLD: bool oasis = d->m_specialOutputFlag == SaveAsOASIS; + // OLD: QCString mimeType = oasis ? nativeOasisMimeType() : nativeFormatMimeType(); + QCString mimeType = d->outputMimeType; + QCString nativeOasisMime = nativeOasisMimeType(); + bool oasis = !mimeType.isEmpty() && ( mimeType == nativeOasisMime || mimeType == nativeOasisMime + "-template" ); + // TODO: use std::auto_ptr or create store on stack [needs API fixing], + // to remove all the 'delete store' in all the branches + KoStore* store = KoStore::createStore( file, KoStore::Write, mimeType, backend ); + if ( store->bad() ) + { + d->lastErrorMessage = i18n( "Could not create the file for saving" ); // more details needed? + delete store; + return false; + } + + if ( oasis ) + { + kdDebug(30003) << "Saving to OASIS format" << endl; + // Tell KoStore not to touch the file names + store->disallowNameExpansion(); + KoOasisStore oasisStore( store ); + KoXmlWriter* manifestWriter = oasisStore.manifestWriter( mimeType ); + + if ( !saveOasis( store, manifestWriter ) ) + { + kdDebug(30003) << "saveOasis failed" << endl; + delete store; + return false; + } + + // Save embedded objects + if ( !saveChildrenOasis( store, manifestWriter ) ) + { + kdDebug(30003) << "saveChildrenOasis failed" << endl; + delete store; + return false; + } + + if ( store->open( "meta.xml" ) ) + { + if ( !d->m_docInfo->saveOasis( store ) || !store->close() ) { + delete store; + return false; + } + manifestWriter->addManifestEntry( "meta.xml", "text/xml" ); + } + else + { + d->lastErrorMessage = i18n( "Not able to write '%1'. Partition full?" ).arg( "meta.xml" ); + delete store; + return false; + } + + if ( store->open( "Thumbnails/thumbnail.png" ) ) + { + if ( !saveOasisPreview( store, manifestWriter ) || !store->close() ) { + d->lastErrorMessage = i18n( "Error while trying to write '%1'. Partition full?" ).arg( "Thumbnails/thumbnail.png" ); + delete store; + return false; + } + // No manifest entry! + } + else + { + d->lastErrorMessage = i18n( "Not able to write '%1'. Partition full?" ).arg( "Thumbnails/thumbnail.png" ); + delete store; + return false; + } + + // Write out manifest file + if ( !oasisStore.closeManifestWriter() ) + { + d->lastErrorMessage = i18n( "Error while trying to write '%1'. Partition full?" ).arg( "META-INF/manifest.xml" ); + delete store; + return false; + } + + delete store; + } + else + { + // Save internal children first since they might get a new url + if ( !saveChildren( store ) && !oasis ) + { + if ( d->lastErrorMessage.isEmpty() ) + d->lastErrorMessage = i18n( "Error while saving embedded documents" ); // more details needed + delete store; + return false; + } + + kdDebug(30003) << "Saving root" << endl; + if ( store->open( "root" ) ) + { + KoStoreDevice dev( store ); + if ( !saveToStream( &dev ) || !store->close() ) + { + kdDebug(30003) << "saveToStream failed" << endl; + delete store; + return false; + } + } + else + { + d->lastErrorMessage = i18n( "Not able to write '%1'. Partition full?" ).arg( "maindoc.xml" ); + delete store; + return false; + } + if ( store->open( "documentinfo.xml" ) ) + { + QDomDocument doc = d->m_docInfo->save(); + KoStoreDevice dev( store ); + + QCString s = doc.toCString(); // this is already Utf8! + (void)dev.writeBlock( s.data(), s.size()-1 ); + (void)store->close(); + } + + if ( store->open( "preview.png" ) ) + { + // ### TODO: missing error checking (The partition could be full!) + savePreview( store ); + (void)store->close(); + } + + if ( !completeSaving( store ) ) + { + delete store; + return false; + } + kdDebug(30003) << "Saving done of url: " << url().url() << endl; + delete store; + } + if ( !saveExternalChildren() ) + { + return false; + } + return true; +} + +bool KoDocument::saveToStream( QIODevice * dev ) +{ + QDomDocument doc = saveXML(); + // Save to buffer + QCString s = doc.toCString(); // utf8 already + // We use QCString::size()-1 here, not the official QCString::length + // It works here, as we are not modifying QCString as QByteArray + int nwritten = dev->writeBlock( s.data(), s.size()-1 ); + if ( nwritten != (int)s.size()-1 ) + kdWarning(30003) << "KoDocument::saveToStream wrote " << nwritten << " - expected " << s.size()-1 << endl; + return nwritten == (int)s.size()-1; +} + +// Called for embedded documents +bool KoDocument::saveToStore( KoStore* _store, const QString & _path ) +{ + kdDebug(30003) << "Saving document to store " << _path << endl; + + // Use the path as the internal url + if ( _path.startsWith( STORE_PROTOCOL ) ) + m_url = KURL( _path ); + else // ugly hack to pass a relative URI + m_url = KURL( INTERNAL_PREFIX + _path ); + + // To make the children happy cd to the correct directory + _store->pushDirectory(); + _store->enterDirectory( _path ); + + // Save childen first since they might get a new url + if ( !saveChildren( _store ) ) + return false; + + // In the current directory we're the king :-) + if ( _store->open( "root" ) ) + { + KoStoreDevice dev( _store ); + if ( !saveToStream( &dev ) ) + { + _store->close(); + return false; + } + if ( !_store->close() ) + return false; + } + + if ( !completeSaving( _store ) ) + return false; + + // Now that we're done leave the directory again + _store->popDirectory(); + + kdDebug(30003) << "Saved document to store" << endl; + + return true; +} + +bool KoDocument::saveOasisPreview( KoStore* store, KoXmlWriter* manifestWriter ) +{ + const QPixmap pix = generatePreview( QSize( 128, 128 ) ); + QImage preview ( pix.convertToImage().convertDepth( 32, Qt::ColorOnly ) ); + if ( !preview.hasAlphaBuffer() ) + { + preview.setAlphaBuffer( true ); + } + // ### TODO: freedesktop.org Thumbnail specification (date...) + KoStoreDevice io ( store ); + if ( !io.open( IO_WriteOnly ) ) + return false; + if ( ! preview.save( &io, "PNG", 0 ) ) + return false; + io.close(); + manifestWriter->addManifestEntry( "Thumbnails/", "" ); + manifestWriter->addManifestEntry( "Thumbnails/thumbnail.png", "" ); + return true; +} + +bool KoDocument::savePreview( KoStore* store ) +{ + QPixmap pix = generatePreview(QSize(256, 256)); + // Reducing to 8bpp reduces file sizes quite a lot. + const QImage preview ( pix.convertToImage().convertDepth( 8, Qt::AvoidDither | Qt::DiffuseDither) ); + KoStoreDevice io ( store ); + if ( !io.open( IO_WriteOnly ) ) + return false; + if ( ! preview.save( &io, "PNG" ) ) // ### TODO What is -9 in quality terms? + return false; + io.close(); + return true; +} + +QPixmap KoDocument::generatePreview( const QSize& size ) +{ + double docWidth, docHeight; + int pixmapSize = QMAX(size.width(), size.height()); + + if (m_pageLayout.ptWidth > 1.0) { + docWidth = m_pageLayout.ptWidth / 72 * KoGlobal::dpiX(); + docHeight = m_pageLayout.ptHeight / 72 * KoGlobal::dpiY(); + + } else { + // If we don't have a page layout, just draw the top left hand corner + docWidth = 500.0; + docHeight = 500.0; + } + + double ratio = docWidth / docHeight; + + QPixmap pix; + int previewWidth, previewHeight; + if (ratio > 1.0) + { + previewWidth = (int) pixmapSize; + previewHeight = (int) (pixmapSize / ratio); + } + else + { + previewWidth = (int) (pixmapSize * ratio); + previewHeight = (int) pixmapSize; + } + + pix.resize((int)docWidth, (int)docHeight); + + pix.fill( QColor( 245, 245, 245 ) ); + + QRect rc(0, 0, pix.width(), pix.height()); + + QPainter p; + p.begin(&pix); + paintEverything(p, rc, false); + p.end(); + + // ### TODO: why re-convert it to a QPixmap, when mostly it will be re-converted back to a QImage in the calling function + pix.convertFromImage(pix.convertToImage().smoothScale(previewWidth, previewHeight)); + + return pix; +} + +QString KoDocument::autoSaveFile( const QString & path ) const +{ + // set local again as it can happen that it gets resetted + // so that all saved numbers have a . and not e.g a , as a + // decimal seperator + setlocale( LC_NUMERIC, "C" ); + + // Using the extension allows to avoid relying on the mime magic when opening + KMimeType::Ptr mime = KMimeType::mimeType( nativeFormatMimeType() ); + QString extension = mime->property( "X-KDE-NativeExtension" ).toString(); + if ( path.isEmpty() ) + { + // Never saved? Use a temp file in $HOME then + // Yes, two open unnamed docs will overwrite each other's autosave file, + // but hmm, we can only do something if that's in the same process anyway... + QString ret = QDir::homeDirPath() + "/." + QString::fromLatin1(instance()->instanceName()) + ".autosave" + extension; + return ret; + } + else + { + KURL url( path ); + Q_ASSERT( url.isLocalFile() ); + QString dir = url.directory(false); + QString filename = url.fileName(); + return dir + "." + filename + ".autosave" + extension; + } +} + +bool KoDocument::checkAutoSaveFile() +{ + QString asf = autoSaveFile( QString::null ); // the one in $HOME + //kdDebug(30003) << "asf=" << asf << endl; + if ( QFile::exists( asf ) ) + { + QDateTime date = QFileInfo(asf).lastModified(); + QString dateStr = date.toString(Qt::LocalDate); + int res = KMessageBox::warningYesNoCancel( + 0, i18n( "An autosaved file for an unnamed document exists in %1.\nThis file is dated %2\nDo you want to open it?" ) + .arg(asf, dateStr) ); + switch(res) { + case KMessageBox::Yes : { + KURL url; + url.setPath( asf ); + bool ret = openURL( url ); + if ( ret ) + resetURL(); + return ret; + } + case KMessageBox::No : + QFile::remove( asf ); + return false; + default: // Cancel + return false; + } + } + return false; +} + +bool KoDocument::import( const KURL & _url ) +{ + bool ret; + + kdDebug (30003) << "KoDocument::import url=" << _url.url() << endl; + d->m_isImporting = true; + + // open... + ret = openURL (_url); + + // reset m_url & m_file (kindly? set by KParts::openURL()) to simulate a + // File --> Import + if (ret) + { + kdDebug (30003) << "KoDocument::import success, resetting url" << endl; + resetURL (); + setTitleModified (); + } + + d->m_isImporting = false; + + return ret; +} + +bool KoDocument::openURL( const KURL & _url ) +{ + kdDebug(30003) << "KoDocument::openURL url=" << _url.url() << endl; + d->lastErrorMessage = QString::null; + + // Reimplemented, to add a check for autosave files and to improve error reporting + if ( !_url.isValid() ) + { + d->lastErrorMessage = i18n( "Malformed URL\n%1" ).arg( _url.url() ); // ## used anywhere ? + return false; + } + if ( !closeURL() ) + return false; + + KURL url( _url ); + bool autosaveOpened = false; + d->m_bLoading = true; + if ( url.isLocalFile() && d->m_shouldCheckAutoSaveFile ) + { + QString file = url.path(); + QString asf = autoSaveFile( file ); + if ( QFile::exists( asf ) ) + { + //kdDebug(30003) << "KoDocument::openURL asf=" << asf << endl; + // ## TODO compare timestamps ? + int res = KMessageBox::warningYesNoCancel( 0, + i18n( "An autosaved file exists for this document.\nDo you want to open it instead?" )); + switch(res) { + case KMessageBox::Yes : + url.setPath( asf ); + autosaveOpened = true; + break; + case KMessageBox::No : + QFile::remove( asf ); + break; + default: // Cancel + d->m_bLoading = false; + return false; + } + } + } + + bool ret = KParts::ReadWritePart::openURL( url ); + + if ( autosaveOpened ) + resetURL(); // Force save to act like 'Save As' + else + { + // We have no koffice shell when we are being embedded as a readonly part. + //if ( d->m_shells.isEmpty() ) + // kdWarning(30003) << "KoDocument::openURL no shell yet !" << endl; + // Add to recent actions list in our shells + QPtrListIterator<KoMainWindow> it( d->m_shells ); + for (; it.current(); ++it ) + it.current()->addRecentURL( _url ); + } + return ret; +} + +bool KoDocument::openFile() +{ + //kdDebug(30003) << "KoDocument::openFile for " << m_file << endl; + if ( !QFile::exists(m_file) ) + { + QApplication::restoreOverrideCursor(); + if ( d->m_autoErrorHandlingEnabled ) + // Maybe offer to create a new document with that name ? + KMessageBox::error(0L, i18n("The file %1 does not exist.").arg(m_file) ); + d->m_bLoading = false; + return false; + } + + QApplication::setOverrideCursor( waitCursor ); + + d->m_specialOutputFlag = 0; + QCString _native_format = nativeFormatMimeType(); + + KURL u; + u.setPath( m_file ); + QString typeName = KMimeType::findByURL( u, 0, true )->name(); + + // Allow to open backup files, don't keep the mimetype application/x-trash. + if ( typeName == "application/x-trash" ) + { + QString path = u.path(); + QStringList patterns = KMimeType::mimeType( typeName )->patterns(); + // Find the extension that makes it a backup file, and remove it + for( QStringList::Iterator it = patterns.begin(); it != patterns.end(); ++it ) { + QString ext = *it; + if ( !ext.isEmpty() && ext[0] == '*' ) + { + ext.remove(0, 1); + if ( path.endsWith( ext ) ) { + path.truncate( path.length() - ext.length() ); + break; + } + } + } + typeName = KMimeType::findByPath( path, 0, true )->name(); + } + + // Special case for flat XML files (e.g. using directory store) + if ( u.fileName() == "maindoc.xml" || u.fileName() == "content.xml" || typeName == "inode/directory" ) + { + typeName = _native_format; // Hmm, what if it's from another app? ### Check mimetype + d->m_specialOutputFlag = SaveAsDirectoryStore; + kdDebug(30003) << "KoDocument::openFile loading " << u.fileName() << ", using directory store for " << m_file << "; typeName=" << typeName << endl; + } + kdDebug(30003) << "KoDocument::openFile " << m_file << " type:" << typeName << endl; + + QString importedFile = m_file; + + if ( !isNativeFormat( typeName.latin1() ) ) { + if ( !d->filterManager ) + d->filterManager = new KoFilterManager( this ); + KoFilter::ConversionStatus status; + importedFile = d->filterManager->import( m_file, status ); + if ( status != KoFilter::OK ) + { + QApplication::restoreOverrideCursor(); + + QString msg; + switch( status ) + { + case KoFilter::OK: break; + + case KoFilter::CreationError: + msg = i18n( "Creation error" ); break; + + case KoFilter::FileNotFound: + msg = i18n( "File not found" ); break; + + case KoFilter::StorageCreationError: + msg = i18n( "Cannot create storage" ); break; + + case KoFilter::BadMimeType: + msg = i18n( "Bad MIME type" ); break; + + case KoFilter::EmbeddedDocError: + msg = i18n( "Error in embedded document" ); break; + + case KoFilter::WrongFormat: + msg = i18n( "Format not recognized" ); break; + + case KoFilter::NotImplemented: + msg = i18n( "Not implemented" ); break; + + case KoFilter::ParsingError: + msg = i18n( "Parsing error" ); break; + + case KoFilter::PasswordProtected: + msg = i18n( "Document is password protected" ); break; + + case KoFilter::InternalError: + case KoFilter::UnexpectedEOF: + case KoFilter::UnexpectedOpcode: + case KoFilter::StupidError: // ?? what is this ?? + case KoFilter::UsageError: + msg = i18n( "Internal error" ); break; + + case KoFilter::OutOfMemory: + msg = i18n( "Out of memory" ); break; + + case KoFilter::UserCancelled: + case KoFilter::BadConversionGraph: + // intentionally we do not prompt the error message here + break; + + default: msg = i18n( "Unknown error" ); break; + } + + if( d->m_autoErrorHandlingEnabled && !msg.isEmpty()) + { + QString errorMsg( i18n( "Could not open\n%2.\nReason: %1" ) ); + QString docUrl = url().prettyURL( 0, KURL::StripFileProtocol ); + KMessageBox::error( 0L, errorMsg.arg(msg).arg(docUrl) ); + } + + d->m_bLoading = false; + return false; + } + kdDebug(30003) << "KoDocument::openFile - importedFile '" << importedFile + << "', status: " << static_cast<int>( status ) << endl; + } + + QApplication::restoreOverrideCursor(); + + bool ok = true; + + if (!importedFile.isEmpty()) // Something to load (tmp or native file) ? + { + // The filter, if any, has been applied. It's all native format now. + if ( !loadNativeFormat( importedFile ) ) + { + ok = false; + if ( d->m_autoErrorHandlingEnabled ) + { + showLoadingErrorDialog(); + } + } + } + + if ( importedFile != m_file ) + { + // We opened a temporary file (result of an import filter) + // Set document URL to empty - we don't want to save in /tmp ! + // But only if in readwrite mode (no saving problem otherwise) + // -- + // But this isn't true at all. If this is the result of an + // import, then importedFile=temporary_file.kwd and + // m_file/m_url=foreignformat.ext so m_url is correct! + // So don't resetURL() or else the caption won't be set when + // foreign files are opened (an annoying bug). + // - Clarence + // +#if 0 + if ( isReadWrite() ) + resetURL(); +#endif + + // remove temp file - uncomment this to debug import filters + if(!importedFile.isEmpty()) { + QFile::remove( importedFile ); + } + } + + if ( ok && d->m_bSingleViewMode ) + { + // See addClient below + KXMLGUIFactory* guiFactory = factory(); + if( guiFactory ) // 0L when splitting views in konq, for some reason + guiFactory->removeClient( this ); + + if ( !d->m_views.isEmpty() ) + { + // We already had a view (this happens when doing reload in konqueror) + KoView* v = d->m_views.first(); + if( guiFactory ) + guiFactory->removeClient( v ); + removeView( v ); + delete v; + Q_ASSERT( d->m_views.isEmpty() ); + } + + KoView *view = createView( d->m_wrapperWidget ); + d->m_wrapperWidget->setKoView( view ); + view->show(); + + // Ok, now we have a view, so action() and domDocument() will work as expected + // -> rebuild GUI + if ( guiFactory ) + guiFactory->addClient( this ); + } + + if ( ok ) + { + setMimeTypeAfterLoading( typeName ); + } + d->m_bLoading = false; + return ok; +} + +// shared between openFile and koMainWindow's "create new empty document" code +void KoDocument::setMimeTypeAfterLoading( const QString& mimeType ) +{ + d->mimeType = mimeType.latin1(); + + d->outputMimeType = d->mimeType; + + const bool needConfirm = !isNativeFormat( d->mimeType ); + setConfirmNonNativeSave( false, needConfirm ); + setConfirmNonNativeSave( true, needConfirm ); +} + +// The caller must call store->close() if loadAndParse returns true. +bool KoDocument::oldLoadAndParse(KoStore* store, const QString& filename, QDomDocument& doc) +{ + //kdDebug(30003) << "oldLoadAndParse: Trying to open " << filename << endl; + + if (!store->open(filename)) + { + kdWarning(30003) << "Entry " << filename << " not found!" << endl; + d->lastErrorMessage = i18n( "Could not find %1" ).arg( filename ); + return false; + } + // Error variables for QDomDocument::setContent + QString errorMsg; + int errorLine, errorColumn; + bool ok = doc.setContent( store->device(), &errorMsg, &errorLine, &errorColumn ); + if ( !ok ) + { + kdError(30003) << "Parsing error in " << filename << "! Aborting!" << endl + << " In line: " << errorLine << ", column: " << errorColumn << endl + << " Error message: " << errorMsg << endl; + d->lastErrorMessage = i18n( "Parsing error in %1 at line %2, column %3\nError message: %4" ) + .arg( filename ).arg( errorLine ).arg( errorColumn ) + .arg( i18n ( "QXml", errorMsg.utf8() ) ); + store->close(); + return false; + } + kdDebug(30003) << "File " << filename << " loaded and parsed" << endl; + return true; +} + +bool KoDocument::loadNativeFormat( const QString & file ) +{ + QFileInfo fileInfo( file ); + if ( !fileInfo.exists() ) // check duplicated from openURL, but this is useful for templates + { + d->lastErrorMessage = i18n("The file %1 does not exist.").arg(file); + return false; + } + if ( !fileInfo.isFile() ) + { + d->lastErrorMessage = i18n( "%1 is not a file." ).arg(file); + return false; + } + + QApplication::setOverrideCursor( waitCursor ); + + kdDebug(30003) << "KoDocument::loadNativeFormat( " << file << " )" << endl; + + QFile in; + bool isRawXML = false; + if ( d->m_specialOutputFlag != SaveAsDirectoryStore ) // Don't try to open a directory ;) + { + in.setName(file); + if ( !in.open( IO_ReadOnly ) ) + { + QApplication::restoreOverrideCursor(); + d->lastErrorMessage = i18n( "Could not open the file for reading (check read permissions)." ); + return false; + } + + // Try to find out whether it is a mime multi part file + char buf[5]; + if ( in.readBlock( buf, 4 ) < 4 ) + { + QApplication::restoreOverrideCursor(); + in.close(); + d->lastErrorMessage = i18n( "Could not read the beginning of the file." ); + return false; + } + // ### TODO: allow UTF-16 + isRawXML = (strncasecmp( buf, "<?xm", 4 ) == 0); + if ( !isRawXML ) { + // Still check for broken MathML files, which seem to be rather common + in.reset(); + // Remove spaces + do { + if ( in.readBlock( buf , 1 ) < 1 ) + { + QApplication::restoreOverrideCursor(); + in.close(); + d->lastErrorMessage = i18n( "Could not read the beginning of the file." ); + return false; + } + } while ( QChar( buf[0] ).isSpace() ); + if ( buf[0] == '<' ) { // First not-space character + if ( in.readBlock( buf , 4 ) < 4 ) + { + QApplication::restoreOverrideCursor(); + in.close(); + d->lastErrorMessage = i18n( "Could not read the beginning of the file." ); + return false; + } + isRawXML = (strncasecmp( buf, "math", 4 ) == 0); // file begins with <math ? + } + } + //kdDebug(30003) << "PATTERN=" << buf << endl; + } + // Is it plain XML? + if ( isRawXML ) + { + in.at(0); + QString errorMsg; + int errorLine; + int errorColumn; + QDomDocument doc; + bool res; + if ( doc.setContent( &in, true, &errorMsg, &errorLine, &errorColumn ) ) + { + res = loadXML( &in, doc ); + if ( res ) + res = completeLoading( 0L ); + } + else + { + kdError (30003) << "Parsing Error! Aborting! (in KoDocument::loadNativeFormat (QFile))" << endl + << " Line: " << errorLine << " Column: " << errorColumn << endl + << " Message: " << errorMsg << endl; + d->lastErrorMessage = i18n( "parsing error in the main document at line %1, column %2\nError message: %3" ) + .arg( errorLine ).arg( errorColumn ).arg( i18n ( errorMsg.utf8() ) ); + res=false; + } + + QApplication::restoreOverrideCursor(); + in.close(); + m_bEmpty = false; + return res; + } else + { // It's a koffice store (tar.gz, zip, directory, etc.) + in.close(); + + return loadNativeFormatFromStore( file ); + } +} + +bool KoDocument::loadNativeFormatFromStore( const QString& file ) +{ + KoStore::Backend backend = (d->m_specialOutputFlag == SaveAsDirectoryStore) ? KoStore::Directory : KoStore::Auto; + KoStore * store = KoStore::createStore( file, KoStore::Read, "", backend ); + + if ( store->bad() ) + { + d->lastErrorMessage = i18n( "Not a valid KOffice file: %1" ).arg( file ); + delete store; + QApplication::restoreOverrideCursor(); + return false; + } + + bool oasis = true; + // OASIS/OOo file format? + if ( store->hasFile( "content.xml" ) ) + { + store->disallowNameExpansion(); + + KoOasisStore oasisStore( store ); + // We could check the 'mimetype' file, but let's skip that and be tolerant. + + if ( !loadOasisFromStore( store ) ) { + delete store; + QApplication::restoreOverrideCursor(); + return false; + } + + } else if ( store->hasFile( "root" ) ) // Fallback to "old" file format (maindoc.xml) + { + oasis = false; + + QDomDocument doc; + bool ok = oldLoadAndParse( store, "root", doc ); + if ( ok ) + ok = loadXML( store->device(), doc ); + if ( !ok ) + { + delete store; + QApplication::restoreOverrideCursor(); + return false; + } + store->close(); + + if ( !loadChildren( store ) ) + { + kdError(30003) << "ERROR: Could not load children" << endl; + // Don't abort, proceed nonetheless + } + + } else + { + kdError(30003) << "ERROR: No maindoc.xml" << endl; + d->lastErrorMessage = i18n( "Invalid document: no file 'maindoc.xml'." ); + delete store; + QApplication::restoreOverrideCursor(); + return false; + } + + if ( oasis && store->hasFile( "meta.xml" ) ) { + QDomDocument metaDoc; + KoOasisStore oasisStore( store ); + if ( oasisStore.loadAndParse( "meta.xml", metaDoc, d->lastErrorMessage ) ) { + d->m_docInfo->loadOasis( metaDoc ); + } + } + else if ( !oasis && store->hasFile( "documentinfo.xml" ) ) + { + QDomDocument doc; + if ( oldLoadAndParse( store, "documentinfo.xml", doc ) ) { + store->close(); + d->m_docInfo->load( doc ); + } + } + else + { + //kdDebug( 30003 ) << "cannot open document info" << endl; + delete d->m_docInfo; + d->m_docInfo = new KoDocumentInfo( this, "document info" ); + } + + bool res = completeLoading( store ); + delete store; + QApplication::restoreOverrideCursor(); + m_bEmpty = false; + return res; +} + +// For embedded documents +bool KoDocument::loadFromStore( KoStore* _store, const QString& url ) +{ + if ( _store->open( url ) ) + { + QDomDocument doc; + doc.setContent( _store->device() ); + if ( !loadXML( _store->device(), doc ) ) + { + _store->close(); + return false; + } + _store->close(); + } else { + kdWarning() << "couldn't open " << url << endl; + } + + _store->pushDirectory(); + // Store as document URL + if ( url.startsWith( STORE_PROTOCOL ) ) { + m_url = KURL( url ); + } else { + m_url = KURL( INTERNAL_PREFIX + url ); + _store->enterDirectory( url ); + } + + if ( !loadChildren( _store ) ) + { + kdError(30003) << "ERROR: Could not load children" << endl; +#if 0 + return false; +#endif + } + + bool result = completeLoading( _store ); + + // Restore the "old" path + _store->popDirectory(); + + return result; +} + +bool KoDocument::loadOasisFromStore( KoStore* store ) +{ + KoOasisStyles oasisStyles; + QDomDocument contentDoc; + QDomDocument settingsDoc; + KoOasisStore oasisStore( store ); + bool ok = oasisStore.loadAndParse( "content.xml", contentDoc, d->lastErrorMessage ); + if ( !ok ) + return false; + + QDomDocument stylesDoc; + (void)oasisStore.loadAndParse( "styles.xml", stylesDoc, d->lastErrorMessage ); + // Load styles from style.xml + oasisStyles.createStyleMap( stylesDoc, true ); + // Also load styles from content.xml + oasisStyles.createStyleMap( contentDoc, false ); + + // TODO post 1.4, pass manifestDoc to the apps so that they don't have to do it themselves + // (when calling KoDocumentChild::loadOasisDocument) + //QDomDocument manifestDoc; + //KoOasisStore oasisStore( store ); + //if ( !oasisStore.loadAndParse( "tar:/META-INF/manifest.xml", manifestDoc, d->lastErrorMessage ) ) + // return false; + + if ( store->hasFile( "settings.xml" ) ) { + (void)oasisStore.loadAndParse( "settings.xml", settingsDoc, d->lastErrorMessage ); + } + if ( !loadOasis( contentDoc, oasisStyles, settingsDoc, store ) ) + return false; + + return true; +} + +bool KoDocument::isInOperation() const +{ + return d->m_numOperations > 0; +} + +void KoDocument::emitBeginOperation() +{ + + /* if we're already in an operation, don't send the signal again */ + if (!isInOperation()) + emit sigBeginOperation(); + d->m_numOperations++; +} + +void KoDocument::emitEndOperation() +{ + d->m_numOperations--; + + /* don't end the operation till we've cleared all the nested operations */ + if (d->m_numOperations == 0) + emit sigEndOperation(); + else if (d->m_numOperations < 0) + /* ignore 'end' calls with no matching 'begin' call */ + d->m_numOperations = 0; +} + + +bool KoDocument::isStoredExtern() const +{ + return !storeInternal() && hasExternURL(); +} + +void KoDocument::setModified( bool mod ) +{ + if ( isAutosaving() ) // ignore setModified calls due to autosaving + return; + + //kdDebug(30003)<<k_funcinfo<<" url:" << m_url.path() << endl; + //kdDebug(30003)<<k_funcinfo<<" mod="<<mod<<" MParts mod="<<KParts::ReadWritePart::isModified()<<" isModified="<<isModified()<<endl; + + if ( mod && !d->modifiedAfterAutosave ) { + // First change since last autosave -> start the autosave timer + setAutoSave( d->m_autoSaveDelay ); + } + d->modifiedAfterAutosave = mod; + + if ( mod == isModified() ) + return; + + KParts::ReadWritePart::setModified( mod ); + + if ( mod ) { + m_bEmpty = FALSE; + } else { + // When saving this document, all non-external child documents get saved too. + QPtrListIterator<KoDocumentChild> it = children(); + for (; it.current(); ++it ) + { + KoDocument *doc = it.current()->document(); + if ( doc && !it.current()->isStoredExtern() && !it.current()->isDeleted() && doc->isModified() ) + doc->setModified( false ); + } + } + + // This influences the title + setTitleModified(); + emit modified( mod ); +} + +void KoDocument::setDoNotSaveExtDoc( bool on ) +{ + d->m_doNotSaveExtDoc = on; +} + +int KoDocument::queryCloseDia() +{ + //kdDebug(30003)<<k_funcinfo<<endl; + + QString name; + if ( documentInfo() ) + { + name = documentInfo()->title(); + } + if ( name.isEmpty() ) + name = url().fileName(); + + if ( name.isEmpty() ) + name = i18n( "Untitled" ); + + int res = KMessageBox::warningYesNoCancel( 0L, + i18n( "<p>The document <b>'%1'</b> has been modified.</p><p>Do you want to save it?</p>" ).arg(name)); + + switch(res) + { + case KMessageBox::Yes : + setDoNotSaveExtDoc(); // Let save() only save myself and my internal docs + save(); // NOTE: External files always in native format. ###TODO: Handle non-native format + setModified( false ); // Now when queryClose() is called by closeEvent it won't do anything. + break; + case KMessageBox::No : + removeAutoSaveFiles(); + setModified( false ); // Now when queryClose() is called by closeEvent it won't do anything. + break; + default : // case KMessageBox::Cancel : + return res; // cancels the rest of the files + } + return res; +} + +int KoDocument::queryCloseExternalChildren() +{ + //kdDebug(30003)<<k_funcinfo<<" checking for children in: "<<url().url()<<endl; + setDoNotSaveExtDoc(false); + QPtrListIterator<KoDocumentChild> it( children() ); + for (; it.current(); ++it ) + { + if ( !it.current()->isDeleted() ) + { + KoDocument *doc = it.current()->document(); + if ( doc ) + { + bool foo = doc->isStoredExtern(); + kdDebug(36001) << "========== isStoredExtern() returned " + << foo << " ==========" << endl; + + if ( foo ) //###TODO: Handle non-native mimetype docs + { + { + kdDebug(30003)<<k_funcinfo<<" found modified child: "<<doc->url().url()<<" extern="<<doc->isStoredExtern()<<endl; + if ( doc->queryCloseDia() == KMessageBox::Cancel ) + return KMessageBox::Cancel; + } + } + if ( doc->queryCloseExternalChildren() == KMessageBox::Cancel ) + return KMessageBox::Cancel; + } + } + } + return KMessageBox::Ok; +} + +void KoDocument::setTitleModified( const QString caption, bool mod ) +{ + //kdDebug(30003)<<k_funcinfo<<" url: "<<url().url()<<" caption: "<<caption<<" mod: "<<mod<<endl; + KoDocument *doc = dynamic_cast<KoDocument *>( parent() ); + if ( doc ) + { + doc->setTitleModified( caption, mod ); + return; + } + // we must be root doc so update caption in all related windows + QPtrListIterator<KoMainWindow> it( d->m_shells ); + for (; it.current(); ++it ) + { + it.current()->updateCaption(); + it.current()->updateReloadFileAction(this); + it.current()->updateVersionsFileAction(this); + } +} + +void KoDocument::setTitleModified() +{ + //kdDebug(30003)<<k_funcinfo<<" url: "<<url().url()<<" extern: "<<isStoredExtern()<<" current: "<<d->m_current<<endl; + KoDocument *doc = dynamic_cast<KoDocument *>( parent() ); + QString caption; + if ( (url().isEmpty() || isStoredExtern()) && d->m_current ) + { + // Get caption from document info (title(), in about page) + if ( documentInfo() ) + { + KoDocumentInfoPage * page = documentInfo()->page( QString::fromLatin1("about") ); + if (page) + caption = static_cast<KoDocumentInfoAbout *>(page)->title(); + } + if ( caption.isEmpty() ) + caption = url().prettyURL( 0, KURL::StripFileProtocol ); // Fall back to document URL + + //kdDebug(30003)<<k_funcinfo<<" url: "<<url().url()<<" caption: "<<caption<<endl; + if ( doc ) + { + doc->setTitleModified( caption, isModified() ); + return; + } + else + { + // we must be root doc so update caption in all related windows + setTitleModified( caption, isModified() ); + return; + } + } + if ( doc ) + { + // internal doc or not current doc, so pass on the buck + doc->setTitleModified(); + } +} + +bool KoDocument::loadChildren( KoStore* ) +{ + return true; +} + +bool KoDocument::completeLoading( KoStore* ) +{ + return true; +} + +bool KoDocument::completeSaving( KoStore* ) +{ + return true; +} + +QDomDocument KoDocument::createDomDocument( const QString& tagName, const QString& version ) const +{ + return createDomDocument( instance()->instanceName(), tagName, version ); +} + +//static +QDomDocument KoDocument::createDomDocument( const QString& appName, const QString& tagName, const QString& version ) +{ + QDomImplementation impl; + QString url = QString("http://www.koffice.org/DTD/%1-%1.dtd").arg(appName).arg(version); + QDomDocumentType dtype = impl.createDocumentType( tagName, + QString("-//KDE//DTD %1 %1//EN").arg(appName).arg(version), + url ); + // The namespace URN doesn't need to include the version number. + QString namespaceURN = QString("http://www.koffice.org/DTD/%1").arg(appName); + QDomDocument doc = impl.createDocument( namespaceURN, tagName, dtype ); + doc.insertBefore( doc.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ), doc.documentElement() ); + return doc; +} + +KoXmlWriter* KoDocument::createOasisXmlWriter( QIODevice* dev, const char* rootElementName ) +{ + KoXmlWriter* writer = new KoXmlWriter( dev ); + writer->startDocument( rootElementName ); + writer->startElement( rootElementName ); + writer->addAttribute( "xmlns:office", KoXmlNS::office ); + writer->addAttribute( "xmlns:meta", KoXmlNS::meta ); + + if ( qstrcmp( rootElementName, "office:document-meta" ) != 0 ) { + writer->addAttribute( "xmlns:config", KoXmlNS::config ); + writer->addAttribute( "xmlns:text", KoXmlNS::text ); + writer->addAttribute( "xmlns:table", KoXmlNS::table ); + writer->addAttribute( "xmlns:draw", KoXmlNS::draw ); + writer->addAttribute( "xmlns:presentation", KoXmlNS::presentation ); + writer->addAttribute( "xmlns:dr3d", KoXmlNS::dr3d ); + writer->addAttribute( "xmlns:chart", KoXmlNS::chart ); + writer->addAttribute( "xmlns:form", KoXmlNS::form ); + writer->addAttribute( "xmlns:script", KoXmlNS::script ); + writer->addAttribute( "xmlns:style", KoXmlNS::style ); + writer->addAttribute( "xmlns:number", KoXmlNS::number ); + writer->addAttribute( "xmlns:math", KoXmlNS::math ); + writer->addAttribute( "xmlns:svg", KoXmlNS::svg ); + writer->addAttribute( "xmlns:fo", KoXmlNS::fo ); + writer->addAttribute( "xmlns:koffice", KoXmlNS::koffice ); + } + // missing: office:version="1.0" + + writer->addAttribute( "xmlns:dc", KoXmlNS::dc ); + writer->addAttribute( "xmlns:xlink", KoXmlNS::xlink ); + return writer; +} + +QDomDocument KoDocument::saveXML() +{ + kdError(30003) << "KoDocument::saveXML not implemented" << endl; + d->lastErrorMessage = i18n( "Internal error: saveXML not implemented" ); + return QDomDocument(); +} + +KService::Ptr KoDocument::nativeService() +{ + if ( !m_nativeService ) + m_nativeService = readNativeService( instance() ); + + return m_nativeService; +} + +QCString KoDocument::nativeFormatMimeType() const +{ + KService::Ptr service = const_cast<KoDocument *>(this)->nativeService(); + if ( !service ) + return QCString(); + QCString nativeMimeType = service->property( "X-KDE-NativeMimeType" ).toString().latin1(); + if ( nativeMimeType.isEmpty() ) { + // shouldn't happen, let's find out why it happened + if ( !service->serviceTypes().contains( "KOfficePart" ) ) + kdWarning(30003) << "Wrong desktop file, KOfficePart isn't mentionned" << endl; + else if ( !KServiceType::serviceType( "KOfficePart" ) ) + kdWarning(30003) << "The KOfficePart service type isn't installed!" << endl; + } + return nativeMimeType; +} + +QCString KoDocument::nativeOasisMimeType() const +{ + KService::Ptr service = const_cast<KoDocument *>(this)->nativeService(); + if ( !service ) + return QCString(); + return service->property( "X-KDE-NativeOasisMimeType" ).toString().latin1(); +} + + +//static +KService::Ptr KoDocument::readNativeService( KInstance *instance ) +{ + QString instname = instance ? instance->instanceName() : kapp->instanceName(); + + // The new way is: we look for a foopart.desktop in the kde_services dir. + QString servicepartname = instname + "part.desktop"; + KService::Ptr service = KService::serviceByDesktopPath( servicepartname ); + if ( service ) + kdDebug(30003) << servicepartname << " found." << endl; + if ( !service ) + { + // The old way is kept as fallback for compatibility, but in theory this is really never used anymore. + + // Try by path first, so that we find the global one (which has the native mimetype) + // even if the user created a kword.desktop in ~/.kde/share/applnk or any subdir of it. + // If he created it under ~/.kde/share/applnk/Office/ then no problem anyway. + service = KService::serviceByDesktopPath( QString::fromLatin1("Office/%1.desktop").arg(instname) ); + } + if ( !service ) + service = KService::serviceByDesktopName( instname ); + + return service; +} + +QCString KoDocument::readNativeFormatMimeType( KInstance *instance ) //static +{ + KService::Ptr service = readNativeService( instance ); + if ( !service ) + return QCString(); + + if ( service->property( "X-KDE-NativeMimeType" ).toString().isEmpty() ) + { + // It may be that the servicetype "KOfficePart" is missing, which leads to this property not being known + if ( KServiceType::serviceType( "KOfficePart" ) == 0L ) + kdError(30003) << "The serviceType KOfficePart is missing. Check that you have a kofficepart.desktop file in the share/servicetypes directory." << endl; + else { + QString instname = instance ? instance->instanceName() : kapp->instanceName(); + if ( instname != "koshell" ) // hack for koshell + kdWarning(30003) << service->desktopEntryPath() << ": no X-KDE-NativeMimeType entry!" << endl; + } + } + + return service->property( "X-KDE-NativeMimeType" ).toString().latin1(); +} + +QStringList KoDocument::readExtraNativeMimeTypes( KInstance *instance ) //static +{ + KService::Ptr service = readNativeService( instance ); + if ( !service ) + return QStringList(); + return service->property( "X-KDE-ExtraNativeMimeTypes" ).toStringList(); +} + +void KoDocument::setupXmlReader( QXmlSimpleReader& reader, bool namespaceProcessing ) +{ + if ( namespaceProcessing ) + { + reader.setFeature( "http://xml.org/sax/features/namespaces", TRUE ); + reader.setFeature( "http://xml.org/sax/features/namespace-prefixes", FALSE ); + } + else + { + reader.setFeature( "http://xml.org/sax/features/namespaces", FALSE ); + reader.setFeature( "http://xml.org/sax/features/namespace-prefixes", TRUE ); + } + reader.setFeature( "http://trolltech.com/xml/features/report-whitespace-only-CharData", TRUE ); +} + + +bool KoDocument::isNativeFormat( const QCString& mimetype ) const +{ + if ( mimetype == nativeFormatMimeType() ) + return true; + return extraNativeMimeTypes().contains( mimetype ); +} + +QStringList KoDocument::extraNativeMimeTypes() const +{ + QStringList lst; + // This implementation is temporary while we treat both koffice-1.3 and OASIS formats as native. + // But it's good to have this virtual method, in case some app want to + // support more than one native format. + KService::Ptr service = const_cast<KoDocument *>(this)->nativeService(); + if ( !service ) // can't happen + return lst; + return service->property( "X-KDE-ExtraNativeMimeTypes" ).toStringList(); +} + +int KoDocument::supportedSpecialFormats() const +{ + // Apps which support special output flags can add reimplement and add to this. + // E.g. this is how did "saving in the 1.1 format". + // SaveAsDirectoryStore is a given since it's implemented by KoDocument itself. + return SaveAsDirectoryStore; +} + +void KoDocument::addShell( KoMainWindow *shell ) +{ + if ( d->m_shells.findRef( shell ) == -1 ) + { + //kdDebug(30003) << "addShell: shell " << (void*)shell << " added to doc " << this << endl; + d->m_shells.append( shell ); + } +} + +void KoDocument::removeShell( KoMainWindow *shell ) +{ + //kdDebug(30003) << "removeShell: shell " << (void*)shell << " removed from doc " << this << endl; + d->m_shells.removeRef( shell ); +} + +const QPtrList<KoMainWindow>& KoDocument::shells() const +{ + return d->m_shells; +} + +int KoDocument::shellCount() const +{ + return d->m_shells.count(); +} + +DCOPObject * KoDocument::dcopObject() +{ + if ( !d->m_dcopObject ) + d->m_dcopObject = new KoDocumentIface( this ); + return d->m_dcopObject; +} + +QCString KoDocument::dcopObjectId() const +{ + return const_cast<KoDocument *>(this)->dcopObject()->objId(); +} + +void KoDocument::setErrorMessage( const QString& errMsg ) +{ + d->lastErrorMessage = errMsg; +} + +QString KoDocument::errorMessage() const +{ + return d->lastErrorMessage; +} + +void KoDocument::showSavingErrorDialog() +{ + if ( d->lastErrorMessage.isEmpty() ) + { + KMessageBox::error( 0L, i18n( "Could not save\n%1" ).arg( m_file ) ); + } + else if ( d->lastErrorMessage != "USER_CANCELED" ) + { + KMessageBox::error( 0L, i18n( "Could not save %1\nReason: %2" ).arg( m_file, d->lastErrorMessage ) ); + } +} + +void KoDocument::showLoadingErrorDialog() +{ + if ( d->lastErrorMessage.isEmpty() ) + { + KMessageBox::error( 0L, i18n( "Could not open\n%1" ).arg( url().prettyURL( 0, KURL::StripFileProtocol ) ) ); + } + else if ( d->lastErrorMessage != "USER_CANCELED" ) + { + KMessageBox::error( 0L, i18n( "Could not open %1\nReason: %2" ).arg( url().prettyURL( 0, KURL::StripFileProtocol ), d->lastErrorMessage ) ); + } +} + +bool KoDocument::isAutosaving() const +{ + return d->m_autosaving; +} + +bool KoDocument::isLoading() const +{ + return d->m_bLoading; +} + +void KoDocument::removeAutoSaveFiles() +{ + // Eliminate any auto-save file + QString asf = autoSaveFile( m_file ); // the one in the current dir + if ( QFile::exists( asf ) ) + QFile::remove( asf ); + asf = autoSaveFile( QString::null ); // and the one in $HOME + if ( QFile::exists( asf ) ) + QFile::remove( asf ); +} + +void KoDocument::setBackupFile( bool _b ) +{ + d->m_backupFile = _b; +} + +bool KoDocument::backupFile()const +{ + return d->m_backupFile; +} + + +void KoDocument::setBackupPath( const QString & _path) +{ + d->m_backupPath = _path; +} + +QString KoDocument::backupPath()const +{ + return d->m_backupPath; +} + +void KoDocument::setCurrent( bool on ) +{ + //kdDebug(30003)<<k_funcinfo<<" url: "<<url().url()<<" set to: "<<on<<endl; + KoDocument *doc = dynamic_cast<KoDocument *>( parent() ); + if ( doc ) + { + if ( !isStoredExtern() ) + { + // internal doc so set next external to current (for safety) + doc->setCurrent( true ); + return; + } + // only externally stored docs shall have file name in title + d->m_current = on; + if ( !on ) + { + doc->setCurrent( true ); // let my next external parent take over + return; + } + doc->forceCurrent( false ); // everybody else should keep off + } + else + d->m_current = on; + + setTitleModified(); +} + +void KoDocument::forceCurrent( bool on ) +{ + //kdDebug(30003)<<k_funcinfo<<" url: "<<url().url()<<" force to: "<<on<<endl; + d->m_current = on; + KoDocument *doc = dynamic_cast<KoDocument *>( parent() ); + if ( doc ) + { + doc->forceCurrent( false ); + } +} + +bool KoDocument::isCurrent() const +{ + return d->m_current; +} + +bool KoDocument::storeInternal() const +{ + return d->m_storeInternal; +} + +void KoDocument::setStoreInternal( bool i ) +{ + d->m_storeInternal = i; + //kdDebug(30003)<<k_funcinfo<<"="<<d->m_storeInternal<<" doc: "<<url().url()<<endl; +} + +bool KoDocument::hasExternURL() const +{ + return !url().protocol().isEmpty() && url().protocol() != STORE_PROTOCOL && url().protocol() != INTERNAL_PROTOCOL; +} + +void KoDocument::slotStarted( KIO::Job* job ) +{ + if ( job ) + { + job->setWindow( d->m_shells.current() ); + } +} + +static const struct { + const char* localName; + const char* documentType; +} TN2DTArray[] = { + { "text", I18N_NOOP( "a word processing" ) }, + { "spreadsheet", I18N_NOOP( "a spreadsheet" ) }, + { "presentation", I18N_NOOP( "a presentation" ) }, + { "chart", I18N_NOOP( "a chart" ) }, + { "drawing", I18N_NOOP( "a drawing" ) } +}; +static const unsigned int numTN2DT = sizeof( TN2DTArray ) / sizeof( *TN2DTArray ); + +QString KoDocument::tagNameToDocumentType( const QString& localName ) +{ + for ( unsigned int i = 0 ; i < numTN2DT ; ++i ) + if ( localName == TN2DTArray[i].localName ) + return i18n( TN2DTArray[i].documentType ); + return localName; +} + +QValueList<KoTextDocument *> KoDocument::allTextDocuments() const +{ + return QValueList<KoTextDocument *>(); +} + +KoPageLayout KoDocument::pageLayout(int /*pageNumber*/) const +{ + return m_pageLayout; +} + +KoUnit::Unit KoDocument::unit() const +{ + return d->m_unit; +} + +void KoDocument::setUnit( KoUnit::Unit unit ) +{ + if ( d->m_unit != unit ) + { + d->m_unit = unit; + emit unitChanged( unit ); + } +} + +QString KoDocument::unitName() const +{ + return KoUnit::unitName( unit() ); +} + +void KoDocument::showStartUpWidget( KoMainWindow* parent, bool alwaysShow ) +{ + if(!alwaysShow) { + KConfigGroup cfgGrp( instance()->config(), "TemplateChooserDialog" ); + QString fullTemplateName = cfgGrp.readPathEntry( "AlwaysUseTemplate" ); + + if( !fullTemplateName.isEmpty() ) { + openTemplate( fullTemplateName ); + shells().getFirst()->setRootDocument( this ); + return; + } + } + + if(d->m_startUpWidget){ + d->m_startUpWidget->show(); + } else { + d->m_startUpWidget = createOpenPane( parent->centralWidget(), instance(), templateType() ); + } + + parent->setDocToOpen( this ); + parent->factory()->container("mainToolBar", parent)->hide(); +} + +void KoDocument::openExistingFile( const QString& file ) +{ + KURL url( file ); + bool ok = openURL( url ); + setModified( false ); + + if( ok ) + QTimer::singleShot( 0, this, SLOT( deleteOpenPane() ) ); +} + +void KoDocument::openTemplate( const QString& file ) +{ + bool ok = loadNativeFormat( file ); + setModified( false ); + + if ( ok ) { + deleteOpenPane(); + resetURL(); + setEmpty(); + } else { + showLoadingErrorDialog(); + initEmpty(); + } +} + +void KoDocument::initEmpty() +{ + setEmpty(); + setModified(false); +} + +void KoDocument::startCustomDocument() { + deleteOpenPane(); +} + +KoOpenPane* KoDocument::createOpenPane( QWidget* parent, KInstance* instance, + const QString& templateType ) +{ + KoOpenPane* openPane = new KoOpenPane( parent, instance, templateType ); + QWidget *customDoc = createCustomDocumentWidget(openPane); + if(customDoc) { + openPane->setCustomDocumentWidget( customDoc ); + connect( customDoc, SIGNAL( documentSelected() ), this, SLOT( startCustomDocument() ) ); + } + openPane->show(); + + connect( openPane, SIGNAL( openExistingFile( const QString& ) ), + this, SLOT( openExistingFile( const QString& ) ) ); + connect( openPane, SIGNAL( openTemplate( const QString& ) ), + this, SLOT( openTemplate( const QString& ) ) ); + + return openPane; +} + +void KoDocument::setTemplateType( const QString& _templateType ) +{ + d->m_templateType = _templateType; +} + +QString KoDocument::templateType() const +{ + return d->m_templateType; +} + +void KoDocument::deleteOpenPane() +{ + if( d->m_startUpWidget ) { + d->m_startUpWidget->hide(); + QTimer::singleShot(1000, this, SLOT(deleteOpenPaneDelayed())); + + shells().getFirst()->factory()->container("mainToolBar", shells().getFirst())->show(); + shells().getFirst()->setRootDocument( this ); + } else { + emit closeEmbedInitDialog(); + } +} + +void KoDocument::deleteOpenPaneDelayed() +{ + delete d->m_startUpWidget; + d->m_startUpWidget = 0; +} + +QWidget* KoDocument::createCustomDocumentWidget(QWidget */*parent*/) { + return 0; +} + +bool KoDocument::showEmbedInitDialog(QWidget* parent) +{ + KDialogBase dlg(parent, "EmbedInitDialog", true, i18n("Embedding Object"), 0, KDialogBase::NoDefault); + KoOpenPane* pane = createOpenPane(&dlg, instance(), templateType()); + pane->layout()->setMargin(0); + dlg.setMainWidget(pane); + dlg.setInitialSize(dlg.configDialogSize("EmbedInitDialog")); + connect(this, SIGNAL(closeEmbedInitDialog()), &dlg, SLOT(slotOk())); + + bool ok = dlg.exec() == QDialog::Accepted; + + dlg.saveDialogSize("EmbedInitDialog"); + + return ok; +} + +#include "KoDocument_p.moc" +#include "KoDocument.moc" diff --git a/lib/kofficecore/KoDocument.h b/lib/kofficecore/KoDocument.h new file mode 100644 index 00000000..1e8f1f83 --- /dev/null +++ b/lib/kofficecore/KoDocument.h @@ -0,0 +1,1158 @@ +// -*- c-basic-offset: 4 -*- +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> + Copyright (C) 2000-2005 David Faure <faure@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 __ko_document_h__ +#define __ko_document_h__ + +class KoTextDocument; +namespace std { } +using namespace std; +#include <qwmatrix.h> + +#include <kparts/part.h> +#include <kurl.h> +#include <kservice.h> +#include <KoGlobal.h> +#include <KoUnit.h> +#include <KoPageLayout.h> +#include <koffice_export.h> + +class QDomElement; +class QDomDocument; +class QXmlSimpleReader; + +class KoStore; +class KoMainWindow; + +class KoChild; +class KoDocumentChild; +class KoView; +class KoDocumentInfo; +class DCOPObject; +class KoOasisStyles; +class KoXmlWriter; +class KoOpenPane; + +/** + * The %KOffice document class + * + * This class provides some functionality each %KOffice document should have. + * + * @short The %KOffice document class + */ +class KOFFICECORE_EXPORT KoDocument : public KParts::ReadWritePart +{ + Q_OBJECT + Q_PROPERTY( QCString dcopObjectId READ dcopObjectId) + Q_PROPERTY( bool backupFile READ backupFile WRITE setBackupFile ) + +public: + + /** + * Constructor. + * The first 4 arguments are the same as the ones passed to KParts::Factory::createPart. + * + * @param parentWidget the parent widget, in case we create a wrapper widget + * (in single view mode). + * Usually the first argument passed by KParts::Factory::createPart. + * @param widgetName name of the widget. + * @param parent may be another KoDocument, or anything else. + * Usually the third argument of KParts::Factory::createPart. + * @param name is used to identify this document via DCOP so you may want to + * pass a meaningful name here which matches the pattern [A-Za-z_][A-Za-z_0-9]*. + * @param singleViewMode determines whether the document may only have one view. In this case + * the @p parent must be a QWidget derived class. KoDocument will then create a wrapper widget + * (KoViewWrapperWidget) which is a child of @p parentWidget. + * This widget can be retrieved by calling widget(). + * + * @todo explain what the purpose of widgetName is. + */ + KoDocument( QWidget* parentWidget, + const char* widgetName, + QObject* parent, + const char* name, + bool singleViewMode = false ); + + /** + * Destructor. + * + * The destructor does not delete any attached KoView objects and it does not + * delete the attached widget as returned by widget(). + */ + virtual ~KoDocument(); + + /** + * Tells whether this document is in singleview mode. This mode can only be set + * in the constructor. + */ + bool isSingleViewMode() const; + + /** + * Is the document embedded? + */ + bool isEmbedded() const; + + /** + * Returns the action described action object. In fact only the "name" attribute + * of @p element is of interest here. The method searches first in the + * KActionCollection of the first view and then in the KActionCollection of this + * document. + * This allows %KOffice applications to define actions in both the view and the document. + * They should only define view-actions (like zooming and stuff) in the view. + * Every action which changes the document should be defined in the document. + * + * Please notice that KoDocument indirectly inherits KXMLGUIClient. + * + * @see KXMLGUIClient + * @see KXMLGUIClient::actionCollection + * @see KoView::action + */ + virtual KAction *action( const QDomElement &element ) const; + + /** + * Returns the DOM document which describes the GUI of the + * first view. + */ + virtual QDomDocument domDocument() const; + + /** + * @internal + */ + virtual void setManager( KParts::PartManager *manager ); + + /** + * Reimplemented from KParts::ReadWritePart for internal reasons + * (for the autosave functionality) + */ + virtual bool openURL( const KURL & url ); + + /** + * Opens the document given by @p url, without storing the URL + * in the KoDocument. + * Call this instead of openURL() to implement KoMainWindow's + * File --> Import feature. + * + * @note This will call openURL(). To differentiate this from an ordinary + * Open operation (in any reimplementation of openURL() or openFile()) + * call isImporting(). + */ + bool import( const KURL &url ); + + /** + * Saves the document as @p url without changing the state of the + * KoDocument (URL, modified flag etc.). Call this instead of + * KParts::ReadWritePart::saveAs() to implement KoMainWindow's + * File --> Export feature. + * + * @note This will call KoDocument::saveAs(). To differentiate this + * from an ordinary Save operation (in any reimplementation of + * saveFile()) call isExporting(). + */ + bool exp0rt( const KURL &url ); + + /** + * @brief Sets whether the document can be edited or is read only. + * + * This recursively applied to all child documents and + * KoView::updateReadWrite is called for every attached + * view. + */ + virtual void setReadWrite( bool readwrite = true ); + + /** + * @brief Used by KoApplication, and by KoMainWindow, when no document exists yet. + * + * With the help of @p instance or KApplication::instance() this + * method figures out which .desktop file matches this application. In this + * file it searches for the "X-KDE-NativeMimeType" entry and returns it. + * + * @see KService + * @see KDesktopFile + */ + static QCString readNativeFormatMimeType( KInstance *instance = 0 ); + + /** + * Used by KoMainWindow, when no document exists yet. + * + * With the help of @p instance or KApplication::instance() this + * method figures out which .desktop file matches this application. In this + * file it searches for the "X-KDE-ExtraNativeMimeTypes" entry and returns it. + * + * @see KService + * @see KDesktopFile + */ + static QStringList readExtraNativeMimeTypes( KInstance *instance = 0 ); + + /** + * With the help of @p instance or KApplication::instance() this + * method figures out which .desktop file matches this application, + * and returns the KService instance for it. + */ + static KService::Ptr readNativeService( KInstance *instance = 0 ); + + /** + * setup the XML reader, so that we don't have to duplicate the code. + */ + static void setupXmlReader( QXmlSimpleReader& reader, bool namespaceProcessing = false ); + + /** + * To be preferred when a document exists. It is fast when calling + * it multiple times since it caches the result that readNativeFormatMimeType() + * delivers. + * This comes from the X-KDE-NativeMimeType key in the .desktop file + * You do NOT have to reimplement this (it is only virtual for kounavail). + */ + virtual QCString nativeFormatMimeType() const; + + /** + * Returns the OASIS OpenDocument mimetype of the document, if supported + * This comes from the X-KDE-NativeOasisMimeType key in the .desktop file + */ + QCString nativeOasisMimeType() const; + + /// Checks whether a given mimetype can be handled natively. + bool isNativeFormat( const QCString& mimetype ) const; + + /// Returns a list of the mimetypes considered "native", i.e. which can + /// be saved by KoDocument without a filter, in *addition* to the main one + virtual QStringList extraNativeMimeTypes() const; + + /// Enum values used by specialOutputFlag - note that it's a bitfield for supportedSpecialFormats + enum { /*SaveAsKOffice1dot1 = 1,*/ // old and removed + SaveAsDirectoryStore = 2, + SaveAsFlatXML = 4 + // bitfield! next value is 8 + }; + + /** + * Return the set of SupportedSpecialFormats that the application wants to + * offer in the "Save" file dialog. + */ + virtual int supportedSpecialFormats() const; + + /** + * Returns the actual mimetype of the document + */ + QCString mimeType() const; + + /** + * @brief Sets the mime type for the document. + * + * When choosing "save as" this is also the mime type + * selected by default. + */ + void setMimeType( const QCString & mimeType ); + + /** + * @brief Set the format in which the document should be saved. + * + * This is called on loading, and in "save as", so you shouldn't + * have to call it. + * + * @param mimeType the mime type (format) to use. + * @param specialOutputFlag is for "save as older version" etc. + */ + void setOutputMimeType( const QCString & mimeType, int specialOutputFlag = 0 ); + QCString outputMimeType() const; + int specialOutputFlag() const; + + /** + * Returns true if this document was the result of opening a foreign + * file format and if the user hasn't yet saved the document (in any + * format). + * + * Used by KoMainWindow to warn the user when s/he lazily presses + * CTRL+S to save in the same foreign format, putting all his/her + * formatting at risk (normally an export confirmation only comes up + * with Save As). + * + * @param exporting specifies whether this is the setting for a + * File --> Export or File --> Save/Save As operation. + */ + bool confirmNonNativeSave( const bool exporting ) const; + void setConfirmNonNativeSave( const bool exporting, const bool on ); + + virtual bool wantExportConfirmation() const; + + /** + * Sets the error message to be shown to the user (use i18n()!) + * when loading or saving fails. + * If you asked the user about something and he chose "Cancel", + * set the message to the magic string "USER_CANCELED", to skip the error dialog. + */ + void setErrorMessage( const QString& errMsg ); + + /** + * Return the last error message. Usually KoDocument takes care of + * showing it; this method is mostly provided for non-interactive use. + * @since 1.4 + */ + QString errorMessage() const; + + /** + * Show the last error message in a message box. + * The dialog box will mention a saving problem. + * Note that save/saveFile takes care of doing it. + * @since 1.4 + */ + void showSavingErrorDialog(); + + /** + * Show the last error message in a message box. + * The dialog box will mention a loading problem. + * openURL/openFile takes care of doing it, but not loadNativeFormat itself, + * so this is often called after loadNativeFormat returned false. + * @since 1.4 + */ + void showLoadingErrorDialog(); + + /** + * Create a new view for the document. + */ + KoView *createView( QWidget *parent = 0, const char *name = 0 ); + + /** + * Adds a view to the document. + * + * This calls KoView::updateReadWrite to tell the new view + * whether the document is readonly or not. + */ + virtual void addView( KoView *view ); + + /** + * Removes a view of the document. + */ + virtual void removeView( KoView *view ); + + /** + * @return a list of views this document is displayed in + */ + const QPtrList<KoView> & views() const; + + /** + * @return number of views this document is displayed in + */ + int viewCount() const; + + /** + * Reimplemented from KParts::Part + */ + virtual KParts::Part *hitTest( QWidget *widget, const QPoint &globalPos ); + + /** + * Find the most nested child document which contains the + * requested point. The point is in the coordinate system + * of this part. If no child document contains this point, then + * a pointer to this document is returned. + * + * This function has to be overloaded if the document features child documents. + * + * @param pos is in (unzoomed) document coordinates + * @param matrix transforms points from the documents coordinate system + * to the coordinate system of the requested point. This is used by + * transformed child documents, see KoDocumentChild/KoChild. + * + * @return Pointer to the document under the mouse at that position + */ + virtual KoDocument *hitTest( const QPoint &pos, const QWMatrix& matrix = QWMatrix() ); + /// Temporary API for accessing the view that calls hitTest. + /// Will be passed to hitTest() in 2.x. + /// Only call this from within hitTest()! + KoView* hitTestView(); + + /** + * Paints the whole document into the given painter object. + * + * @param painter The painter object onto which will be drawn. + * @param rect The rect that should be used in the painter object. + * @param transparent If true then the entire rectangle is erased before painting. + * @param view The KoView is needed to fiddle about with the active widget, when painting children. + * @param zoomX The zoom value to be applied to X coordinates when painting. + * @param zoomY The zoom value to be applied to Y coordinates when painting. + */ + virtual void paintEverything( QPainter &painter, const QRect &rect, bool transparent = false, + KoView *view = 0L, double zoomX = 1.0, double zoomY = 1.0 ); + + /** + * @brief Generates a preview picture of the document + * @note The preview is used in the File Dialog and also to create the Thumbnail + */ + virtual QPixmap generatePreview( const QSize& size ); + + /** + * Paints all of the documents children into the given painter object. + * + * @param painter The painter object onto which will be drawn. + * @param rect The rect that should be used in the painter object. + * @param view The KoView is needed to fiddle about with the active widget. + * @param zoomX The zoom value to be applied to X coordinates when painting. + * @param zoomY The zoom value to be applied to Y coordinates when painting. + * + * @see #paintChild #paintEverything #paintContent + */ + virtual void paintChildren( QPainter &painter, const QRect &rect, KoView *view, double zoomX = 1.0, double zoomY = 1.0 ); + + /** + * Paint a given child. Normally called by paintChildren(). + * + * @param child The child to be painted. + * @param painter The painter object onto which will be drawn. + * @param view The KoView is needed to fiddle about with the active widget. + * @param zoomX The zoom value to be applied to X coordinates when painting. + * @param zoomY The zoom value to be applied to Y coordinates when painting. + * + * @see #paintEverything #paintChildren #paintContent + */ + virtual void paintChild( KoDocumentChild *child, QPainter &painter, KoView *view, + double zoomX = 1.0, double zoomY = 1.0 ); + + /** + * Paints the data itself. Normally called by paintEverything(). It does not + * paint the children. + * It's this method that %KOffice Parts have to implement. + * + * @param painter The painter object onto which will be drawn. + * @param rect The rect that should be used in the painter object. + * @param transparent If false the implementing method should fill the background. + * @param zoomX The zoom value to be applied to X coordinates when painting. + * @param zoomY The zoom value to be applied to Y coordinates when painting. + * + * @see #paintEverything + */ + virtual void paintContent( QPainter &painter, const QRect &rect, bool transparent = false, + double zoomX = 1.0, double zoomY = 1.0 ) = 0; + + /** + * Called by koApplication to check for an autosave file in $HOME + */ + bool checkAutoSaveFile(); + + /** + * This setting indicates who is calling initDoc. + * Usually the app will want to + * - show the template dialog with 'everything' if InitDocAppStarting, InitDocFileClose or InitDocEmbedded + * - show the template dialog with 'templates only' if InitDocFileNew + * - create an empty document with default settings if InitDocEmpty + */ + enum InitDocFlags { InitDocAppStarting, InitDocFileNew, InitDocFileClose, InitDocEmbedded, InitDocEmpty }; + + /** + * Initializes an empty document (display the template dialog!). + * You have to overload this method to initialize all your document variables. + * @param flags see InitDocFlags + * @param parentWidget the widget this document belongs with + */ + virtual bool initDoc(InitDocFlags flags, QWidget* parentWidget=0) = 0; + + /** + * Creates and shows the start up widget. + * @param parent the KoMainWindow used as parent for the widget. + * @param alwaysShow always show the widget even if the user has configured it to not show. + * @since 1.5 + */ + virtual void showStartUpWidget(KoMainWindow* parent, bool alwaysShow = false); + + /** + * Sets the modified flag on the document. This means that it has + * to be saved or not before deleting it. + */ + virtual void setModified( bool _mod ); + + /** + * Tells the document that its title has been modified, either because + * the modified status changes (this is done by setModified() ) or + * because the URL or the document-info's title changed. + */ + virtual void setTitleModified(); + + /** + * @return true if the document is empty. + */ + virtual bool isEmpty() const { return m_bEmpty; } + + /** + * @brief Sets the document to empty. + * + * Used after loading a template + * (which is not empty, but not the user's input). + * + * @see isEmpty() + */ + virtual void setEmpty() { m_bEmpty = true; } + + /** + * @brief Loads a document from a store. + * + * You should never have to reimplement. + * + * @param store The store to load from + * @param url An internal url, like tar:/1/2 + */ + virtual bool loadFromStore( KoStore* store, const QString& url ); + + /** + * @brief Loads an OASIS document from a store. + * This is used for both the main document and embedded objects. + */ + virtual bool loadOasisFromStore( KoStore* store ); + + /** + * @brief Saves a document to a store. + * + * You should not have to reimplement this - but call it in saveChildren(). + */ + virtual bool saveToStore( KoStore* store, const QString& path ); + + /** + * Reimplement this method to load the contents of your %KOffice document, + * from the XML document. This is for the pre-Oasis file format (maindoc.xml). + * + * You are supposed to use the QDomDocument. The QIODevice is provided only + * for the cases where some pre-processing is needed, like kpresenter's kprconverter. + * Note that the QIODevice could be 0L, when called from an import filter. + */ + virtual bool loadXML( QIODevice *, const QDomDocument & doc ) = 0; + + /** + * Reimplement this method to load the contents of your %KOffice document, + * from the XML document ("content.xml"). The styles have been parsed already, + * you can find them in the oasisStyles parameter. The store can be used + * to load images and embedded documents. + */ + virtual bool loadOasis( const QDomDocument & doc, KoOasisStyles& oasisStyles, + const QDomDocument & settings, KoStore* store ) = 0; + + /** + * Reimplement this method to save the contents of your %KOffice document, + * using the OASIS format. + */ + virtual bool saveOasis( KoStore* store, KoXmlWriter* manifestWriter ) = 0; + + /** + * Reimplement this to save the contents of the %KOffice document into + * a QDomDocument. The framework takes care of saving it to the store. + */ + virtual QDomDocument saveXML(); + + /** + * Return a correctly created QDomDocument for this KoDocument, + * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. + * @param tagName the name of the tag for the root element + * @param version the DTD version (usually the application's version). + * @deprecated use createOasisXmlWriter instead + */ + QDomDocument createDomDocument( const QString& tagName, const QString& version ) const; + + /** + * Return an XML writer for saving Oasis XML into the device @p dev, + * including the XML processing instruction, + * and the root element with all its namespaces. + * You can add more namespaces afterwards with addAttribute. + * + * @param dev the device into which the XML will be written. + * @param rootElementName the tag name of the root element. + * This is either office:document, office:document-content, + * office:document-styles, office:document-meta or office:document-settings + * @return the KoXmlWriter instance. It becomes owned by the caller, which + * must delete it at some point. + * + * Once done with writing the contents of the root element, you + * will need to call endElement(); endDocument(); before destroying the KoXmlWriter. + * @note OASIS-specific + */ + static KoXmlWriter* createOasisXmlWriter( QIODevice* dev, const char* rootElementName ); + + /** + * Return a correctly created QDomDocument for an old (1.3-style) %KOffice document, + * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. + * This static method can be used e.g. by filters. + * @param appName the app's instance name, e.g. kword, kspread, kpresenter etc. + * @param tagName the name of the tag for the root element, e.g. DOC for kword/kpresenter. + * @param version the DTD version (usually the application's version). + */ + static QDomDocument createDomDocument( const QString& appName, const QString& tagName, const QString& version ); + + /** + * The first thing to do in loadOasis is get hold of the office:body tag, then its child. + * If the child isn't the expected one, the error message can indicate what it is instead. + * This method returns a translated name for the type of document, + * e.g. i18n("Word Processing") for office:text. + */ + static QString tagNameToDocumentType( const QString& localName ); + + /** + * Save the document. The default implementation is to call + * saveXML(). This method exists only for applications that + * don't use QDomDocument for saving, i.e. kword and kpresenter. + */ + virtual bool saveToStream( QIODevice * dev ); + + /** + * Loads a document in the native format from a given URL. + * Reimplement if your native format isn't XML. + * + * @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter + */ + virtual bool loadNativeFormat( const QString & file ); + + /** + * Saves the document in native format, to a given file + * You should never have to reimplement. + * Made public for writing templates. + */ + virtual bool saveNativeFormat( const QString & file ); + + /** + * Activate/deactivate/configure the autosave feature. + * @param delay in seconds, 0 to disable + */ + void setAutoSave( int delay ); + + /** + * Checks whether the document is currently in the process of autosaving + */ + bool isAutosaving() const; + + /** + * Set whether the next openURL call should check for an auto-saved file + * and offer to open it. This is usually true, but can be turned off + * (e.g. for the preview module). + */ + void setCheckAutoSaveFile( bool b ); + + /** + * Set whether the next openURL call should show error message boxes in case + * of errors. This is usually the case, but e.g. not when generating thumbnail + * previews. + */ + void setAutoErrorHandlingEnabled( bool b ); + + /** + * Checks whether error message boxes should be shown. + * @since 1.3.1 + */ + bool isAutoErrorHandlingEnabled() const; + + /** + * Retrieve the default value for autosave in seconds. + * Called by the applications to use the correct default in their config + */ + static int defaultAutoSave() { return s_defaultAutoSave; } + + /** + * @return the list of all children. Do not modify the + * returned list. + */ + const QPtrList<KoDocumentChild>& children() const; + + /** + * @return the KoDocumentChild associated with the given Document, but only if + * @p doc is a direct child of this document. + * + * This is a convenience function. You could get the same result + * by traversing the list returned by children(). + */ + KoDocumentChild *child( KoDocument *doc ); + + /** + * @return the information concerning this document. + * @see KoDocumentInfo + */ + KoDocumentInfo *documentInfo() const; + + void setViewBuildDocument( KoView *view, const QDomDocument &doc ); + QDomDocument viewBuildDocument( KoView *view ); + + /** + * Appends the shell to the list of shells which show this + * document as their root document. + * + * This method is automatically called from KoMainWindow::setRootDocument, + * so you dont need to call it. + */ + virtual void addShell( KoMainWindow *shell ); + + /** + * Removes the shell from the list. That happens automatically if the shell changes its + * root document. Usually you dont need to call this method. + */ + virtual void removeShell( KoMainWindow *shell ); + + /** + * @return the list of shells for the main window + */ + const QPtrList<KoMainWindow>& shells() const; + + /** + * @return the number of shells for the main window + */ + int shellCount() const; + + /** + * @return the list of all the currently opened documents + */ + static QPtrList<KoDocument> *documentList() { return s_documentList; } + + /** + * @brief Return a DCOP interface for this document + * + * %KOffice parts are strongly recommended to reimplement this method, + * so that their DCOP interface provides more functionality than the basic KoDocumentIface + */ + virtual DCOPObject * dcopObject(); + + /** + * @return the ID of the DCOP interface for this document. + **/ + QCString dcopObjectId() const; + + /** + * Signal the progress of operations such as loading or saving. + */ + void emitProgress( int value ) { emit sigProgress( value ); } + + bool isInOperation() const; + virtual void emitBeginOperation(); + virtual void emitEndOperation(); + + /** + * Return true if url() is a real filename, false if url() is + * an internal url in the store, like "tar:/..." + */ + virtual bool isStoredExtern() const; + + /** + * @return the page layout associated with this document (margins, pageSize, etc). + * Override this if you want to provide different sized pages. + * + * @see KoPageLayout + */ + virtual KoPageLayout pageLayout(int pageNumber = 0) const; + + /** + * Performs a cleanup of unneeded backup files + */ + void removeAutoSaveFiles(); + + void setBackupFile( bool _b ); + + bool backupFile()const; + + /** + * Returns true if this document or any of its internal child documents are modified. + */ + bool isModified() const; + + /** + * Returns true during loading (openURL can be asynchronous) + */ + bool isLoading() const; + + int queryCloseExternalChildren(); + int queryCloseDia(); + + /** + * @brief Set when we do not want to save external children when saving our 'main' doc. + * + * This makes it possible to save 'main' doc + all its internal children first, then + * go on to save external children. Typically used by query close. + * Use: + * @code + * doc->setDoNotSaveExtDoc(); + * doc->save(); // saves doc and its internal children, + * //also calls saveExternalChildren() which sets setDoNotSaveExtDoc(false) + * doc->saveExternalChildren(); + * @endcode + */ + void setDoNotSaveExtDoc( bool on = true ); + + /** + * Sets the backup path of the document + */ + void setBackupPath( const QString & _path ); + + /** + * @return path to the backup document + */ + QString backupPath()const; + + /** + * Indicates that this document is currently viewed + * and thus should control the title caption. + * Also resets current flag for all parents. + */ + void setCurrent( bool on = true ); + + /** + * Sets current flag for this document and all its parents + */ + void forceCurrent( bool on ); + bool isCurrent() const; + + void setTitleModified( const QString caption, bool mod ); + + /** + * Sets the document URL to empty URL + * KParts doesn't allow this, but %KOffice apps have e.g. templates + * After using loadNativeFormat on a template, one wants + * to set the url to KURL() + */ + void resetURL() { m_url = KURL(); m_file = QString::null; } + + /** + * Set when you want an external embedded document to be stored internally + */ + void setStoreInternal( bool i ); + + /** + * @return true when external embedded documents are stored internally + */ + bool storeInternal() const; + + bool hasExternURL() const; + + /** + * Sets the document URL to @p url + * KParts doesn't really allow this, but it is needed for undo of setStoreInternal() + */ + void setURL( const KURL& url ) { m_url = url; } + + /** + * _Only_ use these functions to restore m_file (in KoMainWindow) after a + * failed save (remember to use setURL() to restore the URL as well). + * + * @warning Do _not_ use these functions for any other purpose. + * + * @internal + */ + QString &file() { return m_file; } + + /** + * _Only_ use these functions to restore m_file (in KoMainWindow) after a + * failed save (remember to use setURL() to restore the URL as well). + * + * @warning Do _not_ use these functions for any other purpose. + * + * @internal + */ + void setFile( const QString &file ) { m_file = file; } + + /** + * @internal (public for KoMainWindow) + */ + void setMimeTypeAfterLoading( const QString& mimeType ); + + /** + * @return returns the number of pages in the document. + */ + virtual int pageCount() const { return 1; } + + /** + * @return all kotext-based text objects in the document + * This is used by some text-analyzer plugins. + * @since 1.5 + */ + virtual QValueList<KoTextDocument *> allTextDocuments() const; + + /** + * Returns the unit used to display all measures/distances. + * @since 1.5 + */ + KoUnit::Unit unit() const; + + /** + * Sets the unit used to display all measures/distances. + * @since 1.5 + */ + void setUnit( KoUnit::Unit u ); + + /** + * Returns the name of the unit used to display all measures/distances. + * Use this method for displaying it in the user interface, but use + * unit() for everything else (conversions etc.) + * @since 1.5 + */ + QString unitName() const; + + /** + * Set the template type used. This is used by the start up widget to show + * the correct templates. + * @since 1.5 + */ + void setTemplateType(const QString& _templateType); + /** + * Template type used. This is used by the start up widget to show + * the correct templates. + * @since 1.5 + */ + QString templateType() const; + + /** + * Shows the init dialog when embeding + * @param parent the parent widget + * @since 1.5 + */ + virtual bool showEmbedInitDialog(QWidget* parent); + +public slots: + /** + * Initialize an empty document using default values + * @since 1.5 + */ + virtual void initEmpty(); + +signals: + + /** + * This signal is emitted when the unit is changed by setUnit() + * It is common to connect views to it, in order to change the displayed units + * (e.g. in the rulers) + */ + void unitChanged(KoUnit::Unit); + + /** + * This signal is emitted when a direct or indirect child document changes + * and needs to be updated in all views. + * + * If one of your child documents emits the childChanged signal, then you may + * usually just want to redraw this child. In this case you can ignore the parameter + * passed by the signal. + */ + void childChanged( KoDocumentChild *child ); + + /** + * Progress info while loading or saving. The value is in percents (i.e. a number between 0 and 100) + * Your KoDocument-derived class should emit the signal now and then during load/save. + * KoMainWindow will take care of displaying a progress bar automatically. + */ + void sigProgress( int value ); + + /** + * Emitted e.g. at the beginning of a save operation + * This is emitted by KoDocument and used by KoView to display a statusbar message + */ + void sigStatusBarMessage( const QString& text ); + + /** + * Emitted e.g. at the end of a save operation + * This is emitted by KoDocument and used by KoView to clear the statusbar message + */ + void sigClearStatusBarMessage(); + + void sigBeginOperation(); + void sigEndOperation(); + + /** + * Emitted when the document is modified + */ + void modified( bool ); + + void closeEmbedInitDialog(); + +protected slots: + /** + * This slot loads an existing file and deletes the start up widget. + * @param file the file to load (including path) + * @since 1.5 + */ + virtual void openExistingFile( const QString& file ); + /** + * This slot loads a template and deletes the start up widget. + * @param file the template to load + * @since 1.5 + */ + virtual void openTemplate( const QString& file ); + + void deleteOpenPaneDelayed(); + +protected: + + QString autoSaveFile( const QString & path ) const; + + virtual KoView *createViewInstance( QWidget *parent, const char *name ) = 0; + + /** + * Loads a document from KReadOnlyPart::m_file (KParts takes care of downloading + * remote documents). + * Applies a filter if necessary, and calls loadNativeFormat in any case + * You should not have to reimplement, except for very special cases. + * + * This method is called from the KReadOnlyPart::openURL method. + */ + virtual bool openFile(); + + /** + * Saves a document to KReadOnlyPart::m_file (KParts takes care of uploading + * remote documents) + * Applies a filter if necessary, and calls saveNativeFormat in any case + * You should not have to reimplement, except for very special cases. + */ + virtual bool saveFile(); + + /** + * Override this method in your derived class to show a widget in the startup 'dialog'. + * This widget should allow the user to set settings for a custom document (i.e. one + * not based on a template). + * The returned widget should provide its own button (preferrably 'Create') and + * implement the logic to implement the document instance correctly. + * After initializing the widget should emit a signal called 'documentSelected()' which + * will remove the startupWidget and show the document. + * @param parent the parent of the to be created widget. + */ + virtual QWidget* createCustomDocumentWidget(QWidget *parent); + + /** + * OLD XML method. For OASIS just call KoDocumentChild::loadOasisDocument + * after KoDocumentChild::loadOasis. + * + * You need to overload this function if your document may contain + * embedded documents. This function is called to load embedded documents. + * + * An example implementation may look like this: + * @code + * QPtrListIterator<KoDocumentChild> it( children() ); + * for( ; it.current(); ++it ) + * { + * if ( !it.current()->loadDocument( _store ) ) + * { + * return false; + * } + * } + * return true; + * @endcode + */ + virtual bool loadChildren( KoStore* ); + + /** + * Saves all internal children (only!). + * @see saveExternalChildren if you have external children. + * Returns true on success. + */ + virtual bool saveChildren( KoStore* store ); + + /** + * Saves all internal children (only!), to the store, using the OASIS format. + * This is called automatically during saveNativeFormat. + * @see saveExternalChildren if you have external children. + * Returns true on success. + */ + virtual bool saveChildrenOasis( KoStore* store, KoXmlWriter* manifestWriter ); + + /** + * Overload this function if you have to load additional files + * from a store. This function is called after loadXML() + * and after loadChildren() have been called. + */ + virtual bool completeLoading( KoStore* store ); + + /** + * If you want to write additional files to a store, + * then you must do it here. + * In the implementation, you should prepend the document + * url (using url().url()) before the filename, so that everything is kept relative + * to this document. For instance it will produce urls such as + * tar:/1/pictures/picture0.png, if the doc url is tar:/1 + * But do this ONLY if the document is not stored extern (see isStoredExtern() ). + * If it is, then the pictures should be saved to tar:/pictures. + */ + virtual bool completeSaving( KoStore* store ); + + /** + * Inserts the new child in the list of children and emits the + * childChanged() signal. + * + * At the same time this method marks this document as modified. + * + * To remove a child, just delete it. KoDocument will detect this + * and remove the child from its lists. + * + * @see #isModified + */ + virtual void insertChild( KoDocumentChild *child ); + + /** @internal */ + virtual void setModified() { KParts::ReadWritePart::setModified(); } + + /** @internal */ + virtual void insertChild(QObject *o) { QObject::insertChild(o); } + + KoPageLayout m_pageLayout; + + /** + * Saves all externally stored children. + * Returns true on success. + * @see #saveChildren for internal children + */ + virtual bool saveExternalChildren(); + + /** + * Returns whether or not the current openURL() or openFile() call is + * actually an import operation (like File --> Import). + * This is for informational purposes only. + */ + bool isImporting() const; + + /** + * Returns whether or not the current saveFile() call is actually an export + * operation (like File --> Export). + * If this function returns true during saveFile() and you are changing + * some sort of state, you _must_ restore it before the end of saveFile(); + * otherwise, File --> Export will not work properly. + */ + bool isExporting() const; + + /** + * Creates the open widget showed at application start up. + * @param parent the parent widget + * @param instance the KInstance to be used for KConfig data + * @param templateType the template-type (group) that should be selected on creation. + * @since 1.5 + */ + KoOpenPane* createOpenPane( QWidget* parent, KInstance* instance, + const QString& templateType = QString::null); + +private slots: + void slotChildChanged( KoChild *c ); + void slotChildDestroyed(); + void slotAutoSave(); + void slotStarted( KIO::Job* ); + void startCustomDocument(); + /** + * Removes the open widget showed at application start up. + * @since 1.5 + */ + void deleteOpenPane(); + +private: + KService::Ptr nativeService(); + bool oldLoadAndParse( KoStore* store, const QString& filename, QDomDocument& doc ); + bool loadNativeFormatFromStore( const QString& file ); + bool savePreview( KoStore* store ); + bool saveOasisPreview( KoStore* store, KoXmlWriter* manifestWriter ); + class Private; + Private *d; + KService::Ptr m_nativeService; + bool m_bEmpty; + static QPtrList<KoDocument> *s_documentList; + static const int s_defaultAutoSave; +}; + +#endif diff --git a/lib/kofficecore/KoDocumentChild.cpp b/lib/kofficecore/KoDocumentChild.cpp new file mode 100644 index 00000000..8157b95a --- /dev/null +++ b/lib/kofficecore/KoDocumentChild.cpp @@ -0,0 +1,523 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> + Copyright (C) 2004-2006 David Faure <faure@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 "KoDocumentChild.h" +#include "KoOasisStore.h" +#include <KoDocument.h> +#include <KoQueryTrader.h> +#include <KoXmlWriter.h> +#include <KoXmlNS.h> +#include <KoUnit.h> +#include <KoStore.h> +#include <KoStoreDevice.h> + +#include <kparts/partmanager.h> + +#include <kmimetype.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kdebug.h> + +#include <qapplication.h> + +#include <assert.h> + +// Define the protocol used here for embedded documents' URL +// This used to "store" but KURL didn't like it, +// so let's simply make it "tar" ! +#define STORE_PROTOCOL "tar" +#define INTERNAL_PROTOCOL "intern" +// Warning, keep it sync in koStore.cc and koDocument.cc + +/********************************************************** + * + * KoDocumentChild + * + **********************************************************/ + +class KoDocumentChildPrivate +{ +public: + KoDocumentChildPrivate() + { + } + ~KoDocumentChildPrivate() + { + } + + KoDocument *m_parent; + KoDocument *m_doc; + bool m_deleted; +}; + +KoDocumentChild::KoDocumentChild( KoDocument* parent, KoDocument* doc, const QRect& geometry ) + : KoChild( parent ) +{ + d = new KoDocumentChildPrivate; + d->m_parent = parent; + d->m_doc = doc; + setGeometry( geometry ); + d->m_deleted = false; + if ( doc ) + doc->setStoreInternal( !doc->hasExternURL() ); +} + +KoDocumentChild::KoDocumentChild( KoDocument* parent ) + : KoChild( parent ) +{ + d = new KoDocumentChildPrivate; + d->m_parent = parent; + d->m_doc = 0L; + d->m_deleted = false; +} + +void KoDocumentChild::setDocument( KoDocument *doc, const QRect &geometry ) +{ + kdDebug()<<k_funcinfo<<"doc: "<<doc->url().url()<<endl; + d->m_doc = doc; + setGeometry( geometry ); + + updateMatrix(); +} + +KoDocument *KoDocumentChild::document() const +{ + return d->m_doc; +} + +KoDocument* KoDocumentChild::parentDocument() const +{ + return d->m_parent; +} + +KoDocument* KoDocumentChild::hitTest( const QPoint &p, const QWMatrix &_matrix ) +{ + if ( !region( _matrix ).contains( p ) || !d->m_doc ) + return 0L; + + QWMatrix m( _matrix ); + m = matrix() * m; + m.scale( xScaling(), yScaling() ); + + return d->m_doc->hitTest( p, m ); +} + +void KoDocumentChild::loadOasis( const QDomElement &frameElement, const QDomElement& objectElement ) +{ + double x, y, w, h; + x = KoUnit::parseValue( frameElement.attributeNS( KoXmlNS::svg, "x", QString::null ) ); + y = KoUnit::parseValue( frameElement.attributeNS( KoXmlNS::svg, "y", QString::null ) ); + w = KoUnit::parseValue( frameElement.attributeNS( KoXmlNS::svg, "width", QString::null ) ); + h = KoUnit::parseValue( frameElement.attributeNS( KoXmlNS::svg, "height", QString::null ) ); + m_tmpGeometry = QRect((int)x, (int)y, (int)w, (int)h); // #### double->int conversion + setGeometry(m_tmpGeometry); + + QString url = objectElement.attributeNS( KoXmlNS::xlink, "href", QString::null ); + if ( url[0] == '#' ) + url = url.mid( 1 ); + if ( url.startsWith( "./" ) ) + m_tmpURL = QString( INTERNAL_PROTOCOL ) + ":/" + url.mid( 2 ); + else + m_tmpURL = url; + kdDebug() << k_funcinfo << m_tmpURL << endl; +} + + +bool KoDocumentChild::load( const QDomElement& element, bool uppercase ) +{ + if ( element.hasAttribute( "url" ) ) + m_tmpURL = element.attribute("url"); + if ( element.hasAttribute("mime") ) + m_tmpMimeType = element.attribute("mime"); + + if ( m_tmpURL.isEmpty() ) + { + kdDebug(30003) << "Empty 'url' attribute in OBJECT" << endl; + return false; + } + if ( m_tmpMimeType.isEmpty() ) + { + kdDebug(30003) << "Empty 'mime' attribute in OBJECT" << endl; + return false; + } + + bool brect = FALSE; + QDomNode n = element.firstChild(); + for( ; !n.isNull(); n = n.nextSibling() ) + { + QDomElement e = n.toElement(); + if ( e.isNull() ) continue; + if ( e.tagName() == "rect" || ( uppercase && e.tagName() == "RECT" ) ) + { + brect = true; + int x, y, w, h; + x=y=w=h=0; + if ( e.hasAttribute( "x" ) ) + x = e.attribute( "x" ).toInt(&brect); + if ( e.hasAttribute( "y" ) ) + y = e.attribute( "y" ).toInt(&brect); + if ( e.hasAttribute( "w" ) ) + w = e.attribute( "w" ).toInt(&brect); + if ( e.hasAttribute( "h" ) ) + h = e.attribute( "h" ).toInt(&brect); + m_tmpGeometry = QRect(x, y, w, h); + setGeometry(m_tmpGeometry); + } + } + + if ( !brect ) + { + kdDebug(30003) << "Missing RECT in OBJECT" << endl; + return false; + } + + return true; +} + +bool KoDocumentChild::loadDocument( KoStore* store ) +{ + assert( !m_tmpURL.isEmpty() ); + + kdDebug(30003) << "KoDocumentChild::loadDocument: trying to load " << m_tmpURL << endl; + + // Backwards compatibility + if ( m_tmpMimeType == "application/x-killustrator" ) + m_tmpMimeType = "application/x-kontour"; + + KoDocumentEntry e = KoDocumentEntry::queryByMimeType( m_tmpMimeType ); + if ( e.isEmpty() ) + { + kdWarning(30003) << "Could not create child document with type " << m_tmpMimeType << endl; + bool res = createUnavailDocument( store, true, m_tmpMimeType ); + if ( res ) + { + // Try to turn the mimetype name into its comment + QString mimeName = m_tmpMimeType; + KMimeType::Ptr mime = KMimeType::mimeType( m_tmpMimeType ); + if ( mime->name() != KMimeType::defaultMimeType() ) + mimeName = mime->comment(); + d->m_doc->setProperty( "unavailReason", i18n( "No handler found for %1" ).arg( mimeName ) ); + } + return res; + } + + return loadDocumentInternal( store, e, true /*open url*/, false /*not oasis*/ ); +} + +bool KoDocumentChild::loadOasisDocument( KoStore* store, const QDomDocument& manifestDoc ) +{ + QString path = m_tmpURL; + if ( m_tmpURL.startsWith( INTERNAL_PROTOCOL ) ) { + path = store->currentDirectory(); + if ( !path.isEmpty() ) + path += '/'; + QString relPath = KURL( m_tmpURL ).path(); + path += relPath.mid( 1 ); // remove leading '/' + } + if ( !path.endsWith( "/" ) ) + path += '/'; + const QString mimeType = KoOasisStore::mimeForPath( manifestDoc, path ); + kdDebug() << k_funcinfo << "path for manifest file=" << path << " mimeType=" << mimeType << endl; + if ( mimeType.isEmpty() ) { + kdError(30003) << "Manifest doesn't have media-type for " << path << endl; + return false; + } + + KoDocumentEntry e = KoDocumentEntry::queryByMimeType( mimeType ); + if ( e.isEmpty() ) + { + kdWarning(30003) << "Could not create child document with type " << mimeType << endl; + bool res = createUnavailDocument( store, true, mimeType ); + if ( res ) + { + // Try to turn the mimetype name into its comment + QString mimeName = mimeType; + KMimeType::Ptr mime = KMimeType::mimeType( mimeType ); + if ( mime->name() != KMimeType::defaultMimeType() ) + mimeName = mime->comment(); + d->m_doc->setProperty( "unavailReason", i18n( "No handler found for %1" ).arg( mimeName ) ); + } + return res; + } + + const bool oasis = mimeType.startsWith( "application/vnd.oasis.opendocument" ); + if ( !oasis ) { + m_tmpURL += "/maindoc.xml"; + kdDebug() << " m_tmpURL adjusted to " << m_tmpURL << endl; + } + return loadDocumentInternal( store, e, true /*open url*/, oasis ); +} + +bool KoDocumentChild::loadDocumentInternal( KoStore* store, const KoDocumentEntry& e, bool doOpenURL, bool oasis ) +{ + kdDebug(30003) << "KoDocumentChild::loadDocumentInternal doOpenURL=" << doOpenURL << " m_tmpURL=" << m_tmpURL << endl; + KoDocument * doc = e.createDoc( d->m_parent ); + if (!doc) { + kdWarning(30003) << "createDoc failed" << endl; + return false; + } + setDocument( doc, m_tmpGeometry ); + + bool res = true; + if ( doOpenURL ) + { + bool internalURL = false; + if ( m_tmpURL.startsWith( STORE_PROTOCOL ) || m_tmpURL.startsWith( INTERNAL_PROTOCOL ) || KURL::isRelativeURL( m_tmpURL ) ) + { + if ( oasis ) { + store->pushDirectory(); + assert( m_tmpURL.startsWith( INTERNAL_PROTOCOL ) ); + QString relPath = KURL( m_tmpURL ).path().mid( 1 ); + store->enterDirectory( relPath ); + res = d->m_doc->loadOasisFromStore( store ); + store->popDirectory(); + } else { + if ( m_tmpURL.startsWith( INTERNAL_PROTOCOL ) ) + m_tmpURL = KURL( m_tmpURL ).path().mid( 1 ); + res = d->m_doc->loadFromStore( store, m_tmpURL ); + } + internalURL = true; + d->m_doc->setStoreInternal( true ); + } + else + { + // Reference to an external document. Hmmm... + d->m_doc->setStoreInternal( false ); + KURL url( m_tmpURL ); + if ( !url.isLocalFile() ) + { + QApplication::restoreOverrideCursor(); + // For security reasons we need to ask confirmation if the url is remote + int result = KMessageBox::warningYesNoCancel( + 0, i18n( "This document contains an external link to a remote document\n%1").arg(m_tmpURL), + i18n( "Confirmation Required" ), i18n( "Download" ), i18n( "Skip" ) ); + + if ( result == KMessageBox::Cancel ) + { + d->m_parent->setErrorMessage("USER_CANCELED"); + return false; + } + if ( result == KMessageBox::Yes ) + res = d->m_doc->openURL( url ); + // and if == No, res will still be false so we'll use a kounavail below + } + else + res = d->m_doc->openURL( url ); + } + if ( !res ) + { + // Keep the error message from the attempted loading + QString errorMessage = d->m_doc->errorMessage(); + delete d->m_doc; + d->m_doc = 0; + QString tmpURL = m_tmpURL; // keep a copy, createUnavailDocument will erase it + // Not found -> use a kounavail instead + res = createUnavailDocument( store, false /* the URL doesn't exist, don't try to open it */, m_tmpMimeType ); + if ( res ) + { + d->m_doc->setProperty( "realURL", tmpURL ); // so that it gets saved correctly + d->m_doc->setStoreInternal( true ); + if ( internalURL ) + d->m_doc->setProperty( "unavailReason", i18n( "Could not load embedded object:\n%1" ).arg( errorMessage ) ); + else + d->m_doc->setProperty( "unavailReason", i18n( "Could not load external document %1:\n%2" ).arg( tmpURL, errorMessage ) ); + } + return res; + } + // Still waiting... + QApplication::setOverrideCursor( waitCursor ); + } + + m_tmpURL = QString::null; + + // see KoDocument::insertChild for an explanation what's going on + // now :-) + if ( parentDocument() ) + { + KoDocument *parent = parentDocument(); + + if ( parent->manager() && parent->manager()->parts() ) + { + KParts::PartManager *manager = parent->manager(); + + if ( !manager->parts()->containsRef( d->m_doc ) && + !parent->isSingleViewMode() ) + manager->addPart( d->m_doc, false ); + } + } + + QApplication::restoreOverrideCursor(); + + return res; +} + +bool KoDocumentChild::createUnavailDocument( KoStore* store, bool doOpenURL, const QString& mimeType ) +{ + // We don't need a trader query here. We're looking for a very specific component. + KService::Ptr serv = KService::serviceByDesktopName( "kounavail" ); + if ( serv == 0L ) + { + kdWarning(30003) << "ERROR: service kounavail not found " << endl; + return false; + } + KoDocumentEntry e( serv ); + if ( !loadDocumentInternal( store, e, doOpenURL, false ) ) + return false; + d->m_doc->setProperty( "mimetype", mimeType ); + return true; +} + +bool KoDocumentChild::saveOasis( KoStore* store, KoXmlWriter* manifestWriter ) +{ + QString path; + if ( d->m_doc->isStoredExtern() ) + { + kdDebug(30003)<<k_funcinfo<<" external (don't save) url:" << d->m_doc->url().url()<<endl; + path = d->m_doc->url().url(); + } + else + { + // The name comes from KoDocumentChild (which was set while saving the + // parent document). + assert( d->m_doc->url().protocol() == INTERNAL_PROTOCOL ); + const QString name = d->m_doc->url().path(); + kdDebug() << k_funcinfo << "saving " << name << endl; + + if ( d->m_doc->nativeOasisMimeType().isEmpty() ) { + // Embedded object doesn't support OASIS OpenDocument, save in the old format. + kdDebug() << k_funcinfo << "Embedded object doesn't support OASIS OpenDocument, save in the old format." << endl; + + if ( !d->m_doc->saveToStore( store, name ) ) + return false; + } + else + { + // To make the children happy cd to the correct directory + store->pushDirectory(); + store->enterDirectory( name ); + + if ( !d->m_doc->saveOasis( store, manifestWriter ) ) { + kdWarning(30003) << "KoDocumentChild::saveOasis failed" << endl; + return false; + } + // Now that we're done leave the directory again + store->popDirectory(); + } + + assert( d->m_doc->url().protocol() == INTERNAL_PROTOCOL ); + path = store->currentDirectory(); + if ( !path.isEmpty() ) + path += '/'; + path += d->m_doc->url().path(); + if ( path.startsWith( "/" ) ) + path = path.mid( 1 ); // remove leading '/', no wanted in manifest + } + + // OOo uses a trailing slash for the path to embedded objects (== directories) + if ( !path.endsWith( "/" ) ) + path += '/'; + QCString mimetype = d->m_doc->nativeOasisMimeType(); + if ( mimetype.isEmpty() ) + mimetype = d->m_doc->nativeFormatMimeType(); + manifestWriter->addManifestEntry( path, mimetype ); + + return true; +} + +void KoDocumentChild::saveOasisAttributes( KoXmlWriter &xmlWriter, const QString& name ) +{ + if ( !d->m_doc->isStoredExtern() ) + { + // set URL in document so that KoDocument::saveChildrenOasis will save + // the actual embedded object with the right name in the store. + KURL u; + u.setProtocol( INTERNAL_PROTOCOL ); + u.setPath( name ); + kdDebug() << k_funcinfo << u << endl; + d->m_doc->setURL( u ); + } + + //<draw:object draw:style-name="standard" draw:id="1" draw:layer="layout" svg:width="14.973cm" svg:height="4.478cm" svg:x="11.641cm" svg:y="14.613cm" xlink:href="#./Object 1" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad"/> + xmlWriter.addAttribute( "xlink:type", "simple" ); + xmlWriter.addAttribute( "xlink:show", "embed" ); + xmlWriter.addAttribute( "xlink:actuate", "onLoad" ); + + const QString ref = d->m_doc->isStoredExtern() ? d->m_doc->url().url() : "./"+ name; + kdDebug(30003) << "KoDocumentChild::saveOasis saving reference to embedded document as " << ref << endl; + xmlWriter.addAttribute( "xlink:href", /*"#" + */ref ); +} + +QDomElement KoDocumentChild::save( QDomDocument& doc, bool uppercase ) +{ + if( d->m_doc ) + { + QDomElement e = doc.createElement( ( uppercase ? "OBJECT" : "object" ) ); + if ( d->m_doc->url().protocol() != INTERNAL_PROTOCOL ) { + e.setAttribute( "url", d->m_doc->url().url() ); + kdDebug() << "KoDocumentChild::save url=" << d->m_doc->url().url() << endl; + } + else { + e.setAttribute( "url", d->m_doc->url().path().mid( 1 ) ); + kdDebug() << "KoDocumentChild::save url=" << d->m_doc->url().path().mid( 1 ) << endl; + } + e.setAttribute( "mime", d->m_doc->nativeFormatMimeType() ); + kdDebug() << "KoDocumentChild::save mime=" << d->m_doc->nativeFormatMimeType() << endl; + QDomElement rect = doc.createElement( ( uppercase ? "RECT" : "rect" ) ); + rect.setAttribute( "x", geometry().left() ); + rect.setAttribute( "y", geometry().top() ); + rect.setAttribute( "w", geometry().width() ); + rect.setAttribute( "h", geometry().height() ); + e.appendChild(rect); + return e; + } + return QDomElement(); +} + +bool KoDocumentChild::isStoredExtern() const +{ + assert( d->m_doc ); + return d->m_doc->isStoredExtern(); +} + +KURL KoDocumentChild::url() const +{ + return ( d->m_doc ? d->m_doc->url() : KURL() ); +} + +KoDocumentChild::~KoDocumentChild() +{ + if ( d->m_doc ) { + delete d->m_doc; + d->m_doc=0L; + } + delete d; +} + +bool KoDocumentChild::isDeleted() const +{ + return d->m_deleted; +} + +void KoDocumentChild::setDeleted( bool on ) +{ + d->m_deleted = on; +} + +#include "KoDocumentChild.moc" diff --git a/lib/kofficecore/KoDocumentChild.h b/lib/kofficecore/KoDocumentChild.h new file mode 100644 index 00000000..349961ed --- /dev/null +++ b/lib/kofficecore/KoDocumentChild.h @@ -0,0 +1,180 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@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 __koDocumentChild_h__ +#define __koDocumentChild_h__ + +#include <KoChild.h> +#include <koffice_export.h> +class QDomDocument; +class QDomElement; +class KURL; +class KoStore; +class KoDocument; +class KoDocumentChildPrivate; +class KoDocumentEntry; +class KoXmlWriter; + +/** + * @brief Holds an embedded object. + * + * In addition to its base class @ref KoChild it cares about the content + * of an embedded document. That means it supports operations like + * loading and saving. + * + * If you need a representation for embedded documents in your KOffice + * component then you should inherit from this class. + */ +class KOFFICECORE_EXPORT KoDocumentChild : public KoChild +{ + Q_OBJECT +public: + KoDocumentChild( KoDocument* parent, KoDocument* doc, const QRect& geometry ); + + /** + * When using this constructor you must call @ref #setDocument before + * you can call any other function of this class. + */ + KoDocumentChild( KoDocument* parent ); + + virtual ~KoDocumentChild(); + + /** + * Call this function only directly after calling the constructor + * that takes only a parent as argument. + */ + virtual void setDocument( KoDocument *doc, const QRect &geometry ); + + /** + * @return document contained in this child + * + * @see KoDocument + */ + KoDocument *document() const; + + /** + * @return parent document of this child + * + * @see KoDocument + */ + virtual KoDocument *parentDocument() const; // TODO: remove virtual, makes no sense + + virtual KoDocument* hitTest( const QPoint& p, const QWMatrix& _matrix = QWMatrix() ); + + /** + * @note Can be empty (which is why it doesn't return a const KURL &) + */ + KURL url() const; + + /** + * Writes the "object" tag, but does NOT write the content of the + * embedded documents. Saving the embedded documents themselves + * is done in @ref KoDocument::saveChildren. This function just stores information + * about the position and id of the embedded document and should be + * called from within KoDocument::saveXML. + * + * The "object" element is not added to the document. It is just created + * and returned. + * + * @return the element containing the "object" tag. + */ + virtual QDomElement save( QDomDocument& doc, bool uppercase=false ); + + /** + * Save an embedded object to OASIS. + * This method sets the attributes for the draw:object element in the parent XML document. + * It also prepares the embedded object for being saved into the store at + * the end of saving (see saveOasisToStore). + * Note that @p name is only used for "internal" documents (not extern). + */ + void saveOasisAttributes( KoXmlWriter &xmlWriter, const QString& name ); + + /** + * Save an embedded object to an OASIS store. + * This is called automatically by the parent KoDocument's saveOasis + */ + virtual bool saveOasis( KoStore* store, KoXmlWriter* manifestWriter ); + + /** + * Parses the "object" tag. This does NOT mean creating the child documents. + * AFTER the 'parser' has finished parsing, you must use @ref #loadDocument + * to actually load the embedded documents. + * + * What you should have in mind is that this method is called from within + * @ref KoDocument::loadXML while @ref #loadDocument is called from within + * @ref KoDocument::loadChildren, respectively from your implementation + * of these methods. + */ + virtual bool load( const QDomElement& element, bool uppercase=false ); + + void loadOasis( const QDomElement &frameElement, const QDomElement& objectElement ); + + /** + * Actually loads the document from the disk/net or from the store, + * depending on @ref #url + */ + virtual bool loadDocument( KoStore* ); + + /** + * Actually loads the document from the disk/net or from the store + * depending on the document's url + */ + virtual bool loadOasisDocument( KoStore* store, const QDomDocument& manifestDoc ); + + virtual bool isStoredExtern() const; + + /** + * This document (child) is deleted. + */ + bool isDeleted() const; + void setDeleted( bool on = true ); + +protected: // Should be private, but KWord needs access to the variables + // because it reimplements load/save (for uppercase tags) + + /** + * Holds the source of this object, for example "file:/home/weis/image.gif" + * or "tar:/table1/2" if it is stored in a koffice store. This variable is + * set after parsing the OBJECT tag in @ref #load and is reset after + * calling @ref #loadDocument. + */ + QString m_tmpURL; + + /** + * This variable is + * set after parsing the OBJECT tag in @ref #load and is reset after + * calling @ref #loadDocument. + */ + QRect m_tmpGeometry; + + /** + * This variable is + * set after parsing the OBJECT tag in @ref #load and is reset after + * calling @ref #loadDocument. + */ + QString m_tmpMimeType; + +private: + bool createUnavailDocument( KoStore* store, bool doOpenURL, const QString& mimeType ); + bool loadDocumentInternal( KoStore* _store, const KoDocumentEntry& e, bool doOpenURL, bool oasis ); + +private: + KoDocumentChildPrivate *d; +}; + +#endif diff --git a/lib/kofficecore/KoDocumentIface.cc b/lib/kofficecore/KoDocumentIface.cc new file mode 100644 index 00000000..2ff2f5fb --- /dev/null +++ b/lib/kofficecore/KoDocumentIface.cc @@ -0,0 +1,568 @@ +/* This file is part of the KDE project + Copyright (C) 2000 David Faure <faure@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. + + 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 "KoDocument.h" +#include "KoDocumentIface.h" +#include "KoDocumentInfoDlg.h" +#include "KoDocumentInfo.h" +#include "KoView.h" +#include <kapplication.h> +#include <dcopclient.h> +#include <kdcopactionproxy.h> +#include <kaction.h> +#include <kdebug.h> +#include <kdcoppropertyproxy.h> + +//static +QCString KoDocumentIface::newIfaceName() +{ + static int s_docIFNumber = 0; + QCString name; name.setNum( s_docIFNumber++ ); name.prepend("Document-"); + return name; +} + +KoDocumentIface::KoDocumentIface( KoDocument * doc, const char * name ) + : DCOPObject( name ? QCString(name) : newIfaceName() ) +{ + m_pDoc = doc; + m_actionProxy = new KDCOPActionProxy( doc->actionCollection(), this ); +} + +KoDocumentIface::~KoDocumentIface() +{ + delete m_actionProxy; +} + +void KoDocumentIface::openURL( QString url ) +{ + m_pDoc->openURL( KURL( url ) ); +} + +bool KoDocumentIface::isLoading() +{ + return m_pDoc->isLoading(); +} + +QString KoDocumentIface::url() +{ + return m_pDoc->url().url(); +} + +bool KoDocumentIface::isModified() +{ + return m_pDoc->isModified(); +} + +int KoDocumentIface::viewCount() +{ + return m_pDoc->viewCount(); +} + +DCOPRef KoDocumentIface::view( int idx ) +{ + QPtrList<KoView> views = m_pDoc->views(); + KoView *v = views.at( idx ); + if ( !v ) + return DCOPRef(); + + DCOPObject *obj = v->dcopObject(); + + if ( !obj ) + return DCOPRef(); + + return DCOPRef( kapp->dcopClient()->appId(), obj->objId() ); +} + +DCOPRef KoDocumentIface::action( const QCString &name ) +{ + return DCOPRef( kapp->dcopClient()->appId(), m_actionProxy->actionObjectId( name ) ); +} + +QCStringList KoDocumentIface::actions() +{ + QCStringList res; + QValueList<KAction *> lst = m_actionProxy->actions(); + QValueList<KAction *>::ConstIterator it = lst.begin(); + QValueList<KAction *>::ConstIterator end = lst.end(); + for (; it != end; ++it ) + res.append( (*it)->name() ); + + return res; +} + +QMap<QCString,DCOPRef> KoDocumentIface::actionMap() +{ + return m_actionProxy->actionMap(); +} + +void KoDocumentIface::save() +{ + m_pDoc->save(); +} + +void KoDocumentIface::saveAs( const QString & url ) +{ + m_pDoc->saveAs( KURL( url ) ); + m_pDoc->waitSaveComplete(); // see ReadWritePart +} + +void KoDocumentIface::setOutputMimeType( const QCString & mimetype ) +{ + m_pDoc->setOutputMimeType( mimetype ); +} + +QString KoDocumentIface::documentInfoAuthorName() const +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + return QString::null; + } + else + return authorPage->fullName(); +} + +QString KoDocumentIface::documentInfoEmail() const +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + return QString::null; + } + else + return authorPage->email(); +} + +QString KoDocumentIface::documentInfoCompanyName() const +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + return QString::null; + } + else + return authorPage->company(); +} + +QString KoDocumentIface::documentInfoTelephone() const +{ + kdDebug()<<" Keep compatibility with koffice <= 1.3 : use documentInfoTelephoneWork\n"; + return documentInfoTelephoneWork(); +} + +QString KoDocumentIface::documentInfoTelephoneWork() const +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + return QString::null; + } + else + return authorPage->telephoneWork(); +} + +QString KoDocumentIface::documentInfoTelephoneHome() const +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + return QString::null; + } + else + return authorPage->telephoneHome(); +} + + +QString KoDocumentIface::documentInfoFax() const +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + return QString::null; + } + else + return authorPage->fax(); + +} +QString KoDocumentIface::documentInfoCountry() const +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + return QString::null; + } + else + return authorPage->country(); + +} +QString KoDocumentIface::documentInfoPostalCode() const +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + return QString::null; + } + else + return authorPage->postalCode(); + +} +QString KoDocumentIface::documentInfoCity() const +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + return QString::null; + } + else + return authorPage->city(); + +} + +QString KoDocumentIface::documentInfoInitial() const +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + return QString::null; + } + else + return authorPage->initial(); +} + +QString KoDocumentIface::documentInfoAuthorPostion() const +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + return QString::null; + } + else + return authorPage->position(); +} + + +QString KoDocumentIface::documentInfoStreet() const +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + return QString::null; + } + else + return authorPage->street(); + +} + +QString KoDocumentIface::documentInfoTitle() const +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAbout * aboutPage = static_cast<KoDocumentInfoAbout *>(info->page( "about" )); + if ( !aboutPage ) + { + kdWarning() << "'About' page not found in documentInfo !" << endl; + return QString::null; + } + else + return aboutPage->title(); + +} + +QString KoDocumentIface::documentInfoAbstract() const +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAbout * aboutPage = static_cast<KoDocumentInfoAbout *>(info->page( "about" )); + if ( !aboutPage ) + { + kdWarning() << "'About' page not found in documentInfo !" << endl; + return QString::null; + } + else + return aboutPage->abstract(); +} + +QString KoDocumentIface::documentInfoKeywords() const +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAbout * aboutPage = static_cast<KoDocumentInfoAbout *>(info->page( "about" )); + if ( !aboutPage ) + { + kdWarning() << "'About' page not found in documentInfo !" << endl; + return QString::null; + } + else + return aboutPage->keywords(); +} + +QString KoDocumentIface::documentInfoSubject() const +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAbout * aboutPage = static_cast<KoDocumentInfoAbout *>(info->page( "about" )); + if ( !aboutPage ) + { + kdWarning() << "'About' page not found in documentInfo !" << endl; + return QString::null; + } + else + return aboutPage->subject(); +} +void KoDocumentIface::setDocumentInfoKeywords(const QString & text ) +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAbout * aboutPage = static_cast<KoDocumentInfoAbout *>(info->page( "about" )); + if ( !aboutPage ) + { + kdWarning() << "'About' page not found in documentInfo !" << endl; + } + else + aboutPage->setKeywords(text); +} + +void KoDocumentIface::setDocumentInfoSubject(const QString & text) +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAbout * aboutPage = static_cast<KoDocumentInfoAbout *>(info->page( "about" )); + if ( !aboutPage ) + { + kdWarning() << "'About' page not found in documentInfo !" << endl; + } + else + aboutPage->setSubject(text); +} + +void KoDocumentIface::setDocumentInfoAuthorName(const QString & text) +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + } + else + authorPage->setFullName(text); + +} + +void KoDocumentIface::setDocumentInfoEmail(const QString &text) +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + } + else + authorPage->setEmail(text); +} + +void KoDocumentIface::setDocumentInfoCompanyName(const QString &text) +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + } + else + authorPage->setCompany(text); +} + +void KoDocumentIface::setDocumentInfoAuthorPosition(const QString &text) +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + } + else + authorPage->setPosition(text); +} + + +void KoDocumentIface::setDocumentInfoTelephone(const QString &text) +{ + kdDebug()<<"Keep compatibility with koffice <= 1.3 : use setDocumentInfoTelephoneWork\n"; + setDocumentInfoTelephoneWork(text); +} + +void KoDocumentIface::setDocumentInfoTelephoneWork(const QString &text) +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + } + else + authorPage->setTelephoneWork(text); +} + +void KoDocumentIface::setDocumentInfoTelephoneHome(const QString &text) +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + } + else + authorPage->setTelephoneHome(text); +} + + +void KoDocumentIface::setDocumentInfoFax(const QString &text) +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + } + else + authorPage->setFax(text); +} + +void KoDocumentIface::setDocumentInfoCountry(const QString &text) +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + } + else + authorPage->setCountry(text); + +} + +void KoDocumentIface::setDocumentInfoTitle(const QString & text) +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAbout * aboutPage = static_cast<KoDocumentInfoAbout *>(info->page( "about" )); + if ( !aboutPage ) + { + kdWarning() << "'About' page not found in documentInfo !" << endl; + } + else + aboutPage->setTitle(text); +} + +void KoDocumentIface::setDocumentInfoPostalCode(const QString &text) +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + } + else + authorPage->setPostalCode(text); + +} + + +void KoDocumentIface::setDocumentInfoCity(const QString & text) +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + } + else + authorPage->setCity(text); +} + +void KoDocumentIface::setDocumentInfoInitial(const QString & text) +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + } + else + authorPage->setInitial(text); +} + + +void KoDocumentIface::setDocumentInfoStreet(const QString &text) +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(info->page( "author" )); + if ( !authorPage ) + { + kdWarning() << "Author information not found in documentInfo !" << endl; + } + else + authorPage->setStreet(text); + +} + + +void KoDocumentIface::setDocumentInfoAbstract(const QString &text) +{ + KoDocumentInfo * info = m_pDoc->documentInfo(); + KoDocumentInfoAbout * aboutPage = static_cast<KoDocumentInfoAbout *>(info->page( "about" )); + if ( !aboutPage ) + { + kdWarning() << "'About' page not found in documentInfo !" << endl; + } + else + aboutPage->setAbstract(text); +} + +QCStringList KoDocumentIface::functionsDynamic() +{ + return DCOPObject::functionsDynamic() + KDCOPPropertyProxy::functions( m_pDoc ); +} + +bool KoDocumentIface::processDynamic( const QCString &fun, const QByteArray &data, + QCString& replyType, QByteArray &replyData ) +{ + if ( KDCOPPropertyProxy::isPropertyRequest( fun, m_pDoc ) ) + return KDCOPPropertyProxy::processPropertyRequest( fun, data, replyType, replyData, m_pDoc ); + + return DCOPObject::processDynamic( fun, data, replyType, replyData ); +} + diff --git a/lib/kofficecore/KoDocumentIface.h b/lib/kofficecore/KoDocumentIface.h new file mode 100644 index 00000000..2713cb43 --- /dev/null +++ b/lib/kofficecore/KoDocumentIface.h @@ -0,0 +1,157 @@ +/* This file is part of the KDE project + Copyright (C) 2000 David Faure <faure@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. + + 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 __KoDocumentIface_h__ +#define __KoDocumentIface_h__ + +#include <dcopobject.h> +#include <qvaluelist.h> +#include <dcopref.h> +#include <koffice_export.h> +class KoDocument; +class KDCOPActionProxy; + +/** + * DCOP interface for any KOffice document + * Use KoApplicationIface to get hold of an existing document's interface, + * or to create a document. + * + * Note: KOffice Applications may (and should) reimplement KoDocument::dcopObject() + * In this case, don't look here... (unless the DCOP interface for the document + * inherits KoDocumentIface, which is a good thing to do) + */ +class KOFFICECORE_EXPORT KoDocumentIface : public DCOPObject +{ + K_DCOP +public: + + KoDocumentIface( KoDocument * doc, const char * name = 0 ); + ~KoDocumentIface(); + + /** + * Generate a name for this interface. Automatically used if name=0 is + * passed to the constructor + */ + static QCString newIfaceName(); + +k_dcop: + /** + * Returns the URL for this document (empty, real URL, or internal one) + */ + QString url(); + + /** + * Opens a document stored in @p url + * Warning: this is asynchronous. The document might not be loaded yet when + * this call returns. See isLoading. + */ + void openURL( QString url ); + + /** + * @return TRUE is the document is still loading + */ + bool isLoading(); + + /** + * @return TRUE is the document has been modified + */ + bool isModified(); + + /** + * @return the number of views this document is displayed in + */ + int viewCount(); + + /** + * @return a DCOP reference (DCOPRef) to the view with index @p idx + */ + DCOPRef view( int idx ); + + /** + * DCOP-action proxy + */ + DCOPRef action( const QCString &name ); + /** + * @return list of actions + */ + QCStringList actions(); + /** + * @return a map of (action name, DCOP reference) + */ + QMap<QCString,DCOPRef> actionMap(); + + /** + * Saves the document under its existing filename + */ + void save(); + + /** + * Saves the document under a new name + */ + void saveAs( const QString & url ); + + void setOutputMimeType( const QCString & mimetype ); + + QString documentInfoAuthorName() const; + QString documentInfoEmail() const; + QString documentInfoCompanyName() const; + QString documentInfoTitle() const; + QString documentInfoAbstract() const; + QString documentInfoKeywords() const; + QString documentInfoSubject() const; + QString documentInfoTelephone() const; + QString documentInfoTelephoneWork() const; + QString documentInfoTelephoneHome() const; + QString documentInfoFax() const; + QString documentInfoCountry() const; + QString documentInfoPostalCode() const; + QString documentInfoCity() const; + QString documentInfoStreet() const; + QString documentInfoInitial() const; + QString documentInfoAuthorPostion() const; + void setDocumentInfoAuthorName(const QString & text); + void setDocumentInfoEmail(const QString &text); + void setDocumentInfoCompanyName(const QString &text); + void setDocumentInfoTelephone(const QString &text); + void setDocumentInfoTelephoneWork(const QString &text); + void setDocumentInfoTelephoneHome(const QString &text); + void setDocumentInfoFax(const QString &text); + void setDocumentInfoCountry(const QString &text); + void setDocumentInfoTitle(const QString & text); + void setDocumentInfoPostalCode(const QString &text); + void setDocumentInfoCity(const QString & text); + void setDocumentInfoStreet(const QString &text); + void setDocumentInfoAbstract(const QString &text); + void setDocumentInfoInitial(const QString & text); + void setDocumentInfoKeywords(const QString & text ); + void setDocumentInfoSubject(const QString & text); + void setDocumentInfoAuthorPosition(const QString & text); + +public: + virtual QCStringList functionsDynamic(); + virtual bool processDynamic( const QCString &fun, const QByteArray &data, + QCString& replyType, QByteArray &replyData ); + +protected: + KoDocument * m_pDoc; + KDCOPActionProxy *m_actionProxy; +}; + +#endif + diff --git a/lib/kofficecore/KoDocumentInfo.cpp b/lib/kofficecore/KoDocumentInfo.cpp new file mode 100644 index 00000000..3d4e0505 --- /dev/null +++ b/lib/kofficecore/KoDocumentInfo.cpp @@ -0,0 +1,943 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999, 2000 Torben Weis <weis@kde.org> + Copyright (C) 2004 David Faure <faure@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 "KoDocumentInfo.h" +#include "KoDom.h" +#include "KoDocument.h" +#include "kofficeversion.h" +#include "KoApplication.h" + +#include <KoStoreDevice.h> +#include <KoXmlWriter.h> + +#include <kconfig.h> +#include <kdebug.h> +#include <kglobal.h> +#include <klocale.h> + +#include <qobjectlist.h> +#include <qdatetime.h> +#include "KoXmlNS.h" + +/***************************************** + * + * KoDocumentInfo + * + *****************************************/ + +KoDocumentInfo::KoDocumentInfo( QObject* parent, const char* name ) + : QObject( parent, name ) +{ + (void)new KoDocumentInfoUserMetadata( this ); + (void)new KoDocumentInfoAuthor( this ); + (void)new KoDocumentInfoAbout( this ); +} + +KoDocumentInfo::~KoDocumentInfo() +{ +} + +// KOffice-1.3 format +bool KoDocumentInfo::load( const QDomDocument& doc ) +{ + QStringList lst = pages(); + QStringList::ConstIterator it = lst.begin(); + for( ; it != lst.end(); ++it ) + { + KoDocumentInfoPage* p = page( *it ); + Q_ASSERT( p ); + if ( !p->load( doc.documentElement() ) ) + return false; + } + + return true; +} + +bool KoDocumentInfo::loadOasis( const QDomDocument& metaDoc ) +{ + //kdDebug()<<" metaDoc.toString() :"<<metaDoc.toString()<<endl; + QStringList lst = pages(); + QStringList::ConstIterator it = lst.begin(); + for( ; it != lst.end(); ++it ) + { + KoDocumentInfoPage* p = page( *it ); + Q_ASSERT( p ); + + QDomNode meta = KoDom::namedItemNS( metaDoc, KoXmlNS::office, "document-meta" ); + QDomNode office = KoDom::namedItemNS( meta, KoXmlNS::office, "meta" ); + + if ( office.isNull() ) + return false; + + if ( !p->loadOasis( office ) ) + return false; + } + return true; +} + +// KOffice-1.3 format +QDomDocument KoDocumentInfo::save() +{ + QDomDocument doc = KoDocument::createDomDocument( "document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1" ); + QDomElement e = doc.documentElement(); + + const QStringList lst = pages(); + QStringList::ConstIterator it = lst.begin(); + for( ; it != lst.end(); ++it ) + { + KoDocumentInfoPage* p = page( *it ); + Q_ASSERT( p ); + QDomElement s = p->save( doc ); + if ( s.isNull() ) + continue; + e.appendChild( s ); + } + if ( e.isNull() ) + return QDomDocument(); + + return doc; +} + +bool KoDocumentInfo::saveOasis( KoStore* store ) +{ + KoStoreDevice dev( store ); + KoXmlWriter* xmlWriter = KoDocument::createOasisXmlWriter( &dev, "office:document-meta" ); + xmlWriter->startElement( "office:meta" ); + + xmlWriter->startElement( "meta:generator"); + xmlWriter->addTextNode( QString( "KOffice/%1" ).arg( KOFFICE_VERSION_STRING ) ); + xmlWriter->endElement(); + QStringList lst = pages(); + QStringList::ConstIterator it = lst.begin(); + for( ; it != lst.end(); ++it ) + { + KoDocumentInfoPage* p = page( *it ); + Q_ASSERT( p ); + if ( !p->saveOasis( *xmlWriter ) ) + return false; + } + xmlWriter->endElement(); + xmlWriter->endElement(); // root element + xmlWriter->endDocument(); + delete xmlWriter; + return true; +} + +KoDocumentInfoPage* KoDocumentInfo::page( const QString& name ) const +{ + QObject* obj = const_cast<KoDocumentInfo*>(this)->child( name.latin1() ); + + return (KoDocumentInfoPage*)obj; +} + +QStringList KoDocumentInfo::pages() const +{ + QStringList ret; + + const QObjectList *list = children(); + if ( list ) + { + QObjectListIt it( *list ); + QObject *obj; + while ( ( obj = it.current() ) ) + { + ret.prepend( obj->name() ); + ++it; + } + } + + return ret; +} + +QString KoDocumentInfo::title() const +{ + KoDocumentInfoAbout * aboutPage = static_cast<KoDocumentInfoAbout *>(page( "about" )); + if ( !aboutPage ) { + kdWarning() << "'About' page not found in documentInfo !" << endl; + return QString::null; + } + else + return aboutPage->title(); +} + +QString KoDocumentInfo::creator() const +{ + KoDocumentInfoAuthor * authorPage = static_cast<KoDocumentInfoAuthor *>(page( "author" )); + if ( !authorPage ) { + kdWarning() << "'Author' page not found in documentInfo !" << endl; + return QString::null; + } + else + return authorPage->fullName(); +} + +/***************************************** + * + * KoDocumentInfoPage + * + *****************************************/ + +KoDocumentInfoPage::KoDocumentInfoPage( QObject* parent, const char* name ) + : QObject( parent, name ) +{ +} + +/***************************************** + * + * KoDocumentInfoAuthor + * + *****************************************/ + +KoDocumentInfoAuthor::KoDocumentInfoAuthor( KoDocumentInfo* info ) + : KoDocumentInfoPage( info, "author" ) +{ + initParameters(); +} + +KoDocumentInfoAuthor::~KoDocumentInfoAuthor() +{ + delete m_emailCfg; +} +void KoDocumentInfoAuthor::initParameters() +{ + KConfig* config = KoGlobal::kofficeConfig(); + if ( config->hasGroup( "Author" ) ) { + KConfigGroupSaver cgs( config, "Author" ); + m_telephoneHome=config->readEntry( "telephone" ); + m_telephoneWork=config->readEntry( "telephone-work" ); + m_fax=config->readEntry( "fax" ); + m_country=config->readEntry( "country" ); + m_postalCode=config->readEntry( "postal-code" ); + m_city=config->readEntry( "city" ); + m_street=config->readEntry( "street" ); + } + + m_emailCfg = new KConfig( "emaildefaults", true ); + m_emailCfg->setGroup( "Defaults" ); + QString group = m_emailCfg->readEntry("Profile","Default"); + m_emailCfg->setGroup(QString("PROFILE_%1").arg(group)); + + if ( m_fullName.isNull() ) // only if null. Empty means the user made it explicitly empty. + { + QString name = m_emailCfg->readEntry( "FullName" ); + if ( !name.isEmpty() ) + m_fullName = name; + } + if ( m_company.isNull() ) + { + QString name = m_emailCfg->readEntry( "Organization" ); + if ( !name.isEmpty() ) + m_company = name; + } +} + +bool KoDocumentInfoAuthor::saveOasis( KoXmlWriter &xmlWriter ) +{ + if ( !m_fullName.isEmpty() ) + { + xmlWriter.startElement( "dc:creator"); + xmlWriter.addTextNode( m_fullName ); + xmlWriter.endElement(); + } + if ( !m_initial.isEmpty() ) + { + xmlWriter.startElement( "meta:user-defined"); + xmlWriter.addAttribute( "meta:name", "initial" ); + xmlWriter.addTextNode( m_initial ); + xmlWriter.endElement(); + } + if ( !m_title.isEmpty() ) + { + xmlWriter.startElement( "meta:user-defined"); + xmlWriter.addAttribute( "meta:name", "author-title" ); + xmlWriter.addTextNode( m_title ); + xmlWriter.endElement(); + } + if ( !m_company.isEmpty() ) + { + xmlWriter.startElement( "meta:user-defined"); + xmlWriter.addAttribute( "meta:name", "company" ); + xmlWriter.addTextNode( m_company ); + xmlWriter.endElement(); + } + if ( !m_email.isEmpty() ) + { + xmlWriter.startElement( "meta:user-defined"); + xmlWriter.addAttribute( "meta:name", "email" ); + xmlWriter.addTextNode( m_email ); + xmlWriter.endElement(); + } + if ( !m_telephoneHome.isEmpty() ) + { + xmlWriter.startElement( "meta:user-defined"); + xmlWriter.addAttribute( "meta:name", "telephone" ); + xmlWriter.addTextNode( m_telephoneHome ); + xmlWriter.endElement(); + } + if ( !m_telephoneWork.isEmpty() ) + { + xmlWriter.startElement( "meta:user-defined"); + xmlWriter.addAttribute( "meta:name", "telephone-work" ); + xmlWriter.addTextNode( m_telephoneWork ); + xmlWriter.endElement(); + } + if ( !m_fax.isEmpty() ) + { + xmlWriter.startElement( "meta:user-defined"); + xmlWriter.addAttribute( "meta:name", "fax" ); + xmlWriter.addTextNode( m_fax ); + xmlWriter.endElement(); + } + if ( !m_country.isEmpty() ) + { + xmlWriter.startElement( "meta:user-defined"); + xmlWriter.addAttribute( "meta:name", "country" ); + xmlWriter.addTextNode( m_country ); + xmlWriter.endElement(); + } + if ( !m_postalCode.isEmpty() ) + { + xmlWriter.startElement( "meta:user-defined"); + xmlWriter.addAttribute( "meta:name", "postal-code" ); + xmlWriter.addTextNode( m_postalCode ); + xmlWriter.endElement(); + } + if ( !m_city.isEmpty() ) + { + xmlWriter.startElement( "meta:user-defined"); + xmlWriter.addAttribute( "meta:name", "city" ); + xmlWriter.addTextNode( m_city ); + xmlWriter.endElement(); + } + if ( !m_street.isEmpty() ) + { + xmlWriter.startElement( "meta:user-defined"); + xmlWriter.addAttribute( "meta:name", "street" ); + xmlWriter.addTextNode( m_street ); + xmlWriter.endElement(); + } + if ( !m_position.isEmpty() ) + { + xmlWriter.startElement( "meta:user-defined"); + xmlWriter.addAttribute( "meta:name", "position" ); + xmlWriter.addTextNode( m_position ); + xmlWriter.endElement(); + } + return true; +} + +bool KoDocumentInfoAuthor::loadOasis( const QDomNode& metaDoc ) +{ + QDomElement e = KoDom::namedItemNS( metaDoc, KoXmlNS::dc, "creator" ); + if ( !e.isNull() && !e.text().isEmpty() ) + m_fullName = e.text(); + QDomNode n = metaDoc.firstChild(); + for ( ; !n.isNull(); n = n.nextSibling() ) + { + if (n.isElement()) + { + QDomElement e = n.toElement(); + if ( e.namespaceURI() == KoXmlNS::meta && e.localName() == "user-defined" && !e.text().isEmpty() ) + { + QString name = e.attributeNS( KoXmlNS::meta, "name", QString::null ); + if ( name == "initial" ) + m_initial = e.text(); + else if ( name == "author-title" ) + m_title = e.text(); + else if ( name == "company" ) + m_company = e.text(); + else if ( name == "email" ) + m_email = e.text(); + else if ( name == "telephone" ) + m_telephoneHome = e.text(); + else if ( name == "telephone-work" ) + m_telephoneWork = e.text(); + else if ( name == "fax" ) + m_fax = e.text(); + else if ( name == "country" ) + m_country = e.text(); + else if ( name == "postal-code" ) + m_postalCode = e.text(); + else if ( name == "city" ) + m_city = e.text(); + else if ( name == "street" ) + m_street = e.text(); + else if ( name == "position" ) + m_position = e.text(); + } + } + } + return true; +} + +// KOffice-1.3 format +bool KoDocumentInfoAuthor::load( const QDomElement& e ) +{ + QDomNode n = e.namedItem( "author" ).firstChild(); + for( ; !n.isNull(); n = n.nextSibling() ) + { + QDomElement e = n.toElement(); + if ( e.isNull() ) continue; + if ( e.tagName() == "full-name" ) + m_fullName = e.text(); + else if ( e.tagName() == "initial" ) + m_initial = e.text(); + else if ( e.tagName() == "title" ) + m_title = e.text(); + else if ( e.tagName() == "company" ) + m_company = e.text(); + else if ( e.tagName() == "email" ) + m_email = e.text(); + else if ( e.tagName() == "telephone" ) + m_telephoneHome = e.text(); + else if ( e.tagName() == "telephone-work" ) + m_telephoneWork = e.text(); + else if ( e.tagName() == "fax" ) + m_fax = e.text(); + else if ( e.tagName() == "country" ) + m_country = e.text(); + else if ( e.tagName() == "postal-code" ) + m_postalCode = e.text(); + else if ( e.tagName() == "city" ) + m_city = e.text(); + else if ( e.tagName() == "street" ) + m_street = e.text(); + else if ( e.tagName() == "position" ) + m_position = e.text(); + } + return true; +} + +QDomElement KoDocumentInfoAuthor::save( QDomDocument& doc ) +{ + QDomElement e = doc.createElement( "author" ); + + QDomElement t = doc.createElement( "full-name" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_fullName ) ); + + t = doc.createElement( "initial" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_initial ) ); + + + t = doc.createElement( "title" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_title ) ); + + t = doc.createElement( "company" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_company ) ); + + t = doc.createElement( "email" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_email ) ); + + t = doc.createElement( "telephone" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_telephoneHome ) ); + + t = doc.createElement( "telephone-work" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_telephoneWork ) ); + + t = doc.createElement( "fax" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_fax ) ); + + t = doc.createElement( "country" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_country ) ); + + t = doc.createElement( "postal-code" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_postalCode ) ); + + t = doc.createElement( "city" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_city ) ); + + t = doc.createElement( "street" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_street ) ); + + t = doc.createElement( "position" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_position ) ); + + return e; +} + +QString KoDocumentInfoAuthor::fullName() const +{ + return m_fullName; +} + +QString KoDocumentInfoAuthor::initial() const +{ + return m_initial; +} + +QString KoDocumentInfoAuthor::title() const +{ + return m_title; +} + +QString KoDocumentInfoAuthor::company() const +{ + return m_company; +} + +QString KoDocumentInfoAuthor::email() const +{ + return m_email; +} + +QString KoDocumentInfoAuthor::telephoneHome() const +{ + return m_telephoneHome; +} + +QString KoDocumentInfoAuthor::telephoneWork() const +{ + return m_telephoneWork; +} + +QString KoDocumentInfoAuthor::fax() const +{ + return m_fax; +} + +QString KoDocumentInfoAuthor::country() const +{ + return m_country; +} + +QString KoDocumentInfoAuthor::postalCode() const +{ + return m_postalCode; +} + +QString KoDocumentInfoAuthor::city() const +{ + return m_city; +} + +QString KoDocumentInfoAuthor::street() const +{ + return m_street; +} + +QString KoDocumentInfoAuthor::position() const +{ + return m_position; +} + +void KoDocumentInfoAuthor::setFullName( const QString& n ) +{ + m_fullName = n; +} + +void KoDocumentInfoAuthor::setInitial( const QString& n ) +{ + m_initial = n; +} + +void KoDocumentInfoAuthor::setTitle( const QString& n ) +{ + m_title = n; +} + +void KoDocumentInfoAuthor::setCompany( const QString& n ) +{ + m_company = n; +} + +void KoDocumentInfoAuthor::setEmail( const QString& n ) +{ + m_email = n; +} + +void KoDocumentInfoAuthor::setTelephoneHome( const QString& n ) +{ + m_telephoneHome = n; +} + +void KoDocumentInfoAuthor::setTelephoneWork( const QString& n ) +{ + m_telephoneWork = n; +} + +void KoDocumentInfoAuthor::setFax( const QString& n ) +{ + m_fax = n; +} + +void KoDocumentInfoAuthor::setCountry( const QString& n ) +{ + m_country = n; +} + +void KoDocumentInfoAuthor::setPostalCode( const QString& n ) +{ + m_postalCode = n; +} + +void KoDocumentInfoAuthor::setCity( const QString& n ) +{ + m_city = n; +} + +void KoDocumentInfoAuthor::setStreet( const QString& n ) +{ + m_street = n; +} + +void KoDocumentInfoAuthor::setPosition( const QString& n ) +{ + m_position = n; +} + + +/***************************************** + * + * KoDocumentInfoAbout + * + *****************************************/ + +KoDocumentInfoAbout::KoDocumentInfoAbout( KoDocumentInfo* info ) + : KoDocumentInfoPage( info, "about" ) +{ + m_firstSave = true; + m_docInfo = info; + m_editingCycles = 0; + m_initialCreator = m_docInfo->creator(); + m_creationDate = QDateTime::currentDateTime(); +} + +void KoDocumentInfoAbout::saveParameters() +{ + KoDocument* doc = dynamic_cast< KoDocument* >( m_docInfo->parent() ); + if ( m_firstSave && doc && !doc->isAutosaving() ) + m_editingCycles++; + m_modificationDate = QDateTime::currentDateTime(); + m_firstSave = false; +} + +bool KoDocumentInfoAbout::saveOasis( KoXmlWriter &xmlWriter ) +{ + saveParameters(); + if ( !m_title.isEmpty() ) + { + xmlWriter.startElement( "dc:title" ); + xmlWriter.addTextNode( m_title ); + xmlWriter.endElement(); + } + if ( !m_abstract.isEmpty() ) + { + xmlWriter.startElement( "dc:description" ); + xmlWriter.addTextNode( m_abstract ); + xmlWriter.endElement(); + } + if ( !m_keywords.isEmpty() ) + { + xmlWriter.startElement( "meta:keyword" ); + xmlWriter.addTextNode( m_keywords ); + xmlWriter.endElement(); + } + if ( !m_subject.isEmpty() ) + { + xmlWriter.startElement( "dc:subject" ); + xmlWriter.addTextNode( m_subject ); + xmlWriter.endElement(); + } + if ( !m_initialCreator.isEmpty() ) + { + xmlWriter.startElement( "meta:initial-creator" ); + xmlWriter.addTextNode( m_initialCreator ); + xmlWriter.endElement(); + } + + xmlWriter.startElement( "meta:editing-cycles" ); + xmlWriter.addTextNode( QString::number( m_editingCycles ) ); + xmlWriter.endElement(); + + if ( m_creationDate.isValid() ) + { + xmlWriter.startElement( "meta:creation-date" ); + xmlWriter.addTextNode( m_creationDate.toString( Qt::ISODate ) ); + xmlWriter.endElement(); + } + + if ( m_modificationDate.isValid() ) + { + xmlWriter.startElement( "dc:date" ); + xmlWriter.addTextNode( m_modificationDate.toString( Qt::ISODate ) ); + xmlWriter.endElement(); + } + return true; +} + +bool KoDocumentInfoAbout::loadOasis( const QDomNode& metaDoc ) +{ + QDomElement e = KoDom::namedItemNS( metaDoc, KoXmlNS::dc, "title" ); + if ( !e.isNull() && !e.text().isEmpty() ) + { + m_title = e.text(); + } + e = KoDom::namedItemNS( metaDoc, KoXmlNS::dc, "description" ); + if ( !e.isNull() && !e.text().isEmpty() ) + { + m_abstract = e.text(); + } + e = KoDom::namedItemNS( metaDoc, KoXmlNS::dc, "subject" ); + if ( !e.isNull() && !e.text().isEmpty() ) + { + m_subject = e.text(); + } + e = KoDom::namedItemNS( metaDoc, KoXmlNS::meta, "keyword" ); + if ( !e.isNull() && !e.text().isEmpty() ) + { + m_keywords = e.text(); + } + e = KoDom::namedItemNS( metaDoc, KoXmlNS::meta, "initial-creator" ); + if ( !e.isNull() && !e.text().isEmpty() ) + m_initialCreator = e.text(); + else + m_initialCreator = i18n( "Unknown" ); + + e = KoDom::namedItemNS( metaDoc, KoXmlNS::meta, "editing-cycles" ); + if ( !e.isNull() && !e.text().isEmpty() ) + m_editingCycles = e.text().toInt(); + + e = KoDom::namedItemNS( metaDoc, KoXmlNS::meta, "creation-date" ); + if ( !e.isNull() && !e.text().isEmpty() ) + m_creationDate = QDateTime::fromString( e.text(), Qt::ISODate ); + else + m_creationDate = QDateTime(); + + e = KoDom::namedItemNS( metaDoc, KoXmlNS::dc, "date" ); + if ( !e.isNull() && !e.text().isEmpty() ) + m_modificationDate = QDateTime::fromString( e.text(), Qt::ISODate ); + return true; +} + +// KOffice-1.3 format +bool KoDocumentInfoAbout::load( const QDomElement& e ) +{ + QDomNode n = e.namedItem( "about" ).firstChild(); + for( ; !n.isNull(); n = n.nextSibling() ) + { + QDomElement e = n.toElement(); + if ( e.isNull() ) continue; + if ( e.tagName() == "abstract" ) + m_abstract = e.text(); + else if ( e.tagName() == "title" ) + m_title = e.text(); + else if ( e.tagName() == "subject" ) + m_subject = e.text(); + else if ( e.tagName() == "keyword" ) + m_keywords = e.text(); + else if ( e.tagName() == "initial-creator" ) + m_initialCreator = e.text(); + else if ( e.tagName() == "editing-cycles" ) + m_editingCycles = e.text().toInt(); + else if ( e.tagName() == "creation-date" ) + m_creationDate = QDateTime::fromString( e.text(), Qt::ISODate ); + else if ( e.tagName() == "date" ) + m_modificationDate = QDateTime::fromString( e.text(), Qt::ISODate ); + } + + return true; +} + +// KOffice-1.3 format +QDomElement KoDocumentInfoAbout::save( QDomDocument& doc ) +{ + saveParameters(); + QDomElement e = doc.createElement( "about" ); + + QDomElement t = doc.createElement( "abstract" ); + e.appendChild( t ); + t.appendChild( doc.createCDATASection( m_abstract ) ); + + t = doc.createElement( "title" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_title ) ); + + t = doc.createElement( "keyword" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_keywords ) ); + + t = doc.createElement( "subject" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_subject ) ); + + t = doc.createElement( "initial-creator" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_initialCreator ) ); + + t = doc.createElement( "editing-cycles" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( QString::number( m_editingCycles ) ) ); + + t = doc.createElement( "creation-date" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_creationDate.toString( Qt::ISODate ) ) ); + + t = doc.createElement( "date" ); + e.appendChild( t ); + t.appendChild( doc.createTextNode( m_modificationDate.toString( Qt::ISODate ) ) ); + return e; +} + +QString KoDocumentInfoAbout::title() const +{ + return m_title; +} + +QString KoDocumentInfoAbout::abstract() const +{ + return m_abstract; +} + +QString KoDocumentInfoAbout::initialCreator() const +{ + return m_initialCreator; +} + +QString KoDocumentInfoAbout::editingCycles() const +{ + return QString::number( m_editingCycles ); +} + +QString KoDocumentInfoAbout::creationDate() const +{ + if ( m_creationDate.isValid() ) + return KGlobal::locale()->formatDateTime( m_creationDate ); + else + return QString::null; +} + +QString KoDocumentInfoAbout::modificationDate() const +{ + if ( m_modificationDate.isValid() ) + return KGlobal::locale()->formatDateTime( m_modificationDate ); + else + return QString::null; +} + +void KoDocumentInfoAbout::setTitle( const QString& n ) +{ + m_title = n; +} + +void KoDocumentInfoAbout::setAbstract( const QString& n ) +{ + m_abstract = n; +} + +QString KoDocumentInfoAbout::keywords() const +{ + return m_keywords; +} + +QString KoDocumentInfoAbout::subject() const +{ + return m_subject; +} + +void KoDocumentInfoAbout::setKeywords( const QString& n ) +{ + m_keywords = n; +} + +void KoDocumentInfoAbout::setSubject( const QString& n ) +{ + m_subject = n; +} + +void KoDocumentInfoAbout::resetMetaData() +{ + m_editingCycles = 0; + m_initialCreator = m_docInfo->creator(); + m_creationDate = QDateTime::currentDateTime(); + m_modificationDate = QDateTime(); +} + +/***************************************** + * + * KoDocumentInfoUserMetadata + * + *****************************************/ + +KoDocumentInfoUserMetadata::KoDocumentInfoUserMetadata( KoDocumentInfo* info ) + : KoDocumentInfoPage( info, "user_metadata" ) +{ + m_reserved << "initial" << "author-title" << "company" << "email" << "telephone" + << "telephone-work" << "fax" << "country" << "postal-code" << "city" << "street" + << "position"; +} + +bool KoDocumentInfoUserMetadata::saveOasis( KoXmlWriter &xmlWriter ) +{ + QMap<QString, QString>::iterator it; + for ( it = m_metaList.begin(); it != m_metaList.end(); ++it ) + { + xmlWriter.startElement( "meta:user-defined"); + xmlWriter.addAttribute( "meta:name", it.key() ); + xmlWriter.addTextNode( it.data() ); + xmlWriter.endElement(); + } + return true; +} + +bool KoDocumentInfoUserMetadata::loadOasis( const QDomNode& metaDoc ) +{ + QDomNode n = metaDoc.firstChild(); + for ( ; !n.isNull(); n = n.nextSibling() ) + { + if (n.isElement()) + { + QDomElement e = n.toElement(); + if ( e.namespaceURI() == KoXmlNS::meta && e.localName() == "user-defined" && !e.text().isEmpty() ) + { + QString name = e.attributeNS( KoXmlNS::meta, "name", QString::null ); + if ( !m_reserved.contains( name ) ) + m_metaList[ name ] = e.text(); + } + } + } + return true; +} + +// KOffice-1.3 format +bool KoDocumentInfoUserMetadata::load( const QDomElement& ) +{ + return true; +} + +// KOffice-1.3 format +QDomElement KoDocumentInfoUserMetadata::save( QDomDocument& ) +{ + return QDomElement(); +} + +#include <KoDocumentInfo.moc> diff --git a/lib/kofficecore/KoDocumentInfo.h b/lib/kofficecore/KoDocumentInfo.h new file mode 100644 index 00000000..30e74cf7 --- /dev/null +++ b/lib/kofficecore/KoDocumentInfo.h @@ -0,0 +1,196 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999, 2000 Torben Weis <weis@kde.org> + Copyright (C) 2004 David Faure <faure@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 KO_DOCUMENT_INFO_H +#define KO_DOCUMENT_INFO_H + +#include <qobject.h> +#include <qmap.h> +#include <kconfig.h> +#include <koffice_export.h> + +class QString; +class QStringList; +class QDomDocument; +class QDomElement; +class QDomNode; +class QDateTime; +class KoStore; +class KoDocumentInfoPage; +class KoXmlWriter; + +class KOFFICECORE_EXPORT KoDocumentInfo : public QObject +{ + Q_OBJECT +public: + KoDocumentInfo( QObject* parent = 0, const char* name = 0 ); + virtual ~KoDocumentInfo(); + + bool load( const QDomDocument& doc ); + bool loadOasis( const QDomDocument& metaDoc ); + + QDomDocument save(); + bool saveOasis( KoStore* store ); + + /** + * This info has an accessor because it's the most commonly used. + * Equivalent to page("about")->title() (but checking that the page exists) + */ + QString title() const; + QString creator() const; + + KoDocumentInfoPage* page( const QString& name ) const; + QStringList pages() const; + void documentInfochanged() { emit sigDocumentInfoModifed();} + signals: + void sigDocumentInfoModifed(); +}; + +class KOFFICECORE_EXPORT KoDocumentInfoPage : public QObject +{ +public: + KoDocumentInfoPage( QObject *parent, const char* name ); + + virtual bool load( const QDomElement& e ) = 0; + virtual QDomElement save( QDomDocument& doc ) = 0; + virtual bool loadOasis( const QDomNode& metaDoc ) = 0; + virtual bool saveOasis( KoXmlWriter &xmlWriter ) = 0; +}; + +class KOFFICECORE_EXPORT KoDocumentInfoAuthor : public KoDocumentInfoPage +{ + Q_OBJECT +public: + KoDocumentInfoAuthor( KoDocumentInfo* info ); + ~KoDocumentInfoAuthor(); + + virtual bool load( const QDomElement& e ); + virtual QDomElement save( QDomDocument& doc ); + virtual bool loadOasis( const QDomNode& metaDoc ); + virtual bool saveOasis( KoXmlWriter &xmlWriter ); + + QString fullName() const; + QString initial() const; + QString title() const; + QString company() const; + QString email() const; + QString telephoneHome() const; + QString telephoneWork() const; + QString fax() const; + QString country() const; + QString postalCode() const; + QString city() const; + QString street() const; + QString position() const; + + void setFullName( const QString& n ); + void setTitle( const QString& n ); + void setCompany( const QString& n ); + void setEmail( const QString& n ); + void setTelephoneHome( const QString& n ); + void setTelephoneWork( const QString& n ); + void setFax( const QString& n ); + void setCountry( const QString& n ); + void setPostalCode( const QString& n ); + void setCity( const QString& n ); + void setStreet( const QString& n ); + void setInitial( const QString& n ); + void setPosition( const QString& n ); + void initParameters(); + +private: + QString m_fullName; + QString m_title; + QString m_company; + QString m_email; + QString m_telephoneHome; + QString m_telephoneWork; + QString m_fax; + QString m_country; + QString m_postalCode; + QString m_city; + QString m_street; + QString m_initial; + QString m_position; + KConfig *m_emailCfg; +}; + +class KOFFICECORE_EXPORT KoDocumentInfoAbout : public KoDocumentInfoPage +{ + Q_OBJECT +public: + KoDocumentInfoAbout( KoDocumentInfo* info ); + + virtual bool load( const QDomElement& e ); + virtual QDomElement save( QDomDocument& doc ); + virtual bool loadOasis( const QDomNode& metaDoc ); + virtual bool saveOasis( KoXmlWriter &xmlWriter ); + + QString title() const; + QString abstract() const; + QString subject() const; + QString keywords() const; + QString initialCreator() const; + QString editingCycles() const; + QString creationDate() const; + QString modificationDate() const; + + void setKeywords( const QString &n ); + void setSubject( const QString& n ); + + void setTitle( const QString& n ); + void setAbstract( const QString& n ); + void saveParameters(); + +public slots: + void resetMetaData(); + +private: + KoDocumentInfo* m_docInfo; + QString m_title; + QString m_abstract; + QString m_keywords; + QString m_subject; + QString m_initialCreator; + QDateTime m_creationDate; + QDateTime m_modificationDate; + int m_editingCycles; + bool m_firstSave; +}; + +class KOFFICECORE_EXPORT KoDocumentInfoUserMetadata : public KoDocumentInfoPage +{ + Q_OBJECT +public: + KoDocumentInfoUserMetadata( KoDocumentInfo* info ); + + virtual bool load( const QDomElement& e ); + virtual QDomElement save( QDomDocument& doc ); + virtual bool loadOasis( const QDomNode& metaDoc ); + virtual bool saveOasis( KoXmlWriter &xmlWriter ); + + QMap<QString, QString>* metadataList() { return &m_metaList; } + +private: + QMap<QString, QString> m_metaList; + QStringList m_reserved; +}; + +#endif diff --git a/lib/kofficecore/KoDocumentInfoDlg.cpp b/lib/kofficecore/KoDocumentInfoDlg.cpp new file mode 100644 index 00000000..f9ad8904 --- /dev/null +++ b/lib/kofficecore/KoDocumentInfoDlg.cpp @@ -0,0 +1,555 @@ +/* This file is part of the KDE project + Copyright (c) 2000 Simon Hausmann <hausmann@kde.org> + + $Id: KoDocumentInfoDlg.cpp 512890 2006-02-23 21:37:08Z illissius $ + + 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 "KoDocumentInfoDlg.h" +#include "KoDocumentInfo.h" +#include "koDocumentInfoAboutWidget.h" +#include "koDocumentInfoAuthorWidget.h" +#include "koDocumentInfoUserMetadataWidget.h" +#include "KoDocument.h" + +#include <KoGlobal.h> +#include <KoStore.h> + +#include <sys/stat.h> +#include <unistd.h> +#include <assert.h> + +#include <qlabel.h> +#include <qbuffer.h> +#include <qdom.h> +#include <qdir.h> +#include <qvbox.h> +#include <qdatetime.h> + +#include <kabc/addressee.h> +#include <kabc/stdaddressbook.h> +#include <kdeversion.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <ktar.h> +#include <kdebug.h> +#include <ktempfile.h> +#include <kmimetype.h> +#include <qlayout.h> +#include <klistview.h> +#include <qgrid.h> +#include <qmap.h> +#include <kfilterdev.h> +#include <klineedit.h> +#include <ktextedit.h> +#include <kiconloader.h> +#include <kpushbutton.h> +#include <klocale.h> + +class KoDocumentInfoDlg::KoDocumentInfoDlgPrivate +{ +public: + KoDocumentInfoDlgPrivate() + { + } + ~KoDocumentInfoDlgPrivate() + { + } + + KoDocumentInfo *m_info; + KoDocumentInfoAboutWidget *m_aboutWidget; + KoDocumentInfoAuthorWidget *m_authorWidget; + KoDocumentInfoUserMetadataWidget *m_metaWidget; + + bool m_bDeleteDialog; + KDialogBase *m_dialog; +}; + +KoDocumentInfoDlg::KoDocumentInfoDlg( KoDocumentInfo *docInfo, QWidget *parent, const char *name, + KDialogBase *dialog ) +: QObject( parent, "docinfodlg" ) +{ + d = new KoDocumentInfoDlgPrivate; + d->m_info = docInfo; + + d->m_dialog = dialog; + d->m_bDeleteDialog = false; + + if ( !dialog ) + { + d->m_dialog = new KDialogBase( KDialogBase::Tabbed, + i18n( "Document Information" ), + KDialogBase::Ok | KDialogBase::Cancel, + KDialogBase::Ok, parent, name, true, false ); + d->m_dialog->setInitialSize( QSize( 500, 500 ) ); + d->m_bDeleteDialog = true; + } + + QStringList pages = docInfo->pages(); + QStringList::ConstIterator it = pages.begin(); + QStringList::ConstIterator end = pages.end(); + for (; it != end; ++it ) + { + KoDocumentInfoPage *pg = docInfo->page( *it ); + if ( pg->inherits( "KoDocumentInfoAuthor" ) ) + addAuthorPage( static_cast<KoDocumentInfoAuthor *>( pg ) ); + else if ( pg->inherits( "KoDocumentInfoAbout" ) ) + addAboutPage( static_cast<KoDocumentInfoAbout *>( pg ) ); +/* else if ( pg->inherits( "KoDocumentInfoUserMetadata" ) ) + addUserMetadataPage( static_cast<KoDocumentInfoUserMetadata *>( pg ) );*/ + } +} + +KoDocumentInfoDlg::~KoDocumentInfoDlg() +{ + if ( d->m_bDeleteDialog ) + delete d->m_dialog; + + delete d; +} + +int KoDocumentInfoDlg::exec() +{ + return d->m_dialog->exec(); +} + +KDialogBase *KoDocumentInfoDlg::dialog() const +{ + return d->m_dialog; +} + +void KoDocumentInfoDlg::loadFromKABC() +{ + KABC::StdAddressBook *ab = static_cast<KABC::StdAddressBook*> + ( KABC::StdAddressBook::self() ); + + if ( !ab ) + return; + + KABC::Addressee addr = ab->whoAmI(); + if ( addr.isEmpty() ) + { + KMessageBox::sorry( 0L, i18n( "No personal contact data set, please use the option \ + \"Set as Personal Contact Data\" from the \"Edit\" menu in KAddressbook to set one." ) ); + return; + } + + d->m_authorWidget->leFullName->setText( addr.formattedName() ); + d->m_authorWidget->leInitial->setText( addr.givenName()[ 0 ] + ". " + + addr.familyName()[ 0 ] + "." ); + d->m_authorWidget->leAuthorTitle->setText( addr.title() ); + d->m_authorWidget->leCompany->setText( addr.organization() ); + d->m_authorWidget->leEmail->setText( addr.preferredEmail() ); + + KABC::PhoneNumber phone = addr.phoneNumber( KABC::PhoneNumber::Home ); + d->m_authorWidget->leTelephoneHome->setText( phone.number() ); + phone = addr.phoneNumber( KABC::PhoneNumber::Work ); + d->m_authorWidget->leTelephoneWork->setText( phone.number() ); + + phone = addr.phoneNumber( KABC::PhoneNumber::Fax ); + d->m_authorWidget->leFax->setText( phone.number() ); + + KABC::Address a = addr.address( KABC::Address::Home ); + d->m_authorWidget->leCountry->setText( a.country() ); + d->m_authorWidget->lePostalCode->setText( a.postalCode() ); + d->m_authorWidget->leCity->setText( a.locality() ); + d->m_authorWidget->leStreet->setText( a.street() ); + + emit changed(); +} + +void KoDocumentInfoDlg::deleteInfo() +{ + d->m_authorWidget->leFullName->setText( QString::null ); + d->m_authorWidget->leInitial->setText( QString::null ); + d->m_authorWidget->leAuthorTitle->setText( QString::null ); + d->m_authorWidget->leCompany->setText( QString::null ); + d->m_authorWidget->leEmail->setText( QString::null ); + d->m_authorWidget->leTelephoneHome->setText( QString::null ); + d->m_authorWidget->leTelephoneWork->setText( QString::null ); + d->m_authorWidget->leFax->setText( QString::null ); + d->m_authorWidget->leCountry->setText( QString::null ); + d->m_authorWidget->lePostalCode->setText( QString::null ); + d->m_authorWidget->leCity->setText( QString::null ); + d->m_authorWidget->leStreet->setText( QString::null ); + emit changed(); +} + +void KoDocumentInfoDlg::resetMetaData() +{ + QString s = KGlobal::locale()->formatDateTime( QDateTime::currentDateTime() ); + d->m_aboutWidget->labelCreated->setText( s + ", " + d->m_info->creator() ); + d->m_aboutWidget->labelModified->setText( "" ); + d->m_aboutWidget->labelRevision->setText( "0" ); + emit changed(); +} + +void KoDocumentInfoDlg::addAuthorPage( KoDocumentInfoAuthor *authorInfo ) +{ + QVBox *page = d->m_dialog->addVBoxPage( i18n( "Author" ) ); + d->m_authorWidget = new KoDocumentInfoAuthorWidget( page ); + d->m_authorWidget->labelAuthor->setPixmap( KGlobal::iconLoader()->loadIcon( "personal", KIcon::Desktop, 48 ) ); + d->m_authorWidget->pbLoadKABC->setIconSet( QIconSet( KGlobal::iconLoader()->loadIcon( "kaddressbook", KIcon::Small ) ) ); + d->m_authorWidget->pbDelete->setIconSet( QIconSet( KGlobal::iconLoader()->loadIcon( "eraser", KIcon::Small ) ) ); + + d->m_authorWidget->leFullName->setText( authorInfo->fullName() ); + d->m_authorWidget->leInitial->setText( authorInfo->initial() ); + d->m_authorWidget->leAuthorTitle->setText( authorInfo->title() ); + d->m_authorWidget->leCompany->setText( authorInfo->company() ); + d->m_authorWidget->leEmail->setText( authorInfo->email() ); + d->m_authorWidget->leTelephoneWork->setText( authorInfo->telephoneWork() ); + d->m_authorWidget->leTelephoneHome->setText( authorInfo->telephoneHome() ); + d->m_authorWidget->leFax->setText( authorInfo->fax() ); + d->m_authorWidget->leCountry->setText( authorInfo->country() ); + d->m_authorWidget->lePostalCode->setText( authorInfo->postalCode() ); + d->m_authorWidget->leCity->setText( authorInfo->city() ); + d->m_authorWidget->leStreet->setText( authorInfo->street() ); + d->m_authorWidget->leAuthorPosition->setText( authorInfo->position() ); + + connect( d->m_authorWidget->leFullName, SIGNAL( textChanged( const QString & ) ), + this, SIGNAL( changed() ) ); + connect( d->m_authorWidget->leInitial, SIGNAL( textChanged( const QString & ) ), + this, SIGNAL( changed() ) ); + connect( d->m_authorWidget->leAuthorTitle, SIGNAL( textChanged( const QString & ) ), + this, SIGNAL( changed() ) ); + connect( d->m_authorWidget->leCompany, SIGNAL( textChanged( const QString & ) ), + this, SIGNAL( changed() ) ); + connect( d->m_authorWidget->leEmail, SIGNAL( textChanged( const QString & ) ), + this, SIGNAL( changed() ) ); + connect( d->m_authorWidget->leTelephoneWork, SIGNAL( textChanged( const QString & ) ), + this, SIGNAL( changed() ) ); + connect( d->m_authorWidget->leTelephoneHome, SIGNAL( textChanged( const QString & ) ), + this, SIGNAL( changed() ) ); + connect( d->m_authorWidget->leFax, SIGNAL( textChanged( const QString & ) ), + this, SIGNAL( changed() ) ); + connect( d->m_authorWidget->leCountry, SIGNAL( textChanged( const QString & ) ), + this, SIGNAL( changed() ) ); + connect( d->m_authorWidget->lePostalCode, SIGNAL( textChanged( const QString & ) ), + this, SIGNAL( changed() ) ); + connect( d->m_authorWidget->leCity, SIGNAL( textChanged( const QString & ) ), + this, SIGNAL( changed() ) ); + connect( d->m_authorWidget->leStreet, SIGNAL( textChanged( const QString & ) ), + this, SIGNAL( changed() ) ); + connect( d->m_authorWidget->leAuthorPosition, SIGNAL( textChanged( const QString & ) ), + this, SIGNAL( changed() ) ); + connect( d->m_authorWidget->pbLoadKABC, SIGNAL( clicked() ), + this, SLOT( loadFromKABC() ) ); + connect( d->m_authorWidget->pbDelete, SIGNAL( clicked() ), + this, SLOT( deleteInfo() ) ); +} + +void KoDocumentInfoDlg::addAboutPage( KoDocumentInfoAbout *aboutInfo ) +{ + QVBox *page = d->m_dialog->addVBoxPage( i18n( "General" ) ); + d->m_aboutWidget = new KoDocumentInfoAboutWidget( page ); + d->m_aboutWidget->pbReset->setIconSet( QIconSet( KGlobal::iconLoader()->loadIcon( "reload", KIcon::Small ) ) ); + KoDocument* doc = dynamic_cast< KoDocument* >( d->m_info->parent() ); + if ( doc ) + { + d->m_aboutWidget->leDocFile->setText( doc->file() ); + d->m_aboutWidget->labelType->setText( KMimeType::mimeType( doc->mimeType() )->comment() ); + d->m_aboutWidget->pixmapLabel->setPixmap( KMimeType::mimeType( doc->mimeType() )->pixmap( KIcon::Desktop, 48 ) ); + } + if ( aboutInfo->creationDate() != QString::null ) + d->m_aboutWidget->labelCreated->setText( aboutInfo->creationDate() + ", " + aboutInfo->initialCreator() ); + if ( aboutInfo->modificationDate() != QString::null ) + d->m_aboutWidget->labelModified->setText( aboutInfo->modificationDate() + ", " + d->m_info->creator() ); + d->m_aboutWidget->labelRevision->setText( aboutInfo->editingCycles() ); + d->m_aboutWidget->leDocTitle->setText( aboutInfo->title() ); + d->m_aboutWidget->leDocSubject->setText( aboutInfo->subject() ); + d->m_aboutWidget->leDocKeywords->setText( aboutInfo->keywords() ); + d->m_aboutWidget->meDocAbstract->setText( aboutInfo->abstract() ); + + connect( d->m_aboutWidget->leDocTitle, SIGNAL( textChanged( const QString & ) ), + this, SIGNAL( changed() ) ); + connect( d->m_aboutWidget->meDocAbstract, SIGNAL( textChanged() ), + this, SIGNAL( changed() ) ); + connect( d->m_aboutWidget->leDocSubject, SIGNAL( textChanged( const QString & ) ), + this, SIGNAL( changed() ) ); + connect( d->m_aboutWidget->leDocKeywords, SIGNAL( textChanged( const QString & ) ), + this, SIGNAL( changed() ) ); + connect( d->m_aboutWidget->pbReset, SIGNAL( clicked() ), + aboutInfo, SLOT( resetMetaData() ) ); + connect( d->m_aboutWidget->pbReset, SIGNAL( clicked() ), + this, SLOT( resetMetaData() ) ); +} + +void KoDocumentInfoDlg::addUserMetadataPage( KoDocumentInfoUserMetadata *userMetadataInfo ) +{ + QVBox *page = d->m_dialog->addVBoxPage( i18n( "User-Defined Metadata" ) ); + d->m_metaWidget = new KoDocumentInfoUserMetadataWidget( page ); + + d->m_metaWidget->metaListView->addColumn( "Name" ); + d->m_metaWidget->metaListView->setFullWidth( true ); + + QMap<QString, QString>::iterator it; + for ( it = userMetadataInfo->metadataList()->begin(); it != userMetadataInfo->metadataList()->end(); ++it ) + { + QString name = it.key(); + QString value = it.data(); + KListViewItem* it = new KListViewItem( d->m_metaWidget->metaListView, name, value ); + it->setPixmap( 0, KGlobal::iconLoader()->loadIcon( "text", KIcon::Small ) ); + } +} + +void KoDocumentInfoDlg::save() +{ + QStringList pages = d->m_info->pages(); + QStringList::ConstIterator it = pages.begin(); + QStringList::ConstIterator end = pages.end(); + bool saveInfo=false; + for (; it != end; ++it ) + { + KoDocumentInfoPage *pg = d->m_info->page( *it ); + if ( pg->inherits( "KoDocumentInfoAuthor" ) ) + { + saveInfo=true; + save( static_cast<KoDocumentInfoAuthor *>( pg ) ); + } + else if ( pg->inherits( "KoDocumentInfoAbout" ) ) + { + saveInfo=true; + save( static_cast<KoDocumentInfoAbout *>( pg ) ); + } + } + if(saveInfo) + d->m_info->documentInfochanged(); +} + +void KoDocumentInfoDlg::save( KoDocumentInfoAuthor *authorInfo ) +{ + authorInfo->setFullName( d->m_authorWidget->leFullName->text() ); + authorInfo->setInitial( d->m_authorWidget->leInitial->text() ); + authorInfo->setTitle( d->m_authorWidget->leAuthorTitle->text() ); + authorInfo->setCompany( d->m_authorWidget->leCompany->text() ); + authorInfo->setEmail( d->m_authorWidget->leEmail->text() ); + authorInfo->setTelephoneWork( d->m_authorWidget->leTelephoneWork->text() ); + authorInfo->setTelephoneHome( d->m_authorWidget->leTelephoneHome->text() ); + authorInfo->setFax( d->m_authorWidget->leFax->text() ); + authorInfo->setCountry( d->m_authorWidget->leCountry->text() ); + authorInfo->setPostalCode( d->m_authorWidget->lePostalCode->text() ); + authorInfo->setCity( d->m_authorWidget->leCity->text() ); + authorInfo->setStreet( d->m_authorWidget->leStreet->text() ); + authorInfo->setPosition( d->m_authorWidget->leAuthorPosition->text() ); + + KConfig* config = KoGlobal::kofficeConfig(); + KConfigGroupSaver cgs( config, "Author" ); + config->writeEntry("telephone", d->m_authorWidget->leTelephoneHome->text()); + config->writeEntry("telephone-work", d->m_authorWidget->leTelephoneWork->text()); + config->writeEntry("fax", d->m_authorWidget->leFax->text()); + config->writeEntry("country",d->m_authorWidget->leCountry->text()); + config->writeEntry("postal-code",d->m_authorWidget->lePostalCode->text()); + config->writeEntry("city", d->m_authorWidget->leCity->text()); + config->writeEntry("street", d->m_authorWidget->leStreet->text()); + config->sync(); +} + +void KoDocumentInfoDlg::save( KoDocumentInfoAbout *aboutInfo ) +{ + aboutInfo->setTitle( d->m_aboutWidget->leDocTitle->text() ); + aboutInfo->setSubject( d->m_aboutWidget->leDocSubject->text() ); + aboutInfo->setKeywords( d->m_aboutWidget->leDocKeywords->text() ); + aboutInfo->setAbstract( d->m_aboutWidget->meDocAbstract->text() ); +} + +void KoDocumentInfoDlg::save( KoDocumentInfoUserMetadata* ) +{ + // FIXME +} + +class KoDocumentInfoPropsPage::KoDocumentInfoPropsPagePrivate +{ +public: + KoDocumentInfo *m_info; + KoDocumentInfoDlg *m_dlg; + KURL m_url; + KTarGz *m_src; + KTarGz *m_dst; + + const KTarFile *m_docInfoFile; +}; + +KoDocumentInfoPropsPage::KoDocumentInfoPropsPage( KPropertiesDialog *props, + const char *, + const QStringList & ) +: KPropsDlgPlugin( props ) +{ + d = new KoDocumentInfoPropsPagePrivate; + d->m_info = new KoDocumentInfo( this, "docinfo" ); + d->m_url = props->item()->url(); + d->m_dlg = 0; + + if ( !d->m_url.isLocalFile() ) + return; + + d->m_dst = 0; + +#ifdef __GNUC__ +#warning TODO port this to KoStore !!! +#endif + d->m_src = new KTarGz( d->m_url.path(), "application/x-gzip" ); + + if ( !d->m_src->open( IO_ReadOnly ) ) + return; + + const KTarDirectory *root = d->m_src->directory(); + if ( !root ) + return; + + const KTarEntry *entry = root->entry( "documentinfo.xml" ); + + if ( entry && entry->isFile() ) + { + d->m_docInfoFile = static_cast<const KTarFile *>( entry ); + + QBuffer buffer( d->m_docInfoFile->data() ); + buffer.open( IO_ReadOnly ); + + QDomDocument doc; + doc.setContent( &buffer ); + + d->m_info->load( doc ); + } + + d->m_dlg = new KoDocumentInfoDlg( d->m_info, 0, 0, props ); + connect( d->m_dlg, SIGNAL( changed() ), + this, SIGNAL( changed() ) ); +} + +KoDocumentInfoPropsPage::~KoDocumentInfoPropsPage() +{ + delete d->m_info; + delete d->m_src; + delete d->m_dst; + delete d->m_dlg; + delete d; +} + +void KoDocumentInfoPropsPage::applyChanges() +{ + const KTarDirectory *root = d->m_src->directory(); + if ( !root ) + return; + + struct stat statBuff; + + if ( stat( QFile::encodeName( d->m_url.path() ), &statBuff ) != 0 ) + return; + + KTempFile tempFile( d->m_url.path(), QString::null, statBuff.st_mode ); + + tempFile.setAutoDelete( true ); + + if ( tempFile.status() != 0 ) + return; + + if ( !tempFile.close() ) + return; + + d->m_dst = new KTarGz( tempFile.name(), "application/x-gzip" ); + + if ( !d->m_dst->open( IO_WriteOnly ) ) + return; + + KMimeType::Ptr mimeType = KMimeType::findByURL( d->m_url, 0, true ); + if ( mimeType && dynamic_cast<KFilterDev *>( d->m_dst->device() ) != 0 ) + { + QCString appIdentification( "KOffice " ); // We are limited in the number of chars. + appIdentification += mimeType->name().latin1(); + appIdentification += '\004'; // Two magic bytes to make the identification + appIdentification += '\006'; // more reliable (DF) + d->m_dst->setOrigFileName( appIdentification ); + } + + bool docInfoSaved = false; + + QStringList entries = root->entries(); + QStringList::ConstIterator it = entries.begin(); + QStringList::ConstIterator end = entries.end(); + for (; it != end; ++it ) + { + const KTarEntry *entry = root->entry( *it ); + + assert( entry ); + + if ( entry->name() == "documentinfo.xml" || + ( !docInfoSaved && !entries.contains( "documentinfo.xml" ) ) ) + { + d->m_dlg->save(); + + QBuffer buffer; + buffer.open( IO_WriteOnly ); + QTextStream str( &buffer ); + str << d->m_info->save(); + buffer.close(); + + kdDebug( 30003 ) << "writing documentinfo.xml" << endl; + d->m_dst->writeFile( "documentinfo.xml", entry->user(), entry->group(), buffer.buffer().size(), + buffer.buffer().data() ); + + docInfoSaved = true; + } + else + copy( QString::null, entry ); + } + + d->m_dst->close(); + + QDir dir; + dir.rename( tempFile.name(), d->m_url.path() ); + + delete d->m_dst; + d->m_dst = 0; +} + +void KoDocumentInfoPropsPage::copy( const QString &path, const KArchiveEntry *entry ) +{ + kdDebug( 30003 ) << "copy " << entry->name() << endl; + if ( entry->isFile() ) + { + const KTarFile *file = static_cast<const KTarFile *>( entry ); + kdDebug( 30003 ) << "file :" << entry->name() << endl; + kdDebug( 30003 ) << "full path is: " << path << entry->name() << endl; + d->m_dst->writeFile( path + entry->name(), entry->user(), entry->group(), + file->size(), + file->data().data() ); + } + else + { + const KTarDirectory *dir = static_cast<const KTarDirectory *>( entry ); + kdDebug( 30003 ) << "dir : " << entry->name() << endl; + kdDebug( 30003 ) << "full path is: " << path << entry->name() << endl; + + QString p = path + entry->name(); + if ( p != "/" ) + { + d->m_dst->writeDir( p, entry->user(), entry->group() ); + p.append( "/" ); + } + + QStringList entries = dir->entries(); + QStringList::ConstIterator it = entries.begin(); + QStringList::ConstIterator end = entries.end(); + for (; it != end; ++it ) + copy( p, dir->entry( *it ) ); + } +} + +/* vim: sw=2 et + */ + +#include "KoDocumentInfoDlg.moc" diff --git a/lib/kofficecore/KoDocumentInfoDlg.h b/lib/kofficecore/KoDocumentInfoDlg.h new file mode 100644 index 00000000..d3e1e9a3 --- /dev/null +++ b/lib/kofficecore/KoDocumentInfoDlg.h @@ -0,0 +1,85 @@ +/* This file is part of the KDE project + Copyright (c) 2000 Simon Hausmann <hausmann@kde.org> + + $Id: KoDocumentInfoDlg.h 508787 2006-02-12 18:28:12Z ingwa $ + + 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 __koDocumentInfoDlg_h__ +#define __koDocumentInfoDlg_h__ + +#include <kpropertiesdialog.h> +#include <koffice_export.h> + +class KDialogBase; +class KoDocumentInfo; +class KoDocumentInfoAuthor; +class KoDocumentInfoAbout; +class KoDocumentInfoUserMetadata; +class KArchiveEntry; + +class KOFFICECORE_EXPORT KoDocumentInfoDlg : public QObject +{ + Q_OBJECT +public: + KoDocumentInfoDlg( KoDocumentInfo *docInfo, QWidget *parent = 0, const char *name = 0, + KDialogBase *dialog = 0 ); + virtual ~KoDocumentInfoDlg(); + + int exec(); + KDialogBase *dialog() const; + + void save(); + +signals: + void changed(); + +private slots: + void loadFromKABC(); + void deleteInfo(); + void resetMetaData(); + +private: + void addAuthorPage( KoDocumentInfoAuthor *authorInfo ); + void addAboutPage( KoDocumentInfoAbout *aboutInfo ); + void addUserMetadataPage( KoDocumentInfoUserMetadata *userMetadataInfo ); + + void save( KoDocumentInfoAuthor *authorInfo ); + void save( KoDocumentInfoAbout *aboutInfo ); + void save( KoDocumentInfoUserMetadata *userMetadataInfo ); + + class KoDocumentInfoDlgPrivate; + KoDocumentInfoDlgPrivate *d; +}; + +class KOFFICECORE_EXPORT KoDocumentInfoPropsPage : public KPropsDlgPlugin +{ + Q_OBJECT +public: + KoDocumentInfoPropsPage( KPropertiesDialog *props, const char *name = 0, + const QStringList & = QStringList() ); + virtual ~KoDocumentInfoPropsPage(); + + virtual void applyChanges(); + +private: + void copy( const QString &path, const KArchiveEntry *entry ); + class KoDocumentInfoPropsPagePrivate; + KoDocumentInfoPropsPagePrivate *d; +}; + +#endif diff --git a/lib/kofficecore/KoDocument_p.h b/lib/kofficecore/KoDocument_p.h new file mode 100644 index 00000000..1b21f69a --- /dev/null +++ b/lib/kofficecore/KoDocument_p.h @@ -0,0 +1,41 @@ +/* This file is part of the KDE project + Copyright (C) 2001 David Faure <faure@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. + + 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 __koDocument_p_h__ +#define __koDocument_p_h__ + +#include <kparts/browserextension.h> + +class KoDocument; + +/** + * Used in singleViewMode, when embedded into a browser + * @internal + */ +class KoBrowserExtension : public KParts::BrowserExtension +{ + Q_OBJECT +public: + KoBrowserExtension( KoDocument * doc, const char * name = 0 ); + +public slots: + /// Automatically detected by konqueror + void print(); +}; + +#endif diff --git a/lib/kofficecore/KoDom.cpp b/lib/kofficecore/KoDom.cpp new file mode 100644 index 00000000..45b00acd --- /dev/null +++ b/lib/kofficecore/KoDom.cpp @@ -0,0 +1,30 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <faure@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 "KoDom.h" + +QDomElement KoDom::namedItemNS( const QDomNode& node, const char* nsURI, const char* localName ) +{ + QDomNode n = node.firstChild(); + for ( ; !n.isNull(); n = n.nextSibling() ) { + if ( n.isElement() && n.localName() == localName && n.namespaceURI() == nsURI ) + return n.toElement(); + } + return QDomElement(); +} diff --git a/lib/kofficecore/KoDom.h b/lib/kofficecore/KoDom.h new file mode 100644 index 00000000..4d95a42b --- /dev/null +++ b/lib/kofficecore/KoDom.h @@ -0,0 +1,70 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <faure@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 KODOM_H +#define KODOM_H + +#include <qdom.h> +#include <koffice_export.h> +/** + * This namespace contains a few convenience functions to simplify code using QDom + * (when loading OASIS documents, in particular). + * + * To find the child element with a given name, use KoDom::namedItemNS. + * + * To find all child elements with a given name, use + * QDomElement e; + * forEachElement( e, parent ) + * { + * if ( e.localName() == "..." && e.namespaceURI() == KoXmlNS::... ) + * { + * ... + * } + * } + * Note that this means you don't ever need to use QDomNode nor toElement anymore! + * Also note that localName is the part without the prefix, this is the whole point + * of namespace-aware methods. + * + * To find the attribute with a given name, use QDomElement::attributeNS. + * + * Do not use getElementsByTagNameNS, it's recursive (which is never needed in KOffice). + * Do not use tagName() or nodeName() or prefix(), since the prefix isn't fixed. + * + * @author David Faure <faure@kde.org> + */ +namespace KoDom { + + /** + * A namespace-aware version of QDomNode::namedItem(), + * which also takes care of casting to a QDomElement. + * Use this when a domelement is known to have only *one* child element + * with a given tagname. + * + * Note: do *NOT* use getElementsByTagNameNS, it's recursive! + */ + KOFFICECORE_EXPORT QDomElement namedItemNS( const QDomNode& node, const char* nsURI, const char* localName ); + +} + +#define forEachElement( elem, parent ) \ + for ( QDomNode _node = parent.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) \ + if ( !( elem = _node.toElement() ).isNull() ) + +#endif /* KODOM_H */ + diff --git a/lib/kofficecore/KoFactory.cpp b/lib/kofficecore/KoFactory.cpp new file mode 100644 index 00000000..d858e8e9 --- /dev/null +++ b/lib/kofficecore/KoFactory.cpp @@ -0,0 +1,45 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999, 2000 Torben Weis <weis@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 <KoFactory.h> + + +class KoFactory::KoFactoryPrivate +{ +public: + KoFactoryPrivate() + { + } + ~KoFactoryPrivate() + { + } +}; + +KoFactory::KoFactory( QObject *parent, const char *name ) + : KParts::Factory( parent, name ) +{ + //d = new KoFactoryPrivate; +} + +KoFactory::~KoFactory() +{ + //delete d; +} + +#include <KoFactory.moc> diff --git a/lib/kofficecore/KoFactory.h b/lib/kofficecore/KoFactory.h new file mode 100644 index 00000000..bcc77439 --- /dev/null +++ b/lib/kofficecore/KoFactory.h @@ -0,0 +1,39 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999, 2000 Torben Weis <weis@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 __koFactory_h__ +#define __koFactory_h__ + +#include <kparts/factory.h> +#include <koffice_export.h> +class KInstance; + +class KOFFICECORE_EXPORT KoFactory : public KParts::Factory +{ + Q_OBJECT +public: + KoFactory( QObject *parent = 0, const char *name = 0 ); + virtual ~KoFactory(); + +private: + class KoFactoryPrivate; + KoFactoryPrivate *d; +}; + +#endif diff --git a/lib/kofficecore/KoFileDialog.cpp b/lib/kofficecore/KoFileDialog.cpp new file mode 100644 index 00000000..b80c23e0 --- /dev/null +++ b/lib/kofficecore/KoFileDialog.cpp @@ -0,0 +1,125 @@ +/* This file is part of the KDE project + Copyright (C) 2002-2005 David Faure <faure@kde.org> + Copyright (C) 2002-2004 Clarence Dang <dang@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 "KoFileDialog.h" +#include "KoDocument.h" +#include <kfilefiltercombo.h> +#include <klocale.h> +#include <kdiroperator.h> +#include <kdebug.h> + +KoFileDialog::KoFileDialog(const QString& startDir, const QString& filter, + QWidget *parent, const char *name, + bool modal) + : KFileDialog( startDir, filter, parent, name, modal ) +{ + connect( filterWidget, SIGNAL( activated( int) ), + this, SLOT( slotChangedfilter( int ) ) ); +} + +void KoFileDialog::slotChangedfilter( int index ) +{ + // Switch to "directory selection" mode for SaveAsDirectoryStore, + // switch back to "file selection" mode otherwise. + KFile::Mode newMode = KFile::File; + if ( index >= 1 && index <= (int)m_specialFormats.count() + && m_specialFormats[index-1] == KoDocument::SaveAsDirectoryStore ) { + newMode = KFile::Directory; + } + if ( newMode != mode() ) + { + ops->setMode( newMode ); + updateAutoSelectExtension(); + } +} + +void KoFileDialog::setSpecialMimeFilter( QStringList& mimeFilter, + const QString& currentFormat, const int specialOutputFlag, + const QString& nativeFormat, + int supportedSpecialFormats ) +{ + Q_ASSERT( !mimeFilter.isEmpty() ); + Q_ASSERT( mimeFilter[0] == nativeFormat ); + + bool addUncompressed = supportedSpecialFormats & KoDocument::SaveAsDirectoryStore; + bool addFlatXML = supportedSpecialFormats & KoDocument::SaveAsFlatXML; + + int idxSpecialOutputFlag = 0; + int numSpecialEntries = 0; + if ( addUncompressed ) { + ++numSpecialEntries; + m_specialFormats.append( KoDocument::SaveAsDirectoryStore ); + if ( specialOutputFlag == KoDocument::SaveAsDirectoryStore ) + idxSpecialOutputFlag = numSpecialEntries; + } + if ( addFlatXML ) { + ++numSpecialEntries; + m_specialFormats.append( KoDocument::SaveAsFlatXML ); + if ( specialOutputFlag == KoDocument::SaveAsFlatXML ) + idxSpecialOutputFlag = numSpecialEntries; + } + + // Insert numSpecialEntries entries with native mimetypes, for the special entries. + QStringList::Iterator mimeFilterIt = mimeFilter.at( 1 ); + mimeFilter.insert( mimeFilterIt /* before 1 -> after 0 */, numSpecialEntries, nativeFormat ); + + // Fill in filter combo + // Note: if currentFormat doesn't exist in mimeFilter, filterWidget + // will default to the first item (native format) + setMimeFilter( mimeFilter, currentFormat.isEmpty() ? nativeFormat : currentFormat ); + + // To get a different description in the combo, we need to change its entries afterwards + KMimeType::Ptr type = KMimeType::mimeType( nativeFormat ); + int idx = 1; // 0 is the native format + + if ( addUncompressed ) + filterWidget->changeItem( i18n("%1 (Uncompressed XML Files)").arg( type->comment() ), idx++ ); + if ( addFlatXML ) + filterWidget->changeItem( i18n("%1 (Flat XML File)").arg( type->comment() ), idx++ ); + // if you add an entry here, update numSpecialEntries above and specialEntrySelected() below + + // For native format... + if (currentFormat == nativeFormat || currentFormat.isEmpty()) + { + // KFileFilterCombo selected the _last_ "native mimetype" entry, select the correct one + filterWidget->setCurrentItem( idxSpecialOutputFlag ); + slotChangedfilter( filterWidget->currentItem() ); + } + // [Mainly KWord] Tell MS Office users that they can save in RTF! + int i = 0; + for (mimeFilterIt = mimeFilter.begin (); mimeFilterIt != mimeFilter.end (); ++mimeFilterIt, i++) + { + KMimeType::Ptr mime = KMimeType::mimeType (*mimeFilterIt); + QString compatString = mime->property ("X-KDE-CompatibleApplication").toString (); + if (!compatString.isEmpty ()) + filterWidget->changeItem (i18n ("%1 (%2 Compatible)").arg (mime->comment ()).arg (compatString), i); + } +} + +int KoFileDialog::specialEntrySelected() +{ + int i = filterWidget->currentItem(); + // Item 0 is the native format, the following ones are the special formats + if ( i >= 1 && i <= (int)m_specialFormats.count() ) + return m_specialFormats[i-1]; + return 0; +} + +#include "KoFileDialog.moc" diff --git a/lib/kofficecore/KoFileDialog.h b/lib/kofficecore/KoFileDialog.h new file mode 100644 index 00000000..fc3b61e2 --- /dev/null +++ b/lib/kofficecore/KoFileDialog.h @@ -0,0 +1,55 @@ +/* This file is part of the KDE project + Copyright (C) 2002-2004 David Faure <faure@kde.org> + Copyright (C) 2002-2004 Clarence Dang <dang@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 KOFILEDIALOG_H +#define KOFILEDIALOG_H + +#include <kfiledialog.h> +#include <qvaluevector.h> + +/** + * Extension to KFileDialog in order to add special entries to the filter combo, + * like "save as koffice-1.1", "save as dir" etc. + * @note Used only when saving! + * + * This class is INTERNAL to KoMainWindow, do NOT use in your application. + * Binary compatibility is not guaranteed. + */ +class KoFileDialog : public KFileDialog +{ + Q_OBJECT +public: + KoFileDialog( const QString& startDir, const QString& filter, + QWidget *parent, const char *name, + bool modal ); + void setSpecialMimeFilter( QStringList& mimeFilter, + const QString& currentFormat, const int specialOutputFlag, + const QString& nativeFormat, + int supportedSpecialFormats ); + int specialEntrySelected(); + +private slots: + void slotChangedfilter( int index ); + +private: + QValueVector<int> m_specialFormats; +}; + +#endif /* KOFILEDIALOG_H */ diff --git a/lib/kofficecore/KoFilter.cpp b/lib/kofficecore/KoFilter.cpp new file mode 100644 index 00000000..5b1d4898 --- /dev/null +++ b/lib/kofficecore/KoFilter.cpp @@ -0,0 +1,151 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001 Werner Trobin <trobin@kde.org> + 2002 Werner Trobin <trobin@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 <KoFilter.h> + +#include <qfile.h> + +#include <kurl.h> +#include <kmimetype.h> +#include <ktempfile.h> +#include <kdebug.h> +#include <KoFilterManager.h> + + +KoFilter::KoFilter() : QObject( 0, 0 ), m_chain( 0 ) +{ +} + +KoFilter::~KoFilter() +{ +} + + +KoEmbeddingFilter::~KoEmbeddingFilter() +{ + if ( m_partStack.count() != 1 ) + kdWarning() << "Someone messed with the part stack" << endl; + delete m_partStack.pop(); +} + +int KoEmbeddingFilter::lruPartIndex() const +{ + return m_partStack.top()->m_lruPartIndex; +} + +QString KoEmbeddingFilter::mimeTypeByExtension( const QString& extension ) +{ + // We need to resort to an ugly hack to determine the mimetype + // from the extension, as kservicetypefactory.h isn't installed + KURL url; + url.setPath( QString( "dummy.%1" ).arg( extension ) ); + KMimeType::Ptr m( KMimeType::findByURL( url, 0, true, true ) ); + return m->name(); +} + +KoEmbeddingFilter::KoEmbeddingFilter() : KoFilter() +{ + m_partStack.push( new PartState() ); +} + +int KoEmbeddingFilter::embedPart( const QCString& from, QCString& to, + KoFilter::ConversionStatus& status, const QString& key ) +{ + ++( m_partStack.top()->m_lruPartIndex ); + + KTempFile tempIn; + tempIn.setAutoDelete( true ); + savePartContents( tempIn.file() ); + tempIn.file()->close(); + + KoFilterManager *manager = new KoFilterManager( tempIn.name(), from, m_chain ); + status = manager->exp0rt( QString::null, to ); + delete manager; + + // Add the part to the current "stack frame", using the number as key + // if the key string is empty + PartReference ref( lruPartIndex(), to ); + m_partStack.top()->m_partReferences.insert( key.isEmpty() ? QString::number( lruPartIndex() ) : key, ref ); + + return lruPartIndex(); +} + +void KoEmbeddingFilter::startInternalEmbedding( const QString& key, const QCString& mimeType ) +{ + filterChainEnterDirectory( QString::number( ++( m_partStack.top()->m_lruPartIndex ) ) ); + PartReference ref( lruPartIndex(), mimeType ); + m_partStack.top()->m_partReferences.insert( key, ref ); + m_partStack.push( new PartState() ); +} + +void KoEmbeddingFilter::endInternalEmbedding() +{ + if ( m_partStack.count() == 1 ) { + kdError( 30500 ) << "You're trying to endInternalEmbedding more often than you started it" << endl; + return; + } + delete m_partStack.pop(); + filterChainLeaveDirectory(); +} + +int KoEmbeddingFilter::internalPartReference( const QString& key ) const +{ + QMapConstIterator<QString, PartReference> it = m_partStack.top()->m_partReferences.find( key ); + if ( it == m_partStack.top()->m_partReferences.end() ) + return -1; + return it.data().m_index; +} + +QCString KoEmbeddingFilter::internalPartMimeType( const QString& key ) const +{ + QMapConstIterator<QString, PartReference> it = m_partStack.top()->m_partReferences.find( key ); + if ( it == m_partStack.top()->m_partReferences.end() ) + return QCString(); + return it.data().m_mimeType; +} + +KoEmbeddingFilter::PartReference::PartReference( int index, const QCString& mimeType ) : + m_index( index ), m_mimeType( mimeType ) +{ +} + +bool KoEmbeddingFilter::PartReference::isValid() const +{ + return m_index != 1 && !m_mimeType.isEmpty(); +} + +KoEmbeddingFilter::PartState::PartState() : m_lruPartIndex( 0 ) +{ +} + +void KoEmbeddingFilter::savePartContents( QIODevice* ) +{ +} + +void KoEmbeddingFilter::filterChainEnterDirectory( const QString& directory ) const +{ + m_chain->enterDirectory( directory ); +} + +void KoEmbeddingFilter::filterChainLeaveDirectory() const +{ + m_chain->leaveDirectory(); +} + +#include <KoFilter.moc> diff --git a/lib/kofficecore/KoFilter.h b/lib/kofficecore/KoFilter.h new file mode 100644 index 00000000..9d82a34f --- /dev/null +++ b/lib/kofficecore/KoFilter.h @@ -0,0 +1,315 @@ +/* This file is part of the KOffice libraries + Copyright (C) 2001 Werner Trobin <trobin@kde.org> + 2002 Werner Trobin <trobin@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 __koffice_filter_h__ +#define __koffice_filter_h__ + +#include <qobject.h> +#include <qmap.h> +#include <qptrstack.h> +#include <koffice_export.h> +class QIODevice; +class KoFilterChain; + +/** + * @brief The base class for import and export filters. + * + * Derive your filter class from this base class and implement + * the @ref convert() method. Don't forget to specify the Q_OBJECT + * macro in your class even if you don't use signals or slots. + * This is needed as filters are created on the fly. + * The m_chain member allows access to the @ref KoFilterChain + * which invokes the filter to query for input/output. + * + * @note Take care: The m_chain pointer is invalid while the constructor + * runs due to the implementation -- @em don't use it in the constructor. + * After the constructor, when running the @ref convert() method it's + * guaranteed to be valid, so no need to check against 0. + * + * @author Werner Trobin <trobin@kde.org> + * @todo the class has no constructor and therefore cannot initialize its private class + */ +class KOFFICECORE_EXPORT KoFilter : public QObject +{ + Q_OBJECT + + friend class KoFilterEntry; // needed for the filter chain pointer :( + friend class KoFilterChain; + +public: + /** + * This enum is used to signal the return state of your filter. + * Return OK in @ref convert() in case everything worked as expected. + * Feel free to add some more error conditions @em before the last item + * if it's needed. + */ + enum ConversionStatus { OK, StupidError, UsageError, CreationError, FileNotFound, + StorageCreationError, BadMimeType, BadConversionGraph, + EmbeddedDocError, WrongFormat, NotImplemented, + ParsingError, InternalError, UnexpectedEOF, + UnexpectedOpcode, UserCancelled, OutOfMemory, + PasswordProtected, + JustInCaseSomeBrokenCompilerUsesLessThanAByte = 255 }; + + virtual ~KoFilter(); + + /** + * The filter chain calls this method to perform the actual conversion. + * The passed mimetypes should be a pair of those you specified in your + * .desktop file. + * You @em have to implement this method to make the filter work. + * + * @param from The mimetype of the source file/document + * @param to The mimetype of the destination file/document + * @return The error status, see the @ref #ConversionStatus enum. + * KoFilter::OK means that everything is alright. + */ + virtual ConversionStatus convert( const QCString& from, const QCString& to ) = 0; + +signals: + /** + * Emit this signal with a value in the range of 1...100 to have some + * progress feedback for the user in the statusbar of the application. + * + * @param value The actual progress state. Should always remain in + * the range 1..100. + */ + void sigProgress( int value ); + +protected: + /** + * This is the constructor your filter has to call, obviously. + */ + KoFilter(); + + /** + * Use this pointer to access all information about input/output + * during the conversion. @em Don't use it in the constructor - + * it's invalid while constructing the object! + */ + KoFilterChain* m_chain; + +private: + KoFilter( const KoFilter& rhs ); + KoFilter& operator=( const KoFilter& rhs ); + + class Private; + Private* d; +}; + + +/** + * The base class for all @em import filters embedding other filters. Right + * now we don't support embedding for export filters, but if there's a + * request for that feature please don't hesitate to contact Werner Trobin + * <trobin@kde.org>. + * + * To make use of embedding features you have to know that there are two kinds + * of embedding for filters: embedding the output of a different filter (library) + * or embedding the output of several internal filters (no separate library). + * The first case is the simpler one. You just have to override savePartContents() + * and call @ref #embedPart to trigger the embedding process. One example for such + * a filter is Kontour's MSOD (MS Office Drawing) filter. + * + * The more complex case is embedding various streams from within the same filter + * library. This is neccesary for OLE like files (at least with the current design + * of the OLEFilter). In this case you have to use @ref #startInternalEmbedding and + * @ref #endInternalEmbedding accordingly. Try to use the previous method if possible. + * + * If you're using this class you can also setup a signal/slot communication + * between parent and child filter. To make that work you simply have to define + * signals and slots along the following rules: + * Signals should be named "commSignal\<name\>" where \<name\> is the name of the signal, + * slots should be named "commSlot\<name\>". The connection will be done automatically + * if names and signatures are matching. + * + * @author Werner Trobin + * @todo the class has no constructor and therefore cannot initialize its private class + */ +class KOFFICECORE_EXPORT KoEmbeddingFilter : public KoFilter +{ + Q_OBJECT + + friend class KoFilterChain; + +public: + virtual ~KoEmbeddingFilter(); + + /** + * @internal + * This method returns the last recently used part index at the + * current directory level. It can be (and is ;) used to generate + * the per-directory-unique address for the next part we have to save. + * It will get updated automatically, you most likely don't have to + * care about that one at all. + */ + int lruPartIndex() const; + + /** + * A static helper method to determine the mimetype via the + * file extension. It allows to go from "wmf" to image/x-wmf + * and so on. Note that you should only pass the pure extension + * and not a whole pattern like "*.doc" or so. + */ + static QString mimeTypeByExtension( const QString& extension ); + +protected: + /** + * Constructs a filter. Note that the m_chain pointer is 0 inside + * the constructor. Most likely your constructor will be empty. + */ + KoEmbeddingFilter(); + + /** + * Embed some document using an external filter (i.e. a different + * filter library). This method works according to the template method + * pattern and calls savePartContents() during execution. + * Call this method when you want to convert some data using one or more + * KOffice filters selected via the filter manager. + * This is the way to go when it comes to embedding unless you have very + * special requirements. + * + * @param from The mimetype of the source data + * @param to The mimetype of the destination part. If this field is set + * to "" the filter manager will try to find the best native + * KOffice mimetype. When the method returns this parameter will + * hold the string of the used mimetype. + * @param status Returns the error status of the filter + * @param key Optional key field to allow custom keys inside the part + * map (see @ref #internalPartReference). If this field is left + * empty we generate a key from the part number (e.g. 1 -> "1") + * @return The number of the part (can be used to refer to the part from + * within the embedding filter). + */ + int embedPart( const QCString& from, QCString& to, + KoFilter::ConversionStatus& status, + const QString& key = QString::null ); + + /** + * Method to perform "internal" embedding of parts in olefilter-style. + * This method can be used to signal the start of a new embedding + * level within your filter. Very evil, but what shall I say ;) + * Unless you really have to you should always use @ref #embedPart as + * it's easier to use and not as hacky. + * + * @param key The key we use to store reference/mimetype of your new part + * @param mimeType The mimetype of the part you're about to embed + */ + void startInternalEmbedding( const QString& key, const QCString& mimeType ); + + /** + * This method signals the end of an internal embedding session. + * You have to call that exactly as often as you call @ref #startInternalEmbedding + * or you'll mess up the internal stack and your file will be invalid. + * Again: use @ref #embedPart if you can :-) + */ + void endInternalEmbedding(); + + /** + * Query the internal part map for the reference of the part + * matching the given key. Note that you can use that plain + * simple int to refer to the respective part (when used as string). + * + * @param key The key you would like to look up + * @return The reference or -1 if we didn't find a part with the + * given key + */ + int internalPartReference( const QString& key ) const; + + /** + * Query the internal part map for the mimetype of the part + * matching the given key. + * + * @param key The key you would like to look up + * @return The mimetype, might be empty if the part matching + * the given key doesn't exist. + */ + QCString internalPartMimeType( const QString& key ) const; + +private: + /** + * Holds the directory's number and the mimetype of the part + * for internal parts. This is all we need to locate a part. + * @internal + */ + struct PartReference + { + PartReference( int index = -1, const QCString& mimeType = "" ); + bool isValid() const; + + int m_index; + QCString m_mimeType; + }; + + /** + * This struct keeps track of the last used index for a + * child part and all references to existing children + * We use it to build a whole stack, one PartState per + * embedding level. + * @internal + */ + struct PartState + { + PartState(); + + int m_lruPartIndex; + QMap<QString, PartReference> m_partReferences; + }; + + /// Better do not copy the filters + KoEmbeddingFilter( const KoEmbeddingFilter& rhs ); + /// Better do not assign the filters + KoEmbeddingFilter& operator=( const KoEmbeddingFilter& rhs ); + + /** + * This method will be called by @ref #embedPart as soon as it + * needs the data of the part (template method pattern). You + * have to override that and simply save the part data to the + * (already opened) file. + * No need to override that when you're not using @ref #embedPart + * (as you should ;) + * + * @param file An already opened file + */ + virtual void savePartContents( QIODevice* file ); + + /** + * Internal methods to support the start/endInternalEmbedding + * methods (we have to change directories and stuff). + * These methods are declared friends of the KoFilterChain + */ + void filterChainEnterDirectory( const QString& directory ) const; + /** + * Internal methods to support the start/endInternalEmbedding + * methods (we have to change directories and stuff). + * These methods are declared friends of the KoFilterChain + */ + void filterChainLeaveDirectory() const; + + /** + * A stack which keeps track of the current part references. + * We push one PartState structure for every embedding level. + */ + QPtrStack<PartState> m_partStack; + + class Private; + Private* d; +}; + +#endif diff --git a/lib/kofficecore/KoFilterChain.cpp b/lib/kofficecore/KoFilterChain.cpp new file mode 100644 index 00000000..2ab53780 --- /dev/null +++ b/lib/kofficecore/KoFilterChain.cpp @@ -0,0 +1,936 @@ +/* This file is part of the KOffice libraries + Copyright (C) 2001 Werner Trobin <trobin@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 <qmetaobject.h> +#include <ktempfile.h> +#include <kmimetype.h> +#include <KoFilterChain.h> +#include <KoQueryTrader.h> +#include <KoFilterManager.h> // KoFilterManager::filterAvailable, private API +#include <KoDocument.h> +#include <kdebug.h> + +#include <priorityqueue.h> + +#include <limits.h> // UINT_MAX + +// Those "defines" are needed in the setupConnections method below. +// Please always keep the strings and the length in sync! +namespace { + const char* const SIGNAL_PREFIX = "commSignal"; + const int SIGNAL_PREFIX_LEN = 10; + const char* const SLOT_PREFIX = "commSlot"; + const int SLOT_PREFIX_LEN = 8; +} + + +KoFilterChain::ChainLink::ChainLink( KoFilterChain* chain, KoFilterEntry::Ptr filterEntry, + const QCString& from, const QCString& to ) : + m_chain( chain ), m_filterEntry( filterEntry ), m_from( from ), m_to( to ), + m_filter( 0 ), d( 0 ) +{ +} + +KoFilter::ConversionStatus KoFilterChain::ChainLink::invokeFilter( const ChainLink* const parentChainLink ) +{ + if ( !m_filterEntry ) { + kdError( 30500 ) << "This filter entry is null. Strange stuff going on." << endl; + return KoFilter::CreationError; + } + + m_filter = m_filterEntry->createFilter( m_chain, 0, 0 ); + + if ( !m_filter ) { + kdError( 30500 ) << "Couldn't create the filter." << endl; + return KoFilter::CreationError; + } + + if ( parentChainLink ) + setupCommunication( parentChainLink->m_filter ); + + KoFilter::ConversionStatus status = m_filter->convert( m_from, m_to ); + delete m_filter; + m_filter=0; + return status; +} + +void KoFilterChain::ChainLink::dump() const +{ + kdDebug( 30500 ) << " Link: " << m_filterEntry->service()->name() << endl; +} + +int KoFilterChain::ChainLink::lruPartIndex() const +{ + if ( m_filter && m_filter->inherits( "KoEmbeddingFilter" ) ) + return static_cast<KoEmbeddingFilter*>( m_filter )->lruPartIndex(); + return -1; +} + +void KoFilterChain::ChainLink::setupCommunication( const KoFilter* const parentFilter ) const +{ + // progress information + QObject::connect( m_filter, SIGNAL( sigProgress( int ) ), + m_chain->manager(), SIGNAL( sigProgress( int ) ) ); + + if ( !parentFilter ) + return; + + const QMetaObject* const parent = parentFilter->metaObject(); + const QMetaObject* const child = m_filter->metaObject(); + if ( !parent || !child ) + return; + + setupConnections( parentFilter, parent->signalNames(), m_filter, child->slotNames() ); + setupConnections( m_filter, child->signalNames(), parentFilter, parent->slotNames() ); +} + +void KoFilterChain::ChainLink::setupConnections( const KoFilter* sender, const QStrList& sigs, + const KoFilter* receiver, const QStrList& sl0ts ) const +{ + QStrListIterator signalIt( sigs ); + for ( ; signalIt.current(); ++signalIt ) { + if ( strncmp( signalIt.current(), SIGNAL_PREFIX, SIGNAL_PREFIX_LEN ) == 0 ) { + QStrListIterator slotIt( sl0ts ); + for ( ; slotIt.current(); ++slotIt ) { + if ( strncmp( slotIt.current(), SLOT_PREFIX, SLOT_PREFIX_LEN ) == 0 ) { + if ( strcmp( signalIt.current() + SIGNAL_PREFIX_LEN, slotIt.current() + SLOT_PREFIX_LEN ) == 0 ) { + QCString signalString; + signalString.setNum( QSIGNAL_CODE ); + signalString += signalIt.current(); + QCString slotString; + slotString.setNum( QSLOT_CODE ); + slotString += slotIt.current(); + QObject::connect( sender, signalString, receiver, slotString ); + } + } + } + } + } +} + + +KoFilterChain::~KoFilterChain() +{ + if ( filterManagerParentChain() && filterManagerParentChain()->m_outputStorage ) + filterManagerParentChain()->m_outputStorage->leaveDirectory(); + manageIO(); // Called for the 2nd time in a row -> clean up +} + +KoFilter::ConversionStatus KoFilterChain::invokeChain() +{ + KoFilter::ConversionStatus status = KoFilter::OK; + + m_state = Beginning; + int count = m_chainLinks.count(); + + // This is needed due to nasty Microsoft design + const ChainLink* parentChainLink = 0; + if ( filterManagerParentChain() ) + parentChainLink = filterManagerParentChain()->m_chainLinks.current(); + + // No iterator here, as we need m_chainLinks.current() in outputDocument() + m_chainLinks.first(); + for ( ; count > 1 && m_chainLinks.current() && status == KoFilter::OK; + m_chainLinks.next(), --count ) { + status = m_chainLinks.current()->invokeFilter( parentChainLink ); + m_state = Middle; + manageIO(); + } + + if ( !m_chainLinks.current() ) { + kdWarning( 30500 ) << "Huh?? Found a null pointer in the chain" << endl; + return KoFilter::StupidError; + } + + if ( status == KoFilter::OK ) { + if ( m_state & Beginning ) + m_state |= End; + else + m_state = End; + status = m_chainLinks.current()->invokeFilter( parentChainLink ); + manageIO(); + } + + m_state = Done; + if (status == KoFilter::OK) + finalizeIO(); + return status; +} + +QString KoFilterChain::chainOutput() const +{ + if ( m_state == Done ) + return m_inputFile; // as we already called manageIO() + return QString::null; +} + +QString KoFilterChain::inputFile() +{ + if ( m_inputQueried == File ) + return m_inputFile; + else if ( m_inputQueried != Nil ) { + kdWarning( 30500 ) << "You already asked for some different source." << endl; + return QString::null; + } + m_inputQueried = File; + + if ( m_state & Beginning ) { + if ( static_cast<KoFilterManager::Direction>( filterManagerDirection() ) == + KoFilterManager::Import ) + m_inputFile = filterManagerImportFile(); + else + inputFileHelper( filterManagerKoDocument(), filterManagerImportFile() ); + } + else + if ( m_inputFile.isEmpty() ) + inputFileHelper( m_inputDocument, QString::null ); + + return m_inputFile; +} + +QString KoFilterChain::outputFile() +{ + // sanity check: No embedded filter should ask for a plain file + // ###### CHECK: This will break as soon as we support exporting embedding filters + if ( filterManagerParentChain() ) + kdWarning( 30500 )<< "An embedded filter has to use storageFile()!" << endl; + + if ( m_outputQueried == File ) + return m_outputFile; + else if ( m_outputQueried != Nil ) { + kdWarning( 30500 ) << "You already asked for some different destination." << endl; + return QString::null; + } + m_outputQueried = File; + + if ( m_state & End ) { + if ( static_cast<KoFilterManager::Direction>( filterManagerDirection() ) == + KoFilterManager::Import ) + outputFileHelper( false ); // This (last) one gets deleted by the caller + else + m_outputFile = filterManagerExportFile(); + } + else + outputFileHelper( true ); + + return m_outputFile; +} + +KoStoreDevice* KoFilterChain::storageFile( const QString& name, KoStore::Mode mode ) +{ + // ###### CHECK: This works only for import filters. Do we want something like + // that for export filters too? + if ( m_outputQueried == Nil && mode == KoStore::Write && filterManagerParentChain() ) + return storageInitEmbedding( name ); + + // Plain normal use case + if ( m_inputQueried == Storage && mode == KoStore::Read && + m_inputStorage && m_inputStorage->mode() == KoStore::Read ) + return storageNewStreamHelper( &m_inputStorage, &m_inputStorageDevice, name ); + else if ( m_outputQueried == Storage && mode == KoStore::Write && + m_outputStorage && m_outputStorage->mode() == KoStore::Write ) + return storageNewStreamHelper( &m_outputStorage, &m_outputStorageDevice, name ); + else if ( m_inputQueried == Nil && mode == KoStore::Read ) + return storageHelper( inputFile(), name, KoStore::Read, + &m_inputStorage, &m_inputStorageDevice ); + else if ( m_outputQueried == Nil && mode == KoStore::Write ) + return storageHelper( outputFile(), name, KoStore::Write, + &m_outputStorage, &m_outputStorageDevice ); + else { + kdWarning( 30500 ) << "Oooops, how did we get here? You already asked for a" + << " different source/destination?" << endl; + return 0; + } +} + +KoDocument* KoFilterChain::inputDocument() +{ + if ( m_inputQueried == Document ) + return m_inputDocument; + else if ( m_inputQueried != Nil ) { + kdWarning( 30500 ) << "You already asked for some different source." << endl; + return 0; + } + + if ( ( m_state & Beginning ) && + static_cast<KoFilterManager::Direction>( filterManagerDirection() ) == KoFilterManager::Export && + filterManagerKoDocument() ) + m_inputDocument = filterManagerKoDocument(); + else if ( !m_inputDocument ) + m_inputDocument = createDocument( inputFile() ); + + m_inputQueried = Document; + return m_inputDocument; +} + +KoDocument* KoFilterChain::outputDocument() +{ + // sanity check: No embedded filter should ask for a document + // ###### CHECK: This will break as soon as we support exporting embedding filters + if ( filterManagerParentChain() ) { + kdWarning( 30500 )<< "An embedded filter has to use storageFile()!" << endl; + return 0; + } + + if ( m_outputQueried == Document ) + return m_outputDocument; + else if ( m_outputQueried != Nil ) { + kdWarning( 30500 ) << "You already asked for some different destination." << endl; + return 0; + } + + if ( ( m_state & End ) && + static_cast<KoFilterManager::Direction>( filterManagerDirection() ) == KoFilterManager::Import && + filterManagerKoDocument() ) + m_outputDocument = filterManagerKoDocument(); + else + m_outputDocument = createDocument( m_chainLinks.current()->to() ); + + m_outputQueried = Document; + return m_outputDocument; +} + +void KoFilterChain::dump() const +{ + kdDebug( 30500 ) << "########## KoFilterChain with " << m_chainLinks.count() << " members:" << endl; + QPtrListIterator<ChainLink> it( m_chainLinks ); + for ( ; it.current(); ++it ) + it.current()->dump(); + kdDebug( 30500 ) << "########## KoFilterChain (done) ##########" << endl; +} + +KoFilterChain::KoFilterChain( const KoFilterManager* manager ) : + m_manager( manager ), m_state( Beginning ), m_inputStorage( 0 ), + m_inputStorageDevice( 0 ), m_outputStorage( 0 ), m_outputStorageDevice( 0 ), + m_inputDocument( 0 ), m_outputDocument( 0 ), m_inputTempFile( 0 ), + m_outputTempFile( 0 ), m_inputQueried( Nil ), m_outputQueried( Nil ), d( 0 ) +{ + // We "own" our chain links, the filter entries are implicitly shared + m_chainLinks.setAutoDelete( true ); +} + +void KoFilterChain::appendChainLink( KoFilterEntry::Ptr filterEntry, const QCString& from, const QCString& to ) +{ + m_chainLinks.append( new ChainLink( this, filterEntry, from, to ) ); +} + +void KoFilterChain::prependChainLink( KoFilterEntry::Ptr filterEntry, const QCString& from, const QCString& to ) +{ + m_chainLinks.prepend( new ChainLink( this, filterEntry, from, to ) ); +} + +void KoFilterChain::enterDirectory( const QString& directory ) +{ + // Only a little bit of checking as we (have to :} ) trust KoEmbeddingFilter + // If the output storage isn't initialized yet, we perform that step(s) on init. + if ( m_outputStorage ) + m_outputStorage->enterDirectory( directory ); + m_internalEmbeddingDirectories.append( directory ); +} + +void KoFilterChain::leaveDirectory() +{ + if ( m_outputStorage ) + m_outputStorage->leaveDirectory(); + if ( !m_internalEmbeddingDirectories.isEmpty() ) + m_internalEmbeddingDirectories.pop_back(); +} + +QString KoFilterChain::filterManagerImportFile() const +{ + return m_manager->importFile(); +} + +QString KoFilterChain::filterManagerExportFile() const +{ + return m_manager->exportFile(); +} + +KoDocument* KoFilterChain::filterManagerKoDocument() const +{ + return m_manager->document(); +} + +int KoFilterChain::filterManagerDirection() const +{ + return m_manager->direction(); +} + +KoFilterChain* const KoFilterChain::filterManagerParentChain() const +{ + return m_manager->parentChain(); +} + +void KoFilterChain::manageIO() +{ + m_inputQueried = Nil; + m_outputQueried = Nil; + + delete m_inputStorageDevice; + m_inputStorageDevice = 0; + if ( m_inputStorage ) { + m_inputStorage->close(); + delete m_inputStorage; + m_inputStorage = 0; + } + if ( m_inputTempFile ) { + m_inputTempFile->close(); + delete m_inputTempFile; // autodelete + m_inputTempFile = 0; + } + m_inputFile = QString::null; + + if ( !m_outputFile.isEmpty() ) { + m_inputFile = m_outputFile; + m_outputFile = QString::null; + m_inputTempFile = m_outputTempFile; + m_outputTempFile = 0; + + delete m_outputStorageDevice; + m_outputStorageDevice = 0; + if ( m_outputStorage ) { + m_outputStorage->close(); + // Don't delete the storage if we're just pointing to the + // storage of the parent filter chain + if ( !filterManagerParentChain() || m_outputStorage->mode() != KoStore::Write ) + delete m_outputStorage; + m_outputStorage = 0; + } + } + + if ( m_inputDocument != filterManagerKoDocument() ) + delete m_inputDocument; + m_inputDocument = m_outputDocument; + m_outputDocument = 0; +} + +void KoFilterChain::finalizeIO() +{ + // In case we export (to a file, of course) and the last + // filter chose to output a KoDocument we have to save it. + // Should be very rare, but well... + // Note: m_*input*Document as we already called manageIO() + if ( m_inputDocument && + static_cast<KoFilterManager::Direction>( filterManagerDirection() ) == KoFilterManager::Export ) { + kdDebug( 30500 ) << "Saving the output document to the export file" << endl; + m_inputDocument->saveNativeFormat( filterManagerExportFile() ); + m_inputFile = filterManagerExportFile(); + } +} + +bool KoFilterChain::createTempFile( KTempFile** tempFile, bool autoDelete ) +{ + if ( *tempFile ) { + kdError( 30500 ) << "Ooops, why is there already a temp file???" << endl; + return false; + } + *tempFile = new KTempFile(); + ( *tempFile )->setAutoDelete( autoDelete ); + return ( *tempFile )->status() == 0; +} + +void KoFilterChain::inputFileHelper( KoDocument* document, const QString& alternativeFile ) +{ + if ( document ) { + if ( !createTempFile( &m_inputTempFile ) ) { + delete m_inputTempFile; + m_inputTempFile = 0; + m_inputFile = QString::null; + return; + } + if ( !document->saveNativeFormat( m_inputTempFile->name() ) ) { + delete m_inputTempFile; + m_inputTempFile = 0; + m_inputFile = QString::null; + return; + } + m_inputFile = m_inputTempFile->name(); + } + else + m_inputFile = alternativeFile; +} + +void KoFilterChain::outputFileHelper( bool autoDelete ) +{ + if ( !createTempFile( &m_outputTempFile, autoDelete ) ) { + delete m_outputTempFile; + m_outputTempFile = 0; + m_outputFile = QString::null; + } + else + m_outputFile = m_outputTempFile->name(); +} + +KoStoreDevice* KoFilterChain::storageNewStreamHelper( KoStore** storage, KoStoreDevice** device, + const QString& name ) +{ + delete *device; + *device = 0; + if ( ( *storage )->isOpen() ) + ( *storage )->close(); + if ( ( *storage )->bad() ) + return storageCleanupHelper( storage ); + if ( !( *storage )->open( name ) ) + return 0; + + *device = new KoStoreDevice( *storage ); + return *device; +} + +KoStoreDevice* KoFilterChain::storageHelper( const QString& file, const QString& streamName, + KoStore::Mode mode, KoStore** storage, + KoStoreDevice** device ) +{ + if ( file.isEmpty() ) + return 0; + if ( *storage ) { + kdDebug( 30500 ) << "Uh-oh, we forgot to clean up..." << endl; + return 0; + } + + storageInit( file, mode, storage ); + + if ( ( *storage )->bad() ) + return storageCleanupHelper( storage ); + + // Seems that we got a valid storage, at least. Even if we can't open + // the stream the "user" asked us to open, we nontheless change the + // IOState from File to Storage, as it might be possible to open other streams + if ( mode == KoStore::Read ) + m_inputQueried = Storage; + else // KoStore::Write + m_outputQueried = Storage; + + return storageCreateFirstStream( streamName, storage, device ); +} + +void KoFilterChain::storageInit( const QString& file, KoStore::Mode mode, KoStore** storage ) +{ + QCString appIdentification( "" ); + if ( mode == KoStore::Write ) { + // To create valid storages we also have to add the mimetype + // magic "applicationIndentifier" to the storage. + // As only filters with a KOffice destination should query + // for a storage to write to, we don't check the content of + // the mimetype here. It doesn't do a lot of harm if someome + // "abuses" this method. + appIdentification = m_chainLinks.current()->to(); + } + *storage = KoStore::createStore( file, mode, appIdentification ); +} + +KoStoreDevice* KoFilterChain::storageInitEmbedding( const QString& name ) +{ + if ( m_outputStorage ) { + kdWarning( 30500 ) << "Ooops! Something's really screwed here." << endl; + return 0; + } + + m_outputStorage = filterManagerParentChain()->m_outputStorage; + + if ( !m_outputStorage ) { + // If the storage of the parent hasn't been initialized yet, + // we have to do that here. Quite nasty... + storageInit( filterManagerParentChain()->outputFile(), KoStore::Write, &m_outputStorage ); + + // transfer the ownership + filterManagerParentChain()->m_outputStorage = m_outputStorage; + filterManagerParentChain()->m_outputQueried = Storage; + } + + if ( m_outputStorage->isOpen() ) + m_outputStorage->close(); // to be on the safe side, should never happen + if ( m_outputStorage->bad() ) + return storageCleanupHelper( &m_outputStorage ); + + m_outputQueried = Storage; + + // Now that we have a storage we have to change the directory + // and remember it for later! + const int lruPartIndex = filterManagerParentChain()->m_chainLinks.current()->lruPartIndex(); + if ( lruPartIndex == -1 ) { + kdError( 30500 ) << "Huh! You want to use embedding features w/o inheriting KoEmbeddingFilter?" << endl; + return storageCleanupHelper( &m_outputStorage ); + } + + if ( !m_outputStorage->enterDirectory( QString( "part%1" ).arg( lruPartIndex ) ) ) + return storageCleanupHelper( &m_outputStorage ); + + return storageCreateFirstStream( name, &m_outputStorage, &m_outputStorageDevice ); +} + +KoStoreDevice* KoFilterChain::storageCreateFirstStream( const QString& streamName, KoStore** storage, + KoStoreDevice** device ) +{ + // Before we go and create the first stream in this storage we + // have to perform a little hack in case we're used by any ole-style + // filter which utilizes internal embedding. Ugly, but well... + if ( !m_internalEmbeddingDirectories.isEmpty() ) { + QStringList::ConstIterator it = m_internalEmbeddingDirectories.begin(); + QStringList::ConstIterator end = m_internalEmbeddingDirectories.end(); + for ( ; it != end && ( *storage )->enterDirectory( *it ); ++it ); + } + + if ( !( *storage )->open( streamName ) ) + return 0; + + if ( *device ) { + kdDebug( 30500 ) << "Uh-oh, we forgot to clean up the storage device!" << endl; + ( *storage )->close(); + return storageCleanupHelper( storage ); + } + *device = new KoStoreDevice( *storage ); + return *device; +} + +KoStoreDevice* KoFilterChain::storageCleanupHelper( KoStore** storage ) +{ + // Take care not to delete the storage of the parent chain + if ( *storage != m_outputStorage || !filterManagerParentChain() || + ( *storage )->mode() != KoStore::Write ) + delete *storage; + *storage = 0; + return 0; +} + +KoDocument* KoFilterChain::createDocument( const QString& file ) +{ + KURL url; + url.setPath( file ); + KMimeType::Ptr t = KMimeType::findByURL( url, 0, true ); + if ( t->name() == KMimeType::defaultMimeType() ) { + kdError( 30500 ) << "No mimetype found for " << file << endl; + return 0; + } + + KoDocument *doc = createDocument( QCString( t->name().latin1() ) ); + + if ( !doc || !doc->loadNativeFormat( file ) ) { + kdError( 30500 ) << "Couldn't load from the file" << endl; + delete doc; + return 0; + } + return doc; +} + +KoDocument* KoFilterChain::createDocument( const QCString& mimeType ) +{ + KoDocumentEntry entry = KoDocumentEntry::queryByMimeType(mimeType); + + if (entry.isEmpty()) + { + kdError( 30500 ) << "Couldn't find a part that can handle mimetype " << mimeType << endl; + } + + KoDocument* doc = entry.createDoc(); /*entries.first().createDoc();*/ + if ( !doc ) { + kdError( 30500 ) << "Couldn't create the document" << endl; + return 0; + } + return doc; +} + + +namespace KOffice { + + Edge::Edge( Vertex* vertex, KoFilterEntry::Ptr filterEntry ) : + m_vertex( vertex ), m_filterEntry( filterEntry ), d( 0 ) + { + } + + void Edge::relax( const Vertex* predecessor, PriorityQueue<Vertex>& queue ) + { + if ( !m_vertex || !predecessor || !m_filterEntry ) + return; + if ( m_vertex->setKey( predecessor->key() + m_filterEntry->weight ) ) { + queue.keyDecreased( m_vertex ); // maintain the heap property + m_vertex->setPredecessor( predecessor ); + } + } + + void Edge::dump( const QCString& indent ) const + { + if ( m_vertex ) + kdDebug( 30500 ) << indent << "Edge -> '" << m_vertex->mimeType() + << "' (" << m_filterEntry->weight << ")" << endl; + else + kdDebug( 30500 ) << indent << "Edge -> '(null)' (" + << m_filterEntry->weight << ")" << endl; + } + + + Vertex::Vertex( const QCString& mimeType ) : m_predecessor( 0 ), m_mimeType( mimeType ), + m_weight( UINT_MAX ), m_index( -1 ), d( 0 ) + { + m_edges.setAutoDelete( true ); // we take ownership of added edges + } + + bool Vertex::setKey( unsigned int key ) + { + if ( m_weight > key ) { + m_weight = key; + return true; + } + return false; + } + + void Vertex::reset() + { + m_weight = UINT_MAX; + m_predecessor = 0; + } + + void Vertex::addEdge( const Edge* edge ) + { + if ( !edge || edge->weight() == 0 ) + return; + m_edges.append( edge ); + } + + const Edge* Vertex::findEdge( const Vertex* vertex ) const + { + if ( !vertex ) + return 0; + const Edge* edge = 0; + QPtrListIterator<Edge> it( m_edges ); + + for ( ; it.current(); ++it ) { + if ( it.current()->vertex() == vertex && + ( !edge || it.current()->weight() < edge->weight() ) ) + edge = it.current(); + } + return edge; + } + + void Vertex::relaxVertices( PriorityQueue<Vertex>& queue ) + { + for ( Edge *e = m_edges.first(); e; e = m_edges.next() ) + e->relax( this, queue ); + } + + void Vertex::dump( const QCString& indent ) const + { + kdDebug( 30500 ) << indent << "Vertex: " << m_mimeType << " (" << m_weight << "):" << endl; + const QCString i( indent + " " ); + QPtrListIterator<Edge> it( m_edges ); + for ( ; it.current(); ++it ) + it.current()->dump( i ); + } + + + Graph::Graph( const QCString& from ) : m_vertices( 47 ), m_from( from ), + m_graphValid( false ), d( 0 ) + { + m_vertices.setAutoDelete( true ); + buildGraph(); + shortestPaths(); // Will return after a single lookup if "from" is invalid (->no check here) + } + + void Graph::setSourceMimeType( const QCString& from ) + { + if ( from == m_from ) + return; + m_from = from; + m_graphValid = false; + + // Initialize with "infinity" ... + QAsciiDictIterator<Vertex> it( m_vertices ); + for ( ; it.current(); ++it ) + it.current()->reset(); + + // ...and re-run the shortest path search for the new source mime + shortestPaths(); + } + + KoFilterChain::Ptr Graph::chain( const KoFilterManager* manager, QCString& to ) const + { + if ( !isValid() || !manager ) + return 0; + + if ( to.isEmpty() ) { // if the destination is empty we search the closest KOffice part + to = findKOfficePart(); + if ( to.isEmpty() ) // still empty? strange stuff... + return 0; + } + + const Vertex* vertex = m_vertices[ to ]; + if ( !vertex || vertex->key() == UINT_MAX ) + return 0; + + KoFilterChain::Ptr ret = new KoFilterChain( manager ); + + // Fill the filter chain with all filters on the path + const Vertex* tmp = vertex->predecessor(); + while ( tmp ) { + const Edge* const edge = tmp->findEdge( vertex ); + Q_ASSERT( edge ); + ret->prependChainLink( edge->filterEntry(), tmp->mimeType(), vertex->mimeType() ); + vertex = tmp; + tmp = tmp->predecessor(); + } + return ret; + } + + void Graph::dump() const + { + kdDebug( 30500 ) << "+++++++++ Graph::dump +++++++++" << endl; + kdDebug( 30500 ) << "From: " << m_from << endl; + QAsciiDictIterator<Vertex> it( m_vertices ); + for ( ; it.current(); ++it ) + it.current()->dump( " " ); + kdDebug( 30500 ) << "+++++++++ Graph::dump (done) +++++++++" << endl; + } + + // Query the trader and create the vertices and edges representing + // available mime types and filters. + void Graph::buildGraph() + { + // Make sure that all available parts are added to the graph + QValueList<KoDocumentEntry> parts( KoDocumentEntry::query() ); + QValueList<KoDocumentEntry>::ConstIterator partIt( parts.begin() ); + QValueList<KoDocumentEntry>::ConstIterator partEnd( parts.end() ); + + while ( partIt != partEnd ) { + QStringList nativeMimeTypes = ( *partIt ).service()->property( "X-KDE-ExtraNativeMimeTypes" ).toStringList(); + nativeMimeTypes += ( *partIt ).service()->property( "X-KDE-NativeMimeType" ).toString(); + QStringList::ConstIterator it = nativeMimeTypes.begin(); + QStringList::ConstIterator end = nativeMimeTypes.end(); + for ( ; it != end; ++it ) + if ( !(*it).isEmpty() ) + m_vertices.insert( (*it).latin1(), new Vertex( (*it).latin1() ) ); + ++partIt; + } + + QValueList<KoFilterEntry::Ptr> filters( KoFilterEntry::query() ); // no constraint here - we want *all* :) + QValueList<KoFilterEntry::Ptr>::ConstIterator it = filters.begin(); + QValueList<KoFilterEntry::Ptr>::ConstIterator end = filters.end(); + + for ( ; it != end; ++it ) { + // First add the "starting points" to the dict + QStringList::ConstIterator importIt = ( *it )->import.begin(); + QStringList::ConstIterator importEnd = ( *it )->import.end(); + for ( ; importIt != importEnd; ++importIt ) { + const QCString key = ( *importIt ).latin1(); // latin1 is okay here (werner) + // already there? + if ( !m_vertices[ key ] ) + m_vertices.insert( key, new Vertex( key ) ); + } + + // Are we allowed to use this filter at all? + if ( KoFilterManager::filterAvailable( *it ) ) { + QStringList::ConstIterator exportIt = ( *it )->export_.begin(); + QStringList::ConstIterator exportEnd = ( *it )->export_.end(); + + for ( ; exportIt != exportEnd; ++exportIt ) { + // First make sure the export vertex is in place + const QCString key = ( *exportIt ).latin1(); // latin1 is okay here + Vertex* exp = m_vertices[ key ]; + if ( !exp ) { + exp = new Vertex( key ); + m_vertices.insert( key, exp ); + } + // Then create the appropriate edges + importIt = ( *it )->import.begin(); + for ( ; importIt != importEnd; ++importIt ) + m_vertices[ ( *importIt ).latin1() ]->addEdge( new Edge( exp, *it ) ); + } + } + else + kdDebug( 30500 ) << "Filter: " << ( *it )->service()->name() << " doesn't apply." << endl; + } + } + + // As all edges (=filters) are required to have a positive weight + // we can use Dijkstra's shortest path algorithm from Cormen's + // "Introduction to Algorithms" (p. 527) + // Note: I did some adaptions as our data structures are slightly + // different from the ones used in the book. Further we simply stop + // the algorithm is we don't find any node with a weight != Infinity + // (==UINT_MAX), as this means that the remaining nodes in the queue + // aren't connected anyway. + void Graph::shortestPaths() + { + // Is the requested start mime type valid? + Vertex* from = m_vertices[ m_from ]; + if ( !from ) + return; + + // Inititalize start vertex + from->setKey( 0 ); + + // Fill the priority queue with all the vertices + PriorityQueue<Vertex> queue( m_vertices ); + + while ( !queue.isEmpty() ) { + Vertex *min = queue.extractMinimum(); + // Did we already relax all connected vertices? + if ( min->key() == UINT_MAX ) + break; + min->relaxVertices( queue ); + } + m_graphValid = true; + } + + QCString Graph::findKOfficePart() const + { + // Here we simply try to find the closest KOffice mimetype + QValueList<KoDocumentEntry> parts( KoDocumentEntry::query() ); + QValueList<KoDocumentEntry>::ConstIterator partIt( parts.begin() ); + QValueList<KoDocumentEntry>::ConstIterator partEnd( parts.end() ); + + const Vertex *v = 0; + + // Be sure that v gets initialized correctly + while ( !v && partIt != partEnd ) { + QStringList nativeMimeTypes = ( *partIt ).service()->property( "X-KDE-ExtraNativeMimeTypes" ).toStringList(); + nativeMimeTypes += ( *partIt ).service()->property( "X-KDE-NativeMimeType" ).toString(); + QStringList::ConstIterator it = nativeMimeTypes.begin(); + QStringList::ConstIterator end = nativeMimeTypes.end(); + for ( ; !v && it != end; ++it ) + if ( !(*it).isEmpty() ) + v = m_vertices[ ( *it ).latin1() ]; + ++partIt; + } + if ( !v ) + return ""; + + // Now we try to find the "cheapest" KOffice vertex + while ( partIt != partEnd ) { + QStringList nativeMimeTypes = ( *partIt ).service()->property( "X-KDE-ExtraNativeMimeTypes" ).toStringList(); + nativeMimeTypes += ( *partIt ).service()->property( "X-KDE-NativeMimeType" ).toString(); + QStringList::ConstIterator it = nativeMimeTypes.begin(); + QStringList::ConstIterator end = nativeMimeTypes.end(); + for ( ; !v && it != end; ++it ) { + QString key = *it; + if ( !key.isEmpty() ) { + Vertex* tmp = m_vertices[ key.latin1() ]; + if ( !v || ( tmp && tmp->key() < v->key() ) ) + v = tmp; + } + } + ++partIt; + } + + // It seems it already is a KOffice part + if ( v->key() == 0 ) + return ""; + + return v->mimeType(); + } + +} // namespace KOffice diff --git a/lib/kofficecore/KoFilterChain.h b/lib/kofficecore/KoFilterChain.h new file mode 100644 index 00000000..5b8dd3dd --- /dev/null +++ b/lib/kofficecore/KoFilterChain.h @@ -0,0 +1,410 @@ +/* This file is part of the KOffice libraries + Copyright (C) 2001 Werner Trobin <trobin@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 __koffice_filter_chain_h__ +#define __koffice_filter_chain_h__ + +#include <qcstring.h> +#include <qasciidict.h> +#include <qptrlist.h> +#include <qstringlist.h> + +#include <KoFilter.h> +#include <KoQueryTrader.h> +#include <KoStoreDevice.h> +#include <koffice_export.h> + +class KTempFile; +class KoFilterManager; +class KoDocument; +class QStrList; + +namespace KOffice { + class Graph; +} + +/** + * @brief This class represents a chain of plain @ref KoFilter instances. + * + * Instances of this class are shared, so please just hold + * KoFilterChain::Ptr pointers to it. + * + * @author Werner Trobin <trobin@kde.org> + * @todo the class has no constructor and therefore cannot initialize its private class + */ +class KOFFICECORE_EXPORT KoFilterChain : public KShared +{ + // Only KOffice::Graph is allowed to construct instances and + // add chain links. + friend class KOffice::Graph; + friend class KoFilterManager; + +public: + typedef KSharedPtr<KoFilterChain> Ptr; + + virtual ~KoFilterChain(); + + /** + * The filter manager returned may be 0! + */ + const KoFilterManager* manager() const { return m_manager; } + + /** + * Starts the filtering process. + * @return The return status of the conversion. KoFilter::OK + * if everything is alright. + */ + KoFilter::ConversionStatus invokeChain(); + + /** + * Tells the @ref KoFilterManager the output file of the + * filter chain in case of an import operation. If it's + * QString::null we directly manipulated the document. + */ + QString chainOutput() const; + + /** + * Get the current file to read from. This part of the API + * is for the filters in our chain. + */ + QString inputFile(); + /** + * Get the current file to write to. This part of the API + * is for the filters in our chain. + */ + QString outputFile(); + + /** + * Get a file from a storage. May return 0! + * This part of the API is for the filters in our chain. + * If you call it multiple times with the same stream name + * the stream will be closed and re-opened. + * Note: @em Don't delete that @ref KoStoreDevice we return. + * @param name The name of the stream inside the storage + * @param mode Whether we want to read or write from/to the stream + * @return The storage device to access the stream. May be 0! + */ + KoStoreDevice* storageFile( const QString& name = "root", KoStore::Mode mode = KoStore::Read ); + + /** + * This method allows your filter to work directly on the + * @ref KoDocument of the application. + * This part of the API is for the filters in our chain. + * @return The document containing the data. May return 0 on error. + */ + KoDocument* inputDocument(); + /** + * This method allows your filter to work directly on the + * @ref KoDocument of the application. + * This part of the API is for the filters in our chain. + * @return The document you have to write to. May return 0 on error. + */ + KoDocument* outputDocument(); + + + // debugging + void dump() const; + +private: + // ### API for KOffice::Graph: + // Construct a filter chain belonging to some KoFilterManager. + // The parent filter manager may be 0. + KoFilterChain( const KoFilterManager* manager ); + + void appendChainLink( KoFilterEntry::Ptr filterEntry, const QCString& from, const QCString& to ); + void prependChainLink( KoFilterEntry::Ptr filterEntry, const QCString& from, const QCString& to ); + + // ### API for KoEmbeddingFilter + // This is needed as the embedding filter might have to influence + // the way we change directories (e.g. in the olefilter case) + // The ugly friend methods are needed, but I'd welcome and suggestions for + // better design :} + friend void KoEmbeddingFilter::filterChainEnterDirectory( const QString& directory ) const; + void enterDirectory( const QString& directory ); + friend void KoEmbeddingFilter::filterChainLeaveDirectory() const; + void leaveDirectory(); + + // These methods are friends of KoFilterManager and provide access + // to a private part of its API. As I don't want to include + // koFilterManager.h in this header the direction is "int" here. + QString filterManagerImportFile() const; + QString filterManagerExportFile() const; + KoDocument* filterManagerKoDocument() const; + int filterManagerDirection() const; + KoFilterChain* const filterManagerParentChain() const; + + + // Helper methods which keep track of all the temp files, documents, + // storages,... and properly delete them as soon as they are not + // needed anymore. + void manageIO(); + void finalizeIO(); + + bool createTempFile( KTempFile** tempFile, bool autoDelete = true ); + + void inputFileHelper( KoDocument* document, const QString& alternativeFile ); + void outputFileHelper( bool autoDelete ); + KoStoreDevice* storageNewStreamHelper( KoStore** storage, KoStoreDevice** device, const QString& name ); + KoStoreDevice* storageHelper( const QString& file, const QString& streamName, + KoStore::Mode mode, KoStore** storage, KoStoreDevice** device ); + void storageInit( const QString& file, KoStore::Mode mode, KoStore** storage ); + KoStoreDevice* storageInitEmbedding( const QString& name ); + KoStoreDevice* storageCreateFirstStream( const QString& streamName, KoStore** storage, KoStoreDevice** device ); + KoStoreDevice* storageCleanupHelper( KoStore** storage ); + + KoDocument* createDocument( const QString& file ); + KoDocument* createDocument( const QCString& mimeType ); + + /** + * A small private helper class with represents one single filter + * (one link of the chain) + * @internal + */ + class ChainLink + { + + public: + ChainLink( KoFilterChain* chain, KoFilterEntry::Ptr filterEntry, + const QCString& from, const QCString& to ); + + KoFilter::ConversionStatus invokeFilter( const ChainLink* const parentChainLink ); + + QCString from() const { return m_from; } + QCString to() const { return m_to; } + + // debugging + void dump() const; + + // This hack is only needed due to crappy Microsoft design and + // circular dependencies in their embedded files :} + int lruPartIndex() const; + + private: + ChainLink( const ChainLink& rhs ); + ChainLink& operator=( const ChainLink& rhs ); + + void setupCommunication( const KoFilter* const parentFilter ) const; + void setupConnections( const KoFilter* sender, const QStrList& sigs, + const KoFilter* receiver, const QStrList& sl0ts ) const; + + KoFilterChain* m_chain; + KoFilterEntry::Ptr m_filterEntry; + QCString m_from, m_to; + + // This hack is only needed due to crappy Microsoft design and + // circular dependencies in their embedded files :} + KoFilter* m_filter; + + class Private; + Private* d; + }; + + // "A whole is that which has beginning, middle, and end" - Aristotle + // ...but we also need to signal "Done" state, Mr. Aristotle + enum Whole { Beginning = 1, Middle = 2, End = 4, Done = 8 }; + + // Don't copy or assign filter chains + KoFilterChain( const KoFilterChain& rhs ); + KoFilterChain& operator=( const KoFilterChain& rhs ); + + const KoFilterManager* const m_manager; + QPtrList<ChainLink> m_chainLinks; + + // stuff needed for bookkeeping + int m_state; + + QString m_inputFile; // Did we pass around plain files? + QString m_outputFile; + + KoStore* m_inputStorage; // ...or was it a storage+device? + KoStoreDevice* m_inputStorageDevice; + KoStore* m_outputStorage; + KoStoreDevice* m_outputStorageDevice; + + KoDocument* m_inputDocument; // ...or even documents? + KoDocument* m_outputDocument; + + KTempFile* m_inputTempFile; + KTempFile* m_outputTempFile; + + // These two flags keep track of the input/output the + // filter (=user) asked for + enum IOState { Nil, File, Storage, Document }; + IOState m_inputQueried, m_outputQueried; + + // This stack keeps track of directories we have to enter and + // leave due to internal embedding a la OLE filters. This serves + // as a kind of "memory" even if we didn't initialize the store yet. + // I know that it's ugly, and I'll try to clean up that hack + // sooner or later (Werner) + QStringList m_internalEmbeddingDirectories; + + class Private; + Private* d; +}; + + +// As we use quite generic classnames... +namespace KOffice +{ + class Vertex; + template<class T> class PriorityQueue; + + /** + * An internal class representing a filter (=edge) in the filter graph. + * @internal + */ + class Edge + { + + public: + // creates a new edge to "vertex" with the given weight. + Edge( Vertex* vertex, KoFilterEntry::Ptr filterEntry ); + ~Edge() {} + + unsigned int weight() const { return m_filterEntry ? m_filterEntry->weight : 0; } + KoFilterEntry::Ptr filterEntry() const { return m_filterEntry; } + const Vertex* vertex() const { return m_vertex; } + + // Relaxes the "connected" vertex (i.e. the weight of the + // connected vertex = "predec.->key()" (parameter) + weight of this edge + // As this will only be called once we calculate the weight + // of the edge "on the fly" + // Note: We have to pass the queue as we have to call keyDecreased :} + void relax( const Vertex* predecessor, PriorityQueue<Vertex>& queue ); + + // debugging + void dump( const QCString& indent ) const; + + private: + Edge( const Edge& rhs ); + Edge& operator=( const Edge& rhs ); + + Vertex* m_vertex; + KoFilterEntry::Ptr m_filterEntry; + + class Private; + Private* d; + }; + + + /** + * An internal class representing a mime type (=node, vertex) in the filter graph. + * @internal + */ + class Vertex + { + + public: + Vertex( const QCString& mimeType ); + ~Vertex() {} + + QCString mimeType() const { return m_mimeType; } + + // Current "weight" of the vertex - will be "relaxed" when + // running the shortest path algorithm. Returns true if it + // really has been "relaxed" + bool setKey( unsigned int key ); + unsigned int key() const { return m_weight; } + // Can be used to set the key back to "Infinity" (UINT_MAX) + // and reset the predecessor of this vertex + void reset(); + + // Position in the heap, needed for a fast keyDecreased operation + void setIndex( int index ) { m_index=index; } + int index() const { return m_index; } + + // predecessor on the way from the source to the destination, + // needed for the shortest path algorithm + void setPredecessor( const Vertex* predecessor ) { m_predecessor=predecessor; } + const Vertex* predecessor() const { return m_predecessor; } + + // Adds an outgoing edge to the vertex, transfers ownership + void addEdge( const Edge* edge ); + // Finds the lightest(!) edge pointing to the given vertex, if any (0 if not found) + // This means it will always search the whole list of edges + const Edge* findEdge( const Vertex* vertex ) const; + + // This method is called when we need to relax all "our" edges. + // We need to pass the queue as we have to notify it about key changes - ugly :( + void relaxVertices( PriorityQueue<Vertex>& queue ); + + // debugging + void dump( const QCString& indent ) const; + + private: + Vertex( const Vertex& rhs ); + Vertex& operator=( const Vertex& rhs ); + + QPtrList<Edge> m_edges; + const Vertex* m_predecessor; + QCString m_mimeType; + unsigned int m_weight; // "key" inside the queue + int m_index; // position inside the queue, needed for a fast keyDecreased() + + class Private; + Private* d; + }; + + + /** + * The main worker behind the scenes. Manages the creation of the graph, + * processing the information in it, and creating the filter chains. + * @internal + */ + class Graph + { + + public: + Graph( const QCString& from ); + ~Graph() {} + + bool isValid() const { return m_graphValid; } + + QCString sourceMimeType() const { return m_from; } + void setSourceMimeType( const QCString& from ); + + // Creates a chain from "from" to the "to" mimetype + // If the "to" mimetype isEmpty() then we try to find the + // closest KOffice mimetype and use that as destination. + // After such a search "to" will contain the dest. mimetype (return value) + // if the search was successful. Might return 0! + KoFilterChain::Ptr chain( const KoFilterManager* manager, QCString& to ) const; + + // debugging + void dump() const; + + private: + Graph( const Graph& rhs ); + Graph& operator=( const Graph& rhs ); + + void buildGraph(); + void shortestPaths(); + QCString findKOfficePart() const; + + QAsciiDict<Vertex> m_vertices; + QCString m_from; + bool m_graphValid; + + class Private; + Private* d; + }; + +} // namespace KOffice + +#endif // __koffice_filter_chain_h__ diff --git a/lib/kofficecore/KoFilterManager.cpp b/lib/kofficecore/KoFilterManager.cpp new file mode 100644 index 00000000..f3db5c73 --- /dev/null +++ b/lib/kofficecore/KoFilterManager.cpp @@ -0,0 +1,602 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> + 2000, 2001 Werner Trobin <trobin@kde.org> + Copyright (C) 2004 Nicolas Goutte <goutte@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 <KoFilterManager.h> +#include <KoFilterManager_p.h> + +#include <qfile.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qptrlist.h> +#include <qapplication.h> + +#include <klocale.h> +#include <kmessagebox.h> +#include <KoDocument.h> +#include <klibloader.h> +#include <klistbox.h> +#include <kmimetype.h> +#include <kdebug.h> + +#include <queue> + +#include <unistd.h> + +class KoFilterManager::Private +{ +public: + bool m_batch; +}; + + +KoFilterChooser::KoFilterChooser (QWidget *parent, const QStringList &mimeTypes, const QString &nativeFormat) + : KDialogBase (parent, "kofilterchooser", true, i18n ("Choose Filter"), + KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok, true), + m_mimeTypes (mimeTypes) +{ + setInitialSize (QSize (300, 350)); + + QWidget *page = new QWidget (this); + setMainWidget (page); + + // looks too squashed together without * 2 + QVBoxLayout *layout = new QVBoxLayout (page, marginHint (), spacingHint () * 2); + + QLabel *filterLabel = new QLabel (i18n ("Select a filter:"), page, "filterlabel"); + layout->addWidget (filterLabel); + + m_filterList = new KListBox (page, "filterlist"); + layout->addWidget (m_filterList); + + Q_ASSERT (!m_mimeTypes.isEmpty ()); + for (QStringList::ConstIterator it = m_mimeTypes.begin (); + it != m_mimeTypes.end (); + it++) + { + KMimeType::Ptr mime = KMimeType::mimeType (*it); + m_filterList->insertItem (mime->comment ()); + } + + if (nativeFormat == "application/x-kword") + { + const int index = m_mimeTypes.findIndex ("text/plain"); + if (index > -1) + m_filterList->setCurrentItem (index); + } + + if (m_filterList->currentItem () == -1) + m_filterList->setCurrentItem (0); + + m_filterList->centerCurrentItem (); + m_filterList->setFocus (); + + connect (m_filterList, SIGNAL (selected (int)), this, SLOT (slotOk ())); +} + +KoFilterChooser::~KoFilterChooser () +{ +} + +QString KoFilterChooser::filterSelected () +{ + const int item = m_filterList->currentItem (); + + if (item > -1) + return m_mimeTypes [item]; + else + return QString::null; +} + + +// static cache for filter availability +QMap<QString, bool> KoFilterManager::m_filterAvailable; + +const int KoFilterManager::s_area = 30500; + + +KoFilterManager::KoFilterManager( KoDocument* document ) : + m_document( document ), m_parentChain( 0 ), m_graph( "" ), d( 0 ) +{ + d = new KoFilterManager::Private; + d -> m_batch = false; + if ( document ) + QObject::connect( this, SIGNAL( sigProgress( int ) ), + document, SIGNAL( sigProgress( int ) ) ); +} + + +KoFilterManager::KoFilterManager( const QString& url, const QCString& mimetypeHint, + KoFilterChain* const parentChain ) : + m_document( 0 ), m_parentChain( parentChain ), m_importUrl( url ), m_importUrlMimetypeHint( mimetypeHint ), + m_graph( "" ), d( 0 ) +{ + d = new KoFilterManager::Private; + d -> m_batch = false; +} + +KoFilterManager::~KoFilterManager() +{ + delete d; +} + +QString KoFilterManager::import( const QString& url, KoFilter::ConversionStatus& status ) +{ + // Find the mime type for the file to be imported. + KURL u; + u.setPath( url ); + KMimeType::Ptr t = KMimeType::findByURL( u, 0, true ); + + m_graph.setSourceMimeType( t->name().latin1() ); // .latin1() is okay here (Werner) + if ( !m_graph.isValid() ) { + bool userCancelled = false; + + kdWarning(s_area) << "Can't open " << t->name () << ", trying filter chooser" << endl; + if ( m_document ) + { + if ( !m_document->isAutoErrorHandlingEnabled() ) + { + status = KoFilter::BadConversionGraph; + return QString::null; + } + QCString nativeFormat = m_document->nativeFormatMimeType (); + + QApplication::setOverrideCursor( arrowCursor ); + KoFilterChooser chooser(0, + KoFilterManager::mimeFilter (nativeFormat, KoFilterManager::Import, m_document->extraNativeMimeTypes()), + nativeFormat); + if (chooser.exec ()) + { + QCString f = chooser.filterSelected ().latin1(); + + if (f == nativeFormat) + { + status = KoFilter::OK; + QApplication::restoreOverrideCursor(); + return url; + } + + m_graph.setSourceMimeType (f); + } + else + userCancelled = true; + QApplication::restoreOverrideCursor(); + } + + if (!m_graph.isValid()) + { + kdError(s_area) << "Couldn't create a valid graph for this source mimetype: " + << t->name() << endl; + importErrorHelper( t->name(), userCancelled ); + status = KoFilter::BadConversionGraph; + return QString::null; + } + } + + KoFilterChain::Ptr chain( 0 ); + // Are we owned by a KoDocument? + if ( m_document ) { + QCString mimeType = m_document->nativeFormatMimeType(); + QStringList extraMimes = m_document->extraNativeMimeTypes(); + int i=0, n = extraMimes.count(); + chain = m_graph.chain( this, mimeType ); + while( !chain && i<n) { + mimeType = extraMimes[i].utf8(); + chain = m_graph.chain( this, mimeType ); + ++i; + } + } + else { + kdError(s_area) << "You aren't supposed to use import() from a filter!" << endl; + status = KoFilter::UsageError; + return QString::null; + } + + if ( !chain ) { + kdError(s_area) << "Couldn't create a valid filter chain!" << endl; + importErrorHelper( t->name() ); + status = KoFilter::BadConversionGraph; + return QString::null; + } + + // Okay, let's invoke the filters one after the other + m_direction = Import; // vital information! + m_importUrl = url; // We want to load that file + m_exportUrl = QString::null; // This is null for sure, as embedded stuff isn't + // allowed to use that method + status = chain->invokeChain(); + + m_importUrl = QString::null; // Reset the import URL + + if ( status == KoFilter::OK ) + return chain->chainOutput(); + return QString::null; +} + +KoFilter::ConversionStatus KoFilterManager::exp0rt( const QString& url, QCString& mimeType ) +{ + bool userCancelled = false; + + // The import url should already be set correctly (null if we have a KoDocument + // file manager and to the correct URL if we have an embedded manager) + m_direction = Export; // vital information! + m_exportUrl = url; + + KoFilterChain::Ptr chain = 0; + if ( m_document ) { + // We have to pick the right native mimetype as source. + QStringList nativeMimeTypes; + nativeMimeTypes.append( m_document->nativeFormatMimeType() ); + nativeMimeTypes += m_document->extraNativeMimeTypes(); + QStringList::ConstIterator it = nativeMimeTypes.begin(); + const QStringList::ConstIterator end = nativeMimeTypes.end(); + for ( ; !chain && it != end; ++it ) + { + m_graph.setSourceMimeType( (*it).latin1() ); + if ( m_graph.isValid() ) + chain = m_graph.chain( this, mimeType ); + } + } + else if ( !m_importUrlMimetypeHint.isEmpty() ) { + kdDebug(s_area) << "Using the mimetype hint: '" << m_importUrlMimetypeHint << "'" << endl; + m_graph.setSourceMimeType( m_importUrlMimetypeHint ); + } + else { + KURL u; + u.setPath( m_importUrl ); + KMimeType::Ptr t = KMimeType::findByURL( u, 0, true ); + if ( t->name() == KMimeType::defaultMimeType() ) { + kdError(s_area) << "No mimetype found for " << m_importUrl << endl; + return KoFilter::BadMimeType; + } + m_graph.setSourceMimeType( t->name().latin1() ); + + if ( !m_graph.isValid() ) { + kdWarning(s_area) << "Can't open " << t->name () << ", trying filter chooser" << endl; + + QApplication::setOverrideCursor( arrowCursor ); + KoFilterChooser chooser(0, KoFilterManager::mimeFilter ()); + if (chooser.exec ()) + m_graph.setSourceMimeType (chooser.filterSelected ().latin1 ()); + else + userCancelled = true; + + QApplication::restoreOverrideCursor(); + } + } + + if (!m_graph.isValid ()) + { + kdError(s_area) << "Couldn't create a valid graph for this source mimetype." << endl; + if (!userCancelled) KMessageBox::error( 0L, i18n("Could not export file."), i18n("Missing Export Filter") ); + return KoFilter::BadConversionGraph; + } + + if ( !chain ) // already set when coming from the m_document case + chain = m_graph.chain( this, mimeType ); + + if ( !chain ) { + kdError(s_area) << "Couldn't create a valid filter chain to " << mimeType << " !" << endl; + KMessageBox::error( 0L, i18n("Could not export file."), i18n("Missing Export Filter") ); + return KoFilter::BadConversionGraph; + } + + return chain->invokeChain(); +} + +namespace // in order not to mess with the global namespace ;) +{ + // This class is needed only for the static mimeFilter method + class Vertex + { + public: + Vertex( const QCString& mimeType ) : m_color( White ), m_mimeType( mimeType ) {} + + enum Color { White, Gray, Black }; + Color color() const { return m_color; } + void setColor( Color color ) { m_color = color; } + + QCString mimeType() const { return m_mimeType; } + + void addEdge( Vertex* vertex ) { if ( vertex ) m_edges.append( vertex ); } + QPtrList<Vertex> edges() const { return m_edges; } + + private: + Color m_color; + QCString m_mimeType; + QPtrList<Vertex> m_edges; + }; + + // Some helper methods for the static stuff + // This method builds up the graph in the passed ascii dict + void buildGraph( QAsciiDict<Vertex>& vertices, KoFilterManager::Direction direction ) + { + QStringList stopList; // Lists of mimetypes that are considered end of chains + stopList << "text/plain"; + stopList << "text/csv"; + stopList << "text/x-tex"; + stopList << "text/html"; + + vertices.setAutoDelete( true ); + + // partly copied from build graph, but I don't see any other + // way without crude hacks, as we have to obey the direction here + QValueList<KoDocumentEntry> parts( KoDocumentEntry::query(false, QString::null) ); + QValueList<KoDocumentEntry>::ConstIterator partIt( parts.begin() ); + QValueList<KoDocumentEntry>::ConstIterator partEnd( parts.end() ); + + while ( partIt != partEnd ) { + QStringList nativeMimeTypes = ( *partIt ).service()->property( "X-KDE-ExtraNativeMimeTypes" ).toStringList(); + nativeMimeTypes += ( *partIt ).service()->property( "X-KDE-NativeMimeType" ).toString(); + QStringList::ConstIterator it = nativeMimeTypes.begin(); + const QStringList::ConstIterator end = nativeMimeTypes.end(); + for ( ; it != end; ++it ) + if ( !(*it).isEmpty() ) + vertices.insert( (*it).latin1(), new Vertex( (*it).latin1() ) ); + ++partIt; + } + + QValueList<KoFilterEntry::Ptr> filters = KoFilterEntry::query(); // no constraint here - we want *all* :) + QValueList<KoFilterEntry::Ptr>::ConstIterator it = filters.begin(); + const QValueList<KoFilterEntry::Ptr>::ConstIterator end = filters.end(); + + for ( ; it != end; ++it ) { + + QStringList impList; // Import list + QStringList expList; // Export list + + const QStringList::Iterator stopEnd = stopList.end(); + // Now we have to exclude the "stop" mimetypes (in the right direction!) + if ( direction == KoFilterManager::Import ) { + // Import: "stop" mime type should not appear in export + QStringList::ConstIterator testIt = ( *it )->export_.begin(); + QStringList::ConstIterator testEnd = ( *it )->export_.end(); + for ( ; testIt != testEnd ; ++testIt ) { + if ( stopList.find( *testIt ) == stopEnd ) { + expList.append( *testIt ); + } + } + impList = ( *it )->import; + } + else { + // Export: "stop" mime type should not appear in import + QStringList::ConstIterator testIt = ( *it )->import.begin(); + const QStringList::ConstIterator testEnd = ( *it )->import.end(); + for ( ; testIt != testEnd ; ++testIt ) { + if ( stopList.find( *testIt ) == stopEnd ) { + impList.append( *testIt ); + } + } + expList = ( *it )->export_; + } + + if ( impList.empty() || expList.empty() ) + { + // This filter cannot be used under these conditions + kdDebug( 30500 ) << "Filter: " << ( *it )->service()->name() << " ruled out" << endl; + continue; + } + + // First add the "starting points" to the dict + QStringList::ConstIterator importIt = impList.begin(); + const QStringList::ConstIterator importEnd = impList.end(); + for ( ; importIt != importEnd; ++importIt ) { + const QCString key = ( *importIt ).latin1(); // latin1 is okay here (werner) + // already there? + if ( !vertices[ key ] ) + vertices.insert( key, new Vertex( key ) ); + } + + // Are we allowed to use this filter at all? + if ( KoFilterManager::filterAvailable( *it ) ) { + QStringList::ConstIterator exportIt = expList.begin(); + const QStringList::ConstIterator exportEnd = expList.end(); + + for ( ; exportIt != exportEnd; ++exportIt ) { + // First make sure the export vertex is in place + const QCString key = ( *exportIt ).latin1(); // latin1 is okay here + Vertex* exp = vertices[ key ]; + if ( !exp ) { + exp = new Vertex( key ); + vertices.insert( key, exp ); + } + // Then create the appropriate edges depending on the + // direction (import/export) + // This is the chunk of code which actually differs from the + // graph stuff (apart from the different vertex class) + importIt = impList.begin(); // ### TODO: why only the first one? + if ( direction == KoFilterManager::Import ) { + for ( ; importIt != importEnd; ++importIt ) + exp->addEdge( vertices[ ( *importIt ).latin1() ] ); + } else { + for ( ; importIt != importEnd; ++importIt ) + vertices[ ( *importIt ).latin1() ]->addEdge( exp ); + } + } + } + else + kdDebug( 30500 ) << "Filter: " << ( *it )->service()->name() << " does not apply." << endl; + } + } + + // This method runs a BFS on the graph to determine the connected + // nodes. Make sure that the graph is "cleared" (the colors of the + // nodes are all white) + QStringList connected( const QAsciiDict<Vertex>& vertices, const QCString& mimetype ) + { + if ( mimetype.isEmpty() ) + return QStringList(); + Vertex *v = vertices[ mimetype ]; + if ( !v ) + return QStringList(); + + v->setColor( Vertex::Gray ); + std::queue<Vertex*> queue; + queue.push( v ); + QStringList connected; + + while ( !queue.empty() ) { + v = queue.front(); + queue.pop(); + QPtrList<Vertex> edges = v->edges(); + QPtrListIterator<Vertex> it( edges ); + for ( ; it.current(); ++it ) { + if ( it.current()->color() == Vertex::White ) { + it.current()->setColor( Vertex::Gray ); + queue.push( it.current() ); + } + } + v->setColor( Vertex::Black ); + connected.append( v->mimeType() ); + } + return connected; + } +} + +// The static method to figure out to which parts of the +// graph this mimetype has a connection to. +QStringList KoFilterManager::mimeFilter( const QCString& mimetype, Direction direction, const QStringList& extraNativeMimeTypes ) +{ + //kdDebug(s_area) << "mimetype=" << mimetype << " extraNativeMimeTypes=" << extraNativeMimeTypes << endl; + QAsciiDict<Vertex> vertices; + buildGraph( vertices, direction ); + + // TODO maybe use the fake vertex trick from the method below, to make the search faster? + + QStringList nativeMimeTypes; + nativeMimeTypes.append( QString::fromLatin1( mimetype ) ); + nativeMimeTypes += extraNativeMimeTypes; + + // Add the native mimetypes first so that they are on top. + QStringList lst = nativeMimeTypes; + + // Now look for filters which output each of those natives mimetypes + for( QStringList::ConstIterator natit = nativeMimeTypes.begin(); natit != nativeMimeTypes.end(); ++natit ) { + const QStringList outMimes = connected( vertices, (*natit).latin1() ); + //kdDebug(s_area) << k_funcinfo << "output formats connected to mime " << *natit << " : " << outMimes << endl; + for ( QStringList::ConstIterator mit = outMimes.begin(); mit != outMimes.end(); ++mit ) + if ( lst.find( *mit ) == lst.end() ) // append only if not there already. Qt4: QSet<QString>? + lst.append( *mit ); + } + return lst; +} + +QStringList KoFilterManager::mimeFilter() +{ + QAsciiDict<Vertex> vertices; + buildGraph( vertices, KoFilterManager::Import ); + + QValueList<KoDocumentEntry> parts( KoDocumentEntry::query(false, QString::null) ); + QValueList<KoDocumentEntry>::ConstIterator partIt( parts.begin() ); + QValueList<KoDocumentEntry>::ConstIterator partEnd( parts.end() ); + + if ( partIt == partEnd ) + return QStringList(); + + // To find *all* reachable mimetypes, we have to resort to + // a small hat trick, in order to avoid multiple searches: + // We introduce a fake vertex, which is connected to every + // single KOffice mimetype. Due to that one BFS is enough :) + // Now we just need an... ehrm.. unique name for our fake mimetype + Vertex *v = new Vertex( "supercalifragilistic/x-pialadocious" ); + vertices.insert( "supercalifragilistic/x-pialadocious", v ); + while ( partIt != partEnd ) { + QStringList nativeMimeTypes = ( *partIt ).service()->property( "X-KDE-ExtraNativeMimeTypes" ).toStringList(); + nativeMimeTypes += ( *partIt ).service()->property( "X-KDE-NativeMimeType" ).toString(); + QStringList::ConstIterator it = nativeMimeTypes.begin(); + const QStringList::ConstIterator end = nativeMimeTypes.end(); + for ( ; it != end; ++it ) + if ( !(*it).isEmpty() ) + v->addEdge( vertices[ (*it).latin1() ] ); + ++partIt; + } + QStringList result = connected( vertices, "supercalifragilistic/x-pialadocious" ); + + // Finally we have to get rid of our fake mimetype again + result.remove( "supercalifragilistic/x-pialadocious" ); + return result; +} + +// Here we check whether the filter is available. This stuff is quite slow, +// but I don't see any other convenient (for the user) way out :} +bool KoFilterManager::filterAvailable( KoFilterEntry::Ptr entry ) +{ + if ( !entry ) + return false; + if ( entry->available != "check" ) + return true; + + //kdDebug( 30500 ) << "Checking whether " << entry->service()->name() << " applies." << endl; + // generate some "unique" key + QString key( entry->service()->name() ); + key += " - "; + key += entry->service()->library(); + + if ( !m_filterAvailable.contains( key ) ) { + //kdDebug( 30500 ) << "Not cached, checking..." << endl; + + KLibrary* library = KLibLoader::self()->library( QFile::encodeName( entry->service()->library() ) ); + if ( !library ) { + kdWarning( 30500 ) << "Huh?? Couldn't load the lib: " + << KLibLoader::self()->lastErrorMessage() << endl; + m_filterAvailable[ key ] = false; + return false; + } + + // This code is "borrowed" from klibloader ;) + QCString symname; + symname.sprintf("check_%s", library->name().latin1() ); + void* sym = library->symbol( symname ); + if ( !sym ) + { + kdWarning( 30500 ) << "The library " << library->name() + << " does not offer a check_" << library->name() + << " function." << endl; + m_filterAvailable[ key ] = false; + } + else { + typedef int (*t_func)(); + t_func check = (t_func)sym; + m_filterAvailable[ key ] = check() == 1; + } + } + return m_filterAvailable[ key ]; +} + +void KoFilterManager::importErrorHelper( const QString& mimeType, const bool suppressDialog ) +{ + QString tmp = i18n("Could not import file of type\n%1").arg( mimeType ); + // ###### FIXME: use KLibLoader::lastErrorMessage() here + if (!suppressDialog) KMessageBox::error( 0L, tmp, i18n("Missing Import Filter") ); +} + +void KoFilterManager::setBatchMode( const bool batch ) +{ + d->m_batch = batch; +} + +bool KoFilterManager::getBatchMode( void ) const +{ + return d->m_batch; +} + +#include <KoFilterManager.moc> +#include <KoFilterManager_p.moc> diff --git a/lib/kofficecore/KoFilterManager.h b/lib/kofficecore/KoFilterManager.h new file mode 100644 index 00000000..952c8475 --- /dev/null +++ b/lib/kofficecore/KoFilterManager.h @@ -0,0 +1,178 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> + 2000, 2001 Werner Trobin <trobin@kde.org> + Copyright (C) 2004 Nicolas Goutte <goutte@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 __koffice_filter_manager_h__ +#define __koffice_filter_manager_h__ + +#include <qobject.h> +#include <qmap.h> +#include <KoFilterChain.h> +#include <koffice_export.h> +class KoDocument; + +/** + * @brief The class managing all the filters. + * + * This class manages all filters for a %KOffice application. Normally + * you will not have to use it, since KoMainWindow takes care of loading + * and saving documents. + * + * @ref KoFilter + * + * @author Kalle Dalheimer <kalle@kde.org> + * @author Torben Weis <weis@kde.org> + * @author Werner Trobin <trobin@kde.org> + */ +class KOFFICECORE_EXPORT KoFilterManager : public QObject +{ + Q_OBJECT +public: + /** + * This enum is used to distinguish the import/export cases + */ + enum Direction { Import = 1, Export = 2 }; + + /** + * Create a filter manager for a document + */ + KoFilterManager( KoDocument* document ); + /** + * Create a filter manager for a filter which wants to embed something. + * The url it passes is the file to convert, obviously. You cannot use + * the @ref import() method -- use @ref exp0rt() to convert the file to + * the destination mimetype you prefer. + * + * @param url The file you want to export + * @param mimetypeHint The mimetype of the file you want to export. You have + * to specify this information only if the automatic detection will + * fail because e.g. you saved an embedded stream to a *.tmp file. + * Most likely you do not have to care about that. + * @param parentChain The parent filter chain of this filter manager. Used + * to allow embedding for filters. Most likely you do not have to care. + */ + KoFilterManager( const QString& url, const QCString& mimetypeHint = "", + KoFilterChain* const parentChain = 0 ); + + virtual ~KoFilterManager(); + + /** + * Imports the passed URL and returns the resultant filename + * (most likely some file in /tmp). + * The @p status variable signals the success/error of the conversion + * If the QString which is returned isEmpty() and the status is OK, + * then we imported the file directly into the document. + */ + QString import( const QString& url, KoFilter::ConversionStatus& status ); + /** + * @brief Exports the given file/document to the specified URL/mimetype. + * + * If @p mimeType is empty, then the closest matching KOffice part is searched + * and when the method returns @p mimeType contains this mimetype. + * Oh, well, export is a C++ keyword ;) + */ + KoFilter::ConversionStatus exp0rt( const QString& url, QCString& mimeType ); + + + + ///@name Static API + //@{ + /** + * Suitable for passing to KFileDialog::setMimeFilter. The default mime + * gets set by the "users" of this method, as we do not have enough + * information here. + * Optionally, @p extraNativeMimeTypes are added after the native mimetype. + */ + KOFFICECORE_EXPORT static QStringList mimeFilter( const QCString& mimetype, Direction direction, + const QStringList& extraNativeMimeTypes = QStringList() ); + + /** + * The same method as KoFilterManager::mimeFilter but suited for KoShell. + * We do not need the mimetype, as we will simply use all available + * %KOffice mimetypes. The Direction enum is omitted, as we only + * call this for importing. When saving from KoShell we already + * know the KOffice part we are using. + */ + static QStringList mimeFilter(); + + /** + * Method used to check if that filter is available at all. + * @note Slow, but cached + */ + static bool filterAvailable( KoFilterEntry::Ptr entry ); + + //@} + + /** + * Set the filter manager is batch mode (no dialog shown) + * instead of the interactive mode (dialog shown) + * @since 1.4 + */ + void setBatchMode ( const bool batch ); + + /** + * Get if the filter manager is batch mode (true) + * or in interactive mode (true) + * @since 1.4 + */ + bool getBatchMode ( void ) const; + +signals: + void sigProgress( int ); + +private: + // === API for KoFilterChains === (internal) + // The friend methods are private in KoFilterChain and + // just forward calls to the methods here. Should be + // pretty save. + friend QString KoFilterChain::filterManagerImportFile() const; + QString importFile() const { return m_importUrl; } + friend QString KoFilterChain::filterManagerExportFile() const; + QString exportFile() const { return m_exportUrl; } + friend KoDocument* KoFilterChain::filterManagerKoDocument() const; + KoDocument* document() const { return m_document; } + friend int KoFilterChain::filterManagerDirection() const; + int direction() const { return static_cast<int>( m_direction ); } + friend KoFilterChain* const KoFilterChain::filterManagerParentChain() const; + KoFilterChain* const parentChain() const { return m_parentChain; } + + // Private API + KoFilterManager( const KoFilterManager& rhs ); + KoFilterManager &operator=( const KoFilterManager& rhs ); + + void importErrorHelper( const QString& mimeType, const bool suppressDialog = false ); + + static const int s_area; + + KoDocument* m_document; + KoFilterChain* const m_parentChain; + QString m_importUrl, m_exportUrl; + QCString m_importUrlMimetypeHint; ///< suggested mimetype + KOffice::Graph m_graph; + Direction m_direction; + + /// A static cache for the availability checks of filters + static QMap<QString, bool> m_filterAvailable; + + class Private; + Private *d; +}; + +#endif // __koffice_filter_manager_h__ diff --git a/lib/kofficecore/KoFilterManager_p.h b/lib/kofficecore/KoFilterManager_p.h new file mode 100644 index 00000000..20c6cac8 --- /dev/null +++ b/lib/kofficecore/KoFilterManager_p.h @@ -0,0 +1,46 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Clarence Dang <dang@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 Version 2 for more details. + + You should have received a copy of the GNU Library General Public License + Version 2 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 __koFilterManager_p_h__ +#define __koFilterManager_p_h__ + +#include <qstring.h> +#include <qstringlist.h> + +#include <kdialogbase.h> + +class KListBox; + + +class KoFilterChooser : public KDialogBase +{ +Q_OBJECT + +public: + KoFilterChooser (QWidget *parent, const QStringList &mimeTypes, + const QString &nativeFormat = QString::null); + ~KoFilterChooser (); + + QString filterSelected (); + +private: + QStringList m_mimeTypes; + KListBox *m_filterList; +}; + +#endif diff --git a/lib/kofficecore/KoFrame.cpp b/lib/kofficecore/KoFrame.cpp new file mode 100644 index 00000000..4c07d5c1 --- /dev/null +++ b/lib/kofficecore/KoFrame.cpp @@ -0,0 +1,393 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999, 2000 Torben Weis <weis@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 <KoFrame.h> +#include <KoView.h> + +#include <qpainter.h> +#include <kparts/event.h> +#include <kcursor.h> +#include <kdebug.h> + +#define QMAX32767(a,b) QMAX(QMAX(32767,a),b) + +class KoFramePrivate +{ +public: + KoFramePrivate() + { + } + ~KoFramePrivate() + { + } + + KoView *m_view; + + QPoint m_mousePressPos; + QPoint m_framePos; + int m_width; + int m_height; + int m_mode; + + KoFrame::State m_state; +}; + +KoFrame::KoFrame( QWidget *parent, const char *name ) + : QWidget( parent, name ) +{ + d = new KoFramePrivate; + d->m_state = Inactive; + d->m_mode = -1; + d->m_view = 0; + + setBackgroundColor( white ); + setMouseTracking( true ); +} + +KoFrame::~KoFrame() +{ + delete d; +} + +void KoFrame::setView( KoView *view ) +{ + if ( view == d->m_view ) + return; + + if ( d->m_view ) + d->m_view->removeEventFilter( this ); + + d->m_view = view; + if ( d->m_view ) + d->m_view->installEventFilter( this ); + + resizeEvent( 0L ); +} + +KoView *KoFrame::view() const +{ + return d->m_view; +} + +void KoFrame::setState( State s ) +{ + if ( d->m_state == s ) + return; + + State old = d->m_state; + d->m_state = s; + + if ( d->m_view ) + { + /* + kdDebug(30003) << "KoFrame::setView setMaximumSize " + << "QMAX32767(" << d->m_view->maximumWidth() + 2 * border() << "," << d->m_view->maximumWidth() << "), " + << "QMAX32767(" << d->m_view->maximumHeight() + 2 * border() << "," << d->m_view->maximumHeight() << ")" + << endl; + */ + setMaximumSize( QMAX32767( d->m_view->maximumWidth() + 2 * border(), d->m_view->maximumWidth() ), + QMAX32767( d->m_view->maximumHeight() + 2 * border(), d->m_view->maximumHeight() ) ); + setMinimumSize( d->m_view->minimumWidth() + leftBorder() + rightBorder(), + d->m_view->minimumHeight() + topBorder() + bottomBorder() ); + } + + if ( d->m_state == Inactive ) + { + d->m_state = old; + int l = leftBorder(); + int r = rightBorder(); + int t = topBorder(); + int b = bottomBorder(); + d->m_state = Inactive; + setGeometry( x() + l, y() + t, width() - l - r, height() - t - b ); + } + else if ( ( d->m_state == Active || d->m_state == Selected ) && old == Inactive ) + setGeometry( x() - leftBorder(), y() - topBorder(), + width() + leftBorder() + rightBorder(), + height() + topBorder() + bottomBorder() ); + else if ( d->m_state == Active && old == Selected ) + { + setGeometry( x() - leftBorder() + border(), y() - topBorder() + border(), + width() + leftBorder() + rightBorder() - 2 * border(), + height() + topBorder() + bottomBorder() - 2 * border() ); + } + + update(); +} + +KoFrame::State KoFrame::state() const +{ + return d->m_state; +} + +int KoFrame::leftBorder() const +{ + if ( d->m_state == Inactive ) + return 0; + if ( d->m_state == Selected || !d->m_view ) + return border(); + + return d->m_view->leftBorder() + border(); +} + +int KoFrame::rightBorder() const +{ + if ( d->m_state == Inactive ) + return 0; + if ( d->m_state == Selected || !d->m_view ) + return border(); + + return d->m_view->rightBorder() + border(); +} + +int KoFrame::topBorder() const +{ + if ( d->m_state == Inactive ) + return 0; + if ( d->m_state == Selected || !d->m_view ) + return border(); + + return d->m_view->topBorder() + border(); +} + +int KoFrame::bottomBorder() const +{ + if ( d->m_state == Inactive ) + return 0; + if ( d->m_state == Selected || !d->m_view ) + return border(); + + return d->m_view->bottomBorder() + border(); +} + +int KoFrame::border() const +{ + if ( d->m_state == Inactive ) + return 0; + return 5; +} + +void KoFrame::paintEvent( QPaintEvent* ) +{ + QPainter painter; + painter.begin( this ); + + painter.setPen( black ); + painter.fillRect( 0, 0, width(), height(), BDiagPattern ); + + if ( d->m_state == Selected ) + { + painter.fillRect( 0, 0, 5, 5, black ); + painter.fillRect( 0, height() - 5, 5, 5, black ); + painter.fillRect( width() - 5, height() - 5, 5, 5, black ); + painter.fillRect( width() - 5, 0, 5, 5, black ); + painter.fillRect( width() / 2 - 3, 0, 5, 5, black ); + painter.fillRect( width() / 2 - 3, height() - 5, 5, 5, black ); + painter.fillRect( 0, height() / 2 - 3, 5, 5, black ); + painter.fillRect( width() - 5, height() / 2 - 3, 5, 5, black ); + } + + painter.end(); +} + +void KoFrame::mousePressEvent( QMouseEvent* ev ) +{ + QRect r1( 0, 0, 5, 5 ); + QRect r2( 0, height() - 5, 5, 5 ); + QRect r3( width() - 5, height() - 5, 5, 5 ); + QRect r4( width() - 5, 0, 5, 5 ); + QRect r5( width() / 2 - 3, 0, 5, 5 ); + QRect r6( width() / 2 - 3, height() - 5, 5, 5 ); + QRect r7( 0, height() / 2 - 3, 5, 5 ); + QRect r8( width()- 5, height() / 2 - 3, 5, 5 ); + + if ( r1.contains( ev->pos() ) ) + d->m_mode = 1; + else if ( r2.contains( ev->pos() ) ) + d->m_mode = 2; + else if ( r3.contains( ev->pos() ) ) + d->m_mode = 3; + else if ( r4.contains( ev->pos() ) ) + d->m_mode = 4; + else if ( r5.contains( ev->pos() ) ) + d->m_mode = 5; + else if ( r6.contains( ev->pos() ) ) + d->m_mode = 6; + else if ( r7.contains( ev->pos() ) ) + d->m_mode = 7; + else if ( r8.contains( ev->pos() ) ) + d->m_mode = 8; + else + d->m_mode = 0; + + // if ( d->m_state == Active ) + // d->m_mode = 0; + + kdDebug(30003) << "---- MODE=" << d->m_mode << endl; + + d->m_mousePressPos = mapToParent( ev->pos() ); + d->m_framePos = mapToParent( QPoint( 0, 0 ) ); + d->m_width = width(); + d->m_height = height(); +} + +void KoFrame::mouseMoveEvent( QMouseEvent* ev ) +{ + if ( d->m_mode == 0 ) + { + QPoint p = parentWidget()->mapFromGlobal( ev->globalPos() ); + move( QPoint( p.x() + d->m_framePos.x() - d->m_mousePressPos.x(), + p.y() + d->m_framePos.y() - d->m_mousePressPos.y() ) ); + // The other modes change the geometry so they call resizeEvent. + // This one doesn't, so it has to emit geometryChangedby itself. + emit geometryChanged(); + //kdDebug() << "KoFrame::mouseMoveEvent koFrame position is " << x() << "," << y() << endl; + } + else if ( d->m_mode == 1 ) + { + QPoint p = parentWidget()->mapFromGlobal( ev->globalPos() ); + int w = QMIN( QMAX( d->m_width + d->m_mousePressPos.x() - p.x(), minimumWidth() ), maximumWidth() ); + int h = QMIN( QMAX( d->m_height + d->m_mousePressPos.y() - p.y(), minimumHeight() ), maximumHeight() ); + setGeometry( d->m_framePos.x() - w + d->m_width, + d->m_framePos.y() - h + d->m_height, w, h ); + } + else if ( d->m_mode == 2 ) + { + QPoint p = parentWidget()->mapFromGlobal( ev->globalPos() ); + int w = QMIN( QMAX( d->m_width + d->m_mousePressPos.x() - p.x(), minimumWidth() ), maximumWidth() ); + int h = QMIN( QMAX( d->m_height - d->m_mousePressPos.y() + p.y(), minimumHeight() ), maximumHeight() ); + setGeometry( d->m_framePos.x() - w + d->m_width, + d->m_framePos.y(), w, h ); + } + else if ( d->m_mode == 3 ) + { + QPoint p = parentWidget()->mapFromGlobal( ev->globalPos() ); + int w = QMIN( QMAX( d->m_width - d->m_mousePressPos.x() + p.x(), minimumWidth() ), maximumWidth() ); + int h = QMIN( QMAX( d->m_height - d->m_mousePressPos.y() + p.y(), minimumHeight() ), maximumHeight() ); + resize( w, h ); + } + else if ( d->m_mode == 4 ) + { + QPoint p = parentWidget()->mapFromGlobal( ev->globalPos() ); + int w = QMIN( QMAX( d->m_width - d->m_mousePressPos.x() + p.x(), minimumWidth() ), maximumWidth() ); + int h = QMIN( QMAX( d->m_height + d->m_mousePressPos.y() - p.y(), minimumHeight() ), maximumHeight() ); + setGeometry( d->m_framePos.x(), + d->m_framePos.y() - h + d->m_height, w, h ); + } + else if ( d->m_mode == 5 ) + { + QPoint p = parentWidget()->mapFromGlobal( ev->globalPos() ); + int h = QMIN( QMAX( d->m_height + d->m_mousePressPos.y() - p.y(), minimumHeight() ), maximumHeight() ); + setGeometry( d->m_framePos.x(), + d->m_framePos.y() - h + d->m_height, d->m_width, h ); + } + else if ( d->m_mode == 6 ) + { + QPoint p = parentWidget()->mapFromGlobal( ev->globalPos() ); + int h = QMIN( QMAX( d->m_height - d->m_mousePressPos.y() + p.y(), minimumHeight() ), maximumHeight() ); + resize( d->m_width, h ); + } + else if ( d->m_mode == 7 ) + { + QPoint p = parentWidget()->mapFromGlobal( ev->globalPos() ); + int w = QMIN( QMAX( d->m_width + d->m_mousePressPos.x() - p.x(), minimumWidth() ), maximumWidth() ); + setGeometry( d->m_framePos.x() - w + d->m_width, + d->m_framePos.y(), w, d->m_height ); + } + else if ( d->m_mode == 8 ) + { + QPoint p = parentWidget()->mapFromGlobal( ev->globalPos() ); + int w = QMIN( QMAX( d->m_width - d->m_mousePressPos.x() + p.x(), minimumWidth() ), maximumWidth() ); + resize( w, d->m_height ); + } + else if ( d->m_state == Selected || d->m_state == Active ) + { + QRect r1( 0, 0, 5, 5 ); + QRect r2( 0, height() - 5, 5, 5 ); + QRect r3( width() - 5, height() - 5, 5, 5 ); + QRect r4( width() - 5, 0, 5, 5 ); + QRect r5( width() / 2 - 3, 0, 5, 5 ); + QRect r6( width() / 2 - 3, height() - 5, 5, 5 ); + QRect r7( 0, height() / 2 - 3, 5, 5 ); + QRect r8( width()- 5, height() / 2 - 3, 5, 5 ); + + if ( r1.contains( ev->pos() ) || r3.contains( ev->pos() ) ) + setCursor( sizeFDiagCursor ); + else if ( r2.contains( ev->pos() ) || r4.contains( ev->pos() ) ) + setCursor( sizeBDiagCursor ); + else if ( r5.contains( ev->pos() ) || r6.contains( ev->pos() ) ) + setCursor( sizeVerCursor ); + else if ( r7.contains( ev->pos() ) || r8.contains( ev->pos() ) ) + setCursor( sizeHorCursor ); + else + setCursor( KCursor::handCursor() ); + } + else + setCursor( KCursor::handCursor() ); +} + +void KoFrame::mouseReleaseEvent( QMouseEvent* ) +{ + d->m_mode = -1; +} + +void KoFrame::resizeEvent( QResizeEvent* ) +{ + if ( !d->m_view ) + return; + + if ( d->m_state == Active || d->m_state == Selected ) + d->m_view->setGeometry( 5, 5, width() - 10, height() - 10 ); + else + d->m_view->setGeometry( 0, 0, width(), height() ); + + emit geometryChanged(); +} + +bool KoFrame::eventFilter( QObject* obj, QEvent* ev ) +{ + if ( obj == d->m_view && KParts::PartActivateEvent::test( ev ) ) + { + kdDebug(30003) << "Activate event"<< endl; + KParts::PartActivateEvent* e = (KParts::PartActivateEvent*)ev; + if ( e->part() == (KParts::Part *)d->m_view->koDocument() ) + { + if ( e->activated() ) + setState( Active ); + else + setState( Inactive ); + } + } + else if ( obj == d->m_view && KParts::PartSelectEvent::test( ev ) ) + { + kdDebug(30003) << "Selected event" << endl; + KParts::PartSelectEvent* e = (KParts::PartSelectEvent*)ev; + if ( e->part() == (KParts::Part *)d->m_view->koDocument() ) + { + if ( e->selected() ) + setState( Selected ); + else + setState( Inactive ); + } + } + + return false; +} + +#include <KoFrame.moc> diff --git a/lib/kofficecore/KoFrame.h b/lib/kofficecore/KoFrame.h new file mode 100644 index 00000000..7f729bdb --- /dev/null +++ b/lib/kofficecore/KoFrame.h @@ -0,0 +1,65 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999, 2000 Torben Weis <weis@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 __koFrame_h__ +#define __koFrame_h__ + +#include <qwidget.h> + +class KoView; +class KoFramePrivate; + +class KoFrame : public QWidget +{ + Q_OBJECT +public: + enum State { Inactive, Selected, Active }; + + KoFrame( QWidget *parent, const char *name = 0 ); + virtual ~KoFrame(); + + virtual void setView( KoView *view ); + virtual KoView *view() const; + + virtual void setState( State s ); + virtual State state() const; + + virtual int leftBorder() const; + virtual int rightBorder() const; + virtual int topBorder() const; + virtual int bottomBorder() const; + + virtual int border() const; + +signals: + void geometryChanged(); + +protected: + virtual void paintEvent( QPaintEvent* ); + virtual void mousePressEvent( QMouseEvent* ); + virtual void mouseMoveEvent( QMouseEvent* ); + virtual void mouseReleaseEvent( QMouseEvent* ); + virtual void resizeEvent( QResizeEvent* ); + virtual bool eventFilter( QObject*, QEvent* ); + +private: + KoFramePrivate *d; +}; + +#endif diff --git a/lib/kofficecore/KoGenStyles.cpp b/lib/kofficecore/KoGenStyles.cpp new file mode 100644 index 00000000..503b6802 --- /dev/null +++ b/lib/kofficecore/KoGenStyles.cpp @@ -0,0 +1,405 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2006 David Faure <faure@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 "KoGenStyles.h" +#include <KoXmlWriter.h> +#include <float.h> +#include <kdebug.h> + +KoGenStyles::KoGenStyles() +{ +} + +KoGenStyles::~KoGenStyles() +{ +} + +QString KoGenStyles::lookup( const KoGenStyle& style, const QString& name, int flags ) +{ + StyleMap::iterator it = m_styleMap.find( style ); + if ( it == m_styleMap.end() ) { + // Not found, try if this style is in fact equal to its parent (the find above + // wouldn't have found it, due to m_parentName being set). + if ( !style.parentName().isEmpty() ) { + KoGenStyle testStyle( style ); + const KoGenStyle* parentStyle = this->style( style.parentName() ); // ## linear search + if( !parentStyle ) { + kdDebug(30003) << "KoGenStyles::lookup(" << name << "): parent style '" << style.parentName() << "' not found in collection" << endl; + } else { + if ( testStyle.m_familyName != parentStyle->m_familyName ) + { + kdWarning(30003) << "KoGenStyles::lookup(" << name << ", family=" << testStyle.m_familyName << ") parent style '" << style.parentName() << "' has a different family: " << parentStyle->m_familyName << endl; + } + + testStyle.m_parentName = parentStyle->m_parentName; + // Exclude the type from the comparison. It's ok for an auto style + // to have a user style as parent; they can still be identical + testStyle.m_type = parentStyle->m_type; + // Also it's ok to not have the display name of the parent style + // in the auto style + QMap<QString, QString>::const_iterator it = parentStyle->m_attributes.find( "style:display-name" ); + if ( it != parentStyle->m_attributes.end() ) + testStyle.addAttribute( "style:display-name", *it ); + + if ( *parentStyle == testStyle ) + return style.parentName(); + } + } + + QString styleName( name ); + if ( styleName.isEmpty() ) { + styleName = 'A'; // for "auto". + flags &= ~DontForceNumbering; // i.e. force numbering + } + styleName = makeUniqueName( styleName, flags ); + if ( style.autoStyleInStylesDotXml() ) + m_autoStylesInStylesDotXml.insert( styleName, true /*unused*/ ); + else + m_styleNames.insert( styleName, true /*unused*/ ); + it = m_styleMap.insert( style, styleName ); + NamedStyle s; + s.style = &it.key(); + s.name = styleName; + m_styleArray.append( s ); + } + return it.data(); +} + +QString KoGenStyles::makeUniqueName( const QString& base, int flags ) const +{ + // If this name is not used yet, and numbering isn't forced, then the given name is ok. + if ( ( flags & DontForceNumbering ) + && m_autoStylesInStylesDotXml.find( base ) == m_autoStylesInStylesDotXml.end() + && m_styleNames.find( base ) == m_styleNames.end() ) + return base; + int num = 1; + QString name; + do { + name = base; + name += QString::number( num++ ); + } while ( m_autoStylesInStylesDotXml.find( name ) != m_autoStylesInStylesDotXml.end() + || m_styleNames.find( name ) != m_styleNames.end() ); + return name; +} + +QValueList<KoGenStyles::NamedStyle> KoGenStyles::styles( int type, bool markedForStylesXml ) const +{ + QValueList<KoGenStyles::NamedStyle> lst; + const NameMap& nameMap = markedForStylesXml ? m_autoStylesInStylesDotXml : m_styleNames; + StyleArray::const_iterator it = m_styleArray.begin(); + const StyleArray::const_iterator end = m_styleArray.end(); + for ( ; it != end ; ++it ) { + // Look up if it's marked for styles.xml or not by looking up in the corresponding style map. + if ( (*it).style->type() == type && nameMap.find((*it).name) != nameMap.end() ) { + lst.append( *it ); + } + } + return lst; +} + +const KoGenStyle* KoGenStyles::style( const QString& name ) const +{ + StyleArray::const_iterator it = m_styleArray.begin(); + const StyleArray::const_iterator end = m_styleArray.end(); + for ( ; it != end ; ++it ) { + if ( (*it).name == name ) + return (*it).style; + } + return 0; +} + +KoGenStyle* KoGenStyles::styleForModification( const QString& name ) +{ + return const_cast<KoGenStyle *>( style( name ) ); +} + +void KoGenStyles::markStyleForStylesXml( const QString& name ) +{ + Q_ASSERT( m_styleNames.find( name ) != m_styleNames.end() ); + m_styleNames.remove( name ); + m_autoStylesInStylesDotXml.insert( name, true ); + styleForModification( name )->setAutoStyleInStylesDotXml( true ); +} + +void KoGenStyles::dump() +{ + kdDebug() << "Style array:" << endl; + StyleArray::const_iterator it = m_styleArray.begin(); + const StyleArray::const_iterator end = m_styleArray.end(); + for ( ; it != end ; ++it ) { + kdDebug() << (*it).name << endl; + } + for ( NameMap::const_iterator it = m_styleNames.begin(); it != m_styleNames.end(); ++it ) { + kdDebug() << "style: " << it.key() << endl; + } + for ( NameMap::const_iterator it = m_autoStylesInStylesDotXml.begin(); it != m_autoStylesInStylesDotXml.end(); ++it ) { + kdDebug() << "auto style for style.xml: " << it.key() << endl; + const KoGenStyle* s = style( it.key() ); + Q_ASSERT( s ); + Q_ASSERT( s->autoStyleInStylesDotXml() ); + } +} + +// Returns -1, 0 (equal) or 1 +static int compareMap( const QMap<QString, QString>& map1, const QMap<QString, QString>& map2 ) +{ + QMap<QString, QString>::const_iterator it = map1.begin(); + QMap<QString, QString>::const_iterator oit = map2.begin(); + for ( ; it != map1.end(); ++it, ++oit ) { // both maps have been checked for size already + if ( it.key() != oit.key() ) + return it.key() < oit.key() ? -1 : +1; + if ( it.data() != oit.data() ) + return it.data() < oit.data() ? -1 : +1; + } + return 0; // equal +} + +//// + + +KoGenStyle::KoGenStyle( int type, const char* familyName, + const QString& parentName ) + : m_type( type ), m_familyName( familyName ), m_parentName( parentName ), + m_autoStyleInStylesDotXml( false ), m_defaultStyle( false ) +{ +} + +KoGenStyle::~KoGenStyle() +{ +} + +void KoGenStyle::writeStyleProperties( KoXmlWriter* writer, PropertyType i, + const char* elementName, const KoGenStyle* parentStyle ) const +{ + if ( !m_properties[i].isEmpty() ) { + writer->startElement( elementName ); + QMap<QString, QString>::const_iterator it = m_properties[i].begin(); + const QMap<QString, QString>::const_iterator end = m_properties[i].end(); + for ( ; it != end; ++it ) { + if ( !parentStyle || parentStyle->property( it.key(), i ) != it.data() ) + writer->addAttribute( it.key().utf8(), it.data().utf8() ); + } + writer->endElement(); + } +} + +void KoGenStyle::writeStyle( KoXmlWriter* writer, KoGenStyles& styles, const char* elementName, const QString& name, const char* propertiesElementName, bool closeElement, bool drawElement ) const +{ + //kdDebug(30003) << "writing out style " << name << " display-name=" << m_attributes["style:display-name"] << " family=" << m_familyName << endl; + writer->startElement( elementName ); + const KoGenStyle* parentStyle = 0; + if ( !m_defaultStyle ) { + if ( !drawElement ) + writer->addAttribute( "style:name", name ); + else + writer->addAttribute( "draw:name", name ); + if ( !m_parentName.isEmpty() ) { + parentStyle = styles.style( m_parentName ); + if ( parentStyle && m_familyName.isEmpty() ) { + // get family from parent style, just in case + // Note: this is saving code, don't convert to attributeNS! + const_cast<KoGenStyle *>( this )-> + m_familyName = parentStyle->attribute( "style:family" ).latin1(); + //kdDebug(30003) << "Got familyname " << m_familyName << " from parent" << endl; + } + writer->addAttribute( "style:parent-style-name", m_parentName ); + } + } else { // default-style + Q_ASSERT( qstrcmp( elementName, "style:default-style" ) == 0 ); + Q_ASSERT( m_parentName.isEmpty() ); + } + if ( !m_familyName.isEmpty() ) + const_cast<KoGenStyle *>( this )-> + addAttribute( "style:family", QString::fromLatin1( m_familyName ) ); + else { + if ( qstrcmp( elementName, "style:style" ) == 0 ) + kdWarning(30003) << "User style " << name << " is without family - invalid. m_type=" << m_type << endl; + } + +#if 0 // #ifndef NDEBUG + kdDebug() << "style: " << name << endl; + printDebug(); + if ( parentStyle ) { + kdDebug() << " parent: " << m_parentName << endl; + parentStyle->printDebug(); + } +#endif + + // Write attributes [which differ from the parent style] + // We only look at the direct parent style because we assume + // that styles are fully specified, i.e. the inheritance is + // only in the final file, not in the caller's code. + QMap<QString, QString>::const_iterator it = m_attributes.begin(); + for ( ; it != m_attributes.end(); ++it ) { + bool writeit = true; + if ( parentStyle && it.key() != "style:family" // always write the family out + && parentStyle->attribute( it.key() ) == it.data() ) + writeit = false; + if ( writeit ) + writer->addAttribute( it.key().utf8(), it.data().utf8() ); + } + bool createPropertiesTag = propertiesElementName && propertiesElementName[0] != '\0'; + KoGenStyle::PropertyType i = KoGenStyle::DefaultType; + if ( !m_properties[i].isEmpty() || + !m_properties[KoGenStyle::ChildElement].isEmpty() ) { + if ( createPropertiesTag ) + writer->startElement( propertiesElementName ); // e.g. paragraph-properties + it = m_properties[i].begin(); + for ( ; it != m_properties[i].end(); ++it ) { + if ( !parentStyle || parentStyle->property( it.key(), i ) != it.data() ) + writer->addAttribute( it.key().utf8(), it.data().utf8() ); + } + i = KoGenStyle::ChildElement; + it = m_properties[i].begin(); + for ( ; it != m_properties[i].end(); ++it ) { + if ( !parentStyle || parentStyle->property( it.key(), i ) != it.data() ) { + writer->addCompleteElement( it.data().utf8() ); + } + } + if ( createPropertiesTag ) + writer->endElement(); + } + writeStyleProperties( writer, KoGenStyle::GraphicType, "style:graphic-properties", parentStyle ); + writeStyleProperties( writer, KoGenStyle::ParagraphType, "style:paragraph-properties", parentStyle ); + writeStyleProperties( writer, KoGenStyle::TextType, "style:text-properties", parentStyle ); + + // And now the style maps + for ( uint i = 0; i < m_maps.count(); ++i ) { + bool writeit = true; + if ( parentStyle && compareMap( m_maps[i], parentStyle->m_maps[i] ) == 0 ) + writeit = false; + if ( writeit ) { + writer->startElement( "style:map" ); + QMap<QString, QString>::const_iterator it = m_maps[i].begin(); + for ( ; it != m_maps[i].end(); ++it ) { + writer->addAttribute( it.key().utf8(), it.data().utf8() ); + } + writer->endElement(); // style:map + } + } + if ( closeElement ) + writer->endElement(); +} + +void KoGenStyle::addPropertyPt( const QString& propName, double propValue, PropertyType type ) +{ + QString str; + str.setNum( propValue, 'g', DBL_DIG ); + str += "pt"; + m_properties[type].insert( propName, str ); +} + +void KoGenStyle::addAttributePt( const QString& attrName, double attrValue ) +{ + QString str; + str.setNum( attrValue, 'g', DBL_DIG ); + str += "pt"; + m_attributes.insert( attrName, str ); +} + +#ifndef NDEBUG +void KoGenStyle::printDebug() const +{ + int i = DefaultType; + kdDebug() << m_properties[i].count() << " properties." << endl; + for( QMap<QString,QString>::ConstIterator it = m_properties[i].begin(); it != m_properties[i].end(); ++it ) { + kdDebug() << " " << it.key() << " = " << it.data() << endl; + } + i = TextType; + kdDebug() << m_properties[i].count() << " text properties." << endl; + for( QMap<QString,QString>::ConstIterator it = m_properties[i].begin(); it != m_properties[i].end(); ++it ) { + kdDebug() << " " << it.key() << " = " << it.data() << endl; + } + i = ParagraphType; + kdDebug() << m_properties[i].count() << " paragraph properties." << endl; + for( QMap<QString,QString>::ConstIterator it = m_properties[i].begin(); it != m_properties[i].end(); ++it ) { + kdDebug() << " " << it.key() << " = " << it.data() << endl; + } + i = ChildElement; + kdDebug() << m_properties[i].count() << " child elements." << endl; + for( QMap<QString,QString>::ConstIterator it = m_properties[i].begin(); it != m_properties[i].end(); ++it ) { + kdDebug() << " " << it.key() << " = " << it.data() << endl; + } + kdDebug() << m_attributes.count() << " attributes." << endl; + for( QMap<QString,QString>::ConstIterator it = m_attributes.begin(); it != m_attributes.end(); ++it ) { + kdDebug() << " " << it.key() << " = " << it.data() << endl; + } + kdDebug() << m_maps.count() << " maps." << endl; + for ( uint i = 0; i < m_maps.count(); ++i ) { + kdDebug() << "map " << i << ":" << endl; + for( QMap<QString,QString>::ConstIterator it = m_maps[i].begin(); it != m_maps[i].end(); ++it ) { + kdDebug() << " " << it.key() << " = " << it.data() << endl; + } + } + kdDebug() << endl; +} +#endif + +bool KoGenStyle::operator<( const KoGenStyle &other ) const +{ + if ( m_type != other.m_type ) return m_type < other.m_type; + if ( m_parentName != other.m_parentName ) return m_parentName < other.m_parentName; + if ( m_autoStyleInStylesDotXml != other.m_autoStyleInStylesDotXml ) return m_autoStyleInStylesDotXml; + for ( uint i = 0 ; i < N_NumTypes ; ++i ) + if ( m_properties[i].count() != other.m_properties[i].count() ) + return m_properties[i].count() < other.m_properties[i].count(); + if ( m_attributes.count() != other.m_attributes.count() ) return m_attributes.count() < other.m_attributes.count(); + if ( m_maps.count() != other.m_maps.count() ) return m_maps.count() < other.m_maps.count(); + // Same number of properties and attributes, no other choice than iterating + for ( uint i = 0 ; i < N_NumTypes ; ++i ) { + int comp = compareMap( m_properties[i], other.m_properties[i] ); + if ( comp != 0 ) + return comp < 0; + } + int comp = compareMap( m_attributes, other.m_attributes ); + if ( comp != 0 ) + return comp < 0; + for ( uint i = 0 ; i < m_maps.count() ; ++i ) { + int comp = compareMap( m_maps[i], other.m_maps[i] ); + if ( comp != 0 ) + return comp < 0; + } + return false; +} + +bool KoGenStyle::operator==( const KoGenStyle &other ) const +{ + if ( m_type != other.m_type ) return false; + if ( m_parentName != other.m_parentName ) return false; + if ( m_autoStyleInStylesDotXml != other.m_autoStyleInStylesDotXml ) return false; + for ( uint i = 0 ; i < N_NumTypes ; ++i ) + if ( m_properties[i].count() != other.m_properties[i].count() ) + return false; + if ( m_attributes.count() != other.m_attributes.count() ) return false; + if ( m_maps.count() != other.m_maps.count() ) return false; + // Same number of properties and attributes, no other choice than iterating + for ( uint i = 0 ; i < N_NumTypes ; ++i ) { + int comp = compareMap( m_properties[i], other.m_properties[i] ); + if ( comp != 0 ) + return false; + } + int comp = compareMap( m_attributes, other.m_attributes ); + if ( comp != 0 ) + return false; + for ( uint i = 0 ; i < m_maps.count() ; ++i ) { + int comp = compareMap( m_maps[i], other.m_maps[i] ); + if ( comp != 0 ) + return false; + } + return true; +} diff --git a/lib/kofficecore/KoGenStyles.h b/lib/kofficecore/KoGenStyles.h new file mode 100644 index 00000000..ad05d6e0 --- /dev/null +++ b/lib/kofficecore/KoGenStyles.h @@ -0,0 +1,463 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2006 David Faure <faure@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 KOGENSTYLES_H +#define KOGENSTYLES_H + +#include <qdict.h> +#include <qmap.h> +#include <qvaluevector.h> + +#include <koffice_export.h> + +class KoXmlWriter; +class KoGenStyle; + +/** + * @brief Repository of styles used during saving of OASIS/OOo file. + * + * Each instance of KoGenStyles is a collection of styles whose names + * are in the same "namespace". + * This means there should be one instance for all styles in <office:styles>, + * and automatic-styles, another instance for number formats, another + * one for draw styles, and another one for list styles. + * + * "Style" in this context only means "a collection of properties". + * The "Gen" means both "Generic" and "Generated" :) + * + * The basic design principle is the flyweight pattern: if you need + * a style with the same properties from two different places, you + * get the same object. Here it means rather you get the same name for the style, + * and it will get saved only once to the file. + * + * KoGenStyles features sharing, creation on demand, and name generation. + * Since this is used for saving only, it doesn't feature refcounting, nor + * removal of individual styles. + * + * NOTE: the use of KoGenStyles isn't mandatory, of course. If the application + * is already designed with user and automatic styles in mind for a given + * set of properties, it can go ahead and save all styles directly (after + * ensuring they have unique names). + * + * @author David Faure <faure@kde.org> + */ +class KOFFICECORE_EXPORT KoGenStyles +{ +public: + KoGenStyles(); + ~KoGenStyles(); + + /** + * Those are flags for the lookup() call. + * + * By default, the generated style names will look like "name1", "name2". + * If DontForceNumbering is set, the first name that will be tried is "name", and only if + * that one exists, then "name1" is tried. Set DontForceNumbering if the name given as + * argument is supposed to be the full style name. + * + */ + enum Flags { // bitfield + NoFlag = 0, + ForceNumbering = 0, // it's the default anyway + DontForceNumbering = 1 + }; + // KDE4 TODO: use QFlags and change the arg type in lookup + + /** + * Look up a style in the collection, inserting it if necessary. + * This assigns a name to the style and returns it. + * + * @param style the style to look up. + * @param name proposed (base) name for the style. Note that with the OASIS format, + * the style name is never shown to the user (there's a separate display-name + * attribute for that). So there are little reasons to use named styles anyway. + * But this attribute can be used for clarity of the files. + * If this name is already in use (for another style), then a number is appended + * to it until unique. + * @param flags see Flags + * + * @return the name for this style + * @todo ### rename lookup to insert + */ + QString lookup( const KoGenStyle& style, const QString& name = QString::null, int flags = NoFlag ); + + typedef QMap<KoGenStyle, QString> StyleMap; + /** + * Return the entire collection of styles + * Use this for saving the styles + */ + const StyleMap& styles() const { return m_styleMap; } + + struct NamedStyle { + const KoGenStyle* style; ///< @note owned by the collection + QString name; + }; + /** + * Return all styles of a given type + * Use this for saving the styles + * + * @param type the style type, see the KoGenStyle constructor + * @param markedForStylesXml if true, return only style marked for styles.xml, + * otherwise only those NOT marked for styles.xml. + * @see lookup + */ + QValueList<NamedStyle> styles( int type, bool markedForStylesXml = false ) const; + + /** + * @return an existing style by name + */ + const KoGenStyle* style( const QString& name ) const; + + /** + * @return an existing style by name, which can be modified. + * @warning This is DANGEROUS. + * It basically defeats the purpose of lookup()! + * Only do this if you know for sure no other 'user' of that style will + * be affected. + */ + KoGenStyle* styleForModification( const QString& name ); + + /** + * Mark a given automatic style as being needed in styles.xml. + * For instance styles used by headers and footers need to go there, since + * they are saved in styles.xml, and styles.xml must be independent from content.xml. + * + * Equivalent to using KoGenStyle::setAutoStyleInStylesDotXml() but this can be done after lookup. + * + * This operation can't be undone; once styles are promoted they can't go back + * to being content.xml-only. + * + * @see styles, KoGenStyle::setAutoStyleInStylesDotXml + */ + void markStyleForStylesXml( const QString& name ); + + /** + * Outputs debug information + */ + void dump(); + +private: + QString makeUniqueName( const QString& base, int flags ) const; + + /// style definition -> name + StyleMap m_styleMap; + + /// Map with the style name as key. + /// This map is mainly used to check for name uniqueness + /// The value of the bool doesn't matter. + typedef QMap<QString, bool> NameMap; // KDE4: QSet + NameMap m_styleNames; + NameMap m_autoStylesInStylesDotXml; + + /// List of styles (used to preserve ordering) + typedef QValueVector<NamedStyle> StyleArray; + StyleArray m_styleArray; + + class Private; + Private *d; +}; + +/** + * A generic style, i.e. basically a collection of properties and a name. + * Instances of KoGenStyle can either be held in the KoGenStyles collection, + * or created (e.g. on the stack) and given to KoGenStyles::lookup. + * + * @author David Faure <faure@kde.org> + */ +class KOFFICECORE_EXPORT KoGenStyle +{ +public: + /** + * Possible values for the "type" argument of the KoGenStyle constructor. + * Those values can be extended by applications (starting at number 20), + * it's for their own consumption anyway. + * (The reason for having the very common ones here, is to make it possible to + * use them from libkotext). + */ + enum { STYLE_PAGELAYOUT = 0, + STYLE_USER = 1, + STYLE_AUTO = 2, + STYLE_MASTER = 3, + STYLE_LIST = 4, + STYLE_AUTO_LIST = 5, + STYLE_NUMERIC_NUMBER = 6, + STYLE_NUMERIC_DATE = 7, + STYLE_NUMERIC_TIME = 8, + STYLE_NUMERIC_FRACTION = 9, + STYLE_NUMERIC_PERCENTAGE = 10, + STYLE_NUMERIC_SCIENTIFIC = 11, + STYLE_NUMERIC_CURRENCY = 12, + STYLE_NUMERIC_TEXT = 13, + STYLE_HATCH = 14, + STYLE_GRAPHICAUTO = 15}; + + /** + * Start the definition of a new style. Its name will be set later by KoGenStyles::lookup(), + * but first you must define its properties and attributes. + * + * @param type this is a hook for the application to categorize styles + * See the STYLE_* enum. Ignored when writing out the style. + * + * @param familyName The value for style:family, e.g. text, paragraph, graphic etc. + * The family is for style:style elements only; number styles and list styles don't have one. + * + * @param parentName If set, name of the parent style from which this one inherits. + */ + explicit KoGenStyle( int type = 0, const char* familyName = 0, + const QString& parentName = QString::null ); + ~KoGenStyle(); + + /* + * setAutoStyleInStylesDotXml(true) marks a given automatic style as being needed in styles.xml. + * For instance styles used by headers and footers need to go there, since + * they are saved in styles.xml, and styles.xml must be independent from content.xml. + * + * The application should use KoGenStyles::styles( type, true ) in order to retrieve + * those styles and save them separately. + */ + void setAutoStyleInStylesDotXml( bool b ) { m_autoStyleInStylesDotXml = b; } + /// @return the value passed to setAutoStyleInStylesDotXml; false by default + bool autoStyleInStylesDotXml() const { return m_autoStyleInStylesDotXml; } + + /* + * setDefaultStyle(true) marks a given style as being the default style. + * This means we expect that you will call writeStyle( ...,"style:default-style"), + * and its name will be ommitted in the output. + */ + void setDefaultStyle( bool b ) { m_defaultStyle = b; } + /// @return the value passed to setDefaultStyle; false by default + bool isDefaultStyle() const { return m_defaultStyle; } + + /// Return the type of this style, as set in the constructor + int type() const { return m_type; } + + /// Return the family name + const char* familyName() const { return m_familyName.data(); } + + /// Return the name of style's parent, if set + QString parentName() const { return m_parentName; } + + /** + * @brief The types of properties + * + * Simple styles only write one foo-properties tag, in which case they can just use DefaultType. + * However a given style might want to write several kinds of properties, in which case it would + * need to use other property types than the default one. + * + * For instance this style: + * @code + * <style:style style:family="chart"> + * <style:chart-properties .../> + * <style:graphic-properties .../> + * <style:text-properties .../> + * </style:style> + * @endcode + * would use DefaultType for chart-properties (and would pass "style:chart-properties" to writeStyle(), + * and would use GraphicType and TextType. + */ + enum PropertyType + { + /** + * DefaultType depends on family: e.g. paragraph-properties if family=paragraph + * or on the type of style (e.g. page-layout -> page-layout-properties). + * (In fact that tag name is the one passed to writeStyle) + */ + DefaultType = 0, + /// TextType is always text-properties. + TextType, + /// ParagraphType is always paragraph-properties. + ParagraphType, + /// GraphicType is always graphic-properties. + GraphicType, + Reserved1, ///< @internal for binary compatible extensions + Reserved2, ///< @internal for binary compatible extensions + ChildElement, ///< @internal + N_NumTypes ///< @internal - warning, adding items here affects binary compatibility (size of KoGenStyle) + }; + + /// Add a property to the style + void addProperty( const QString& propName, const QString& propValue, PropertyType type = DefaultType ) { + m_properties[type].insert( propName, propValue ); + } + /// Overloaded version of addProperty that takes a char*, usually for "..." + void addProperty( const QString& propName, const char* propValue, PropertyType type = DefaultType ) { + m_properties[type].insert( propName, QString::fromUtf8( propValue ) ); + } + /// Overloaded version of addProperty that converts an int to a string + void addProperty( const QString& propName, int propValue, PropertyType type = DefaultType ) { + m_properties[type].insert( propName, QString::number( propValue ) ); + } + /// Overloaded version of addProperty that converts a bool to a string (false/true) + void addProperty( const QString& propName, bool propValue, PropertyType type = DefaultType ) { + m_properties[type].insert( propName, propValue ? "true" : "false" ); + } + + /** + * Add a property which represents a distance, measured in pt + * The number is written out with the highest possible precision + * (unlike QString::number and setNum, which default to 6 digits), + * and the unit name ("pt") is appended to it. + */ + void addPropertyPt( const QString& propName, double propValue, PropertyType type = DefaultType ); + + /** + * Add an attribute to the style + * The difference between property and attributes is a bit oasis-format-specific: + * attributes are for the style element itself, and properties are in the style:properties child element + */ + void addAttribute( const QString& attrName, const QString& attrValue ) { + m_attributes.insert( attrName, attrValue ); + } + /// Overloaded version of addAttribute that takes a char*, usually for "..." + void addAttribute( const QString& attrName, const char* attrValue ) { + m_attributes.insert( attrName, QString::fromUtf8( attrValue ) ); + } + /// Overloaded version of addAttribute that converts an int to a string + void addAttribute( const QString& attrName, int attrValue ) { + m_attributes.insert( attrName, QString::number( attrValue ) ); + } + + /// Overloaded version of addAttribute that converts a bool to a string + void addAttribute( const QString& attrName, bool attrValue ) { + m_attributes.insert( attrName, attrValue ? "true" : "false" ); + } + + /** + * Add an attribute which represents a distance, measured in pt + * The number is written out with the highest possible precision + * (unlike QString::number and setNum, which default to 6 digits), + * and the unit name ("pt") is appended to it. + */ + void addAttributePt( const QString& attrName, double attrValue ); + + /** + * @brief Add a child element to the style properties. + * + * What is meant here is that the contents of the QString + * will be written out literally. This means you should use + * KoXmlWriter to generate it: + * @code + * QBuffer buffer; + * buffer.open( IO_WriteOnly ); + * KoXmlWriter elementWriter( &buffer ); // TODO pass indentation level + * elementWriter.startElement( "..." ); + * ... + * elementWriter.endElement(); + * QString elementContents = QString::fromUtf8( buffer.buffer(), buffer.buffer().size() ); + * gs.addChildElement( "...", elementContents ); + * @endcode + * + * The value of @p elementName isn't used, except that it must be unique. + */ + void addChildElement( const QString& elementName, const QString& elementContents ) { + m_properties[ChildElement].insert( elementName, elementContents ); + } + + /** + * @brief Add a style:map to the style. + * @param styleMap the attributes for the map, associated as (name,value). + */ + void addStyleMap( const QMap<QString, QString>& styleMap ) { + m_maps.append( styleMap ); + } + + /** + * @return true if the style has no attributes, no properties, no style map etc. + * This can be used by applications which do not save all attributes unconditionally, + * but only those that differ from the parent. But note that lookup() can't find this out... + */ + bool isEmpty() const { + if ( !m_attributes.isEmpty() || ! m_maps.isEmpty() ) + return false; + for ( uint i = 0 ; i < N_NumTypes ; ++i ) + if ( ! m_properties[i].isEmpty() ) + return false; + return true; + } + + /** + * Write the definition of this style to @p writer, using the OASIS format. + * @param writer the KoXmlWriter in which @p elementName will be created and filled in + * @param styles the styles collection, used to look up the parent style + * @param elementName the name of the XML element, e.g. "style:style". Don't forget to + * pass style:default-style if isDefaultStyle(). + * @param name must come from the collection. It will be ignored if isDefaultStyle() is true. + * @param propertiesElementName the name of the XML element with the style properties, + * e.g. "style:text-properties". Can be 0 in special cases where there should be no such item, + * in which case the attributes and elements are added under the style itself. + * @param closeElement set it to false to be able to add more child elements to the style element + * @param drawElement set it to true to add "draw:name" (used for gradient/hatch style) otherwise add "style:name" + */ + void writeStyle( KoXmlWriter* writer, KoGenStyles& styles, const char* elementName, const QString& name, + const char* propertiesElementName, bool closeElement = true, bool drawElement = false ) const; + + /** + * QMap requires a complete sorting order. + * Another solution would have been a qdict and a key() here, a la KoTextFormat, + * but the key was difficult to generate. + * Solutions with only a hash value (not representative of the whole data) + * require us to write a hashtable by hand.... + */ + bool operator<( const KoGenStyle &other ) const; + + /// Not needed for QMap, but can still be useful + bool operator==( const KoGenStyle &other ) const; + +private: + QString property( const QString& propName, PropertyType type ) const { + QMap<QString, QString>::const_iterator it = m_properties[type].find( propName ); + if ( it != m_properties[type].end() ) + return it.data(); + return QString::null; + } + + QString attribute( const QString& propName ) const { + QMap<QString, QString>::const_iterator it = m_attributes.find( propName ); + if ( it != m_attributes.end() ) + return it.data(); + return QString::null; + } + + void writeStyleProperties( KoXmlWriter* writer, PropertyType i, + const char* elementName, const KoGenStyle* parentStyle ) const; + +#ifndef NDEBUG + void printDebug() const; +#endif + +private: + // Note that the copy constructor and assignment operator are allowed. + // Better not use pointers below! + int m_type; + QCString m_familyName; + QString m_parentName; + /// We use QMaps since they provide automatic sorting on the key (important for unicity!) + QMap<QString, QString> m_properties[N_NumTypes]; + QMap<QString, QString> m_attributes; + typedef QMap<QString, QString> StyleMap; + QValueVector<StyleMap> m_maps; // we can't really sort the maps between themselves... + + bool m_autoStyleInStylesDotXml; + bool m_defaultStyle; + short m_unused2; + + // For lookup + friend class KoGenStyles; +}; + +#endif /* KOGENSTYLES_H */ diff --git a/lib/kofficecore/KoGlobal.cpp b/lib/kofficecore/KoGlobal.cpp new file mode 100644 index 00000000..87717172 --- /dev/null +++ b/lib/kofficecore/KoGlobal.cpp @@ -0,0 +1,205 @@ +/* This file is part of the KDE project + Copyright (C) 2001 David Faure <faure@kde.org> + Copyright 2003 Nicolas GOUTTE <goutte@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 "config.h" +#include <KoGlobal.h> +#include <kdebug.h> +#include <qfont.h> +#include <qfontinfo.h> +#include <kglobalsettings.h> +#include <kglobal.h> +#include <klocale.h> +#include <ksimpleconfig.h> +#include <kstandarddirs.h> +#include <kstaticdeleter.h> +#include <kimageio.h> +#include <kiconloader.h> +#include <kstandarddirs.h> +#include <locale.h> + +KoGlobal* KoGlobal::s_global = 0L; +static KStaticDeleter<KoGlobal> sdg; + +KoGlobal* KoGlobal::self() +{ + if ( !s_global ) + sdg.setObject( s_global, new KoGlobal ); + return s_global; +} + +KoGlobal::KoGlobal() + : m_pointSize( -1 ), m_kofficeConfig( 0L ) +{ + // Make sure that QCString::setNum doesn't give us "," as decimal point, e.g. when saving to OpenDocument. + setlocale( LC_NUMERIC, "C" ); + + // Install the libkoffice* translations + KGlobal::locale()->insertCatalogue("koffice"); + + KImageIO::registerFormats(); + + // Tell KStandardDirs about the koffice prefix + KGlobal::dirs()->addPrefix(PREFIX); + + // Tell the iconloader about share/apps/koffice/icons + KGlobal::iconLoader()->addAppDir("koffice"); + + // Another way to get the DPI of the display would be QPaintDeviceMetrics, + // but we have no widget here (and moving this to KoView wouldn't allow + // using this from the document easily). +#ifdef Q_WS_X11 + m_dpiX = QPaintDevice::x11AppDpiX(); + m_dpiY = QPaintDevice::x11AppDpiY(); +#else + m_dpiX = 75; + m_dpiY = 75; +#endif +} + +KoGlobal::~KoGlobal() +{ + delete m_kofficeConfig; +} + +QFont KoGlobal::_defaultFont() +{ + QFont font = KGlobalSettings::generalFont(); + // we have to use QFontInfo, in case the font was specified with a pixel size + if ( font.pointSize() == -1 ) + { + // cache size into m_pointSize, since QFontInfo loads the font -> slow + if ( m_pointSize == -1 ) + m_pointSize = QFontInfo(font).pointSize(); + Q_ASSERT( m_pointSize != -1 ); + font.setPointSize( m_pointSize ); + } + //kdDebug()<<k_funcinfo<<"QFontInfo(font).pointSize() :"<<QFontInfo(font).pointSize()<<endl; + //kdDebug()<<k_funcinfo<<"font.name() :"<<font.family ()<<endl; + return font; +} + +QStringList KoGlobal::_listOfLanguageTags() +{ + if ( m_langMap.isEmpty() ) + createListOfLanguages(); + return m_langMap.values(); +} + +QStringList KoGlobal::_listOfLanguages() +{ + if ( m_langMap.empty() ) + createListOfLanguages(); + return m_langMap.keys(); +} + +void KoGlobal::createListOfLanguages() +{ + KConfig config( "all_languages", true, false, "locale" ); + // Note that we could also use KLocale::allLanguagesTwoAlpha + + QMap<QString, bool> seenLanguages; + const QStringList langlist = config.groupList(); + for ( QStringList::ConstIterator itall = langlist.begin(); + itall != langlist.end(); ++itall ) + { + const QString tag = *itall; + config.setGroup( tag ); + const QString name = config.readEntry("Name", tag); + // e.g. name is "French" and tag is "fr" + + // The QMap does the sorting on the display-name, so that + // comboboxes are sorted. + m_langMap.insert( name, tag ); + + seenLanguages.insert( tag, true ); + } + + // Also take a look at the installed translations. + // Many of them are already in all_languages but all_languages doesn't + // currently have en_GB or en_US etc. + + const QStringList translationList = KGlobal::dirs()->findAllResources("locale", + QString::fromLatin1("*/entry.desktop")); + for ( QStringList::ConstIterator it = translationList.begin(); + it != translationList.end(); ++it ) + { + // Extract the language tag from the directory name + QString tag = *it; + int index = tag.findRev('/'); + tag = tag.left(index); + index = tag.findRev('/'); + tag = tag.mid(index+1); + + if ( seenLanguages.find( tag ) == seenLanguages.end() ) { + KSimpleConfig entry(*it); + entry.setGroup("KCM Locale"); + + const QString name = entry.readEntry("Name", tag); + // e.g. name is "US English" and tag is "en_US" + m_langMap.insert( name, tag ); + + // enable this if writing a third way of finding languages below + //seenLanguages.insert( tag, true ); + } + + } + + // #### We also might not have an entry for a language where spellchecking is supported, + // but no KDE translation is available, like fr_CA. + // How to add them? +} + +QString KoGlobal::tagOfLanguage( const QString & _lang) +{ + const LanguageMap& map = self()->m_langMap; + QMap<QString,QString>::ConstIterator it = map.find( _lang ); + if ( it != map.end() ) + return *it; + return QString::null; +} + +QString KoGlobal::languageFromTag( const QString &langTag ) +{ + const LanguageMap& map = self()->m_langMap; + QMap<QString,QString>::ConstIterator it = map.begin(); + const QMap<QString,QString>::ConstIterator end = map.end(); + for ( ; it != end; ++it ) + if ( it.data() == langTag ) + return it.key(); + + // Language code not found. Better return the code (tag) than nothing. + return langTag; +} + +KConfig* KoGlobal::_kofficeConfig() +{ + if ( !m_kofficeConfig ) { + m_kofficeConfig = new KConfig( "kofficerc" ); + } + return m_kofficeConfig; +} + +void KoGlobal::setDPI( int x, int y ) +{ + //kdDebug() << k_funcinfo << x << "," << y << endl; + KoGlobal* s = self(); + s->m_dpiX = x; + s->m_dpiY = y; +} diff --git a/lib/kofficecore/KoGlobal.h b/lib/kofficecore/KoGlobal.h new file mode 100644 index 00000000..bab9c261 --- /dev/null +++ b/lib/kofficecore/KoGlobal.h @@ -0,0 +1,107 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> + Copyright 2002, 2003 David Faure <faure@kde.org> + Copyright 2003 Nicolas GOUTTE <goutte@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 koGlobal_h +#define koGlobal_h + +#include <qstringlist.h> +#include <qfont.h> +#include <qmap.h> +class KConfig; + +#include <koffice_export.h> +class KOFFICECORE_EXPORT KoGlobal +{ +public: + /// For KoApplication + static void initialize() { + (void)self(); // I don't want to make KGlobal instances public, so self() is private + } + /** + * Return the default font for KOffice programs. + * This is (currently) the same as the KDE-global default font, + * except that it is guaranteed to have a point size set, + * never a pixel size (see @ref QFont). + */ + static QFont defaultFont() { + return self()->_defaultFont(); + } + + /** + * @return the global KConfig object around kofficerc. + * kofficerc is used for KOffice-wide settings, from totally unrelated classes, + * so this is the centralization of the KConfig object so that the file is + * parsed only once + */ + static KConfig* kofficeConfig() { + return self()->_kofficeConfig(); + } + + static int dpiX() { + return self()->m_dpiX; + } + static int dpiY() { + return self()->m_dpiY; + } + /// @internal, for KoApplication + static void setDPI( int x, int y ); + + /// Return the list of available languages, in their displayable form + /// (translated names) + static QStringList listOfLanguages() { + return self()->_listOfLanguages(); + } + /// Return the list of available languages, in their internal form + /// e.g. "fr" or "en_US", here called "tag" + static QStringList listTagOfLanguages() { // TODO rename to listOfLanguageTags + return self()->_listOfLanguageTags(); + } + /// For a given language display name, return its tag + static QString tagOfLanguage( const QString & _lang ); + /// For a given language tag, return its display name + static QString languageFromTag( const QString &_lang ); + + ~KoGlobal(); + +private: + static KoGlobal* self(); + KoGlobal(); + QFont _defaultFont(); + QStringList _listOfLanguages(); + QStringList _listOfLanguageTags(); + KConfig* _kofficeConfig(); + void createListOfLanguages(); + + int m_pointSize; + typedef QMap<QString, QString> LanguageMap; + LanguageMap m_langMap; // display-name -> language tag + KConfig* m_kofficeConfig; + int m_dpiX; + int m_dpiY; + // No BC problem here, constructor is private, feel free to add members + + // Singleton pattern. Maybe this should even be refcounted, so + // that it gets cleaned up when closing all koffice parts in e.g. konqueror? + static KoGlobal* s_global; + friend class this_is_a_singleton; // work around gcc warning +}; + +#endif // koGlobal diff --git a/lib/kofficecore/KoMainWindow.cpp b/lib/kofficecore/KoMainWindow.cpp new file mode 100644 index 00000000..bdcfaa6a --- /dev/null +++ b/lib/kofficecore/KoMainWindow.cpp @@ -0,0 +1,1701 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> + Copyright (C) 2000-2006 David Faure <faure@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 "KoMainWindow.h" +#include "KoDocument.h" +#include "KoView.h" +#include "KoFilterManager.h" +#include "KoDocumentInfo.h" +#include "KoDocumentInfoDlg.h" +#include "KoQueryTrader.h" +#include "KoMainWindowIface.h" +#include "KoFrame.h" +#include "KoFileDialog.h" +#include "Koversiondialog.h" +#include "kkbdaccessextensions.h" +#include "KoSpeaker.h" + +#include <kaboutdata.h> +#include <kprinter.h> +#include <kdeversion.h> +#include <kstdaction.h> +#include <kapplication.h> +#include <kmessagebox.h> +#include <kstandarddirs.h> +#include <kio/netaccess.h> +#include <kkeydialog.h> +#include <kedittoolbar.h> +#include <kprogress.h> +#include <kpushbutton.h> +#include <kdebug.h> +#include <ktempfile.h> +#include <krecentdocument.h> +#include <kparts/partmanager.h> +#include <kparts/plugin.h> +#include <kparts/event.h> +#include <klocale.h> +#include <kstatusbar.h> +#include <kglobalsettings.h> +#include <ksharedptr.h> + +#include <qobjectlist.h> + +#include <unistd.h> +#include <stdlib.h> + +class KoPartManager : public KParts::PartManager +{ +public: + KoPartManager( QWidget * parent, const char * name = 0L ) + : KParts::PartManager( parent, name ) + { + setSelectionPolicy( KParts::PartManager::TriState ); + setAllowNestedParts( true ); + setIgnoreScrollBars( true ); + // Allow right-click on embedded objects (without activating them) + // But beware: this means right-click on parent, from embedded object, + // doesn't make the parent active first... + setActivationButtonMask( Qt::LeftButton | Qt::MidButton ); + } + virtual bool eventFilter( QObject *obj, QEvent *ev ) + { + if ( !obj->isWidgetType() || ::qt_cast<KoFrame *>( obj ) ) + return false; + return KParts::PartManager::eventFilter( obj, ev ); + } +}; + +class KoMainWindowPrivate +{ +public: + KoMainWindowPrivate() + { + m_rootDoc = 0L; + m_docToOpen = 0L; + m_manager = 0L; + bMainWindowGUIBuilt = false; + m_forQuit=false; + m_splitted=false; + m_activePart = 0L; + m_activeView = 0L; + m_splitter=0L; + m_orientation=0L; + m_removeView=0L; + m_toolbarList.setAutoDelete( true ); + m_firstTime=true; + m_progress=0L; + m_paDocInfo = 0; + m_paSave = 0; + m_paSaveAs = 0; + m_paPrint = 0; + m_paPrintPreview = 0; + statusBarLabel = 0L; + m_dcopObject = 0; + m_sendfile = 0; + m_paCloseFile = 0L; + m_reloadfile = 0L; + m_versionsfile = 0L; + m_importFile = 0; + m_exportFile = 0; + m_isImporting = false; + m_isExporting = false; + m_windowSizeDirty = false; + m_lastExportSpecialOutputFlag = 0; + + // TTS accessibility enhancement (only if KDE 3.4 or later and KTTSD daemon is installed.) + if (KoSpeaker::isKttsdInstalled()) { + if (kospeaker) + m_koSpeaker = kospeaker; + else + m_koSpeaker = new KoSpeaker(); + } else + m_koSpeaker = 0; + } + ~KoMainWindowPrivate() + { + delete m_dcopObject; + } + + KoDocument *m_rootDoc; + KoDocument *m_docToOpen; + QPtrList<KoView> m_rootViews; + KParts::PartManager *m_manager; + + KParts::Part *m_activePart; + KoView *m_activeView; + + QLabel * statusBarLabel; + KProgress *m_progress; + + QPtrList<KAction> m_splitViewActionList; + // This additional list is needed, because we don't plug + // the first list, when an embedded view gets activated (Werner) + QPtrList<KAction> m_veryHackyActionList; + QSplitter *m_splitter; + KSelectAction *m_orientation; + KAction *m_removeView; + KoMainWindowIface *m_dcopObject; + + QPtrList <KAction> m_toolbarList; + + bool bMainWindowGUIBuilt; + bool m_splitted; + bool m_forQuit; + bool m_firstTime; + bool m_windowSizeDirty; + + KAction *m_paDocInfo; + KAction *m_paSave; + KAction *m_paSaveAs; + KAction *m_paPrint; + KAction *m_paPrintPreview; + KAction *m_sendfile; + KAction *m_paCloseFile; + KAction *m_reloadfile; + KAction *m_versionsfile; + KAction *m_importFile; + KAction *m_exportFile; + + bool m_isImporting; + bool m_isExporting; + + KURL m_lastExportURL; + QCString m_lastExportFormat; + int m_lastExportSpecialOutputFlag; + + KSharedPtr<KoSpeaker> m_koSpeaker; +}; + +KoMainWindow::KoMainWindow( KInstance *instance, const char* name ) + : KParts::MainWindow( name ) +{ + setStandardToolBarMenuEnabled(true); // should there be a check for >= 3.1 ? + Q_ASSERT(instance); + d = new KoMainWindowPrivate; + + d->m_manager = new KoPartManager( this ); + + connect( d->m_manager, SIGNAL( activePartChanged( KParts::Part * ) ), + this, SLOT( slotActivePartChanged( KParts::Part * ) ) ); + + if ( instance ) + setInstance( instance, false ); // don't load plugins! we don't want + // the part's plugins with this shell, even though we are using the + // part's instance! (Simon) + + QString doc; + QStringList allFiles = KGlobal::dirs()->findAllResources( "data", "koffice/koffice_shell.rc" ); + setXMLFile( findMostRecentXMLFile( allFiles, doc ) ); + setLocalXMLFile( locateLocal( "data", "koffice/koffice_shell.rc" ) ); + + KStdAction::openNew( this, SLOT( slotFileNew() ), actionCollection(), "file_new" ); + KStdAction::open( this, SLOT( slotFileOpen() ), actionCollection(), "file_open" ); + m_recent = KStdAction::openRecent( this, SLOT(slotFileOpenRecent(const KURL&)), actionCollection() ); + d->m_paSave = KStdAction::save( this, SLOT( slotFileSave() ), actionCollection(), "file_save" ); + d->m_paSaveAs = KStdAction::saveAs( this, SLOT( slotFileSaveAs() ), actionCollection(), "file_save_as" ); + d->m_paPrint = KStdAction::print( this, SLOT( slotFilePrint() ), actionCollection(), "file_print" ); + d->m_paPrintPreview = KStdAction::printPreview( this, SLOT( slotFilePrintPreview() ), actionCollection(), "file_print_preview" ); + d->m_sendfile = KStdAction::mail( this, SLOT( slotEmailFile() ), actionCollection(), "file_send_file"); + + d->m_paCloseFile = KStdAction::close( this, SLOT( slotFileClose() ), actionCollection(), "file_close" ); + KStdAction::quit( this, SLOT( slotFileQuit() ), actionCollection(), "file_quit" ); + + d->m_reloadfile = new KAction( i18n( "Reload"), 0, + this, SLOT( slotReloadFile() ), + actionCollection(), "file_reload_file"); + + d->m_versionsfile = new KAction( i18n( "Versions..."), 0, + this, SLOT( slotVersionsFile() ), + actionCollection(), "file_versions_file"); + + d->m_importFile = new KAction( i18n( "I&mport..." ), 0, // clashing accel key :( + this, SLOT( slotImportFile() ), + actionCollection(), "file_import_file"); + d->m_exportFile = new KAction( i18n( "E&xport..." ), 0, + this, SLOT( slotExportFile() ), + actionCollection(), "file_export_file"); + + /* The following entry opens the document information dialog. Since the action is named so it + intends to show data this entry should not have a trailing ellipses (...). */ + d->m_paDocInfo = new KAction( i18n( "&Document Information" ), "documentinfo", 0, + this, SLOT( slotDocumentInfo() ), + actionCollection(), "file_documentinfo" ); + + KStdAction::keyBindings( this, SLOT( slotConfigureKeys() ), actionCollection() ); + KStdAction::configureToolbars( this, SLOT( slotConfigureToolbars() ), actionCollection() ); + + d->m_paDocInfo->setEnabled( false ); + d->m_paSaveAs->setEnabled( false ); + d->m_reloadfile->setEnabled( false ); + d->m_versionsfile->setEnabled( false ); + d->m_importFile->setEnabled( true ); // always enabled like File --> Open + d->m_exportFile->setEnabled( false ); + d->m_paSave->setEnabled( false ); + d->m_paPrint->setEnabled( false ); + d->m_paPrintPreview->setEnabled( false ); + d->m_sendfile->setEnabled( false); + d->m_paCloseFile->setEnabled( false); + + d->m_splitter=new QSplitter(Qt::Vertical, this, "mw-splitter"); + setCentralWidget( d->m_splitter ); + // Keyboard accessibility enhancements. + new KKbdAccessExtensions(this, "mw-panelSizer"); + // set up the action "list" for "Close all Views" (hacky :) (Werner) + d->m_veryHackyActionList.append( + new KAction(i18n("&Close All Views"), "fileclose", + "ctrl+shift+w", this, SLOT(slotCloseAllViews()), + actionCollection(), "view_closeallviews") ); + + // set up the action list for the splitter stuff + d->m_splitViewActionList.append(new KAction(i18n("&Split View"), "view_split", 0, + this, SLOT(slotSplitView()), + actionCollection(), "view_split")); + d->m_removeView=new KAction(i18n("&Remove View"), "view_remove", 0, + this, SLOT(slotRemoveView()), + actionCollection(), "view_rm_splitter"); + d->m_splitViewActionList.append(d->m_removeView); + d->m_removeView->setEnabled(false); + d->m_orientation=new KSelectAction(i18n("Splitter &Orientation"), "view_orientation", 0, + this, SLOT(slotSetOrientation()), + actionCollection(), "view_splitter_orientation"); + QStringList items; + items << i18n("&Vertical") + << i18n("&Horizontal"); + d->m_orientation->setItems(items); + d->m_orientation->setCurrentItem(static_cast<int>(d->m_splitter->orientation())); + d->m_splitViewActionList.append(d->m_orientation); + d->m_splitViewActionList.append(new KActionSeparator(this)); + + // Load list of recent files + KConfig * config = instance ? instance->config() : KGlobal::config(); + m_recent->loadEntries( config ); + + createShellGUI(); + d->bMainWindowGUIBuilt = true; + + if ( !initialGeometrySet() ) + { + // Default size + const int deskWidth = KGlobalSettings::desktopGeometry(this).width(); + if (deskWidth > 1100) // very big desktop ? + resize( 1000, 800 ); + if (deskWidth > 850) // big desktop ? + resize( 800, 600 ); + else // small (800x600, 640x480) desktop + resize( 600, 400 ); + } + + // Saved size + config->setGroup( "MainWindow" ); + //kdDebug(30003) << "KoMainWindow::restoreWindowSize" << endl; + restoreWindowSize( config ); +} + +KoMainWindow::~KoMainWindow() +{ + // The doc and view might still exist (this is the case when closing the window) + if (d->m_rootDoc) + d->m_rootDoc->removeShell(this); + + if (d->m_docToOpen) { + d->m_docToOpen->removeShell(this); + delete d->m_docToOpen; + } + + // safety first ;) + d->m_manager->setActivePart(0); + + if(d->m_rootViews.findRef(d->m_activeView)==-1) { + delete d->m_activeView; + d->m_activeView=0L; + } + d->m_rootViews.setAutoDelete( true ); + d->m_rootViews.clear(); + + // We have to check if this was a root document. + // -> We aren't allowed to delete the (embedded) document! + // This has to be checked from queryClose, too :) + if ( d->m_rootDoc && d->m_rootDoc->viewCount() == 0 && + !d->m_rootDoc->isEmbedded()) + { + //kdDebug(30003) << "Destructor. No more views, deleting old doc " << d->m_rootDoc << endl; + delete d->m_rootDoc; + } + + delete d->m_manager; + delete d; +} + +void KoMainWindow::setRootDocument( KoDocument *doc ) +{ + if ( d->m_rootDoc == doc ) + return; + + if (d->m_docToOpen && d->m_docToOpen != doc) { + d->m_docToOpen->removeShell(this); + delete d->m_docToOpen; + d->m_docToOpen = 0; + } else { + d->m_docToOpen = 0; + } + + //kdDebug(30003) << "KoMainWindow::setRootDocument this = " << this << " doc = " << doc << endl; + QPtrList<KoView> oldRootViews = d->m_rootViews; + d->m_rootViews.clear(); + KoDocument *oldRootDoc = d->m_rootDoc; + + if ( oldRootDoc ) + oldRootDoc->removeShell( this ); + + d->m_rootDoc = doc; + + if ( doc ) + { + doc->setSelectable( false ); + //d->m_manager->addPart( doc, false ); // done by KoView::setPartManager + d->m_rootViews.append( doc->createView( d->m_splitter, "view" /*not unique, but better than unnamed*/ ) ); + d->m_rootViews.current()->setPartManager( d->m_manager ); + + d->m_rootViews.current()->show(); + // The addShell has been done already if using openURL + if ( !d->m_rootDoc->shells().contains( this ) ) + d->m_rootDoc->addShell( this ); + d->m_removeView->setEnabled(false); + d->m_orientation->setEnabled(false); + } + + bool enable = d->m_rootDoc != 0 ? true : false; + d->m_paDocInfo->setEnabled( enable ); + d->m_paSave->setEnabled( enable ); + d->m_paSaveAs->setEnabled( enable ); + d->m_importFile->setEnabled( enable ); + d->m_exportFile->setEnabled( enable ); + d->m_paPrint->setEnabled( enable ); + d->m_paPrintPreview->setEnabled( enable ); + d->m_sendfile->setEnabled( enable); + d->m_paCloseFile->setEnabled( enable); + updateCaption(); + + d->m_manager->setActivePart( d->m_rootDoc, d->m_rootViews.current() ); + + oldRootViews.setAutoDelete( true ); + oldRootViews.clear(); + + if ( oldRootDoc && oldRootDoc->viewCount() == 0 ) + { + //kdDebug(30003) << "No more views, deleting old doc " << oldRootDoc << endl; + delete oldRootDoc; + } +} + +void KoMainWindow::updateReloadFileAction(KoDocument *doc) +{ + d->m_reloadfile->setEnabled( doc && !doc->url().isEmpty() ); +} + +void KoMainWindow::updateVersionsFileAction(KoDocument *doc) +{ + //TODO activate it just when we save it in oasis file format + d->m_versionsfile->setEnabled( doc && !doc->url().isEmpty()&&doc->isModified()); +} + + +void KoMainWindow::setRootDocumentDirect( KoDocument *doc, const QPtrList<KoView> & views ) +{ + d->m_rootDoc = doc; + d->m_rootViews = views; + bool enable = d->m_rootDoc != 0 ? true : false; + d->m_paDocInfo->setEnabled( enable ); + d->m_paSave->setEnabled( enable ); + d->m_paSaveAs->setEnabled( enable ); + d->m_exportFile->setEnabled( enable ); + d->m_paPrint->setEnabled( enable ); + d->m_paPrintPreview->setEnabled( enable ); + d->m_sendfile->setEnabled( enable); + d->m_paCloseFile->setEnabled( enable ); +} + +void KoMainWindow::addRecentURL( const KURL& url ) +{ + kdDebug(30003) << "KoMainWindow::addRecentURL url=" << url.prettyURL() << endl; + // Add entry to recent documents list + // (call coming from KoDocument because it must work with cmd line, template dlg, file/open, etc.) + if ( !url.isEmpty() ) + { + bool ok = true; + if ( url.isLocalFile() ) + { + QString path = url.path( -1 ); + QStringList tmpDirs = KGlobal::dirs()->resourceDirs( "tmp" ); + for ( QStringList::Iterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it ) + if ( path.contains( *it ) ) + ok = false; // it's in the tmp resource + if ( ok ) + KRecentDocument::add(path); + } + else + KRecentDocument::add(url.url(-1), true); + + if ( ok ) + m_recent->addURL( url ); + saveRecentFiles(); + } +} + +void KoMainWindow::saveRecentFiles() +{ + // Save list of recent files + KConfig * config = instance() ? instance()->config() : KGlobal::config(); + kdDebug(30003) << this << " Saving recent files list into config. instance()=" << instance() << endl; + m_recent->saveEntries( config ); + config->sync(); + if (KMainWindow::memberList) + { + // Tell all windows to reload their list, after saving + // Doesn't work multi-process, but it's a start + KMainWindow *window = KMainWindow::memberList->first(); + for (; window; window = KMainWindow::memberList->next()) + static_cast<KoMainWindow *>(window)->reloadRecentFileList(); + } +} + +void KoMainWindow::reloadRecentFileList() +{ + KConfig * config = instance() ? instance()->config() : KGlobal::config(); + m_recent->loadEntries( config ); +} + +KoDocument* KoMainWindow::createDoc() const +{ + KoDocumentEntry entry = KoDocumentEntry( KoDocument::readNativeService() ); + return entry.createDoc(); +} + +void KoMainWindow::updateCaption() +{ + //kdDebug(30003) << "KoMainWindow::updateCaption()" << endl; + if ( !d->m_rootDoc ) + setCaption(QString::null); + else if ( rootDocument()->isCurrent() ) + { + QString caption; + // Get caption from document info (title(), in about page) + if ( rootDocument()->documentInfo() ) + { + KoDocumentInfoPage * page = rootDocument()->documentInfo()->page( QString::fromLatin1("about") ); + if (page) + caption = static_cast<KoDocumentInfoAbout *>(page)->title(); + } + const QString url = rootDocument()->url().prettyURL( 0, KURL::StripFileProtocol ); + if ( !caption.isEmpty() && !url.isEmpty() ) + caption = QString( "%1 - %2" ).arg( caption ).arg( url ); + else if ( caption.isEmpty() ) + caption = url; + + setCaption( caption, rootDocument()->isModified() ); + if ( !rootDocument()->url().fileName(false).isEmpty() ) + d->m_paSave->setToolTip( i18n("Save as %1").arg(rootDocument()->url().fileName(false)) ); + else + d->m_paSave->setToolTip( i18n("Save") ); + } +} + +void KoMainWindow::updateCaption( QString caption, bool mod ) +{ + //kdDebug(30003)<<"KoMainWindow::updateCaption("<<caption<<","<<mod<<")"<<endl; + setCaption( caption, mod ); +} + +KoDocument *KoMainWindow::rootDocument() const +{ + return d->m_rootDoc; +} + +KoView *KoMainWindow::rootView() const +{ + if(d->m_rootViews.find(d->m_activeView)!=-1) + return d->m_activeView; + return d->m_rootViews.first(); +} + +KParts::PartManager *KoMainWindow::partManager() +{ + return d->m_manager; +} + +bool KoMainWindow::openDocument( const KURL & url ) +{ + if ( !KIO::NetAccess::exists(url,true,0) ) + { + KMessageBox::error(0L, i18n("The file %1 does not exist.").arg(url.url()) ); + m_recent->removeURL(url); //remove the file from the recent-opened-file-list + saveRecentFiles(); + return false; + } + return openDocumentInternal( url ); +} + +// (not virtual) +bool KoMainWindow::openDocument( KoDocument *newdoc, const KURL & url ) +{ + if (!KIO::NetAccess::exists(url,true,0) ) + { + if (!newdoc->checkAutoSaveFile()) + { + newdoc->initEmpty(); //create an emtpy document + } + + setRootDocument( newdoc ); + newdoc->setURL(url); + QString mime = KMimeType::findByURL(url)->name(); + if ( mime.isEmpty() || mime == KMimeType::defaultMimeType() ) + mime = newdoc->nativeFormatMimeType(); + if ( url.isLocalFile() ) // workaround for kde<=3.3 kparts bug, fixed for 3.4 + newdoc->setFile(url.path()); + newdoc->setMimeTypeAfterLoading( mime ); + updateCaption(); + return true; + } + return openDocumentInternal( url, newdoc ); +} + +// ## If you modify anything here, please check KoShellWindow::openDocumentInternal +bool KoMainWindow::openDocumentInternal( const KURL & url, KoDocument *newdoc ) +{ + //kdDebug(30003) << "KoMainWindow::openDocument " << url.url() << endl; + + if ( !newdoc ) + newdoc = createDoc(); + if ( !newdoc ) + return false; + + d->m_firstTime=true; + connect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); + connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); + connect(newdoc, SIGNAL(canceled( const QString & )), this, SLOT(slotLoadCanceled( const QString & ))); + newdoc->addShell( this ); // used by openURL + bool openRet = (!isImporting ()) ? newdoc->openURL(url) : newdoc->import(url); + if(!openRet) + { + newdoc->removeShell(this); + delete newdoc; + return false; + } + updateReloadFileAction(newdoc); + updateVersionsFileAction( newdoc ); + return true; +} + +// Separate from openDocument to handle async loading (remote URLs) +void KoMainWindow::slotLoadCompleted() +{ + kdDebug(30003) << "KoMainWindow::slotLoadCompleted" << endl; + KoDocument* doc = rootDocument(); + KoDocument* newdoc = (KoDocument *)(sender()); + + if ( doc && doc->isEmpty() && !doc->isEmbedded() ) + { + // Replace current empty document + setRootDocument( newdoc ); + } + else if ( doc && !doc->isEmpty() ) + { + // Open in a new shell + // (Note : could create the shell first and the doc next for this + // particular case, that would give a better user feedback...) + KoMainWindow *s = new KoMainWindow( newdoc->instance() ); + s->show(); + newdoc->removeShell( this ); + s->setRootDocument( newdoc ); + } + else + { + // We had no document, set the new one + setRootDocument( newdoc ); + } + disconnect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); + disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); + disconnect(newdoc, SIGNAL(canceled( const QString & )), this, SLOT(slotLoadCanceled( const QString & ))); +} + +void KoMainWindow::slotLoadCanceled( const QString & errMsg ) +{ + kdDebug(30003) << "KoMainWindow::slotLoadCanceled" << endl; + if ( !errMsg.isEmpty() ) // empty when canceled by user + KMessageBox::error( this, errMsg ); + // ... can't delete the document, it's the one who emitted the signal... + + KoDocument* newdoc = (KoDocument *)(sender()); + disconnect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); + disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); + disconnect(newdoc, SIGNAL(canceled( const QString & )), this, SLOT(slotLoadCanceled( const QString & ))); + + newdoc->removeShell(this); + delete newdoc; +} + +void KoMainWindow::slotSaveCanceled( const QString &errMsg ) +{ + kdDebug(30003) << "KoMainWindow::slotSaveCanceled" << endl; + if ( !errMsg.isEmpty() ) // empty when canceled by user + KMessageBox::error( this, errMsg ); + slotSaveCompleted(); +} + +void KoMainWindow::slotSaveCompleted() +{ + kdDebug(30003) << "KoMainWindow::slotSaveCompleted" << endl; + KoDocument* pDoc = (KoDocument *)(sender()); + disconnect(pDoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); + disconnect(pDoc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); + disconnect(pDoc, SIGNAL(canceled( const QString & )), + this, SLOT(slotSaveCanceled( const QString & ))); +} + +// returns true if we should save, false otherwise. +bool KoMainWindow::exportConfirmation( const QCString &outputFormat ) +{ + if (!rootDocument()->wantExportConfirmation()) return true; + KMimeType::Ptr mime = KMimeType::mimeType( outputFormat ); + + const bool neverHeardOfIt = ( mime->name() == KMimeType::defaultMimeType() ); + QString comment = neverHeardOfIt ? + i18n( "%1 (unknown file type)" ).arg( outputFormat ) + : mime->comment(); + + // Warn the user + int ret; + if (!isExporting ()) // File --> Save + { + ret = KMessageBox::warningContinueCancel + ( + this, + i18n( "<qt>Saving as a %1 may result in some loss of formatting." + "<p>Do you still want to save in this format?</qt>" ) + .arg( QString( "<b>%1</b>" ).arg( comment ) ), // in case we want to remove the bold later + i18n( "Confirm Save" ), + KStdGuiItem::save (), + "NonNativeSaveConfirmation", + true + ); + } + else // File --> Export + { + ret = KMessageBox::warningContinueCancel + ( + this, + i18n( "<qt>Exporting as a %1 may result in some loss of formatting." + "<p>Do you still want to export to this format?</qt>" ) + .arg( QString( "<b>%1</b>" ).arg( comment ) ), // in case we want to remove the bold later + i18n( "Confirm Export" ), + i18n ("Export"), + "NonNativeExportConfirmation", // different to the one used for Save (above) + true + ); + } + + return (ret == KMessageBox::Continue); +} + +bool KoMainWindow::saveDocument( bool saveas, bool silent ) +{ + KoDocument* pDoc = rootDocument(); + if(!pDoc) + return true; + + bool reset_url; + if ( pDoc->url().isEmpty() ) + { + emit saveDialogShown(); + reset_url = true; + saveas = true; + } + else + reset_url = false; + + connect(pDoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); + connect(pDoc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); + connect(pDoc, SIGNAL(canceled( const QString & )), + this, SLOT(slotSaveCanceled( const QString & ))); + + KURL oldURL = pDoc->url(); + QString oldFile = pDoc->file(); + QCString _native_format = pDoc->nativeFormatMimeType(); + QCString oldOutputFormat = pDoc->outputMimeType(); + int oldSpecialOutputFlag = pDoc->specialOutputFlag(); + KURL suggestedURL = pDoc->url(); + + QStringList mimeFilter = KoFilterManager::mimeFilter( _native_format, KoFilterManager::Export, pDoc->extraNativeMimeTypes() ); + if (mimeFilter.findIndex (oldOutputFormat) < 0 && !isExporting()) + { + kdDebug(30003) << "KoMainWindow::saveDocument no export filter for '" << oldOutputFormat << "'" << endl; + + // --- don't setOutputMimeType in case the user cancels the Save As + // dialog and then tries to just plain Save --- + + // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) + QString suggestedFilename = suggestedURL.fileName (); + if ( !suggestedFilename.isEmpty () ) // ".kwd" looks strange for a name + { + int c = suggestedFilename.findRev ('.'); + + KMimeType::Ptr mime = KMimeType::mimeType( _native_format ); + QString ext = mime->property( "X-KDE-NativeExtension" ).toString(); + if (!ext.isEmpty ()) + { + if (c < 0) + suggestedFilename += ext; + else + suggestedFilename = suggestedFilename.left (c) + ext; + } + else // current filename extension wrong anyway + { + if (c > 0) + { + // this assumes that a . signifies an extension, not just a . + suggestedFilename = suggestedFilename.left (c); + } + } + + suggestedURL.setFileName (suggestedFilename); + } + + // force the user to choose outputMimeType + saveas = true; + } + + bool ret = false; + + if ( pDoc->url().isEmpty() || saveas ) + { + // if you're just File/Save As'ing to change filter options you + // don't want to be reminded about overwriting files etc. + bool justChangingFilterOptions = false; + + KoFileDialog *dialog = new KoFileDialog( (isExporting() && !d->m_lastExportURL.isEmpty() )? d->m_lastExportURL.url () : suggestedURL.url (), + QString::null, this, "file dialog", true); + + if (!isExporting()) + dialog->setCaption( i18n("Save Document As") ); + else + dialog->setCaption( i18n("Export Document As") ); + + dialog->setOperationMode( KFileDialog::Saving ); + dialog->setSpecialMimeFilter( mimeFilter, + isExporting() ? d->m_lastExportFormat : pDoc->mimeType(), + isExporting() ? d->m_lastExportSpecialOutputFlag : oldSpecialOutputFlag, + _native_format, + pDoc->supportedSpecialFormats() ); + + KURL newURL; + QCString outputFormat = _native_format; + int specialOutputFlag = 0; + bool bOk; + do { + bOk=true; + if(dialog->exec()==QDialog::Accepted) { + newURL=dialog->selectedURL(); + outputFormat=dialog->currentMimeFilter().latin1(); + specialOutputFlag = dialog->specialEntrySelected(); + kdDebug(30003) << "KoMainWindow::saveDocument outputFormat = " << outputFormat << endl; + + if (!isExporting()) + justChangingFilterOptions = (newURL == pDoc->url()) && + (outputFormat == pDoc->mimeType()) && + (specialOutputFlag == oldSpecialOutputFlag); + else + justChangingFilterOptions = (newURL == d->m_lastExportURL) && + (outputFormat == d->m_lastExportFormat) && + (specialOutputFlag == d->m_lastExportSpecialOutputFlag); + } + else + { + bOk = false; + break; + } + + if ( newURL.isEmpty() ) + { + bOk = false; + break; + } + + // adjust URL before doing checks on whether the file exists. + if ( specialOutputFlag == KoDocument::SaveAsDirectoryStore ) { + QString fileName = newURL.fileName(); + if ( fileName != "content.xml" ) { + newURL.addPath( "content.xml" ); + } + } + + // this file exists and we are not just clicking "Save As" to change filter options + // => ask for confirmation + if ( KIO::NetAccess::exists( newURL, false /*will write*/, this ) && !justChangingFilterOptions ) + { + bOk = KMessageBox::questionYesNo( this, + i18n("A document with this name already exists.\n"\ + "Do you want to overwrite it?"), + i18n("Warning") ) == KMessageBox::Yes; + } + } while ( !bOk ); + + delete dialog; + + if (bOk) + { + bool wantToSave = true; + + // don't change this line unless you know what you're doing :) + if (!justChangingFilterOptions || pDoc->confirmNonNativeSave (isExporting ())) { + if ( !pDoc->isNativeFormat( outputFormat ) ) + wantToSave = exportConfirmation( outputFormat ); + } + + if (wantToSave) + { + // + // Note: + // If the user is stupid enough to Export to the current URL, + // we do _not_ change this operation into a Save As. Reasons + // follow: + // + // 1. A check like "isExporting() && oldURL == newURL" + // doesn't _always_ work on case-insensitive filesystems + // and inconsistent behaviour is bad. + // 2. It is probably not a good idea to change pDoc->mimeType + // and friends because the next time the user File/Save's, + // (not Save As) they won't be expecting that they are + // using their File/Export settings + // + // As a bad side-effect of this, the modified flag will not + // be updated and it is possible that what is currently on + // their screen is not what is stored on disk (through loss + // of formatting). But if you are dumb enough to change + // mimetype but not the filename, then arguably, _you_ are + // the "bug" :) + // + // - Clarence + // + + + pDoc->setOutputMimeType( outputFormat, specialOutputFlag ); + if (!isExporting ()) // Save As + { + ret = pDoc->saveAs( newURL ); + + if (ret) + { + kdDebug(30003) << "Successful Save As!" << endl; + addRecentURL( newURL ); + } + else + { + kdDebug(30003) << "Failed Save As!" << endl; + pDoc->setURL( oldURL ), pDoc->setFile( oldFile ); + pDoc->setOutputMimeType( oldOutputFormat, oldSpecialOutputFlag ); + } + } + else // Export + { + ret = pDoc->exp0rt( newURL ); + + if (ret) + { + // a few file dialog convenience things + d->m_lastExportURL = newURL; + d->m_lastExportFormat = outputFormat; + d->m_lastExportSpecialOutputFlag = specialOutputFlag; + } + + // always restore output format + pDoc->setOutputMimeType( oldOutputFormat, oldSpecialOutputFlag ); + } + } // if (wantToSave) + else + ret = false; + } // if (bOk) + else + ret = false; + } + else { // saving + bool needConfirm = pDoc->confirmNonNativeSave( false ) && + !pDoc->isNativeFormat( oldOutputFormat ); + if (!needConfirm || + (needConfirm && exportConfirmation( oldOutputFormat /* not so old :) */ )) + ) + { + // be sure pDoc has the correct outputMimeType! + ret = pDoc->save(); + + if (!ret) + { + kdDebug(30003) << "Failed Save!" << endl; + pDoc->setURL( oldURL ), pDoc->setFile( oldFile ); + } + } + else + ret = false; + } + +// Now that there's a File/Export option, this is no longer necessary. +// If you continue to use File/Save to export to a foreign format, +// this signals your intention to continue working in a foreign format. +// You have already been warned by the DoNotAskAgain exportConfirmation +// about losing formatting when you first saved so don't set modified +// here or else it will be reported as a bug by some MSOffice user. +// You have been warned! Do not click DoNotAskAgain!!! +#if 0 + if (ret && !isExporting()) + { + // When exporting to a non-native format, we don't reset modified. + // This way the user will be reminded to save it again in the native format, + // if he/she doesn't want to lose formatting. + if ( wasModified && pDoc->outputMimeType() != _native_format ) + pDoc->setModified( true ); + } +#endif + + if (!ret && reset_url) + pDoc->resetURL(); //clean the suggested filename as the save dialog was rejected + else if (! silent) // don't let the document change the window caption + pDoc->setTitleModified(); + return ret; +} + +void KoMainWindow::closeEvent(QCloseEvent *e) { + if(queryClose()) { + saveWindowSettings(); + setRootDocument(0L); + KParts::MainWindow::closeEvent(e); + } +} + +void KoMainWindow::saveWindowSettings() +{ + if (d->m_windowSizeDirty && rootDocument()) + { + // Save window size into the config file of our instance + instance()->config()->setGroup( "MainWindow" ); + //kdDebug(30003) << "KoMainWindow::saveWindowSettings" << endl; + saveWindowSize( instance()->config() ); + d->m_windowSizeDirty = false; + // Save toolbar position into the config file of the app, under the doc's instance name + //kdDebug(30003) << "KoMainWindow::closeEvent -> saveMainWindowSettings rootdoc's instance=" << rootDocument()->instance()->instanceName() << endl; + saveMainWindowSettings( KGlobal::config(), rootDocument()->instance()->instanceName() ); + KGlobal::config()->sync(); + resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down + } +} + +void KoMainWindow::resizeEvent( QResizeEvent * e ) +{ + d->m_windowSizeDirty = true; + KParts::MainWindow::resizeEvent( e ); +} + +bool KoMainWindow::queryClose() +{ + if ( rootDocument() == 0 ) + return true; + //kdDebug(30003) << "KoMainWindow::queryClose() viewcount=" << rootDocument()->viewCount() + // << " shellcount=" << rootDocument()->shellCount() << endl; + if ( !d->m_forQuit && rootDocument()->shellCount() > 1 ) + // there are more open, and we are closing just one, so no problem for closing + return true; + + // see DTOR for a descr. of the test + if ( d->m_rootDoc->isEmbedded() ) + return true; + + // main doc + internally stored child documents + if ( d->m_rootDoc->isModified() ) + { + QString name; + if ( rootDocument()->documentInfo() ) + { + name = rootDocument()->documentInfo()->title(); + } + if ( name.isEmpty() ) + name = rootDocument()->url().fileName(); + + if ( name.isEmpty() ) + name = i18n( "Untitled" ); + + int res = KMessageBox::warningYesNoCancel( this, + i18n( "<p>The document <b>'%1'</b> has been modified.</p><p>Do you want to save it?</p>" ).arg(name), + QString::null, + KStdGuiItem::save(), + KStdGuiItem::discard()); + + switch(res) { + case KMessageBox::Yes : { + d->m_rootDoc->setDoNotSaveExtDoc(); // external docs are saved later + bool isNative = ( d->m_rootDoc->outputMimeType() == d->m_rootDoc->nativeFormatMimeType() ); + if (! saveDocument( !isNative ) ) + return false; + break; + } + case KMessageBox::No : + rootDocument()->removeAutoSaveFiles(); + rootDocument()->setModified( false ); // Now when queryClose() is called by closeEvent it won't do anything. + break; + default : // case KMessageBox::Cancel : + return false; + } + } + + if ( d->m_rootDoc->queryCloseExternalChildren() == KMessageBox::Cancel ) + { + return false; + } + + return true; +} + +// Helper method for slotFileNew and slotFileClose +void KoMainWindow::chooseNewDocument( int /*KoDocument::InitDocFlags*/ initDocFlags ) +{ + KoDocument* doc = rootDocument(); + KoDocument *newdoc = createDoc(); + + if ( !newdoc ) + return; + + //FIXME: This needs to be handled differently + connect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); + disconnect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); + + if ( ( !doc && ( initDocFlags == KoDocument::InitDocFileNew ) ) || ( doc && !doc->isEmpty() ) ) + { + KoMainWindow *s = new KoMainWindow( newdoc->instance() ); + s->show(); + newdoc->addShell( s ); + newdoc->showStartUpWidget( s, true /*Always show widget*/ ); + return; + } + + if( doc ) { + setRootDocument( 0 ); + delete d->m_rootDoc; + d->m_rootDoc = 0; + } + + newdoc->addShell( this ); + newdoc->showStartUpWidget( this, true /*Always show widget*/ ); +} + +void KoMainWindow::slotFileNew() +{ + chooseNewDocument( KoDocument::InitDocFileNew ); +} + +void KoMainWindow::slotFileOpen() +{ + KFileDialog *dialog = new KFileDialog(":OpenDialog", QString::null, this, "file dialog", true); + if (!isImporting()) + dialog->setCaption( i18n("Open Document") ); + else + dialog->setCaption( i18n("Import Document") ); + + // The few lines below need to be kept in sync with KoTemplateChooseDia::setupFileDialog + const QStringList mimeFilter = KoFilterManager::mimeFilter( KoDocument::readNativeFormatMimeType(), + KoFilterManager::Import, + KoDocument::readExtraNativeMimeTypes() ); + dialog->setMimeFilter( mimeFilter ); + if(dialog->exec()!=QDialog::Accepted) { + delete dialog; + return; + } + KURL url( dialog->selectedURL() ); + delete dialog; + + if ( url.isEmpty() ) + return; + + (void) openDocument( url ); +} + +void KoMainWindow::slotFileOpenRecent( const KURL & url ) +{ + (void) openDocument( url ); +} + +void KoMainWindow::slotFileSave() +{ + if ( saveDocument() ) + emit documentSaved(); +} + +void KoMainWindow::slotFileSaveAs() +{ + if ( saveDocument( true ) ) + emit documentSaved(); +} + +void KoMainWindow::slotDocumentInfo() +{ + if ( !rootDocument() ) + return; + + KoDocumentInfo *docInfo = rootDocument()->documentInfo(); + + if ( !docInfo ) + return; + + KoDocumentInfoDlg *dlg = new KoDocumentInfoDlg( docInfo, this, "documentInfoDlg" ); + if ( dlg->exec() ) + { + dlg->save(); + rootDocument()->setModified( true ); + rootDocument()->setTitleModified(); + } + + delete dlg; +} + +void KoMainWindow::slotFileClose() +{ + if (queryClose()) + { + saveWindowSettings(); + setRootDocument( 0 ); // don't delete this shell when deleting the document + delete d->m_rootDoc; + d->m_rootDoc = 0; + chooseNewDocument( KoDocument::InitDocFileClose ); + } +} + +void KoMainWindow::slotFileQuit() +{ + close(); +} + +void KoMainWindow::print(bool quick) { + if ( !rootView() ) + { + kdDebug(30003) << "KoMainWindow::slotFilePrint : No root view!" << endl; + return; + } + + KPrinter printer( true /*, QPrinter::HighResolution*/ ); + QString title = rootView()->koDocument()->documentInfo()->title(); + QString fileName = rootView()->koDocument()->url().fileName(); + + // strip off the native extension (I don't want foobar.kwd.ps when printing into a file) + KMimeType::Ptr mime = KMimeType::mimeType( rootView()->koDocument()->outputMimeType() ); + if ( mime ) { + QString extension = mime->property( "X-KDE-NativeExtension" ).toString(); + + if ( fileName.endsWith( extension ) ) + fileName.truncate( fileName.length() - extension.length() ); + } + + if ( title.isEmpty() ) + title = fileName; + if ( title.isEmpty() ) { + // #139905 - breaks message freeze though + //const QString programName = instance()->aboutData() ? instance()->aboutData()->programName() : instance()->instanceName(); + //title = i18n("%1 unsaved document (%2)").arg(programName).arg(KGlobal::locale()->formatDate(QDate::currentDate(), true/*short*/)); + } + printer.setDocName( title ); + printer.setDocFileName( fileName ); + printer.setDocDirectory( rootView()->koDocument()->url().directory() ); + + // ### TODO: apply global koffice settings here + + rootView()->setupPrinter( printer ); + + if ( quick || printer.setup( this ) ) + rootView()->print( printer ); +} + + +void KoMainWindow::slotFilePrint() +{ + print(false); +} + +void KoMainWindow::slotFilePrintPreview() +{ + if ( !rootView() ) + { + kdWarning() << "KoMainWindow::slotFilePrint : No root view!" << endl; + return; + } + KPrinter printer( false ); + KTempFile tmpFile; + // The temp file is deleted by KoPrintPreview + + // This line has to be before setupPrinter to let the apps decide what to + // print and what not (if they want to :) + printer.setFromTo( printer.minPage(), printer.maxPage() ); + printer.setPreviewOnly( true ); + rootView()->setupPrinter( printer ); + + QString oldFileName = printer.outputFileName(); + printer.setOutputFileName( tmpFile.name() ); + int oldNumCopies = printer.numCopies(); + printer.setNumCopies( 1 ); + // Disable kdeprint's own preview, we'd get two. This shows that KPrinter needs + // a "don't use the previous settings" mode. The current way is really too much of a hack. + QString oldKDEPreview = printer.option( "kde-preview" ); + printer.setOption( "kde-preview", "0" ); + + rootView()->print(printer); + //KoPrintPreview::preview(this, "KoPrintPreviewDialog", tmpFile.name()); + + // Restore previous values + printer.setOutputFileName( oldFileName ); + printer.setNumCopies( oldNumCopies ); + printer.setOption( "kde-preview", oldKDEPreview ); +} + +void KoMainWindow::slotConfigureKeys() +{ + guiFactory()->configureShortcuts(); +} + +void KoMainWindow::slotConfigureToolbars() +{ + if (rootDocument()) + saveMainWindowSettings( KGlobal::config(), rootDocument()->instance()->instanceName() ); + KEditToolbar edit(factory(), this); + connect(&edit,SIGNAL(newToolbarConfig()),this,SLOT(slotNewToolbarConfig())); + (void) edit.exec(); +} + +void KoMainWindow::slotNewToolbarConfig() +{ + if (rootDocument()) + applyMainWindowSettings( KGlobal::config(), rootDocument()->instance()->instanceName() ); + KXMLGUIFactory *factory = guiFactory(); + + // Check if there's an active view + if( !d->m_activeView ) + return; + + // This gets plugged in even for embedded views + factory->plugActionList(d->m_activeView, "view_closeallviews", + d->m_veryHackyActionList); + + // This one only for root views + if(d->m_rootViews.findRef(d->m_activeView)!=-1) + factory->plugActionList(d->m_activeView, "view_split", + d->m_splitViewActionList ); + plugActionList( "toolbarlist", d->m_toolbarList ); +} + +void KoMainWindow::slotToolbarToggled( bool toggle ) +{ + //kdDebug(30003) << "KoMainWindow::slotToolbarToggled " << sender()->name() << " toggle=" << true << endl; + // The action (sender) and the toolbar have the same name + KToolBar * bar = toolBar( sender()->name() ); + if (bar) + { + if (toggle) + bar->show(); + else + bar->hide(); + + if (rootDocument()) + saveMainWindowSettings( KGlobal::config(), rootDocument()->instance()->instanceName() ); + } + else + kdWarning(30003) << "slotToolbarToggled : Toolbar " << sender()->name() << " not found!" << endl; +} + +bool KoMainWindow::toolbarIsVisible(const char *tbName) +{ + QWidget *tb = toolBar( tbName); + return !tb->isHidden(); +} + +void KoMainWindow::showToolbar( const char * tbName, bool shown ) +{ + QWidget * tb = toolBar( tbName ); + if ( !tb ) + { + kdWarning(30003) << "KoMainWindow: toolbar " << tbName << " not found." << endl; + return; + } + if ( shown ) + tb->show(); + else + tb->hide(); + + // Update the action appropriately + QPtrListIterator<KAction> it( d->m_toolbarList ); + for ( ; it.current() ; ++it ) + if ( !strcmp( it.current()->name(), tbName ) ) + { + //kdDebug(30003) << "KoMainWindow::showToolbar setChecked " << shown << endl; + static_cast<KToggleAction *>(it.current())->setChecked( shown ); + break; + } +} + +void KoMainWindow::slotSplitView() { + d->m_splitted=true; + d->m_rootViews.append(d->m_rootDoc->createView(d->m_splitter, "splitted-view")); + d->m_rootViews.current()->show(); + d->m_rootViews.current()->setPartManager( d->m_manager ); + d->m_manager->setActivePart( d->m_rootDoc, d->m_rootViews.current() ); + d->m_removeView->setEnabled(true); + d->m_orientation->setEnabled(true); +} + +void KoMainWindow::slotCloseAllViews() { + + // Attention: Very touchy code... you know what you're doing? Goooood :) + d->m_forQuit=true; + if(queryClose()) { + // In case the document is embedded we close all open "extra-shells" + if(d->m_rootDoc && d->m_rootDoc->isEmbedded()) { + hide(); + d->m_rootDoc->removeShell(this); + QPtrListIterator<KoMainWindow> it(d->m_rootDoc->shells()); + while (it.current()) { + it.current()->hide(); + delete it.current(); // this updates the lists' current pointer and thus + // the iterator (the shell dtor calls removeShell) + d->m_rootDoc=0; + } + } + // not embedded -> destroy the document and all shells/views ;) + else + setRootDocument( 0L ); + close(); // close this window (and quit the app if necessary) + } + d->m_forQuit=false; +} + +void KoMainWindow::slotRemoveView() { + KoView *view; + if(d->m_rootViews.findRef(d->m_activeView)!=-1) + view=d->m_rootViews.current(); + else + view=d->m_rootViews.first(); + view->hide(); + if ( !d->m_rootViews.removeRef(view) ) + kdWarning() << "view not found in d->m_rootViews!" << endl; + + if(d->m_rootViews.count()==1) + { + d->m_removeView->setEnabled(false); + d->m_orientation->setEnabled(false); + } + // Prevent the view's destroyed() signal from triggering GUI rebuilding (too early) + d->m_manager->setActivePart( 0, 0 ); + + delete view; + view=0L; + + d->m_rootViews.first()->setPartManager( d->m_manager ); + d->m_manager->setActivePart( d->m_rootDoc, d->m_rootViews.first() ); + + if(d->m_rootViews.count()==1) + d->m_splitted=false; +} + +void KoMainWindow::slotSetOrientation() { + d->m_splitter->setOrientation(static_cast<Qt::Orientation> + (d->m_orientation->currentItem())); +} + +void KoMainWindow::slotProgress(int value) { + //kdDebug(30003) << "KoMainWindow::slotProgress " << value << endl; + if(value==-1) { + if ( d->m_progress ) + { + statusBar()->removeWidget(d->m_progress); + delete d->m_progress; + d->m_progress=0L; + } + d->m_firstTime=true; + return; + } + if(d->m_firstTime) + { + // The statusbar might not even be created yet. + // So check for that first, and create it if necessary + QObjectList *l = queryList( "QStatusBar" ); + if ( !l || !l->first() ) { + statusBar()->show(); + QApplication::sendPostedEvents( this, QEvent::ChildInserted ); + setUpLayout(); + } + delete l; + + if ( d->m_progress ) + { + statusBar()->removeWidget(d->m_progress); + delete d->m_progress; + d->m_progress=0L; + } + statusBar()->setMaximumHeight(statusBar()->height()); + d->m_progress=new KProgress(statusBar()); + //d->m_progress->setMaximumHeight(statusBar()->height()); + statusBar()->addWidget( d->m_progress, 0, true ); + d->m_progress->show(); + d->m_firstTime=false; + } + d->m_progress->setProgress(value); + kapp->processEvents(); +} + + +void KoMainWindow::slotActivePartChanged( KParts::Part *newPart ) +{ + + // This looks very much like KParts::MainWindow::createGUI, but we have + // to reimplement it because it works with an active part, whereas we work + // with an active view _and_ an active part, depending for what. + // Both are KXMLGUIClients, but e.g. the plugin query needs a QObject. + //kdDebug(30003) << "KoMainWindow::slotActivePartChanged( Part * newPart) newPart = " << newPart << endl; + //kdDebug(30003) << "current active part is " << d->m_activePart << endl; + + if ( d->m_activePart && d->m_activePart == newPart && !d->m_splitted ) + { + //kdDebug(30003) << "no need to change the GUI" << endl; + return; + } + + KXMLGUIFactory *factory = guiFactory(); + + setUpdatesEnabled( false ); + + if ( d->m_activeView ) + { + KParts::GUIActivateEvent ev( false ); + QApplication::sendEvent( d->m_activePart, &ev ); + QApplication::sendEvent( d->m_activeView, &ev ); + + + factory->removeClient( d->m_activeView ); + + unplugActionList( "toolbarlist" ); + d->m_toolbarList.clear(); // deletes the actions + } + + if ( !d->bMainWindowGUIBuilt ) + { + // Load mainwindow plugins + KParts::Plugin::loadPlugins( this, this, instance(), true ); + createShellGUI(); + } + + if ( newPart && d->m_manager->activeWidget() && d->m_manager->activeWidget()->inherits( "KoView" ) ) + { + d->m_activeView = (KoView *)d->m_manager->activeWidget(); + d->m_activePart = newPart; + //kdDebug(30003) << "new active part is " << d->m_activePart << endl; + + factory->addClient( d->m_activeView ); + + + // This gets plugged in even for embedded views + factory->plugActionList(d->m_activeView, "view_closeallviews", + d->m_veryHackyActionList); + // This one only for root views + if(d->m_rootViews.findRef(d->m_activeView)!=-1) + factory->plugActionList(d->m_activeView, "view_split", d->m_splitViewActionList ); + + // Position and show toolbars according to user's preference + setAutoSaveSettings( newPart->instance()->instanceName(), false ); + + // Create and plug toolbar list for Settings menu + //QPtrListIterator<KToolBar> it = toolBarIterator(); + QPtrList<QWidget> toolBarList = factory->containers( "ToolBar" ); + QPtrListIterator<QWidget> it( toolBarList ); + for ( ; it.current() ; ++it ) + { + if ( it.current()->inherits("KToolBar") ) + { + KToolBar * tb = static_cast<KToolBar *>(it.current()); + KToggleAction * act = new KToggleAction( i18n("Show %1 Toolbar").arg( tb->text() ), 0, + actionCollection(), tb->name() ); + act->setCheckedState(i18n("Hide %1 Toolbar").arg( tb->text() )); + connect( act, SIGNAL( toggled( bool ) ), this, SLOT( slotToolbarToggled( bool ) ) ); + act->setChecked ( !tb->isHidden() ); + d->m_toolbarList.append( act ); + } + else + kdWarning(30003) << "Toolbar list contains a " << it.current()->className() << " which is not a toolbar!" << endl; + } + plugActionList( "toolbarlist", d->m_toolbarList ); + + // Send the GUIActivateEvent only now, since it might show/hide toolbars too + // (and this has priority over applyMainWindowSettings) + KParts::GUIActivateEvent ev( true ); + QApplication::sendEvent( d->m_activePart, &ev ); + QApplication::sendEvent( d->m_activeView, &ev ); + } + else + { + d->m_activeView = 0L; + d->m_activePart = 0L; + } + setUpdatesEnabled( true ); +} + +QLabel * KoMainWindow::statusBarLabel() +{ + if ( !d->statusBarLabel ) + { + d->statusBarLabel = new QLabel( statusBar() ); + statusBar()->addWidget( d->statusBarLabel, 1, true ); + } + return d->statusBarLabel; +} + +void KoMainWindow::setMaxRecentItems(uint _number) +{ + m_recent->setMaxItems( _number ); +} + +DCOPObject * KoMainWindow::dcopObject() +{ + if ( !d->m_dcopObject ) + { + d->m_dcopObject = new KoMainWindowIface( this ); + } + + return d->m_dcopObject; +} + +void KoMainWindow::slotEmailFile() +{ + if (!rootDocument()) + return; + + // Subject = Document file name + // Attachment = The current file + // Message Body = The current document in HTML export? <-- This may be an option. + QString theSubject; + QStringList urls; + QString fileURL; + if (rootDocument()->url ().isEmpty () || + rootDocument()->isModified()) + { + //Save the file as a temporary file + bool const tmp_modified = rootDocument()->isModified(); + KURL const tmp_url = rootDocument()->url(); + QCString const tmp_mimetype = rootDocument()->outputMimeType(); + KTempFile tmpfile; //TODO: The temorary file should be deleted when the mail program is closed + KURL u; + u.setPath(tmpfile.name()); + rootDocument()->setURL(u); + rootDocument()->setModified(true); + rootDocument()->setOutputMimeType(rootDocument()->nativeFormatMimeType()); + + saveDocument(false, true); + + fileURL = tmpfile.name(); + theSubject = i18n("Document"); + urls.append( fileURL ); + + rootDocument()->setURL(tmp_url); + rootDocument()->setModified(tmp_modified); + rootDocument()->setOutputMimeType(tmp_mimetype); + } + else + { + fileURL = rootDocument()->url().url(); + theSubject = i18n("Document - %1").arg(rootDocument()->url().fileName(false)); + urls.append( fileURL ); + } + + kdDebug(30003) << "(" << fileURL <<")" << endl; + + if (!fileURL.isEmpty()) + { + kapp->invokeMailer(QString::null, QString::null, QString::null, theSubject, + QString::null, //body + QString::null, + urls); // attachments + } +} + +void KoMainWindow::slotVersionsFile() +{ + KoVersionDialog *dlg = new KoVersionDialog( this ); + dlg->exec(); + delete dlg; +} + +void KoMainWindow::slotReloadFile() +{ + KoDocument* pDoc = rootDocument(); + if(!pDoc || pDoc->url().isEmpty() || !pDoc->isModified()) + return; + + bool bOk = KMessageBox::questionYesNo( this, + i18n("You will lose all your changes!\n" + "Do you want to continue?"), + i18n("Warning") ) == KMessageBox::Yes; + if ( !bOk ) + return; + + KURL url = pDoc->url(); + if ( pDoc && !pDoc->isEmpty() ) + { + setRootDocument( 0L ); // don't delete this shell when deleting the document + delete d->m_rootDoc; + d->m_rootDoc = 0L; + } + openDocument( url ); + return; + +} + +void KoMainWindow::slotImportFile() +{ + kdDebug(30003) << "slotImportFile()" << endl; + + d->m_isImporting = true; + slotFileOpen(); + d->m_isImporting = false; +} + +void KoMainWindow::slotExportFile() +{ + kdDebug(30003) << "slotExportFile()" << endl; + + d->m_isExporting = true; + slotFileSaveAs(); + d->m_isExporting = false; +} + +bool KoMainWindow::isImporting() const +{ + return d->m_isImporting; +} + +bool KoMainWindow::isExporting() const +{ + return d->m_isExporting; +} + +void KoMainWindow::setDocToOpen( KoDocument *doc ) +{ + d->m_docToOpen = doc; +} + +#include <KoMainWindow.moc> diff --git a/lib/kofficecore/KoMainWindow.h b/lib/kofficecore/KoMainWindow.h new file mode 100644 index 00000000..2b738db9 --- /dev/null +++ b/lib/kofficecore/KoMainWindow.h @@ -0,0 +1,387 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> + Copyright (C) 2000-2004 David Faure <faure@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 __ko_main_window_h__ +#define __ko_main_window_h__ + +#include <kparts/mainwindow.h> +#include <kfiledialog.h> +#include <koffice_export.h> +#include "KoDocument.h" + +class QLabel; +class KoView; +class KoMainWindowPrivate; +class KURL; +class KRecentFilesAction; +class KoFilterManager; +class DCOPObject; + +namespace KParts +{ + class PartManager; +} + +/** + * @brief Main window for a KOffice application + * + * This class is used to represent a main window + * of a KOffice component. Each main window contains + * a menubar and some toolbars. + * + * @note This class does NOT need to be subclassed in your application. + */ +class KOFFICECORE_EXPORT KoMainWindow : public KParts::MainWindow +{ + Q_OBJECT +public: + + /** + * Constructor. + * + * Initializes a KOffice main window (with its basic GUI etc.). + */ + KoMainWindow( KInstance *instance, const char *_name = 0 ); + + /** + * Destructor. + */ + ~KoMainWindow(); + + /** + * Called when a document is assigned to this mainwindow. + * This creates a view for this document, makes it the active part, etc. + */ + virtual void setRootDocument( KoDocument *doc ); + + /** + * This is used to handle the document used at start up before it actually + * added as root document. + */ + void setDocToOpen( KoDocument *doc ); + + /** + * Update caption from document info - call when document info + * (title in the about page) changes. + */ + virtual void updateCaption(); + + /** + * Retrieves the document that is displayed in the mainwindow. + */ + virtual KoDocument* rootDocument() const; + + virtual KoView *rootView() const; + + virtual KParts::PartManager *partManager(); + + /** + * Prints the document + * @param quick whether the print setup dialog is to be displayed + **/ + void print(bool quick); + + /** + * The application should call this to show or hide a toolbar. + * It also takes care of the corresponding action in the settings menu. + */ + void showToolbar( const char * tbName, bool shown ); + + /** + * @return TRUE if the toolbar @p tbName is visible + */ + bool toolbarIsVisible(const char *tbName); + + /** + * Get hold of the label in the statusbar, to write messages to it. + * You can also insert other items in the status bar by using QStatusBar::addWidget. + */ + QLabel * statusBarLabel(); + + /** + * Sets the maximum number of recent documents entries. + */ + void setMaxRecentItems(uint _number); + + /** + * The document opened a URL -> store into recent documents list. + */ + void addRecentURL( const KURL& url ); + + /** + * Load the desired document and show it. + * @param url the URL to open + * + * @return TRUE on success. + */ + virtual bool openDocument( const KURL & url ); + + /** + * Load the URL into this document (and make it root doc after loading) + * + * Special method for KoApplication::start, don't use. + */ + bool openDocument( KoDocument *newdoc, const KURL & url ); + + virtual DCOPObject * dcopObject(); + + /** + * Reloads the recent documents list. + */ + void reloadRecentFileList(); + + /** + * Updates the window caption based on the document info and path. + */ + virtual void updateCaption( const QString caption, bool mod ); + void updateReloadFileAction(KoDocument *doc); + void updateVersionsFileAction(KoDocument *doc); + +signals: + /** + * This signal is emitted if the document has been saved successfully. + */ + void documentSaved(); + /// This signals is emmitted before the save dialog is shown + void saveDialogShown(); + +public slots: + + /** + * Slot for eMailing the document using KMail + * + * This is a very simple extension that will allow any document + * that is currently being edited to be emailed using KMail. + */ + void slotEmailFile(); + + /** + * Slot for opening a new document. + * + * If the current document is empty, the new document replaces it. + * If not, a new shell will be opened for showing the document. + */ + virtual void slotFileNew(); + + /** + * Slot for opening a saved file. + * + * If the current document is empty, the opened document replaces it. + * If not a new shell will be opened for showing the opened file. + */ + virtual void slotFileOpen(); + + /** + * Slot for opening a file among the recently opened files. + * + * If the current document is empty, the opened document replaces it. + * If not a new shell will be opened for showing the opened file. + */ + virtual void slotFileOpenRecent( const KURL & ); + + /** + * Saves the current document with the current name. + */ + virtual void slotFileSave(); + + /** + * Saves the current document with a new name. + */ + virtual void slotFileSaveAs(); + + /** + * Prints the actual document. + */ + virtual void slotFilePrint(); + + /** + * Show a print preview + */ + void slotFilePrintPreview(); // make virtual later + + /** + * Show a dialog with author and document information. + */ + virtual void slotDocumentInfo(); + + /** + * Closes the document. + */ + virtual void slotFileClose(); + + /** + * Closes the shell. + */ + virtual void slotFileQuit(); + + /** + * Configure key bindings. + */ + virtual void slotConfigureKeys(); + + /** + * Configure toolbars. + */ + virtual void slotConfigureToolbars(); + + /** + * Post toolbar config. + * (Plug action lists back in, etc.) + */ + virtual void slotNewToolbarConfig(); + + /** + * Shows or hides a toolbar + */ + virtual void slotToolbarToggled( bool toggle ); + + /** + * View splitting stuff + */ + virtual void slotSplitView(); + virtual void slotRemoveView(); + virtual void slotSetOrientation(); + + /** + * Close all views + */ + virtual void slotCloseAllViews(); + + /** + * Reload file + */ + void slotReloadFile(); + + /** + * This will call a dialogbox to add version to list of files + */ + void slotVersionsFile(); + + /** + * File --> Import + * + * This will call slotFileOpen(). To differentiate this from an ordinary + * call to slotFileOpen() call @ref isImporting(). + */ + void slotImportFile(); + + /** + * File --> Export + * + * This will call slotFileSaveAs(). To differentiate this from an ordinary + * call to slotFileSaveAs() call @ref isExporting(). + */ + void slotExportFile(); + +protected: // protected methods are mostly for koshell, it's the only one deriving from KoMainWindow + + /// Helper method for slotFileNew and slotFileClose + void chooseNewDocument( int /*KoDocument::InitDocFlags*/ initDocFlags ); + /** + * Special method for KOShell, to allow switching the root + * document (and its views) among a set of them. + */ + void setRootDocumentDirect( KoDocument *doc, const QPtrList<KoView> & views ); + + /** + * Create a new empty document. + */ + virtual KoDocument* createDoc() const; + + /** + * Saves the document, asking for a filename if necessary. + * + * @param saveas if set to TRUE the user is always prompted for a filename + * + * @param silent if set to TRUE rootDocument()->setTitleModified will not be called. + * + * @return TRUE on success, false on error or cancel + * (don't display anything in this case, the error dialog box is also implemented here + * but restore the original URL in slotFileSaveAs) + */ + virtual bool saveDocument( bool saveas = false, bool silent = false ); + + virtual void closeEvent( QCloseEvent * e ); + virtual void resizeEvent( QResizeEvent * e ); + + /** + * Ask user about saving changes to the document upon exit. + */ + virtual bool queryClose(); + + virtual bool openDocumentInternal( const KURL & url, KoDocument * newdoc = 0L ); + + /** + * Returns whether or not the current slotFileSave[As]() or saveDocument() + * call is actually an export operation (like File --> Export). + * + * If this is true, you must call KoDocument::export() instead of + * KoDocument::save() or KoDocument::saveAs(), in any reimplementation of + * saveDocument(). + */ + bool isExporting() const; + + /** + * Returns whether or not the current slotFileOpen() or openDocument() + * call is actually an import operation (like File --> Import). + * + * If this is true, you must call KoDocument::import() instead of + * KoDocument::openURL(), in any reimplementation of openDocument() or + * openDocumentInternal(). + */ + bool isImporting() const; + + /** + * Save the list of recent files. + */ + void saveRecentFiles(); + + KRecentFilesAction *recentAction() const { return m_recent; } + +private: + + /** + * Asks the user if they really want to save the document. + * Called only if outputFormat != nativeFormat. + * + * @return true if the document should be saved + */ + bool exportConfirmation( const QCString &outputFormat ); + + void saveWindowSettings(); + + KRecentFilesAction *m_recent; + +protected slots: + virtual void slotActivePartChanged( KParts::Part *newPart ); + +private slots: + void slotProgress(int value); + void slotLoadCompleted(); + void slotLoadCanceled (const QString &); + void slotSaveCompleted(); + void slotSaveCanceled(const QString &); + +private: + KoMainWindowPrivate *d; + +}; + +#endif diff --git a/lib/kofficecore/KoMainWindowIface.cc b/lib/kofficecore/KoMainWindowIface.cc new file mode 100644 index 00000000..356bd80b --- /dev/null +++ b/lib/kofficecore/KoMainWindowIface.cc @@ -0,0 +1,65 @@ +/* This file is part of the KDE project + Copyright (c) 2001 David Faure <faure@kde.org> + + $Id: KoMainWindowIface.cc 508787 2006-02-12 18:28:12Z ingwa $ + + 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 "KoMainWindowIface.h" + +#include "KoMainWindow.h" + +#include <kapplication.h> +#include <dcopclient.h> +#include <kdcopactionproxy.h> + +KoMainWindowIface::KoMainWindowIface( KoMainWindow *mainwindow ) + : DCOPObject( mainwindow ) +{ + m_pMainWindow = mainwindow; + m_actionProxy = new KDCOPActionProxy( mainwindow->actionCollection(), this ); +} + +KoMainWindowIface::~KoMainWindowIface() +{ + delete m_actionProxy; +} + +DCOPRef KoMainWindowIface::action( const QCString &name ) +{ + return DCOPRef( kapp->dcopClient()->appId(), m_actionProxy->actionObjectId( name ) ); +} + +QCStringList KoMainWindowIface::actions() +{ + QCStringList res; + QValueList<KAction *> lst = m_actionProxy->actions(); + QValueList<KAction *>::ConstIterator it = lst.begin(); + QValueList<KAction *>::ConstIterator end = lst.end(); + for (; it != end; ++it ) + res.append( (*it)->name() ); + + return res; +} + +QMap<QCString,DCOPRef> KoMainWindowIface::actionMap() +{ + return m_actionProxy->actionMap(); +} + +ASYNC KoMainWindowIface::print(bool quick) { + m_pMainWindow->print(quick); +} diff --git a/lib/kofficecore/KoMainWindowIface.h b/lib/kofficecore/KoMainWindowIface.h new file mode 100644 index 00000000..6b7c3cb6 --- /dev/null +++ b/lib/kofficecore/KoMainWindowIface.h @@ -0,0 +1,48 @@ +/* This file is part of the KDE project + Copyright (c) 2001 Davud Faure <faure@kde.org> + + $Id: KoMainWindowIface.h 466447 2005-10-02 17:54:10Z zander $ + + 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 __KoMainWindowIface_h__ +#define __KoMainWindowIface_h__ + +#include <dcopobject.h> +#include <dcopref.h> + +class KDCOPActionProxy; +class KoMainWindow; + +class KoMainWindowIface : public DCOPObject +{ + K_DCOP +public: + KoMainWindowIface( KoMainWindow *mainwindow ); + virtual ~KoMainWindowIface(); + +k_dcop: + DCOPRef action( const QCString &name ); + QCStringList actions(); + QMap<QCString,DCOPRef> actionMap(); + ASYNC print(bool quick); + +protected: + KoMainWindow *m_pMainWindow; + KDCOPActionProxy *m_actionProxy; +}; + +#endif diff --git a/lib/kofficecore/KoOasisLoadingContext.cpp b/lib/kofficecore/KoOasisLoadingContext.cpp new file mode 100644 index 00000000..fdad0ecc --- /dev/null +++ b/lib/kofficecore/KoOasisLoadingContext.cpp @@ -0,0 +1,124 @@ +/* This file is part of the KDE project + Copyright (C) 2005 David Faure <faure@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 "KoOasisLoadingContext.h" +#include <KoOasisStore.h> +#include <KoOasisStyles.h> +#include <KoStore.h> +#include <KoXmlNS.h> +#include <kdebug.h> +#include <KoDom.h> + +KoOasisLoadingContext::KoOasisLoadingContext( KoDocument* doc, + KoOasisStyles& styles, KoStore* store ) + : m_doc( doc ), m_store( store ), m_styles( styles ), + m_metaXmlParsed( false ), m_useStylesAutoStyles( false ) +{ + // Ideally this should be done by KoDocument and passed as argument here... + KoOasisStore oasisStore( store ); + QString dummy; + (void)oasisStore.loadAndParse( "tar:/META-INF/manifest.xml", m_manifestDoc, dummy ); +} + + +KoOasisLoadingContext::~KoOasisLoadingContext() +{ + +} + +void KoOasisLoadingContext::fillStyleStack( const QDomElement& object, const char* nsURI, const char* attrName, const char* family ) +{ + // find all styles associated with an object and push them on the stack + if ( object.hasAttributeNS( nsURI, attrName ) ) { + const QString styleName = object.attributeNS( nsURI, attrName, QString::null ); + const QDomElement* style = 0; + bool isStyleAutoStyle = false; + if ( m_useStylesAutoStyles ) { + // When loading something from styles.xml, look into the styles.xml auto styles first + style = m_styles.findStyleAutoStyle( styleName, family ); + // and fallback to looking at styles(), which includes the user styles from styles.xml + if ( style ) + isStyleAutoStyle = true; + } + if ( !style ) + style = m_styles.findStyle( styleName, family ); + if ( style ) + addStyles( style, family, isStyleAutoStyle ); + else + kdWarning(32500) << "fillStyleStack: no style named " << styleName << " found." << endl; + } +} + +void KoOasisLoadingContext::addStyles( const QDomElement* style, const char* family, bool usingStylesAutoStyles ) +{ + Q_ASSERT( style ); + if ( !style ) return; + // this recursive function is necessary as parent styles can have parents themselves + if ( style->hasAttributeNS( KoXmlNS::style, "parent-style-name" ) ) { + const QString parentStyleName = style->attributeNS( KoXmlNS::style, "parent-style-name", QString::null ); + const QDomElement* parentStyle = 0; + if ( usingStylesAutoStyles ) { + // When loading something from styles.xml, look into the styles.xml auto styles first + parentStyle = m_styles.findStyleAutoStyle( parentStyleName, family ); + // and fallback to looking at styles(), which includes the user styles from styles.xml + } + if ( !parentStyle ) + parentStyle = m_styles.findStyle( parentStyleName, family ); + if ( parentStyle ) + addStyles( parentStyle, family, usingStylesAutoStyles ); + else + kdWarning(32500) << "Parent style not found: " << parentStyleName << endl; + } + else if ( family ) { + const QDomElement* def = m_styles.defaultStyle( family ); + if ( def ) { // on top of all, the default style for this family + //kdDebug(32500) << "pushing default style " << style->attributeNS( KoXmlNS::style, "name", QString::null ) << endl; + m_styleStack.push( *def ); + } + } + + //kdDebug(32500) << "pushing style " << style->attributeNS( KoXmlNS::style, "name", QString::null ) << endl; + m_styleStack.push( *style ); +} + +QString KoOasisLoadingContext::generator() const +{ + parseMeta(); + return m_generator; +} + +void KoOasisLoadingContext::parseMeta() const +{ + if ( !m_metaXmlParsed && m_store ) + { + if ( m_store->hasFile( "meta.xml" ) ) + { + QDomDocument metaDoc; + KoOasisStore oasisStore( m_store ); + QString errorMsg; + if ( oasisStore.loadAndParse( "meta.xml", metaDoc, errorMsg ) ) { + QDomNode meta = KoDom::namedItemNS( metaDoc, KoXmlNS::office, "document-meta" ); + QDomNode office = KoDom::namedItemNS( meta, KoXmlNS::office, "meta" ); + QDomElement generator = KoDom::namedItemNS( office, KoXmlNS::meta, "generator" ); + if ( !generator.isNull() ) + m_generator = generator.text(); + } + } + m_metaXmlParsed = true; + } +} diff --git a/lib/kofficecore/KoOasisLoadingContext.h b/lib/kofficecore/KoOasisLoadingContext.h new file mode 100644 index 00000000..cac1786b --- /dev/null +++ b/lib/kofficecore/KoOasisLoadingContext.h @@ -0,0 +1,122 @@ +/* This file is part of the KDE project + Copyright (C) 2005 David Faure <faure@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 KOOASISLOADINGCONTEXT_H +#define KOOASISLOADINGCONTEXT_H + +class KoXmlWriter; +class QDomElement; +class KoDocument; +class KoOasisStyles; +class KoPictureCollection; +class KoStore; + +#include <qmap.h> +#include <koffice_export.h> +#include <qstringlist.h> +#include <KoStyleStack.h> + +/** + * Used during loading of Oasis format (and discarded at the end of the loading). + * + * @author David Faure <faure@kde.org> + */ +class KOFFICECORE_EXPORT KoOasisLoadingContext +{ +public: + /** + * Stores reference to the KoOasisStyles and stored passed by KoDocument. + * Make sure that the KoOasisStyles instance outlives this KoOasisLoadingContext instance. + * (This is the case during loading, when using the KoOasisStyles given by KoDocument) + * + * @param doc the KoDocument being loaded + * @param styles reference to the KoOasisStyles parsed by KoDocument + * @param store pointer to store, if available, for e.g. loading images. + */ + KoOasisLoadingContext( KoDocument* doc, KoOasisStyles& styles, KoStore* store ); + ~KoOasisLoadingContext(); + + KoDocument* koDocument() { return m_doc; } + KoStore* store() { return m_store; } + + KoOasisStyles& oasisStyles() { return m_styles; } + KoStyleStack& styleStack() { return m_styleStack; } + + const QDomDocument& manifestDocument() const { return m_manifestDoc; } + + /// Return the <meta:generator> of the document, e.g. "KOffice/1.4.0a" + QString generator() const; + + /** + * Convenience method for loading the style of an object + * before loading that object. + * + * Read attribute (nsURI,attrName) from the given dom element, + * treat that attribute as a style name, and load that style + * including all its parent styles. + * @param element the dom element to read the attribute from + * @param nsURI the namespace URI of the attribute to read + * @param attrName the name of the attribute to read + * @param family the style family used for this object + */ + void fillStyleStack( const QDomElement& element, const char* nsURI, const char* attrName, const char* family ); + + /** + * Add @p style to the stack, as well as all its parent styles + * and the default style for this style family. + * + * @param style the dom element containing the style to add to the stack + * @param family the family to use when looking up parent styles + * @param usingStylesAutoStyles if true, the parent styles are looked up + * in the automatic styles from styles.xml, instead of looking up + * in the automatic styles from content.xml as we usually do. + * This is useful for loading headers and footers for instance. + * See setUseStylesAutoStyles(), which makes fillStyleStack() set this bool. + * + * Usually you would call fillStyleStack() instead. + */ + void addStyles( const QDomElement* style, const char* family, bool usingStylesAutoStyles = false ); + + /// Set to true while loading headers and footers, to remember to use auto styles + /// from styles.xml + void setUseStylesAutoStyles( bool useStylesAutoStyles ) { m_useStylesAutoStyles = useStylesAutoStyles; } + //bool useStylesAutoStyles() const { return m_useStylesAutoStyles; } + +private: + void parseMeta() const; + +private: + KoDocument* m_doc; + KoStore* m_store; + KoOasisStyles& m_styles; + KoStyleStack m_styleStack; + + mutable QString m_generator; + mutable bool m_metaXmlParsed; + bool m_useStylesAutoStyles; + bool m_unused1; // padding, can be used later + bool m_unused2; // padding, can be used later + + QDomDocument m_manifestDoc; + + class Private; + Private *d; +}; + +#endif /* KOOASISLOADINGCONTEXT_H */ + diff --git a/lib/kofficecore/KoOasisSettings.cpp b/lib/kofficecore/KoOasisSettings.cpp new file mode 100644 index 00000000..99f63524 --- /dev/null +++ b/lib/kofficecore/KoOasisSettings.cpp @@ -0,0 +1,224 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Laurent Montel <montel@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 "KoOasisSettings.h" +#include "KoXmlNS.h" +#include "KoDom.h" +#include <kdebug.h> + +KoOasisSettings::KoOasisSettings( const QDomDocument& doc ) + : m_settingsElement( KoDom::namedItemNS( doc.documentElement(), KoXmlNS::office, "settings" ) ), + m_configNSURI( KoXmlNS::config ) +{ + const QDomElement contents = doc.documentElement(); + if ( m_settingsElement.isNull() ) + kdDebug() << " document doesn't have tag 'office:settings'\n"; +} + +KoOasisSettings::KoOasisSettings( const QDomDocument& doc, const char* officeNSURI, const char* configNSURI ) + : m_settingsElement( KoDom::namedItemNS( doc.documentElement(), officeNSURI, "settings" ) ), + m_configNSURI( configNSURI ) +{ + const QDomElement contents = doc.documentElement(); + if ( m_settingsElement.isNull() ) + kdDebug() << " document doesn't have tag 'office:settings'\n"; +} + +KoOasisSettings::Items KoOasisSettings::itemSet( const QString& itemSetName ) const +{ + QDomElement e; + forEachElement( e, m_settingsElement ) + { + if ( e.localName() == "config-item-set" && + e.namespaceURI() == m_configNSURI && + e.attributeNS( m_configNSURI, "name", QString::null ) == itemSetName ) + { + return Items( e, this ); + } + } + + return Items( QDomElement(), this ); +} + +KoOasisSettings::IndexedMap KoOasisSettings::Items::indexedMap( const QString& itemMapName ) const +{ + QDomElement configItem; + forEachElement( configItem, m_element ) + { + if ( configItem.localName() == "config-item-map-indexed" && + configItem.namespaceURI() == m_settings->m_configNSURI && + configItem.attributeNS( m_settings->m_configNSURI, "name", QString::null ) == itemMapName ) + { + return IndexedMap( configItem, m_settings ); + } + } + return IndexedMap( QDomElement(), m_settings ); +} + +KoOasisSettings::NamedMap KoOasisSettings::Items::namedMap( const QString& itemMapName ) const +{ + QDomElement configItem; + forEachElement( configItem, m_element ) + { + if ( configItem.localName() == "config-item-map-named" && + configItem.namespaceURI() == m_settings->m_configNSURI && + configItem.attributeNS( m_settings->m_configNSURI, "name", QString::null ) == itemMapName ) + { + return NamedMap( configItem, m_settings ); + } + } + return NamedMap( QDomElement(), m_settings ); +} + +KoOasisSettings::Items KoOasisSettings::IndexedMap::entry( int entryIndex ) const +{ + int i = 0; + QDomElement entry; + forEachElement( entry, m_element ) + { + if ( entry.localName() == "config-item-map-entry" && + entry.namespaceURI() == m_settings->m_configNSURI ) + { + if ( i == entryIndex ) + return Items( entry, m_settings ); + else + ++i; + } + } + return Items( QDomElement(), m_settings ); +} + +KoOasisSettings::Items KoOasisSettings::NamedMap::entry( const QString& entryName ) const +{ + QDomElement entry; + forEachElement( entry, m_element ) + { + if ( entry.localName() == "config-item-map-entry" && + entry.namespaceURI() == m_settings->m_configNSURI && + entry.attributeNS( m_settings->m_configNSURI, "name", QString::null ) == entryName ) + { + return Items( entry, m_settings ); + } + } + return Items( QDomElement(), m_settings ); +} + +// helper method +QString KoOasisSettings::Items::findConfigItem( const QDomElement& element, + const QString& item, bool* ok ) const +{ + QDomElement it; + forEachElement( it, element ) + { + if ( it.localName() == "config-item" && + it.namespaceURI() == m_settings->m_configNSURI && + it.attributeNS( m_settings->m_configNSURI, "name", QString::null ) == item ) + { + *ok = true; + return it.text(); + } + } + *ok = false; + return QString::null; +} + + +QString KoOasisSettings::Items::findConfigItem( const QString& item, bool* ok ) const +{ + return findConfigItem( m_element, item, ok ); +} + +#if 0 // does anyone need this one? passing a default value does the job, too +bool KoOasisSettings::Items::hasConfigItem( const QString& configName ) const +{ + bool ok; + (void)findConfigItem( configName, &ok ); + return ok; +} +#endif + +QString KoOasisSettings::Items::parseConfigItemString( const QString& configName, const QString& defValue ) const +{ + bool ok; + const QString str = findConfigItem( configName, &ok ); + return ok ? str : defValue; +} + +int KoOasisSettings::Items::parseConfigItemInt( const QString& configName, int defValue ) const +{ + bool ok; + const QString str = findConfigItem( configName, &ok ); + int value; + if ( ok ) { + value = str.toInt( &ok ); + if ( ok ) + return value; + } + return defValue; +} + +double KoOasisSettings::Items::parseConfigItemDouble( const QString& configName, double defValue ) const +{ + bool ok; + const QString str = findConfigItem( configName, &ok ); + double value; + if ( ok ) { + value = str.toDouble( &ok ); + if ( ok ) + return value; + } + return defValue; +} + +bool KoOasisSettings::Items::parseConfigItemBool( const QString& configName, bool defValue ) const +{ + bool ok; + const QString str = findConfigItem( configName, &ok ); + if ( str == "true" ) + return true; + else if ( str == "false" ) + return false; + return defValue; +} + +short KoOasisSettings::Items::parseConfigItemShort( const QString& configName, short defValue ) const +{ + bool ok; + const QString str = findConfigItem( configName, &ok ); + short value; + if ( ok ) { + value = str.toShort( &ok ); + if ( ok ) + return value; + } + return defValue; +} + +long KoOasisSettings::Items::parseConfigItemLong( const QString& configName, long defValue ) const +{ + bool ok; + const QString str = findConfigItem( configName, &ok ); + long value; + if ( ok ) { + value = str.toLong( &ok ); + if ( ok ) + return value; + } + return defValue; +} diff --git a/lib/kofficecore/KoOasisSettings.h b/lib/kofficecore/KoOasisSettings.h new file mode 100644 index 00000000..cd1ae112 --- /dev/null +++ b/lib/kofficecore/KoOasisSettings.h @@ -0,0 +1,179 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Laurent Montel <montel@kde.org> + David Faure <faure@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 KOOASISSETTINGS_H +#define KOOASISSETTINGS_H + +#include <qdom.h> +#include <koffice_export.h> + +/** + * @brief Parse settings.xml file. + * + * This class helps parsing the settings.xml file of an OASIS document. + * + * For reference, the structure of settings.xml looks like: + * <pre> + * \<office:settings\> + * \<config:config-item-set config:name="configure-settings"\> + * .... + * \</config:config-item-set\> + * \<config:config-item-set config:name="view-settings"\> + * \<config:config-item-map-indexed config:name="Views"\> + * \<config:config-item-map-entry\> + * \<config:config-item config:name="SnapLinesDrawing" config:type="string"\>value\</config:config-item\> + * .... + * \<config:config-item-map-named config:name="Tables"\> + * \<config:config-item-map-entry config:name="Sheet1"\> + * \<config:config-item config:name="CursorPositionX"\> + * ...... + * \</config:config-item-map-entry\> + * \<config:config-item-map-entry config:name="Sheet2"\> + * .... + * \</config:config-item-map-entry\> + * \</config:config-item-map-named\> + * ..... + * \</config:config-item-map-entry\> + * \</config:config-item-map-indexed\> + * \<config:config-item-map-indexed config:name="Interface"\> + * ....... + * \</config:config-item-map-indexed\> + * \</config:config-item-set\> + * \</office:settings\> + * </pre> + * Basically, an item-set is a set of named \<config-item\>s and/or maps. + * There are two kinds of maps (by-index or by-name), and entries in the + * maps contain \<config-item\>s too, or nested maps. + * + * The API of KoOasisSettings allows the caller to look for a given item-set + * or item-map once, and then lookup multiple items inside it. + * It also allows "drilling down" inside the tree in case of nesting. + */ +class KOFFICECORE_EXPORT KoOasisSettings +{ +public: + /** + * Normal KoOasisSettings constructor, for an OASIS settings.xml + */ + explicit KoOasisSettings( const QDomDocument& doc ); + + /** + * KoOasisSettings constructor for an OpenOffice-1.1 file + */ + KoOasisSettings( const QDomDocument& doc, const char* officeNSURI, const char* configNSURI ); + + class Items; + + /** + * Returns the toplevel item-set named @p itemSetName. + * If not found, the returned items instance is null. + */ + Items itemSet( const QString& itemSetName ) const; + + class IndexedMap; + class NamedMap; + /// Represents a collection of items (config-item or maps). + class KOFFICECORE_EXPORT Items + { + friend class KoOasisSettings; + friend class IndexedMap; + friend class NamedMap; + Items( const QDomElement& elem, const KoOasisSettings* settings ) + : m_element( elem ), m_settings( settings ) {} + public: + bool isNull() const { return m_element.isNull(); } + + /** + * Look for the config-item-map-indexed named @p itemMapName and return it. + * + * An indexed map is an array (or sequence), i.e. items are supposed to + * be retrieved by index. This is useful for e.g. "view 0", "view 1" etc. + */ + IndexedMap indexedMap( const QString& itemMapName ) const; + + /** + * Look for the config-item-map-named named @p mapItemName and return it. + * + * A named map is a map where items are retrieved by entry name, @see selectItemMapEntry + * @return false if no such map was found + */ + NamedMap namedMap( const QString& itemMapName ) const; + + int parseConfigItemInt( const QString& configName, int defValue = 0 ) const; + double parseConfigItemDouble( const QString& configName, double defValue = 0 ) const; + QString parseConfigItemString( const QString& configName, const QString& defValue = QString::null ) const; + bool parseConfigItemBool( const QString& configName, bool defValue = false ) const; + short parseConfigItemShort( const QString& configName, short defValue = 0 ) const; + long parseConfigItemLong( const QString& configName, long defValue = 0 ) const; + private: + /// @internal + QString findConfigItem( const QString& item, bool* ok ) const; + /// @internal + QString findConfigItem( const QDomElement& element, const QString& item, bool* ok ) const; + + QDomElement m_element; + const KoOasisSettings* m_settings; + }; + + /// Internal base class for IndexedMap and NamedMap + class Map + { + public: + bool isNull() const { return m_element.isNull(); } + protected: + Map( const QDomElement& elem, const KoOasisSettings* settings ) + : m_element( elem ), m_settings( settings ) {} + const QDomElement m_element; + const KoOasisSettings* m_settings; + }; + + class KOFFICECORE_EXPORT IndexedMap : public Map + { + friend class Items; + IndexedMap( const QDomElement& elem, const KoOasisSettings* settings ) + : Map( elem, settings ) {} + public: + /// Returns an entry in an indexed map + Items entry( int entryIndex ) const; + }; + + class KOFFICECORE_EXPORT NamedMap : public Map + { + friend class Items; + NamedMap( const QDomElement& elem, const KoOasisSettings* settings ) + : Map( elem, settings ) {} + public: + /// Returns an entry in a named map + Items entry( const QString& entryName ) const; + }; + +private: + friend class Items; + friend class IndexedMap; + friend class NamedMap; + const QDomElement m_settingsElement; + const char* m_configNSURI; + + class Private; + Private* d; +}; + +#endif diff --git a/lib/kofficecore/KoOasisStore.cpp b/lib/kofficecore/KoOasisStore.cpp new file mode 100644 index 00000000..1b7537ae --- /dev/null +++ b/lib/kofficecore/KoOasisStore.cpp @@ -0,0 +1,194 @@ +/* This file is part of the KDE project + Copyright (C) 2005 David Faure <faure@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 "KoOasisStore.h" + +#include "KoDocument.h" +#include "KoXmlNS.h" +#include "KoDom.h" +#include <KoStore.h> +#include <KoStoreDevice.h> +#include <KoXmlWriter.h> + +#include <ktempfile.h> +#include <kdebug.h> +#include <klocale.h> + +#include <qfile.h> +#include <qxml.h> +#include <qbuffer.h> + +KoOasisStore::KoOasisStore( KoStore* store ) + : m_store( store ), + m_storeDevice( 0 ), + m_contentWriter( 0 ), + m_bodyWriter( 0 ), + m_manifestWriter( 0 ), + m_contentTmpFile( 0 ) +{ +} + +KoOasisStore::~KoOasisStore() +{ + // If all the right close methods were called, nothing should remain, + // so those deletes are really just in case. + Q_ASSERT( !m_contentWriter ); + delete m_contentWriter; + Q_ASSERT( !m_bodyWriter ); + delete m_bodyWriter; + Q_ASSERT( !m_storeDevice ); + delete m_storeDevice; + Q_ASSERT( !m_contentTmpFile ); + delete m_contentTmpFile; + Q_ASSERT( !m_manifestWriter ); + delete m_manifestWriter; +} + +KoXmlWriter* KoOasisStore::contentWriter() +{ + if ( !m_contentWriter ) + { + if ( !m_store->open( "content.xml" ) ) + return 0; + m_storeDevice = new KoStoreDevice( m_store ); + m_contentWriter = KoDocument::createOasisXmlWriter( m_storeDevice, "office:document-content" ); + } + return m_contentWriter; +} + +KoXmlWriter* KoOasisStore::bodyWriter() +{ + if ( !m_bodyWriter ) + { + Q_ASSERT( !m_contentTmpFile ); + m_contentTmpFile = new KTempFile; + m_contentTmpFile->setAutoDelete( true ); + m_bodyWriter = new KoXmlWriter( m_contentTmpFile->file(), 1 ); + } + return m_bodyWriter; +} + +bool KoOasisStore::closeContentWriter() +{ + Q_ASSERT( m_bodyWriter ); + Q_ASSERT( m_contentTmpFile ); + + delete m_bodyWriter; m_bodyWriter = 0; + // copy over the contents from the tempfile to the real one + QFile* tmpFile = m_contentTmpFile->file(); + tmpFile->close(); + m_contentWriter->addCompleteElement( tmpFile ); + m_contentTmpFile->close(); + delete m_contentTmpFile; m_contentTmpFile = 0; + + Q_ASSERT( m_contentWriter ); + m_contentWriter->endElement(); // document-content + m_contentWriter->endDocument(); + delete m_contentWriter; m_contentWriter = 0; + delete m_storeDevice; m_storeDevice = 0; + if ( !m_store->close() ) // done with content.xml + return false; + return true; +} + +KoXmlWriter* KoOasisStore::manifestWriter( const char* mimeType ) +{ + if ( !m_manifestWriter ) + { + // the pointer to the buffer is already stored in the KoXmlWriter, no need to store it here as well + QBuffer *manifestBuffer = new QBuffer; + manifestBuffer->open( IO_WriteOnly ); + m_manifestWriter = new KoXmlWriter( manifestBuffer ); + m_manifestWriter->startDocument( "manifest:manifest" ); + m_manifestWriter->startElement( "manifest:manifest" ); + m_manifestWriter->addAttribute( "xmlns:manifest", KoXmlNS::manifest ); + m_manifestWriter->addManifestEntry( "/", mimeType ); + } + return m_manifestWriter; +} + +bool KoOasisStore::closeManifestWriter() +{ + m_manifestWriter->endElement(); + m_manifestWriter->endDocument(); + QBuffer* buffer = static_cast<QBuffer *>( m_manifestWriter->device() ); + delete m_manifestWriter; m_manifestWriter = 0; + bool ok = false; + if ( m_store->open( "META-INF/manifest.xml" ) ) + { + Q_LONG written = m_store->write( buffer->buffer() ); + ok = ( written == (Q_LONG)buffer->buffer().size() && m_store->close() ); + } + delete buffer; + return ok; +} + +bool KoOasisStore::loadAndParse( const QString& fileName, QDomDocument& doc, QString& errorMessage ) +{ + //kdDebug(30003) << "loadAndParse: Trying to open " << fileName << endl; + + if (!m_store->open(fileName)) + { + kdWarning(30003) << "Entry " << fileName << " not found!" << endl; + errorMessage = i18n( "Could not find %1" ).arg( fileName ); + return false; + } + // Error variables for QDomDocument::setContent + QString errorMsg; + int errorLine, errorColumn; + + // We need to be able to see the space in <text:span> </text:span>, this is why + // we activate the "report-whitespace-only-CharData" feature. + // Unfortunately this leads to lots of whitespace text nodes in between real + // elements in the rest of the document, watch out for that. + QXmlInputSource source( m_store->device() ); + // Copied from QDomDocumentPrivate::setContent, to change the whitespace thing + QXmlSimpleReader reader; + KoDocument::setupXmlReader( reader, true /*namespaceProcessing*/ ); + + bool ok = doc.setContent( &source, &reader, &errorMsg, &errorLine, &errorColumn ); + if ( !ok ) + { + kdError(30003) << "Parsing error in " << fileName << "! Aborting!" << endl + << " In line: " << errorLine << ", column: " << errorColumn << endl + << " Error message: " << errorMsg << endl; + errorMessage = i18n( "Parsing error in the main document at line %1, column %2\nError message: %3" ) + .arg( errorLine ).arg( errorColumn ).arg( i18n ( "QXml", errorMsg.utf8() ) ); + } + else + { + kdDebug(30003) << "File " << fileName << " loaded and parsed" << endl; + } + m_store->close(); + return ok; +} + +QString KoOasisStore::mimeForPath( const QDomDocument& doc, const QString& fullPath ) +{ + QDomElement docElem = doc.documentElement(); + QDomElement elem; + forEachElement( elem, docElem ) + { + if ( elem.localName() == "file-entry" && elem.namespaceURI() == KoXmlNS::manifest ) + { + if ( elem.attributeNS( KoXmlNS::manifest, "full-path", QString::null ) == fullPath ) + return elem.attributeNS( KoXmlNS::manifest, "media-type", QString::null ); + } + } + return QString::null; +} diff --git a/lib/kofficecore/KoOasisStore.h b/lib/kofficecore/KoOasisStore.h new file mode 100644 index 00000000..506700b0 --- /dev/null +++ b/lib/kofficecore/KoOasisStore.h @@ -0,0 +1,96 @@ +/* This file is part of the KDE project + Copyright (C) 2005 David Faure <faure@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 KOOASISSTORE_H +#define KOOASISSTORE_H + +class QString; +class QDomDocument; +class KTempFile; +class KoXmlWriter; +class KoStore; +class KoStoreDevice; +class QIODevice; + +/** + * Helper class around KoStore for writing out OASIS files. + * This class helps solving the problem that automatic styles must be before + * the body, but it's easier to iterate over the application's objects only + * once. So we open a KoXmlWriter into a memory buffer, write the body into it, + * collect automatic styles while doing that, write out automatic styles, + * and then copy the body XML from the buffer into the real KoXmlWriter. + * + * The typical use of this class is therefore: + * - write body into bodyWriter() and collect auto styles + * - write auto styles into contentWriter() + * - call closeContentWriter() + * - write other files into the store (styles.xml, settings.xml etc.) + * + * + * TODO: maybe we could encapsulate a bit more things, to e.g. handle + * adding manifest entries automatically. + * + * @author: David Faure <faure@kde.org> + */ +#include <koffice_export.h> + +class KOFFICECORE_EXPORT KoOasisStore +{ +public: + /// @param store recontents the property of the caller + KoOasisStore( KoStore* store ); + + ~KoOasisStore(); + + KoStore* store() const { return m_store; } + + /// Open contents.xml for writing and return the KoXmlWriter + KoXmlWriter* contentWriter(); + + /// Open another KoXmlWriter for writing out the contents + /// into a temporary file, to collect automatic styles while doing that. + KoXmlWriter* bodyWriter(); + + /// This will copy the body into the content writer, + /// delete the bodyWriter and the contentWriter, and then + /// close contents.xml. + bool closeContentWriter(); + + // For other files in the store, use open/addManifestEntry/KoStoreDevice/createOasisXmlWriter/close + + /// Create and return a manifest writer. It will write to a memory buffer. + KoXmlWriter* manifestWriter( const char* mimeType ); + + /// Close the manifest writer, writing its contents to manifest.xml + bool closeManifestWriter(); + + /// A completely unrelated method, for loading a file from an oasis store + bool loadAndParse( const QString& fileName, QDomDocument& doc, QString& errorMessage ); + + /// Another method for loading: get mimetype from full path, using the manifest + static QString mimeForPath( const QDomDocument& doc, const QString& fullPath ); + +private: + KoStore* m_store; + KoStoreDevice* m_storeDevice; + KoXmlWriter* m_contentWriter; + KoXmlWriter* m_bodyWriter; + KoXmlWriter* m_manifestWriter; + KTempFile* m_contentTmpFile; +}; + +#endif /* KOOASISSTORE_H */ diff --git a/lib/kofficecore/KoOasisStyles.cpp b/lib/kofficecore/KoOasisStyles.cpp new file mode 100644 index 00000000..3a018767 --- /dev/null +++ b/lib/kofficecore/KoOasisStyles.cpp @@ -0,0 +1,1604 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2006 David Faure <faure@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 "KoOasisStyles.h" +#include <KoXmlWriter.h> +#include <kdebug.h> +#include <qdom.h> +#include "KoDom.h" +#include "KoXmlNS.h" +#include "KoGenStyles.h" +#include <qbuffer.h> +#include <kglobal.h> +#include <klocale.h> +#include <qbrush.h> +#include <KoStyleStack.h> + +class KoOasisStyles::Private +{ +public: + // The key of the map is the family + QMap<QString, QDict<QDomElement> > m_styles; + QMap<QString, QDict<QDomElement> > m_stylesAutoStyles; +}; + +KoOasisStyles::KoOasisStyles() + : d( new Private ) +{ + m_defaultStyle.setAutoDelete( true ); + m_masterPages.setAutoDelete( true ); + m_listStyles.setAutoDelete( true ); + m_drawStyles.setAutoDelete( true ); +} + +KoOasisStyles::~KoOasisStyles() +{ + delete d; +} + +void KoOasisStyles::createStyleMap( const QDomDocument& doc, bool stylesDotXml ) +{ + const QDomElement docElement = doc.documentElement(); + // We used to have the office:version check here, but better let the apps do that + QDomElement fontStyles = KoDom::namedItemNS( docElement, KoXmlNS::office, "font-decls" ); + + if ( !fontStyles.isNull() ) { + //kdDebug(30003) << "Starting reading in font-decl..." << endl; + insertStyles( fontStyles, false ); + }// else + // kdDebug(30003) << "No items found" << endl; + + //kdDebug(30003) << "Starting reading in office:automatic-styles. stylesDotXml=" << stylesDotXml << endl; + + QDomElement autoStyles = KoDom::namedItemNS( docElement, KoXmlNS::office, "automatic-styles" ); + if ( !autoStyles.isNull() ) { + insertStyles( autoStyles, stylesDotXml ); + }// else + // kdDebug(30003) << "No items found" << endl; + + + //kdDebug(30003) << "Reading in master styles" << endl; + + QDomNode masterStyles = KoDom::namedItemNS( docElement, KoXmlNS::office, "master-styles" ); + + if ( !masterStyles.isNull() ) { + QDomElement master; + forEachElement( master, masterStyles ) + { + if ( master.localName() == "master-page" && + master.namespaceURI() == KoXmlNS::style ) { + const QString name = master.attributeNS( KoXmlNS::style, "name", QString::null ); + kdDebug(30003) << "Master style: '" << name << "' loaded " << endl; + m_masterPages.insert( name, new QDomElement( master ) ); + } else + // OASIS docu mentions style:handout-master and draw:layer-set here + kdWarning(30003) << "Unknown tag " << master.tagName() << " in office:master-styles" << endl; + } + } + + + kdDebug(30003) << "Starting reading in office:styles" << endl; + + const QDomElement officeStyle = KoDom::namedItemNS( docElement, KoXmlNS::office, "styles" ); + + if ( !officeStyle.isNull() ) { + m_officeStyle = officeStyle; + insertOfficeStyles( m_officeStyle ); + + } + + //kdDebug(30003) << "Styles read in." << endl; +} + +QValueVector<QDomElement> KoOasisStyles::userStyles() const +{ + QValueVector<QDomElement> vec; + // Collect user styles + unsigned int i = 0; + QDomElement e; + forEachElement( e, m_officeStyle ) + { + if ( e.localName() == "style" && + e.namespaceURI() == KoXmlNS::style ) + { + vec.resize( i+1 ); + vec[i++] = e; + } + } + return vec; +} + +const QDict<QDomElement>& KoOasisStyles::styles(const QString& family) const +{ + // hmm this can create an empty item in the map, but otherwise we couldn't + // return a const reference. + return d->m_styles[family]; +} + +void KoOasisStyles::insertOfficeStyles( const QDomElement& styles ) +{ + QDomElement e; + forEachElement( e, styles ) + { + const QString localName = e.localName(); + const QString ns = e.namespaceURI(); + if ( ( ns == KoXmlNS::svg && ( + localName == "linearGradient" + || localName == "radialGradient" ) ) + || ( ns == KoXmlNS::draw && ( + localName == "gradient" + || localName == "hatch" + || localName == "fill-image" + || localName == "marker" + || localName == "stroke-dash" + || localName == "opacity" ) ) + ) + { + const QString name = e.attributeNS( KoXmlNS::draw, "name", QString::null ); + Q_ASSERT( !name.isEmpty() ); + QDomElement* ep = new QDomElement( e ); + m_drawStyles.insert( name, ep ); + } + else + insertStyle( e, false ); + } +} + + +void KoOasisStyles::insertStyles( const QDomElement& styles, bool styleAutoStyles ) +{ + //kdDebug(30003) << "Inserting styles from " << styles.tagName() << endl; + QDomElement e; + forEachElement( e, styles ) + insertStyle( e, styleAutoStyles ); +} + +void KoOasisStyles::insertStyle( const QDomElement& e, bool styleAutoStyles ) +{ + const QString localName = e.localName(); + const QString ns = e.namespaceURI(); + + const QString name = e.attributeNS( KoXmlNS::style, "name", QString::null ); + if ( ns == KoXmlNS::style && localName == "style" ) { + const QString family = e.attributeNS( KoXmlNS::style, "family", QString::null ); + + if ( styleAutoStyles ) { + QDict<QDomElement>& dict = d->m_stylesAutoStyles[ family ]; + dict.setAutoDelete( true ); + if ( dict.find( name ) != 0 ) + kdDebug(30003) << "Auto-style: '" << name << "' already exists" << endl; + dict.insert( name, new QDomElement( e ) ); + //kdDebug(30003) << "Style: '" << name << "' loaded as a style auto style" << endl; + } else { + QDict<QDomElement>& dict = d->m_styles[ family ]; + dict.setAutoDelete( true ); + + if ( dict.find( name ) != 0 ) + kdDebug(30003) << "Style: '" << name << "' already exists" << endl; + dict.insert( name, new QDomElement( e ) ); + //kdDebug(30003) << "Style: '" << name << "' loaded " << endl; + } + } else if ( ns == KoXmlNS::style && ( + localName == "page-layout" + || localName == "font-decl" + || localName == "presentation-page-layout" ) ) + { + if ( m_styles.find( name ) != 0 ) + kdDebug(30003) << "Style: '" << name << "' already exists" << endl; + m_styles.insert( name, new QDomElement( e ) ); + } else if ( localName == "default-style" && ns == KoXmlNS::style ) { + const QString family = e.attributeNS( KoXmlNS::style, "family", QString::null ); + if ( !family.isEmpty() ) + m_defaultStyle.insert( family, new QDomElement( e ) ); + } else if ( localName == "list-style" && ns == KoXmlNS::text ) { + m_listStyles.insert( name, new QDomElement( e ) ); + //kdDebug(30003) << "List style: '" << name << "' loaded " << endl; + } else if ( ns == KoXmlNS::number && ( + localName == "number-style" + || localName == "currency-style" + || localName == "percentage-style" + || localName == "boolean-style" + || localName == "text-style" + || localName == "date-style" + || localName == "time-style" ) ) { + importDataStyle( e ); + } + // The rest (text:*-configuration and text:outline-style) is to be done by the apps. +} + +// OO spec 2.5.4. p68. Conversion to Qt format: see qdate.html +// OpenCalcImport::loadFormat has similar code, but slower, intermixed with other stuff, +// lacking long-textual forms. +void KoOasisStyles::importDataStyle( const QDomElement& parent ) +{ + NumericStyleFormat dataStyle; + + const QString localName = parent.localName(); + if (localName == "number-style") + dataStyle.type = NumericStyleFormat::Number; + else if (localName == "currency-style") + dataStyle.type = NumericStyleFormat::Currency; + else if (localName == "percentage-style") + dataStyle.type = NumericStyleFormat::Percentage; + else if (localName == "boolean-style") + dataStyle.type = NumericStyleFormat::Boolean; + else if (localName == "text-style") + dataStyle.type = NumericStyleFormat::Text; + else if (localName == "date-style") + dataStyle.type = NumericStyleFormat::Date; + else if (localName == "time-style") + dataStyle.type = NumericStyleFormat::Time; + + QString format; + int precision = -1; + int leadingZ = 1; + bool thousandsSep = false; + //todo negred + //bool negRed = false; + bool ok = false; + int i = 0; + QDomElement e; + QString prefix; + QString suffix; + forEachElement( e, parent ) + { + if ( e.namespaceURI() != KoXmlNS::number ) + continue; + QString localName = e.localName(); + const QString numberStyle = e.attributeNS( KoXmlNS::number, "style", QString::null ); + const bool shortForm = numberStyle == "short" || numberStyle.isEmpty(); + if ( localName == "day" ) { + format += shortForm ? "d" : "dd"; + } else if ( localName == "day-of-week" ) { + format += shortForm ? "ddd" : "dddd"; + } else if ( localName == "month" ) { + if ( e.attributeNS( KoXmlNS::number, "possessive-form", QString::null ) == "true" ) { + format += shortForm ? "PPP" : "PPPP"; + } + // TODO the spec has a strange mention of number:format-source + else if ( e.attributeNS( KoXmlNS::number, "textual", QString::null ) == "true" ) { + format += shortForm ? "MMM" : "MMMM"; + } else { // month number + format += shortForm ? "M" : "MM"; + } + } else if ( localName == "year" ) { + format += shortForm ? "yy" : "yyyy"; + } else if ( localName == "era" ) { + //TODO I don't know what is it... (define into oo spec) + } else if ( localName == "week-of-year" || localName == "quarter") { + // ### not supported in Qt + } else if ( localName == "hours" ) { + format += shortForm ? "h" : "hh"; + } else if ( localName == "minutes" ) { + format += shortForm ? "m" : "mm"; + } else if ( localName == "seconds" ) { + format += shortForm ? "s" : "ss"; + } else if ( localName == "am-pm" ) { + format += "ap"; + } else if ( localName == "text" ) { // litteral + format += e.text(); + } else if ( localName == "suffix" ) { + suffix = e.text(); + kdDebug()<<" suffix :"<<suffix<<endl; + } else if ( localName == "prefix" ) { + prefix = e.text(); + kdDebug()<<" prefix :"<<prefix<<endl; + } else if ( localName == "currency-symbol" ) { + dataStyle.currencySymbol = e.text(); + kdDebug()<<" currency-symbol: "<<dataStyle.currencySymbol<<endl; + format += e.text(); + //TODO + // number:language="de" number:country="DE">€</number:currency-symbol> + // Stefan: localization of the symbol? + } else if ( localName == "number" ) { + // TODO: number:grouping="true" + if ( e.hasAttributeNS( KoXmlNS::number, "decimal-places" ) ) + { + int d = e.attributeNS( KoXmlNS::number, "decimal-places", QString::null ).toInt( &ok ); + if ( ok ) + precision = d; + } + if ( e.hasAttributeNS( KoXmlNS::number, "min-integer-digits" ) ) + { + int d = e.attributeNS( KoXmlNS::number, "min-integer-digits", QString::null ).toInt( &ok ); + if ( ok ) + leadingZ = d; + } + if ( thousandsSep && leadingZ <= 3 ) + { + format += "#,"; + for ( i = leadingZ; i <= 3; ++i ) + format += '#'; + } + for ( i = 1; i <= leadingZ; ++i ) + { + format += '0'; + if ( ( i % 3 == 0 ) && thousandsSep ) + format =+ ',' ; + } + if (precision > -1) + { + format += '.'; + for ( i = 0; i < precision; ++i ) + format += '0'; + } + } + else if ( localName == "scientific-number" ) { + if (dataStyle.type == NumericStyleFormat::Number) + dataStyle.type = NumericStyleFormat::Scientific; + int exp = 2; + + if ( e.hasAttributeNS( KoXmlNS::number, "decimal-places" ) ) + { + int d = e.attributeNS( KoXmlNS::number, "decimal-places", QString::null ).toInt( &ok ); + if ( ok ) + precision = d; + } + + if ( e.hasAttributeNS( KoXmlNS::number, "min-integer-digits" ) ) + { + int d = e.attributeNS( KoXmlNS::number, "min-integer-digits", QString::null ).toInt( &ok ); + if ( ok ) + leadingZ = d; + } + + if ( e.hasAttributeNS( KoXmlNS::number, "min-exponent-digits" ) ) + { + int d = e.attributeNS( KoXmlNS::number, "min-exponent-digits", QString::null ).toInt( &ok ); + if ( ok ) + exp = d; + if ( exp <= 0 ) + exp = 1; + } + + if ( thousandsSep && leadingZ <= 3 ) + { + format += "#,"; + for ( i = leadingZ; i <= 3; ++i ) + format += '#'; + } + + for ( i = 1; i <= leadingZ; ++i ) + { + format+='0'; + if ( ( i % 3 == 0 ) && thousandsSep ) + format+=','; + } + + if (precision > -1) + { + format += '.'; + for ( i = 0; i < precision; ++i ) + format += '0'; + } + + format+="E+"; + for ( i = 0; i < exp; ++i ) + format+='0'; + } else if ( localName == "fraction" ) { + if (dataStyle.type == NumericStyleFormat::Number) + dataStyle.type = NumericStyleFormat::Fraction; + int integer = 0; + int numerator = 1; + int denominator = 1; + int denominatorValue=0; + if ( e.hasAttributeNS( KoXmlNS::number, "min-integer-digits" ) ) + { + int d = e.attributeNS( KoXmlNS::number, "min-integer-digits", QString::null ).toInt( &ok ); + if ( ok ) + integer = d; + } + if ( e.hasAttributeNS( KoXmlNS::number, "min-numerator-digits" ) ) + { + int d = e.attributeNS( KoXmlNS::number, "min-numerator-digits", QString::null ).toInt( &ok ); + if ( ok ) + numerator = d; + } + if ( e.hasAttributeNS( KoXmlNS::number, "min-denominator-digits" ) ) + { + int d = e.attributeNS( KoXmlNS::number, "min-denominator-digits", QString::null ).toInt( &ok ); + if ( ok ) + denominator = d; + } + if ( e.hasAttributeNS( KoXmlNS::number, "denominator-value" ) ) + { + int d = e.attributeNS( KoXmlNS::number, "denominator-value", QString::null ).toInt( &ok ); + if ( ok ) + denominatorValue = d; + } + + for ( i = 0; i < integer; ++i ) + format+='#'; + + format+=' '; + + for ( i = 0; i < numerator; ++i ) + format+='?'; + + format+='/'; + + if ( denominatorValue != 0 ) + format+=QString::number( denominatorValue ); + else + { + for ( i = 0; i < denominator; ++i ) + format+='?'; + } + } + // Not needed: + // <style:map style:condition="value()>=0" style:apply-style-name="N106P0"/> + // we handle painting negative numbers in red differently + + } + + const QString styleName = parent.attributeNS( KoXmlNS::style, "name", QString::null ); + kdDebug(30003) << "data style: " << styleName << " qt format=" << format << endl; + if ( !prefix.isEmpty() ) + { + kdDebug(30003)<<" format.left( prefix.length() ) :"<<format.left( prefix.length() )<<" prefix :"<<prefix<<endl; + if ( format.left( prefix.length() )==prefix ) + { + format = format.right( format.length()-prefix.length() ); + } + else + prefix = QString::null; + } + if ( !suffix.isEmpty() ) + { + kdDebug(30003)<<"format.right( suffix.length() ) :"<<format.right( suffix.length() )<<" suffix :"<<suffix<<endl; + if ( format.right( suffix.length() )==suffix ) + { + format = format.left( format.length()-suffix.length() ); + } + else + suffix = QString::null; + } + + dataStyle.formatStr = format; + dataStyle.prefix = prefix; + dataStyle.suffix = suffix; + dataStyle.precision = precision; + kdDebug()<<" finish insert format :"<<format<<" prefix :"<<prefix<<" suffix :"<<suffix<<endl; + m_dataFormats.insert( styleName, dataStyle ); +} + +#define addTextNumber( text, elementWriter ) { \ + if ( !text.isEmpty() ) \ + { \ + elementWriter.startElement( "number:text" ); \ + elementWriter.addTextNode( text ); \ + elementWriter.endElement(); \ + text=""; \ + } \ +} + +void KoOasisStyles::parseOasisTimeKlocale(KoXmlWriter &elementWriter, QString & format, QString & text ) +{ + kdDebug(30003)<<"parseOasisTimeKlocale(KoXmlWriter &elementWriter, QString & format, QString & text ) :"<<format<<endl; + do + { + if ( !saveOasisKlocaleTimeFormat( elementWriter, format, text ) ) + { + text += format[0]; + format = format.remove( 0, 1 ); + } + } + while ( format.length() > 0 ); + addTextNumber( text, elementWriter ); +} + +bool KoOasisStyles::saveOasisKlocaleTimeFormat( KoXmlWriter &elementWriter, QString & format, QString & text ) +{ + bool changed = false; + if ( format.startsWith( "%H" ) ) //hh + { + //hour in 24h + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:hours" ); + elementWriter.addAttribute( "number:style", "long" ); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + changed = true; + } + else if ( format.startsWith( "%k" ) )//h + { + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:hours" ); + elementWriter.addAttribute( "number:style", "short" ); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + changed = true; + } + else if ( format.startsWith( "%I" ) )// ????? + { + //TODO hour in 12h + changed = true; + } + else if ( format.startsWith( "%l" ) ) + { + //TODO hour in 12h with 1 digit + changed = true; + } + else if ( format.startsWith( "%M" ) )// mm + { + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:minutes" ); + elementWriter.addAttribute( "number:style", "long" ); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + changed = true; + + } + else if ( format.startsWith( "%S" ) ) //ss + { + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:seconds" ); + elementWriter.addAttribute( "number:style", "long" ); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + changed = true; + } + else if ( format.startsWith( "%p" ) ) + { + //TODO am or pm + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:am-pm" ); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + changed = true; + } + return changed; +} + + +bool KoOasisStyles::saveOasisTimeFormat( KoXmlWriter &elementWriter, QString & format, QString & text, bool &antislash ) +{ + bool changed = false; + //we can also add time to date. + if ( antislash ) + { + text+=format[0]; + format = format.remove( 0, 1 ); + antislash = false; + changed = true; + } + else if ( format.startsWith( "hh" ) ) + { + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:hours" ); + elementWriter.addAttribute( "number:style", "long" ); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + changed = true; + } + else if ( format.startsWith( "h" ) ) + { + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:hours" ); + elementWriter.addAttribute( "number:style", "short" ); + elementWriter.endElement(); + format = format.remove( 0, 1 ); + changed = true; + } + else if ( format.startsWith( "mm" ) ) + { + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:minutes" ); + elementWriter.addAttribute( "number:style", "long" ); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + changed = true; + } + else if ( format.startsWith( "m" ) ) + { + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:minutes" ); + elementWriter.addAttribute( "number:style", "short" ); + elementWriter.endElement(); + format = format.remove( 0, 1 ); + changed = true; + } + else if ( format.startsWith( "ss" ) ) + { + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:seconds" ); + elementWriter.addAttribute( "number:style", "long" ); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + changed = true; + } + else if ( format.startsWith( "s" ) ) + { + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:seconds" ); + elementWriter.addAttribute( "number:style", "short" ); + elementWriter.endElement(); + format = format.remove( 0, 1 ); + changed = true; + } + else if ( format.startsWith( "ap" ) ) + { + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:am-pm" ); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + changed = true; + } + return changed; +} + +QString KoOasisStyles::saveOasisTimeStyle( KoGenStyles &mainStyles, const QString & _format, bool klocaleFormat, + const QString & _prefix, const QString & _suffix ) +{ + Q_UNUSED(_prefix); + Q_UNUSED(_suffix); + kdDebug(30003)<<"QString KoOasisStyles::saveOasisTimeStyle( KoGenStyles &mainStyles, const QString & _format ) :"<<_format<<endl; + QString format( _format ); + KoGenStyle currentStyle( KoGenStyle::STYLE_NUMERIC_TIME ); + QBuffer buffer; + buffer.open( IO_WriteOnly ); + KoXmlWriter elementWriter( &buffer ); // TODO pass indentation level + QString text; + if ( klocaleFormat ) + { + parseOasisTimeKlocale( elementWriter, format, text ); + } + else + { + bool antislash = false; + do + { + if ( !saveOasisTimeFormat( elementWriter, format, text, antislash ) ) + { + QString elem( format[0] ); + format = format.remove( 0, 1 ); + if ( elem == "\\" ) + { + antislash = true; + } + else + { + text += elem; + antislash = false; + } + } + } + while ( format.length() > 0 ); + addTextNumber( text, elementWriter ); + } + QString elementContents = QString::fromUtf8( buffer.buffer(), buffer.buffer().size() ); + currentStyle.addChildElement( "number", elementContents ); + return mainStyles.lookup( currentStyle, "N" ); +} + +//convert klocale string to good format +void KoOasisStyles::parseOasisDateKlocale(KoXmlWriter &elementWriter, QString & format, QString & text ) +{ + kdDebug(30003)<<"KoOasisStyles::parseOasisDateKlocale(KoXmlWriter &elementWriter, QString & format, QString & text ) :"<<format<<endl; + do + { + if ( format.startsWith( "%Y" ) ) + { + addTextNumber( text, elementWriter ); + elementWriter.startElement( "number:year" ); + elementWriter.addAttribute( "number:style", "long" ); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + } + else if ( format.startsWith( "%y" ) ) + { + + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:year" ); + elementWriter.addAttribute( "number:style", "short" ); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + } + else if ( format.startsWith( "%n" ) ) + { + addTextNumber( text, elementWriter ); + elementWriter.startElement( "number:month" ); + elementWriter.addAttribute( "number:style", "short" ); + elementWriter.addAttribute( "number:textual", "false"); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + } + else if ( format.startsWith( "%m" ) ) + { + addTextNumber( text, elementWriter ); + elementWriter.startElement( "number:month" ); + elementWriter.addAttribute( "number:style", "long" ); + elementWriter.addAttribute( "number:textual", "false"); //not necessary remove it + elementWriter.endElement(); + format = format.remove( 0, 2 ); + } + else if ( format.startsWith( "%e" ) ) + { + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:day" ); + elementWriter.addAttribute( "number:style", "short" ); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + } + else if ( format.startsWith( "%d" ) ) + { + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:day" ); + elementWriter.addAttribute( "number:style", "long" ); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + } + else if ( format.startsWith( "%b" ) ) + { + addTextNumber( text, elementWriter ); + elementWriter.startElement( "number:month" ); + elementWriter.addAttribute( "number:style", "short" ); + elementWriter.addAttribute( "number:textual", "true"); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + } + else if ( format.startsWith( "%B" ) ) + { + addTextNumber( text, elementWriter ); + elementWriter.startElement( "number:month" ); + elementWriter.addAttribute( "number:style", "long" ); + elementWriter.addAttribute( "number:textual", "true"); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + } + else if ( format.startsWith( "%a" ) ) + { + addTextNumber( text, elementWriter ); + elementWriter.startElement( "number:day-of-week" ); + elementWriter.addAttribute( "number:style", "short" ); + elementWriter.endElement(); + + format = format.remove( 0, 2 ); + } + else if ( format.startsWith( "%A" ) ) + { + addTextNumber( text, elementWriter ); + elementWriter.startElement( "number:day-of-week" ); + elementWriter.addAttribute( "number:style", "long" ); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + } + else + { + if ( !saveOasisKlocaleTimeFormat( elementWriter, format, text ) ) + { + text += format[0]; + format = format.remove( 0, 1 ); + } + } + } + while ( format.length() > 0 ); + addTextNumber( text, elementWriter ); +} + +QString KoOasisStyles::saveOasisDateStyle( KoGenStyles &mainStyles, const QString & _format, bool klocaleFormat, + const QString & _prefix, const QString & _suffix ) +{ + Q_UNUSED(_prefix); + Q_UNUSED(_suffix); + kdDebug(30003)<<"QString KoOasisStyles::saveOasisDateStyle( KoGenStyles &mainStyles, const QString & _format ) :"<<_format<<endl; + QString format( _format ); + + // Not supported into Qt: "era" "week-of-year" "quarter" + + KoGenStyle currentStyle( KoGenStyle::STYLE_NUMERIC_DATE ); + QBuffer buffer; + buffer.open( IO_WriteOnly ); + KoXmlWriter elementWriter( &buffer ); // TODO pass indentation level + QString text; + if ( klocaleFormat ) + { + parseOasisDateKlocale( elementWriter, format, text ); + } + else + { + bool antislash = false; + do + { + if ( antislash ) + { + text+=format[0]; + format = format.remove( 0, 1 ); + } + //TODO implement loading ! What is it ? + else if ( format.startsWith( "MMMMM" ) ) + { + addTextNumber( text, elementWriter ); + elementWriter.startElement( "number:month" ); + elementWriter.addAttribute( "number:textual", "true"); + elementWriter.endElement(); + format = format.remove( 0, 5 ); + } + else if ( format.startsWith( "MMMM" ) ) + { + addTextNumber( text, elementWriter ); + elementWriter.startElement( "number:month" ); + elementWriter.addAttribute( "number:style", "long" ); + elementWriter.addAttribute( "number:textual", "true"); + elementWriter.endElement(); + format = format.remove( 0, 4 ); + } + else if ( format.startsWith( "MMM" ) ) + { + addTextNumber( text, elementWriter ); + elementWriter.startElement( "number:month" ); + elementWriter.addAttribute( "number:style", "short" ); + elementWriter.addAttribute( "number:textual", "true"); + elementWriter.endElement(); + format = format.remove( 0, 3 ); + } + else if ( format.startsWith( "MM" ) ) + { + addTextNumber( text, elementWriter ); + elementWriter.startElement( "number:month" ); + elementWriter.addAttribute( "number:style", "long" ); + elementWriter.addAttribute( "number:textual", "false"); //not necessary remove it + elementWriter.endElement(); + format = format.remove( 0, 2 ); + } + else if ( format.startsWith( "M" ) ) + { + addTextNumber( text, elementWriter ); + elementWriter.startElement( "number:month" ); + elementWriter.addAttribute( "number:style", "short" ); + elementWriter.addAttribute( "number:textual", "false"); + elementWriter.endElement(); + format = format.remove( 0, 1 ); + } + else if ( format.startsWith( "PPPP" ) ) + { + addTextNumber( text, elementWriter ); + //<number:month number:possessive-form="true" number:textual="true" number:style="long"/> + elementWriter.startElement( "number:month" ); + elementWriter.addAttribute( "number:style", "short" ); + elementWriter.addAttribute( "number:textual", "false"); + elementWriter.addAttribute( "number:possessive-form", "true" ); + elementWriter.endElement(); + format = format.remove( 0, 4 ); + } + else if ( format.startsWith( "PPP" ) ) + { + addTextNumber( text, elementWriter ); + //<number:month number:possessive-form="true" number:textual="true" number:style="short"/> + elementWriter.startElement( "number:month" ); + elementWriter.addAttribute( "number:possessive-form", "true" ); + + elementWriter.addAttribute( "number:style", "short" ); + elementWriter.addAttribute( "number:textual", "false"); + elementWriter.endElement(); + format = format.remove( 0, 3 ); + } + else if ( format.startsWith( "dddd" ) ) + { + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:day-of-week" ); + elementWriter.addAttribute( "number:style", "long" ); + elementWriter.endElement(); + format = format.remove( 0, 4 ); + } + else if ( format.startsWith( "ddd" ) ) + { + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:day-of-week" ); + elementWriter.addAttribute( "number:style", "short" ); + elementWriter.endElement(); + format = format.remove( 0, 3 ); + } + else if ( format.startsWith( "dd" ) ) + { + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:day" ); + elementWriter.addAttribute( "number:style", "long" ); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + } + else if ( format.startsWith( "d" ) ) + { + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:day" ); + elementWriter.addAttribute( "number:style", "short" ); + elementWriter.endElement(); + format = format.remove( 0, 1 ); + } + else if ( format.startsWith( "yyyy" ) ) + { + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:year" ); + elementWriter.addAttribute( "number:style", "long" ); + elementWriter.endElement(); + format = format.remove( 0, 4 ); + } + else if ( format.startsWith( "yy" ) ) + { + addTextNumber( text, elementWriter ); + + elementWriter.startElement( "number:year" ); + elementWriter.addAttribute( "number:style", "short" ); + elementWriter.endElement(); + format = format.remove( 0, 2 ); + } + else + { + if ( !saveOasisTimeFormat( elementWriter, format, text,antislash ) ) + { + QString elem( format[0] ); + format = format.remove( 0, 1 ); + if ( elem == "\\" ) + { + antislash = true; + } + else + { + text += elem; + antislash = false; + } + } + } + } + while ( format.length() > 0 ); + addTextNumber( text, elementWriter ); + } + + QString elementContents = QString::fromUtf8( buffer.buffer(), buffer.buffer().size() ); + currentStyle.addChildElement( "number", elementContents ); + return mainStyles.lookup( currentStyle, "N" ); +} + + +QString KoOasisStyles::saveOasisFractionStyle( KoGenStyles &mainStyles, const QString & _format, const QString &_prefix, const QString &_suffix ) +{ + kdDebug(30003)<<"QString saveOasisFractionStyle( KoGenStyles &mainStyles, const QString & _format ) :"<<_format<<endl; + QString format( _format ); + + KoGenStyle currentStyle( KoGenStyle::STYLE_NUMERIC_FRACTION ); + QBuffer buffer; + buffer.open( IO_WriteOnly ); + KoXmlWriter elementWriter( &buffer ); // TODO pass indentation level + QString text; + int integer = 0; + int numerator = 0; + int denominator = 0; + int denominatorValue = 0; + bool beforeSlash = true; + do + { + if ( format[0]=='#' ) + integer++; + else if ( format[0]=='/' ) + beforeSlash = false; + else if ( format[0]=='?' ) + { + if ( beforeSlash ) + numerator++; + else + denominator++; + } + else + { + bool ok; + int value = format.toInt( &ok ); + if ( ok ) + { + denominatorValue=value; + break; + } + } + format.remove( 0,1 ); + } + while ( format.length() > 0 ); + + text= _prefix; + addTextNumber(text, elementWriter ); + + elementWriter.startElement( "number:fraction" ); + elementWriter.addAttribute( "number:min-integer-digits", integer ); + elementWriter.addAttribute( "number:min-numerator-digits",numerator ); + elementWriter.addAttribute( "number:min-denominator-digits",denominator ); + if ( denominatorValue != 0 ) + elementWriter.addAttribute( "number:denominator-value",denominatorValue ); + elementWriter.endElement(); + + addKofficeNumericStyleExtension( elementWriter, _suffix, _prefix ); + + text=_suffix; + addTextNumber(text, elementWriter ); + + QString elementContents = QString::fromUtf8( buffer.buffer(), buffer.buffer().size() ); + currentStyle.addChildElement( "number", elementContents ); + return mainStyles.lookup( currentStyle, "N" ); +} + + +QString KoOasisStyles::saveOasisNumberStyle( KoGenStyles &mainStyles, const QString & _format, const QString &_prefix, const QString &_suffix ) +{ + kdDebug(30003)<<"QString saveOasisNumberStyle( KoGenStyles &mainStyles, const QString & _format ) :"<<_format<<endl; + QString format( _format ); + + KoGenStyle currentStyle( KoGenStyle::STYLE_NUMERIC_NUMBER ); + QBuffer buffer; + buffer.open( IO_WriteOnly ); + KoXmlWriter elementWriter( &buffer ); // TODO pass indentation level + QString text; + int decimalplaces = 0; + int integerdigits = 0; + bool beforeSeparator = true; + do + { + if ( format[0]=='.' || format[0]==',' ) + beforeSeparator = false; + else if ( format[0]=='0' && beforeSeparator ) + integerdigits++; + else if ( format[0]=='0' && !beforeSeparator ) + decimalplaces++; + else + kdDebug(30003)<<" error format 0 \n"; + format.remove( 0,1 ); + } + while ( format.length() > 0 ); + text= _prefix ; + addTextNumber(text, elementWriter ); + elementWriter.startElement( "number:number" ); + kdDebug(30003)<<" decimalplaces :"<<decimalplaces<<" integerdigits :"<<integerdigits<<endl; + if (!beforeSeparator) + elementWriter.addAttribute( "number:decimal-places", decimalplaces ); + elementWriter.addAttribute( "number:min-integer-digits", integerdigits ); + elementWriter.endElement(); + + text =_suffix ; + addTextNumber(text, elementWriter ); + addKofficeNumericStyleExtension( elementWriter, _suffix,_prefix ); + + QString elementContents = QString::fromUtf8( buffer.buffer(), buffer.buffer().size() ); + currentStyle.addChildElement( "number", elementContents ); + return mainStyles.lookup( currentStyle, "N" ); +} + +QString KoOasisStyles::saveOasisPercentageStyle( KoGenStyles &mainStyles, const QString & _format, const QString &_prefix, const QString &_suffix ) +{ + //<number:percentage-style style:name="N11"> + //<number:number number:decimal-places="2" number:min-integer-digits="1"/> + //<number:text>%</number:text> + //</number:percentage-style> + + kdDebug(30003)<<"QString saveOasisPercentageStyle( KoGenStyles &mainStyles, const QString & _format ) :"<<_format<<endl; + QString format( _format ); + + KoGenStyle currentStyle( KoGenStyle::STYLE_NUMERIC_PERCENTAGE ); + QBuffer buffer; + buffer.open( IO_WriteOnly ); + KoXmlWriter elementWriter( &buffer ); // TODO pass indentation level + QString text; + int decimalplaces = 0; + int integerdigits = 0; + bool beforeSeparator = true; + do + { + if ( format[0]=='.' || format[0]==',' ) + beforeSeparator = false; + else if ( format[0]=='0' && beforeSeparator ) + integerdigits++; + else if ( format[0]=='0' && !beforeSeparator ) + decimalplaces++; + else + kdDebug(30003)<<" error format 0 \n"; + format.remove( 0,1 ); + } + while ( format.length() > 0 ); + text= _prefix ; + addTextNumber(text, elementWriter ); + elementWriter.startElement( "number:number" ); + if (!beforeSeparator) + elementWriter.addAttribute( "number:decimal-places", decimalplaces ); + elementWriter.addAttribute( "number:min-integer-digits", integerdigits ); + elementWriter.endElement(); + + addTextNumber(QString( "%" ), elementWriter ); + + text =_suffix ; + addTextNumber(text, elementWriter ); + addKofficeNumericStyleExtension( elementWriter, _suffix,_prefix ); + + QString elementContents = QString::fromUtf8( buffer.buffer(), buffer.buffer().size() ); + currentStyle.addChildElement( "number", elementContents ); + return mainStyles.lookup( currentStyle, "N" ); + +} + +QString KoOasisStyles::saveOasisScientificStyle( KoGenStyles &mainStyles, const QString & _format, const QString &_prefix, const QString &_suffix ) +{ + //<number:number-style style:name="N60"> + //<number:scientific-number number:decimal-places="2" number:min-integer-digits="1" number:min-exponent-digits="3"/> + //</number:number-style> + + //example 000,000e+0000 + kdDebug(30003)<<"QString saveOasisScientificStyle( KoGenStyles &mainStyles, const QString & _format ) :"<<_format<<endl; + QString format( _format ); + + KoGenStyle currentStyle( KoGenStyle::STYLE_NUMERIC_SCIENTIFIC ); + QBuffer buffer; + buffer.open( IO_WriteOnly ); + int decimalplace = 0; + int integerdigits = 0; + int exponentdigits = 0; + KoXmlWriter elementWriter( &buffer ); // TODO pass indentation level + QString text; + bool beforeSeparator = true; + bool exponential = false; + bool positive = true; + do + { + if ( !exponential ) + { + if ( format[0]=='0' && beforeSeparator ) + integerdigits++; + else if ( format[0]==',' || format[0]=='.' ) + beforeSeparator = false; + else if ( format[0]=='0' && !beforeSeparator ) + decimalplace++; + else if ( format[0].lower()=='e' ) + { + format.remove( 0, 1 ); + if ( format[0]=='+' ) + positive = true; + else if ( format[0]=='-' ) + positive = false; + else + kdDebug(30003)<<"Error into scientific number\n"; + exponential = true; + } + } + else + { + if ( format[0]=='0' && positive ) + exponentdigits++; + else if ( format[0]=='0' && !positive ) + exponentdigits--; + else + kdDebug(30003)<<" error into scientific number exponential value\n"; + } + format.remove( 0,1 ); + } + while ( format.length() > 0 ); + text = _prefix ; + addTextNumber(text, elementWriter ); + + elementWriter.startElement( "number:scientific-number" ); + kdDebug(30003)<<" decimalplace :"<<decimalplace<<" integerdigits :"<<integerdigits<<" exponentdigits :"<<exponentdigits<<endl; + if (!beforeSeparator) + elementWriter.addAttribute( "number:decimal-places", decimalplace ); + elementWriter.addAttribute( "number:min-integer-digits",integerdigits ); + elementWriter.addAttribute( "number:min-exponent-digits",exponentdigits ); + elementWriter.endElement(); + + text = _suffix; + addTextNumber(text, elementWriter ); + addKofficeNumericStyleExtension( elementWriter, _suffix,_prefix ); + + QString elementContents = QString::fromUtf8( buffer.buffer(), buffer.buffer().size() ); + currentStyle.addChildElement( "number", elementContents ); + return mainStyles.lookup( currentStyle, "N" ); +} + +QString KoOasisStyles::saveOasisCurrencyStyle( KoGenStyles &mainStyles, + const QString & _format, const QString &symbol, + const QString &_prefix, const QString &_suffix ) +{ + + //<number:currency-style style:name="N107P0" style:volatile="true"> + //<number:number number:decimal-places="2" number:min-integer-digits="1" number:grouping="true"/> + //<number:text> </number:text> + //<number:currency-symbol>VEB</number:currency-symbol> + //</number:currency-style> + + kdDebug(30003)<<"QString saveOasisCurrencyStyle( KoGenStyles &mainStyles, const QString & _format ) :"<<_format<<endl; + QString format( _format ); + + KoGenStyle currentStyle( KoGenStyle::STYLE_NUMERIC_CURRENCY ); + QBuffer buffer; + buffer.open( IO_WriteOnly ); + KoXmlWriter elementWriter( &buffer ); // TODO pass indentation level + QString text; + int decimalplaces = 0; + int integerdigits = 0; + bool beforeSeparator = true; + do + { + if ( format[0]=='.' || format[0]==',' ) + beforeSeparator = false; + else if ( format[0]=='0' && beforeSeparator ) + integerdigits++; + else if ( format[0]=='0' && !beforeSeparator ) + decimalplaces++; + else + kdDebug(30003)<<" error format 0 \n"; + format.remove( 0,1 ); + } + while ( format.length() > 0 ); + + text = _prefix ; + addTextNumber(text, elementWriter ); + + elementWriter.startElement( "number:number" ); + kdDebug(30003)<<" decimalplaces :"<<decimalplaces<<" integerdigits :"<<integerdigits<<endl; + if (!beforeSeparator) + elementWriter.addAttribute( "number:decimal-places", decimalplaces ); + elementWriter.addAttribute( "number:min-integer-digits", integerdigits ); + elementWriter.endElement(); + + text = _suffix ; + addTextNumber(text, elementWriter ); + addKofficeNumericStyleExtension( elementWriter, _suffix,_prefix ); + + elementWriter.startElement( "number:currency-symbol" ); + kdDebug(30003) << " currency-symbol: " << symbol << endl; + elementWriter.addTextNode( symbol.utf8() ); + elementWriter.endElement(); + + QString elementContents = QString::fromUtf8( buffer.buffer(), buffer.buffer().size() ); + currentStyle.addChildElement( "number", elementContents ); + return mainStyles.lookup( currentStyle, "N" ); +} + +QString KoOasisStyles::saveOasisTextStyle( KoGenStyles &mainStyles, const QString & _format, const QString &_prefix, const QString &_suffix ) +{ + + //<number:text-style style:name="N100"> + //<number:text-content/> + ///</number:text-style> + + kdDebug(30003)<<"QString saveOasisTextStyle( KoGenStyles &mainStyles, const QString & _format ) :"<<_format<<endl; + QString format( _format ); + + KoGenStyle currentStyle( KoGenStyle::STYLE_NUMERIC_TEXT ); + QBuffer buffer; + buffer.open( IO_WriteOnly ); + KoXmlWriter elementWriter( &buffer ); // TODO pass indentation level + QString text; + do + { + format.remove( 0,1 ); + } + while ( format.length() > 0 ); + text = _prefix ; + addTextNumber(text, elementWriter ); + + elementWriter.startElement( "number:text-style" ); + + text = _suffix ; + addTextNumber(text, elementWriter ); + addKofficeNumericStyleExtension( elementWriter, _suffix,_prefix ); + elementWriter.endElement(); + + QString elementContents = QString::fromUtf8( buffer.buffer(), buffer.buffer().size() ); + currentStyle.addChildElement( "number", elementContents ); + return mainStyles.lookup( currentStyle, "N" ); +} + +//This is an extension of numeric style. For the moment we used namespace of oasis format for specific koffice extention. change it for the futur. +void KoOasisStyles::addKofficeNumericStyleExtension( KoXmlWriter & elementWriter, const QString &_suffix, const QString &_prefix ) + { + if ( !_suffix.isEmpty() ) + { + elementWriter.startElement( "number:suffix" ); + elementWriter.addTextNode( _suffix ); + elementWriter.endElement(); + } + if ( !_prefix.isEmpty() ) + { + elementWriter.startElement( "number:prefix" ); + elementWriter.addTextNode( _prefix ); + elementWriter.endElement(); + } +} + +void KoOasisStyles::saveOasisFillStyle( KoGenStyle &styleFill, KoGenStyles& mainStyles, const QBrush & brush ) +{ + if ( brush.style() == Qt::SolidPattern ) + { + styleFill.addProperty( "draw:fill","solid" ); + styleFill.addProperty( "draw:fill-color", brush.color().name() ); + } + else if ( brush.style() == Qt::Dense1Pattern ) + { + styleFill.addProperty( "draw:transparency", "94%" ); + styleFill.addProperty( "draw:fill","solid" ); + styleFill.addProperty( "draw:fill-color", brush.color().name() ); + } + else if ( brush.style() == Qt::Dense2Pattern ) + { + styleFill.addProperty( "draw:transparency", "88%" ); + styleFill.addProperty( "draw:fill","solid" ); + styleFill.addProperty( "draw:fill-color", brush.color().name() ); + } + else if ( brush.style() == Qt::Dense3Pattern ) + { + styleFill.addProperty( "draw:transparency", "63%" ); + styleFill.addProperty( "draw:fill","solid" ); + styleFill.addProperty( "draw:fill-color", brush.color().name() ); + } + else if ( brush.style() == Qt::Dense4Pattern ) + { + styleFill.addProperty( "draw:transparency", "50%" ); + styleFill.addProperty( "draw:fill","solid" ); + styleFill.addProperty( "draw:fill-color", brush.color().name() ); + } + else if ( brush.style() == Qt::Dense5Pattern ) + { + styleFill.addProperty( "draw:transparency", "37%" ); + styleFill.addProperty( "draw:fill","solid" ); + styleFill.addProperty( "draw:fill-color", brush.color().name() ); + } + else if ( brush.style() == Qt::Dense6Pattern ) + { + styleFill.addProperty( "draw:transparency", "12%" ); + styleFill.addProperty( "draw:fill","solid" ); + styleFill.addProperty( "draw:fill-color", brush.color().name() ); + } + else if ( brush.style() == Qt::Dense7Pattern ) + { + styleFill.addProperty( "draw:transparency", "6%" ); + styleFill.addProperty( "draw:fill","solid" ); + styleFill.addProperty( "draw:fill-color", brush.color().name() ); + } + else //otherstyle + { + styleFill.addProperty( "draw:fill","hatch" ); + styleFill.addProperty( "draw:fill-hatch-name", saveOasisHatchStyle( mainStyles,brush ) ); + } + +} + +QString KoOasisStyles::saveOasisHatchStyle( KoGenStyles& mainStyles, const QBrush &brush ) +{ + KoGenStyle hatchStyle( KoGenStyle::STYLE_HATCH /*no family name*/); + hatchStyle.addAttribute( "draw:color", brush.color().name() ); + //hatchStyle.addAttribute( "draw:distance", m_distance ); not implemented into kpresenter + switch( brush.style() ) + { + case Qt::HorPattern: + hatchStyle.addAttribute( "draw:style", "single" ); + hatchStyle.addAttribute( "draw:rotation", 0); + break; + case Qt::BDiagPattern: + hatchStyle.addAttribute( "draw:style", "single" ); + hatchStyle.addAttribute( "draw:rotation", 450); + break; + case Qt::VerPattern: + hatchStyle.addAttribute( "draw:style", "single" ); + hatchStyle.addAttribute( "draw:rotation", 900); + break; + case Qt::FDiagPattern: + hatchStyle.addAttribute( "draw:style", "single" ); + hatchStyle.addAttribute( "draw:rotation", 1350); + break; + case Qt::CrossPattern: + hatchStyle.addAttribute( "draw:style", "double" ); + hatchStyle.addAttribute( "draw:rotation", 0); + break; + case Qt::DiagCrossPattern: + hatchStyle.addAttribute( "draw:style", "double" ); + hatchStyle.addAttribute( "draw:rotation", 450); + break; + default: + break; + } + + return mainStyles.lookup( hatchStyle, "hatch" ); +} + +QBrush KoOasisStyles::loadOasisFillStyle( const KoStyleStack &styleStack, const QString & fill, const KoOasisStyles & oasisStyles ) +{ + QBrush tmpBrush; + if ( fill == "solid" ) + { + tmpBrush.setStyle(static_cast<Qt::BrushStyle>( 1 ) ); + if ( styleStack.hasAttributeNS( KoXmlNS::draw, "fill-color" ) ) + tmpBrush.setColor(styleStack.attributeNS( KoXmlNS::draw, "fill-color" ) ); + if ( styleStack.hasAttributeNS( KoXmlNS::draw, "transparency" ) ) + { + QString transparency = styleStack.attributeNS( KoXmlNS::draw, "transparency" ); + if ( transparency == "94%" ) + { + tmpBrush.setStyle(Qt::Dense1Pattern); + } + else if ( transparency == "88%" ) + { + tmpBrush.setStyle(Qt::Dense2Pattern); + } + else if ( transparency == "63%" ) + { + tmpBrush.setStyle(Qt::Dense3Pattern); + + } + else if ( transparency == "50%" ) + { + tmpBrush.setStyle(Qt::Dense4Pattern); + + } + else if ( transparency == "37%" ) + { + tmpBrush.setStyle(Qt::Dense5Pattern); + + } + else if ( transparency == "12%" ) + { + tmpBrush.setStyle(Qt::Dense6Pattern); + + } + else if ( transparency == "6%" ) + { + tmpBrush.setStyle(Qt::Dense7Pattern); + + } + else + kdDebug()<<" transparency is not defined into kpresenter :"<<transparency<<endl; + } + } + else if ( fill == "hatch" ) + { + QString style = styleStack.attributeNS( KoXmlNS::draw, "fill-hatch-name" ); + kdDebug()<<" hatch style is : "<<style<<endl; + + //type not defined by default + //try to use style. + QDomElement* draw = oasisStyles.drawStyles()[style]; + if ( draw) + { + kdDebug()<<"We have a style\n"; + int angle = 0; + if( draw->hasAttributeNS( KoXmlNS::draw, "rotation" )) + { + angle = (draw->attributeNS( KoXmlNS::draw, "rotation", QString::null ).toInt())/10; + kdDebug()<<"angle :"<<angle<<endl; + } + if(draw->hasAttributeNS( KoXmlNS::draw, "color" ) ) + { + //kdDebug()<<" draw:color :"<<draw->attributeNS( KoXmlNS::draw, "color", QString::null )<<endl; + tmpBrush.setColor(draw->attributeNS( KoXmlNS::draw, "color", QString::null ) ); + } + if( draw->hasAttributeNS( KoXmlNS::draw, "distance" )) + { + //todo implemente it into kpresenter + } + if( draw->hasAttributeNS( KoXmlNS::draw, "display-name")) + { + //todo implement it into kpresenter + } + if( draw->hasAttributeNS( KoXmlNS::draw, "style" )) + { + //todo implemente it into kpresenter + QString styleHash = draw->attributeNS( KoXmlNS::draw, "style", QString::null ); + if( styleHash == "single") + { + switch( angle ) + { + case 0: + case 180: + tmpBrush.setStyle(Qt::HorPattern ); + break; + case 45: + case 225: + tmpBrush.setStyle(Qt::BDiagPattern ); + break; + case 90: + case 270: + tmpBrush.setStyle(Qt::VerPattern ); + break; + case 135: + case 315: + tmpBrush.setStyle(Qt::FDiagPattern ); + break; + default: + //todo fixme when we will have a kopaint + kdDebug()<<" draw:rotation 'angle' : "<<angle<<endl; + break; + } + } + else if( styleHash == "double") + { + switch( angle ) + { + case 0: + case 180: + case 90: + case 270: + tmpBrush.setStyle(Qt::CrossPattern ); + break; + case 45: + case 135: + case 225: + case 315: + tmpBrush.setStyle(Qt::DiagCrossPattern ); + break; + default: + //todo fixme when we will have a kopaint + kdDebug()<<" draw:rotation 'angle' : "<<angle<<endl; + break; + } + + } + else if( styleHash == "triple") + { + kdDebug()<<" it is not implemented :( \n"; + } + } + } + } + return tmpBrush; +} + +const QDomElement* KoOasisStyles::defaultStyle( const QString& family ) const +{ + return m_defaultStyle[family]; +} + +const QDomElement* KoOasisStyles::findStyle( const QString& name ) const +{ + return m_styles[ name ]; +} + +const QDomElement* KoOasisStyles::findStyle( const QString& styleName, const QString& family ) const +{ + const QDomElement* style = d->m_styles[ family ][ styleName ]; + if ( style && !family.isEmpty() ) { + const QString styleFamily = style->attributeNS( KoXmlNS::style, "family", QString::null ); + if ( styleFamily != family ) { + kdWarning() << "KoOasisStyles: was looking for style " << styleName + << " in family " << family << " but got " << styleFamily << endl; + } + } + return style; +} + +const QDomElement* KoOasisStyles::findStyleAutoStyle( const QString& styleName, const QString& family ) const +{ + const QDomElement* style = d->m_stylesAutoStyles[ family ][ styleName ]; + if ( style ) { + const QString styleFamily = style->attributeNS( KoXmlNS::style, "family", QString::null ); + if ( styleFamily != family ) { + kdWarning() << "KoOasisStyles: was looking for style " << styleName + << " in family " << family << " but got " << styleFamily << endl; + } + } + return style; +} diff --git a/lib/kofficecore/KoOasisStyles.h b/lib/kofficecore/KoOasisStyles.h new file mode 100644 index 00000000..caa8d513 --- /dev/null +++ b/lib/kofficecore/KoOasisStyles.h @@ -0,0 +1,165 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2006 David Faure <faure@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 KOOASISSTYLES_H +#define KOOASISSTYLES_H + +#include <qdom.h> +#include <qdict.h> +#include <qvaluevector.h> +#include <qmap.h> +#include <koffice_export.h> + +class KoGenStyles; +class KoXmlWriter; +class QBrush; +class KoGenStyle; +class KoStyleStack; + +/** + * Repository of styles used during loading of OASIS/OOo file + */ +class KOFFICECORE_EXPORT KoOasisStyles +{ +public: + KoOasisStyles(); + ~KoOasisStyles(); + + /// Look into @p doc for styles and remember them + /// @param doc document to look into + /// @param stylesDotXml true when loading styles.xml, false otherwise + void createStyleMap( const QDomDocument& doc, bool stylesDotXml ); + + /** + * Look up a style by name. + * This method can find styles defined by the tags "style:page-layout", + * "style:presentation-page-layout", or "style:font-decl". + * Do NOT use this method for style:style styles. + * + * @param name the style name + * @return the dom element representing the style, or QString::null if it wasn't found. + */ + const QDomElement* findStyle( const QString& name ) const; + + /** + * Look up a style:style by name. + * @param name the style name + * @param family the style family (for a style:style, use 0 otherwise) + * @return the dom element representing the style, or QString::null if it wasn't found. + */ + const QDomElement* findStyle( const QString& name, const QString& family ) const; + + /// Similar to findStyle but for auto-styles in styles.xml only. + const QDomElement* findStyleAutoStyle( const QString& name, const QString& family ) const; + + /// @return the style:styles that are "user styles", i.e. those from office:styles + /// findStyle() is used for lookup. userStyles() is used to load all user styles upfront. + QValueVector<QDomElement> userStyles() const; + + /// @return the default style for a given family ("graphic","paragraph","table" etc.) + /// Returns 0 if no default style for this family is available + const QDomElement* defaultStyle( const QString& family ) const; + + /// @return the office:style element + const QDomElement& officeStyle() const { return m_officeStyle; } + + /// @return all list styles ("text:list-style" elements), hashed by name + const QDict<QDomElement>& listStyles() const { return m_listStyles; } + + /// @return master pages ("style:master-page" elements), hashed by name + const QDict<QDomElement>& masterPages() const { return m_masterPages; } + + /// @return draw styles, hashed by name + const QDict<QDomElement>& drawStyles() const { return m_drawStyles; } + + /// @return all styles ("style:style" elements) for a given family, hashed by name + const QDict<QDomElement>& styles(const QString& family) const; + + /// Prefix and suffix are always included into formatStr. Having them as separate fields simply + /// allows to extract them from formatStr, to display them in separate widgets. + struct NumericStyleFormat + { + QString formatStr; + QString prefix; + QString suffix; + enum { Number, Scientific, Fraction, Currency, Percentage, + Date, Time, Boolean, Text } type; + int precision; + QString currencySymbol; + }; + + typedef QMap<QString, NumericStyleFormat> DataFormatsMap; + /// Value (date/time/number...) formats found while parsing styles. Used e.g. for fields. + /// Key: format name. Value: + const DataFormatsMap& dataFormats() const { return m_dataFormats; } + + static QString saveOasisDateStyle( KoGenStyles &mainStyles, const QString & _format, bool klocaleFormat, + const QString &_prefix = QString::null , const QString &_suffix= QString::null ); + static QString saveOasisTimeStyle( KoGenStyles &mainStyles, const QString & _format, bool klocaleFormat, + const QString &_prefix = QString::null , const QString &_suffix= QString::null ); + static QString saveOasisFractionStyle( KoGenStyles &mainStyles, const QString & _format, + const QString &_prefix = QString::null , const QString &_suffix= QString::null ); + static QString saveOasisScientificStyle( KoGenStyles &mainStyles, const QString & _format, + const QString &_prefix = QString::null , const QString &_suffix= QString::null ); + static QString saveOasisNumberStyle( KoGenStyles &mainStyles, const QString & _format, + const QString &_prefix = QString::null , const QString &_suffix= QString::null ); + static QString saveOasisPercentageStyle( KoGenStyles &mainStyles, const QString & _format, + const QString &_prefix = QString::null , const QString &_suffix= QString::null ); + static QString saveOasisCurrencyStyle( KoGenStyles &mainStyles, const QString & _format, const QString &symbol, + const QString &_prefix = QString::null , const QString &_suffix= QString::null ); + static QString saveOasisTextStyle( KoGenStyles &mainStyles, const QString & _format, + const QString &_prefix = QString::null , const QString &_suffix= QString::null ); + + static void saveOasisFillStyle( KoGenStyle &styleFill, KoGenStyles& mainStyles, const QBrush & brush ); + static QString saveOasisHatchStyle( KoGenStyles& mainStyles, const QBrush &brush ); + + static QBrush loadOasisFillStyle( const KoStyleStack &styleStack, const QString & fill, const KoOasisStyles & oasisStyles ); + +private: + /// Add styles to styles map + void insertStyles( const QDomElement& styles, bool styleAutoStyles = false ); + +private: + void insertOfficeStyles( const QDomElement& styles ); + void insertStyle( const QDomElement& style, bool styleAutoStyles ); + void importDataStyle( const QDomElement& parent ); + static bool saveOasisTimeFormat( KoXmlWriter &elementWriter, QString & format, QString & text, bool &antislash ); + static void parseOasisDateKlocale(KoXmlWriter &elementWriter, QString & format, QString & text ); + static bool saveOasisKlocaleTimeFormat( KoXmlWriter &elementWriter, QString & format, QString & text ); + static void parseOasisTimeKlocale(KoXmlWriter &elementWriter, QString & format, QString & text ); + static void addKofficeNumericStyleExtension( KoXmlWriter & elementWriter, const QString &_suffix, const QString &_prefix ); + + KoOasisStyles( const KoOasisStyles & ); // forbidden + KoOasisStyles& operator=( const KoOasisStyles & ); // forbidden + +private: + QDict<QDomElement> m_styles; // page-layout, font-decl etc. + QDict<QDomElement> m_defaultStyle; + QDomElement m_officeStyle; + + QDict<QDomElement> m_masterPages; + QDict<QDomElement> m_listStyles; + + QDict<QDomElement> m_drawStyles; + DataFormatsMap m_dataFormats; + + class Private; + Private * const d; +}; + +#endif /* KOOASISSTYLES_H */ diff --git a/lib/kofficecore/KoOpenPane.cpp b/lib/kofficecore/KoOpenPane.cpp new file mode 100644 index 00000000..07c9580d --- /dev/null +++ b/lib/kofficecore/KoOpenPane.cpp @@ -0,0 +1,315 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Peter Simonsson <psn@linux.se> + + 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 "KoOpenPane.h" + +#include <qvbox.h> +#include <qlayout.h> +#include <qheader.h> +#include <qwidgetstack.h> +#include <qlabel.h> +#include <qvaluelist.h> +#include <qimage.h> +#include <qpainter.h> +#include <qpen.h> + +#include <klocale.h> +#include <kfiledialog.h> +#include <kinstance.h> +#include <kpushbutton.h> +#include <kiconloader.h> +#include <kdebug.h> +#include <klistview.h> + +#include "KoFilterManager.h" +#include "KoTemplates.h" +#include "KoDocument.h" +#include "KoDetailsPane.h" +#include "koDetailsPaneBase.h" + +#include <limits.h> + +class KoSectionListItem : public QListViewItem +{ + public: + KoSectionListItem(KListView* listView, const QString& name, int sortWeight, int widgetIndex = -1) + : QListViewItem(listView, name), m_sortWeight(sortWeight), m_widgetIndex(widgetIndex) + { + } + + virtual int compare(QListViewItem* i, int, bool) const + { + KoSectionListItem* item = dynamic_cast<KoSectionListItem*>(i); + + if(!item) + return 0; + + return sortWeight() - item->sortWeight(); + } + + virtual void paintCell(QPainter* p, const QColorGroup& cg, int column, int width, int align) + { + if(widgetIndex() >= 0) { + QListViewItem::paintCell(p, cg, column, width, align); + } else { + int ypos = (height() - 2) / 2; + QPen pen(cg.foreground(), 2); + p->setPen(pen); + p->drawLine(0, ypos, width, ypos); + } + } + + int sortWeight() const { return m_sortWeight; } + int widgetIndex() const { return m_widgetIndex; } + + private: + int m_sortWeight; + int m_widgetIndex; +}; + +class KoOpenPanePrivate +{ + public: + KoOpenPanePrivate() : + m_instance(0) + { + } + + KInstance* m_instance; +}; + +KoOpenPane::KoOpenPane(QWidget *parent, KInstance* instance, const QString& templateType) + : KoOpenPaneBase(parent, "OpenPane") +{ + d = new KoOpenPanePrivate; + d->m_instance = instance; + + m_sectionList->header()->hide(); + m_sectionList->setSorting(0); +#if KDE_IS_VERSION(3,4,0) + m_sectionList->setShadeSortColumn(false); +#endif + connect(m_sectionList, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(selectionChanged(QListViewItem*))); + connect(m_sectionList, SIGNAL(pressed(QListViewItem*)), + this, SLOT(itemClicked(QListViewItem*))); + connect(m_sectionList, SIGNAL(spacePressed(QListViewItem*)), + this, SLOT(itemClicked(QListViewItem*))); + connect(m_sectionList, SIGNAL(returnPressed(QListViewItem*)), + this, SLOT(itemClicked(QListViewItem*))); + + KGuiItem openExistingGItem(i18n("Open Existing Document..."), "fileopen"); + m_openExistingButton->setGuiItem(openExistingGItem); + connect(m_openExistingButton, SIGNAL(clicked()), this, SLOT(showOpenFileDialog())); + + initRecentDocs(); + initTemplates(templateType); + + KoSectionListItem* selectedItem = static_cast<KoSectionListItem*>(m_sectionList->selectedItem()); + + if(selectedItem) { + m_widgetStack->widget(selectedItem->widgetIndex())->setFocus(); + } + + QValueList<int> sizes; + sizes << 20 << width() - 20; + m_splitter->setSizes(sizes); + + // Set the sizes of the details pane splitters + KConfigGroup cfgGrp(d->m_instance->config(), "TemplateChooserDialog"); + sizes = cfgGrp.readIntListEntry("DetailsPaneSplitterSizes"); + emit splitterResized(0, sizes); + + connect(this, SIGNAL(splitterResized(KoDetailsPaneBase*, const QValueList<int>&)), + this, SLOT(saveSplitterSizes(KoDetailsPaneBase*, const QValueList<int>&))); +} + +KoOpenPane::~KoOpenPane() +{ + KoSectionListItem* item = dynamic_cast<KoSectionListItem*>(m_sectionList->selectedItem()); + + if(item) { + if(!dynamic_cast<KoDetailsPaneBase*>(m_widgetStack->widget(item->widgetIndex()))) { + KConfigGroup cfgGrp(d->m_instance->config(), "TemplateChooserDialog"); + cfgGrp.writeEntry("LastReturnType", "Custom"); + } + } + + delete d; +} + +void KoOpenPane::showOpenFileDialog() +{ + const QStringList mimeFilter = KoFilterManager::mimeFilter(KoDocument::readNativeFormatMimeType(), + KoFilterManager::Import, KoDocument::readExtraNativeMimeTypes()); + + KURL url = KFileDialog::getOpenURL(":OpenDialog", mimeFilter.join(" "), this); + + if(!url.isEmpty()) { + KConfigGroup cfgGrp(d->m_instance->config(), "TemplateChooserDialog"); + cfgGrp.writeEntry("LastReturnType", "File"); + emit openExistingFile(url.url()); + } +} + +void KoOpenPane::initRecentDocs() +{ + KoRecentDocumentsPane* recentDocPane = new KoRecentDocumentsPane(this, d->m_instance); + connect(recentDocPane, SIGNAL(openFile(const QString&)), this, SIGNAL(openExistingFile(const QString&))); + QListViewItem* item = addPane(i18n("Recent Documents"), "fileopen", recentDocPane, 0); + connect(recentDocPane, SIGNAL(splitterResized(KoDetailsPaneBase*, const QValueList<int>&)), + this, SIGNAL(splitterResized(KoDetailsPaneBase*, const QValueList<int>&))); + connect(this, SIGNAL(splitterResized(KoDetailsPaneBase*, const QValueList<int>&)), + recentDocPane, SLOT(resizeSplitter(KoDetailsPaneBase*, const QValueList<int>&))); + + KoSectionListItem* separator = new KoSectionListItem(m_sectionList, "", 1); + separator->setEnabled(false); + + if(d->m_instance->config()->hasGroup("RecentFiles")) { + m_sectionList->setSelected(item, true); + } +} + +void KoOpenPane::initTemplates(const QString& templateType) +{ + QListViewItem* selectItem = 0; + QListViewItem* firstItem = 0; + const int templateOffset = 1000; + + if(!templateType.isEmpty()) + { + KoTemplateTree templateTree(templateType.local8Bit(), d->m_instance, true); + + for (KoTemplateGroup *group = templateTree.first(); group != 0L; group = templateTree.next()) { + if (group->isHidden()) { + continue; + } + + KoTemplatesPane* pane = new KoTemplatesPane(this, d->m_instance, + group, templateTree.defaultTemplate()); + connect(pane, SIGNAL(openTemplate(const QString&)), this, SIGNAL(openTemplate(const QString&))); + connect(pane, SIGNAL(alwaysUseChanged(KoTemplatesPane*, const QString&)), + this, SIGNAL(alwaysUseChanged(KoTemplatesPane*, const QString&))); + connect(this, SIGNAL(alwaysUseChanged(KoTemplatesPane*, const QString&)), + pane, SLOT(changeAlwaysUseTemplate(KoTemplatesPane*, const QString&))); + connect(pane, SIGNAL(splitterResized(KoDetailsPaneBase*, const QValueList<int>&)), + this, SIGNAL(splitterResized(KoDetailsPaneBase*, const QValueList<int>&))); + connect(this, SIGNAL(splitterResized(KoDetailsPaneBase*, const QValueList<int>&)), + pane, SLOT(resizeSplitter(KoDetailsPaneBase*, const QValueList<int>&))); + QListViewItem* item = addPane(group->name(), group->first()->loadPicture(d->m_instance), + pane, group->sortingWeight() + templateOffset); + + if(!firstItem) { + firstItem = item; + } + + if(group == templateTree.defaultGroup()) { + firstItem = item; + } + + if(pane->isSelected()) { + selectItem = item; + } + } + } else { + firstItem = m_sectionList->firstChild(); + } + + KConfigGroup cfgGrp(d->m_instance->config(), "TemplateChooserDialog"); + + if(selectItem && (cfgGrp.readEntry("LastReturnType") == "Template")) { + m_sectionList->setSelected(selectItem, true); + } else if(!m_sectionList->selectedItem() && firstItem) { + m_sectionList->setSelected(firstItem, true); + } +} + +void KoOpenPane::setCustomDocumentWidget(QWidget *widget) { + Q_ASSERT(widget); + KoSectionListItem* separator = new KoSectionListItem(m_sectionList, "", INT_MAX-1); + separator->setEnabled(false); + + QListViewItem* item = addPane(i18n("Custom Document"), QString::null, widget, INT_MAX); + + KConfigGroup cfgGrp(d->m_instance->config(), "TemplateChooserDialog"); + + if(cfgGrp.readEntry("LastReturnType") == "Custom") { + m_sectionList->setSelected(item, true); + KoSectionListItem* selectedItem = static_cast<KoSectionListItem*>(item); + m_widgetStack->widget(selectedItem->widgetIndex())->setFocus(); + } +} + +QListViewItem* KoOpenPane::addPane(const QString& title, const QString& icon, QWidget* widget, int sortWeight) +{ + return addPane(title, SmallIcon(icon, KIcon::SizeLarge, KIcon::DefaultState, d->m_instance), + widget, sortWeight); +} + +QListViewItem* KoOpenPane::addPane(const QString& title, const QPixmap& icon, QWidget* widget, int sortWeight) +{ + if(!widget) { + return 0; + } + + int id = m_widgetStack->addWidget(widget); + KoSectionListItem* listItem = new KoSectionListItem(m_sectionList, title, sortWeight, id); + + if(!icon.isNull()) { + QImage image = icon.convertToImage(); + + if((image.width() > 48) || (image.height() > 48)) { + image = image.smoothScale(48, 48, QImage::ScaleMin); + } + + image.setAlphaBuffer(true); + image = image.copy((image.width() - 48) / 2, (image.height() - 48) / 2, 48, 48); + listItem->setPixmap(0, QPixmap(image)); + } + + return listItem; +} + +void KoOpenPane::selectionChanged(QListViewItem* item) +{ + KoSectionListItem* section = dynamic_cast<KoSectionListItem*>(item); + + if(!item) + return; + + m_headerLabel->setText(section->text(0)); + m_widgetStack->raiseWidget(section->widgetIndex()); +} + +void KoOpenPane::saveSplitterSizes(KoDetailsPaneBase* /*sender*/, const QValueList<int>& sizes) +{ + KConfigGroup cfgGrp(d->m_instance->config(), "TemplateChooserDialog"); + cfgGrp.writeEntry("DetailsPaneSplitterSizes", sizes); +} + +void KoOpenPane::itemClicked(QListViewItem* item) +{ + KoSectionListItem* selectedItem = static_cast<KoSectionListItem*>(item); + + if(selectedItem) { + m_widgetStack->widget(selectedItem->widgetIndex())->setFocus(); + } +} + +#include "KoOpenPane.moc" diff --git a/lib/kofficecore/KoOpenPane.h b/lib/kofficecore/KoOpenPane.h new file mode 100644 index 00000000..20bd425d --- /dev/null +++ b/lib/kofficecore/KoOpenPane.h @@ -0,0 +1,90 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Peter Simonsson <psn@linux.se> + + 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 KOOPENPANE_H +#define KOOPENPANE_H + +#include <koOpenPaneBase.h> + +class KoCustomDocumentCreator; +class KConfig; +class KoTemplateGroup; +class KoOpenPanePrivate; +class KInstance; +class QPixmap; +class KListViewItem; +class KoTemplatesPane; +class KoDetailsPaneBase; + +class KoOpenPane : public KoOpenPaneBase +{ + Q_OBJECT + + public: + /** + * Constructor + * @param parent the parent widget + * @param instance the KInstance to be used for KConfig data + * @param templateType the template-type (group) that should be selected on creation. + */ + KoOpenPane(QWidget *parent, KInstance* instance, const QString& templateType = QString::null); + virtual ~KoOpenPane(); + + QListViewItem* addPane(const QString& title, const QString& icon, QWidget* widget, int sortWeight); + QListViewItem* addPane(const QString& title, const QPixmap& icon, QWidget* widget, int sortWeight); + + /** + * If the application has a way to create a document not based on a template, but on user + * provided settings, the widget showing these gets set here. + * @see KoDocument::createCustomDocumentWidget() + * @param widget the widget. + */ + void setCustomDocumentWidget(QWidget *widget); + + protected slots: + void showOpenFileDialog(); + + void selectionChanged(QListViewItem* item); + void itemClicked(QListViewItem* item); + + /// Saves the splitter sizes for KoDetailsPaneBase based panes + void saveSplitterSizes(KoDetailsPaneBase* sender, const QValueList<int>& sizes); + + signals: + void openExistingFile(const QString&); + void openTemplate(const QString&); + + /// Emitted when the always use template has changed + void alwaysUseChanged(KoTemplatesPane* sender, const QString& alwaysUse); + + /// Emitted when one of the detail panes have changed it's splitter + void splitterResized(KoDetailsPaneBase* sender, const QValueList<int>& sizes); + + protected: + void initRecentDocs(); + /** + * Populate the list with all templates the user can choose. + * @param templateType the template-type (group) that should be selected on creation. + */ + void initTemplates(const QString& templateType); + + private: + KoOpenPanePrivate* d; +}; + +#endif //KOOPENPANE_H diff --git a/lib/kofficecore/KoPageLayout.cpp b/lib/kofficecore/KoPageLayout.cpp new file mode 100644 index 00000000..36e447e9 --- /dev/null +++ b/lib/kofficecore/KoPageLayout.cpp @@ -0,0 +1,242 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> + Copyright 2002, 2003 David Faure <faure@kde.org> + Copyright 2003 Nicolas GOUTTE <goutte@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 "KoPageLayout.h" +#include <KoUnit.h> +#include <klocale.h> +#include <kprinter.h> +#include <kdebug.h> +#include <kglobal.h> +#include <KoDom.h> +#include <KoXmlNS.h> +#include <qdom.h> + +// paper formats ( mm ) +#define PG_A3_WIDTH 297.0 +#define PG_A3_HEIGHT 420.0 +#define PG_A4_WIDTH 210.0 +#define PG_A4_HEIGHT 297.0 +#define PG_A5_WIDTH 148.0 +#define PG_A5_HEIGHT 210.0 +#define PG_B5_WIDTH 182.0 +#define PG_B5_HEIGHT 257.0 +#define PG_US_LETTER_WIDTH 216.0 +#define PG_US_LETTER_HEIGHT 279.0 +#define PG_US_LEGAL_WIDTH 216.0 +#define PG_US_LEGAL_HEIGHT 356.0 +#define PG_US_EXECUTIVE_WIDTH 191.0 +#define PG_US_EXECUTIVE_HEIGHT 254.0 + +KoGenStyle KoPageLayout::saveOasis() const +{ + KoGenStyle style(KoGenStyle::STYLE_PAGELAYOUT); + style.addPropertyPt("fo:page-width", ptWidth); + style.addPropertyPt("fo:page-height", ptHeight); + style.addPropertyPt("fo:margin-left", ptLeft); + style.addPropertyPt("fo:margin-right", ptRight); + style.addPropertyPt("fo:margin-top", ptTop); + style.addPropertyPt("fo:margin-bottom", ptBottom); + style.addProperty("style:print-orientation", (orientation == PG_LANDSCAPE ? "landscape" : "portrait")); + return style; +} + +void KoPageLayout::loadOasis(const QDomElement &style) +{ + QDomElement properties( KoDom::namedItemNS( style, KoXmlNS::style, "page-layout-properties" ) ); + if ( !properties.isNull() ) + { + ptWidth = KoUnit::parseValue(properties.attributeNS( KoXmlNS::fo, "page-width", QString::null ) ); + ptHeight = KoUnit::parseValue(properties.attributeNS( KoXmlNS::fo, "page-height", QString::null ) ); + if (properties.attributeNS( KoXmlNS::style, "print-orientation", QString::null)=="portrait") + orientation=PG_PORTRAIT; + else + orientation=PG_LANDSCAPE; + ptRight = KoUnit::parseValue( properties.attributeNS( KoXmlNS::fo, "margin-right", QString::null ) ); + ptBottom = KoUnit::parseValue( properties.attributeNS( KoXmlNS::fo, "margin-bottom", QString::null ) ); + ptLeft = KoUnit::parseValue( properties.attributeNS( KoXmlNS::fo, "margin-left", QString::null ) ); + ptTop = KoUnit::parseValue( properties.attributeNS( KoXmlNS::fo, "margin-top", QString::null ) ); + // guessFormat takes millimeters + if ( orientation == PG_LANDSCAPE ) + format = KoPageFormat::guessFormat( POINT_TO_MM(ptHeight), POINT_TO_MM(ptWidth) ); + else + format = KoPageFormat::guessFormat( POINT_TO_MM(ptWidth), POINT_TO_MM(ptHeight) ); + } +} + +KoPageLayout KoPageLayout::standardLayout() +{ + KoPageLayout layout; + layout.format = KoPageFormat::defaultFormat(); + layout.orientation = PG_PORTRAIT; + layout.ptWidth = MM_TO_POINT( KoPageFormat::width( layout.format, layout.orientation ) ); + layout.ptHeight = MM_TO_POINT( KoPageFormat::height( layout.format, layout.orientation ) ); + layout.ptLeft = MM_TO_POINT( 20.0 ); + layout.ptRight = MM_TO_POINT( 20.0 ); + layout.ptTop = MM_TO_POINT( 20.0 ); + layout.ptBottom = MM_TO_POINT( 20.0 ); + layout.ptPageEdge = -1; + layout.ptBindingSide = -1; + return layout; +} + +struct PageFormatInfo +{ + KoFormat format; + KPrinter::PageSize kprinter; + const char* shortName; // Short name + const char* descriptiveName; // Full name, which will be translated + double width; // in mm + double height; // in mm +}; + +// NOTES: +// - the width and height of non-ISO formats are rounded +// http://en.wikipedia.org/wiki/Paper_size can help +// - the comments "should be..." indicates the exact values if the inch sizes would be multiplied by 25.4 mm/inch + +const PageFormatInfo pageFormatInfo[]= +{ + { PG_DIN_A3, KPrinter::A3, "A3", I18N_NOOP("ISO A3"), 297.0, 420.0 }, + { PG_DIN_A4, KPrinter::A4, "A4", I18N_NOOP("ISO A4"), 210.0, 297.0 }, + { PG_DIN_A5, KPrinter::A5, "A5", I18N_NOOP("ISO A5"), 148.0, 210.0 }, + { PG_US_LETTER, KPrinter::Letter, "Letter", I18N_NOOP("US Letter"), 215.9, 279.4 }, + { PG_US_LEGAL, KPrinter::Legal, "Legal", I18N_NOOP("US Legal"), 215.9, 355.6 }, + { PG_SCREEN, KPrinter::A4, "Screen", I18N_NOOP("Screen"), PG_A4_HEIGHT, PG_A4_WIDTH }, // Custom, so fall back to A4 + { PG_CUSTOM, KPrinter::A4, "Custom", I18N_NOOP2("Custom size", "Custom"), PG_A4_WIDTH, PG_A4_HEIGHT }, // Custom, so fall back to A4 + { PG_DIN_B5, KPrinter::B5, "B5", I18N_NOOP("ISO B5"), 182.0, 257.0 }, + // Hmm, wikipedia says 184.15 * 266.7 for executive ! + { PG_US_EXECUTIVE, KPrinter::Executive, "Executive", I18N_NOOP("US Executive"), 191.0, 254.0 }, // should be 190.5 mm x 254.0 mm + { PG_DIN_A0, KPrinter::A0, "A0", I18N_NOOP("ISO A0"), 841.0, 1189.0 }, + { PG_DIN_A1, KPrinter::A1, "A1", I18N_NOOP("ISO A1"), 594.0, 841.0 }, + { PG_DIN_A2, KPrinter::A2, "A2", I18N_NOOP("ISO A2"), 420.0, 594.0 }, + { PG_DIN_A6, KPrinter::A6, "A6", I18N_NOOP("ISO A6"), 105.0, 148.0 }, + { PG_DIN_A7, KPrinter::A7, "A7", I18N_NOOP("ISO A7"), 74.0, 105.0 }, + { PG_DIN_A8, KPrinter::A8, "A8", I18N_NOOP("ISO A8"), 52.0, 74.0 }, + { PG_DIN_A9, KPrinter::A9, "A9", I18N_NOOP("ISO A9"), 37.0, 52.0 }, + { PG_DIN_B0, KPrinter::B0, "B0", I18N_NOOP("ISO B0"), 1030.0, 1456.0 }, + { PG_DIN_B1, KPrinter::B1, "B1", I18N_NOOP("ISO B1"), 728.0, 1030.0 }, + { PG_DIN_B10, KPrinter::B10, "B10", I18N_NOOP("ISO B10"), 32.0, 45.0 }, + { PG_DIN_B2, KPrinter::B2, "B2", I18N_NOOP("ISO B2"), 515.0, 728.0 }, + { PG_DIN_B3, KPrinter::B3, "B3", I18N_NOOP("ISO B3"), 364.0, 515.0 }, + { PG_DIN_B4, KPrinter::B4, "B4", I18N_NOOP("ISO B4"), 257.0, 364.0 }, + { PG_DIN_B6, KPrinter::B6, "B6", I18N_NOOP("ISO B6"), 128.0, 182.0 }, + { PG_ISO_C5, KPrinter::C5E, "C5", I18N_NOOP("ISO C5"), 163.0, 229.0 }, // Some sources tells: 162 mm x 228 mm + { PG_US_COMM10, KPrinter::Comm10E, "Comm10", I18N_NOOP("US Common 10"), 105.0, 241.0 }, // should be 104.775 mm x 241.3 mm + { PG_ISO_DL, KPrinter::DLE, "DL", I18N_NOOP("ISO DL"), 110.0, 220.0 }, + { PG_US_FOLIO, KPrinter::Folio, "Folio", I18N_NOOP("US Folio"), 210.0, 330.0 }, // should be 209.54 mm x 330.2 mm + { PG_US_LEDGER, KPrinter::Ledger, "Ledger", I18N_NOOP("US Ledger"), 432.0, 279.0 }, // should be 431.8 mm x 297.4 mm + { PG_US_TABLOID, KPrinter::Tabloid, "Tabloid", I18N_NOOP("US Tabloid"), 279.0, 432.0 } // should be 297.4 mm x 431.8 mm +}; + +int KoPageFormat::printerPageSize( KoFormat format ) +{ + if ( format == PG_SCREEN ) + { + kdWarning() << "You use the page layout SCREEN. Printing in DIN A4 LANDSCAPE." << endl; + return KPrinter::A4; + } + else if ( format == PG_CUSTOM ) + { + kdWarning() << "The used page layout (CUSTOM) is not supported by KPrinter. Printing in A4." << endl; + return KPrinter::A4; + } + else if ( format <= PG_LAST_FORMAT ) + return pageFormatInfo[ format ].kprinter; + else + return KPrinter::A4; +} + +double KoPageFormat::width( KoFormat format, KoOrientation orientation ) +{ + if ( orientation == PG_LANDSCAPE ) + return height( format, PG_PORTRAIT ); + if ( format <= PG_LAST_FORMAT ) + return pageFormatInfo[ format ].width; + return PG_A4_WIDTH; // should never happen +} + +double KoPageFormat::height( KoFormat format, KoOrientation orientation ) +{ + if ( orientation == PG_LANDSCAPE ) + return width( format, PG_PORTRAIT ); + if ( format <= PG_LAST_FORMAT ) + return pageFormatInfo[ format ].height; + return PG_A4_HEIGHT; +} + +KoFormat KoPageFormat::guessFormat( double width, double height ) +{ + for ( int i = 0 ; i <= PG_LAST_FORMAT ; ++i ) + { + // We have some tolerance. 1pt is a third of a mm, this is + // barely noticeable for a page size. + if ( i != PG_CUSTOM + && kAbs( width - pageFormatInfo[i].width ) < 1.0 + && kAbs( height - pageFormatInfo[i].height ) < 1.0 ) + return static_cast<KoFormat>(i); + } + return PG_CUSTOM; +} + +QString KoPageFormat::formatString( KoFormat format ) +{ + if ( format <= PG_LAST_FORMAT ) + return QString::fromLatin1( pageFormatInfo[ format ].shortName ); + return QString::fromLatin1( "A4" ); +} + +KoFormat KoPageFormat::formatFromString( const QString & string ) +{ + for ( int i = 0 ; i <= PG_LAST_FORMAT ; ++i ) + { + if (string == QString::fromLatin1( pageFormatInfo[ i ].shortName )) + return pageFormatInfo[ i ].format; + } + // We do not know the format name, so we have a custom format + return PG_CUSTOM; +} + +KoFormat KoPageFormat::defaultFormat() +{ + int kprinter = KGlobal::locale()->pageSize(); + for ( int i = 0 ; i <= PG_LAST_FORMAT ; ++i ) + { + if ( pageFormatInfo[ i ].kprinter == kprinter ) + return static_cast<KoFormat>(i); + } + return PG_DIN_A4; +} + +QString KoPageFormat::name( KoFormat format ) +{ + if ( format <= PG_LAST_FORMAT ) + return i18n( pageFormatInfo[ format ].descriptiveName ); + return i18n( pageFormatInfo[ PG_DIN_A4 ].descriptiveName ); +} + +QStringList KoPageFormat::allFormats() +{ + QStringList lst; + for ( int i = 0 ; i <= PG_LAST_FORMAT ; ++i ) + { + lst << i18n( pageFormatInfo[ i ].descriptiveName ); + } + return lst; +} diff --git a/lib/kofficecore/KoPageLayout.h b/lib/kofficecore/KoPageLayout.h new file mode 100644 index 00000000..f9f98fc6 --- /dev/null +++ b/lib/kofficecore/KoPageLayout.h @@ -0,0 +1,260 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> + Copyright 2002, 2003 David Faure <faure@kde.org> + Copyright 2003 Nicolas GOUTTE <goutte@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 KOPAGELAYOUT_H +#define KOPAGELAYOUT_H + +#include <KoGenStyles.h> +#include <qstringlist.h> +#include <koffice_export.h> +class QDomElement; + +/** + * @brief Represents the paper format a document shall be printed on. + * + * For compatibility reasons, and because of screen and custom, + * this enum doesn't map to QPrinter::PageSize but KoPageFormat::printerPageSize + * does the conversion. + * + * @todo convert DIN to ISO in the names + */ +enum KoFormat { + PG_DIN_A3 = 0, + PG_DIN_A4 = 1, + PG_DIN_A5 = 2, + PG_US_LETTER = 3, + PG_US_LEGAL = 4, + PG_SCREEN = 5, + PG_CUSTOM = 6, + PG_DIN_B5 = 7, + PG_US_EXECUTIVE = 8, + PG_DIN_A0 = 9, + PG_DIN_A1 = 10, + PG_DIN_A2 = 11, + PG_DIN_A6 = 12, + PG_DIN_A7 = 13, + PG_DIN_A8 = 14, + PG_DIN_A9 = 15, + PG_DIN_B0 = 16, + PG_DIN_B1 = 17, + PG_DIN_B10 = 18, + PG_DIN_B2 = 19, + PG_DIN_B3 = 20, + PG_DIN_B4 = 21, + PG_DIN_B6 = 22, + PG_ISO_C5 = 23, + PG_US_COMM10 = 24, + PG_ISO_DL = 25, + PG_US_FOLIO = 26, + PG_US_LEDGER = 27, + PG_US_TABLOID = 28, + // update the number below and the static arrays if you add more values to the enum + PG_LAST_FORMAT = PG_US_TABLOID // used by koPageLayout.cpp +}; + +/** + * Represents the orientation of a printed document. + */ +enum KoOrientation { + PG_PORTRAIT = 0, + PG_LANDSCAPE = 1 +}; + +namespace KoPageFormat +{ + /** + * @brief Convert a KoFormat into a KPrinter::PageSize. + * + * If format is 'screen' it will use A4 landscape. + * If format is 'custom' it will use A4 portrait. + * (you may want to take care of those cases separately). + * Usually passed to KPrinter::setPageSize(). + * + * @note We return int instead of the enum to avoid including kprinter.h + */ + KOFFICECORE_EXPORT int /*KPrinter::PageSize*/ printerPageSize( KoFormat format ); + + /** + * Returns the width (in mm) for a given page format and orientation + * 'Custom' isn't supported by this function, obviously. + */ + KOFFICECORE_EXPORT double width( KoFormat format, KoOrientation orientation ); + + /** + * Returns the height (in mm) for a given page format and orientation + * 'Custom' isn't supported by this function, obviously. + */ + KOFFICECORE_EXPORT double height( KoFormat format, KoOrientation orientation ); + + /** + * Returns the internal name of the given page format. + * Use for saving. + */ + KOFFICECORE_EXPORT QString formatString( KoFormat format ); + + /** + * Convert a format string (internal name) to a page format value. + * Use for loading. + */ + KOFFICECORE_EXPORT KoFormat formatFromString( const QString & string ); + + /** + * Returns the default format (based on the KControl settings) + */ + KOFFICECORE_EXPORT KoFormat defaultFormat(); + + /** + * Returns the translated name of the given page format. + * Use for showing the user. + */ + KOFFICECORE_EXPORT QString name( KoFormat format ); + + /** + * Lists the translated names of all the available formats + */ + KOFFICECORE_EXPORT QStringList allFormats(); + + /** + * Try to find the paper format for the given width and height (in mm). + * Useful to some import filters. + */ + KOFFICECORE_EXPORT KoFormat guessFormat( double width, double height ); +} + + +/** + * @brief Header/Footer type. + * + * @note Yes, this should have been a bitfield, but there was only 0, 2, 3 in koffice-1.0. Don't ask why. + * In the long run this should be replaced with a more flexible repetition/section concept. + */ +enum KoHFType { + HF_SAME = 0, ///< 0: Header/Footer is the same on all pages + HF_FIRST_EO_DIFF = 1, ///< 1: Header/Footer is different on first, even and odd pages (2&3) + HF_FIRST_DIFF = 2, ///< 2: Header/Footer for the first page differs + HF_EO_DIFF = 3 ///< 3: Header/Footer for even - odd pages are different +}; + +/** + * This structure defines the page layout, including + * its size in pt, its format (e.g. A4), orientation, unit, margins etc. + */ +struct KoPageLayout +{ + /** Page format */ + KoFormat format; + /** Page orientation */ + KoOrientation orientation; + + /** Page width in pt */ + double ptWidth; + /** Page height in pt */ + double ptHeight; + /** Left margin in pt */ + double ptLeft; + /** Right margin in pt */ + double ptRight; + /** Top margin in pt */ + double ptTop; + /** Bottom margin in pt */ + double ptBottom; + double ptPageEdge; + double ptBindingSide; + + bool operator==( const KoPageLayout& l ) const { + return ( ptWidth == l.ptWidth && + ptHeight == l.ptHeight && + ptLeft == l.ptLeft && + ptRight == l.ptRight && + ptTop == l.ptTop && + ptBottom == l.ptBottom && + ptPageEdge == l.ptPageEdge && + ptBindingSide == l.ptBindingSide); + } + bool operator!=( const KoPageLayout& l ) const { + return !( (*this) == l ); + } + + /** + * Save this page layout to OASIS. + */ + KOFFICECORE_EXPORT KoGenStyle saveOasis() const; + + /** + * Load this page layout from OASIS + */ + KOFFICECORE_EXPORT void loadOasis(const QDomElement &style); + + /** + * @return a page layout with the default page size depending on the locale settings, + * default margins (2 cm), and portrait orientation. + * @since 1.4 + */ + static KOFFICECORE_EXPORT KoPageLayout standardLayout(); +}; + +/** structure for header-footer */ +struct KoHeadFoot +{ + QString headLeft; + QString headMid; + QString headRight; + QString footLeft; + QString footMid; + QString footRight; +}; + +/** structure for columns */ +struct KoColumns +{ + int columns; + double ptColumnSpacing; + bool operator==( const KoColumns& rhs ) const { + return columns == rhs.columns && + QABS(ptColumnSpacing - rhs.ptColumnSpacing) <= 1E-10; + } + bool operator!=( const KoColumns& rhs ) const { + return columns != rhs.columns || + QABS(ptColumnSpacing - rhs.ptColumnSpacing) > 1E-10; + } +}; + +/** structure for KWord header-footer */ +struct KoKWHeaderFooter +{ + KoHFType header; + KoHFType footer; + double ptHeaderBodySpacing; + double ptFooterBodySpacing; + double ptFootNoteBodySpacing; + bool operator==( const KoKWHeaderFooter& rhs ) const { + return header == rhs.header && footer == rhs.footer && + QABS(ptHeaderBodySpacing - rhs.ptHeaderBodySpacing) <= 1E-10 && + QABS(ptFooterBodySpacing - rhs.ptFooterBodySpacing) <= 1E-10 && + QABS(ptFootNoteBodySpacing - rhs.ptFootNoteBodySpacing) <= 1E-10; + } + bool operator!=( const KoKWHeaderFooter& rhs ) const { + return !( *this == rhs ); + } +}; + +#endif /* KOPAGELAYOUT_H */ + diff --git a/lib/kofficecore/KoPicture.cpp b/lib/kofficecore/KoPicture.cpp new file mode 100644 index 00000000..11bb89dd --- /dev/null +++ b/lib/kofficecore/KoPicture.cpp @@ -0,0 +1,290 @@ +/* This file is part of the KDE project + Copyright (c) 2001 Simon Hausmann <hausmann@kde.org> + Copyright (C) 2002, 2003 Nicolas GOUTTE <goutte@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 <qpainter.h> +#include <qfile.h> + +#include <kdebug.h> +#include <kurl.h> +#include <kio/netaccess.h> + +#include "KoPictureKey.h" +#include "KoPictureBase.h" +#include "KoPictureShared.h" +#include "KoPicture.h" + +uint KoPicture::uniqueValue = 0; + + +KoPicture::KoPicture(void) : m_sharedData(NULL) +{ + m_uniqueName = "Pictures"+ QString::number(uniqueValue++); +} + +KoPicture::~KoPicture(void) +{ + unlinkSharedData(); +} + +QString KoPicture::uniqueName() const +{ + return m_uniqueName; +} + +KoPicture::KoPicture(const KoPicture &other) +{ + m_sharedData=NULL; + (*this)=other; +} + +void KoPicture::assignPictureId( uint _id) +{ + if ( m_sharedData ) + m_sharedData->assignPictureId(_id); +} + +QString KoPicture::uniquePictureId() const +{ + if ( m_sharedData ) + return m_sharedData->uniquePictureId(); + else + return QString::null; +} + +KoPicture& KoPicture::operator=( const KoPicture &other ) +{ + //kdDebug(30003) << "KoPicture::= before" << endl; + if (other.m_sharedData) + other.linkSharedData(); + if (m_sharedData) + unlinkSharedData(); + m_sharedData=other.m_sharedData; + m_key=other.m_key; + //kdDebug(30003) << "KoPicture::= after" << endl; + return *this; +} + +void KoPicture::unlinkSharedData(void) +{ + if (m_sharedData && m_sharedData->deref()) + delete m_sharedData; + + m_sharedData=NULL; +} + +void KoPicture::linkSharedData(void) const +{ + if (m_sharedData) + m_sharedData->ref(); +} + +void KoPicture::createSharedData(void) +{ + if (!m_sharedData) + { + m_sharedData=new KoPictureShared(); + // Do not call m_sharedData->ref() + } +} + +KoPictureType::Type KoPicture::getType(void) const +{ + if (m_sharedData) + return m_sharedData->getType(); + return KoPictureType::TypeUnknown; +} + +KoPictureKey KoPicture::getKey(void) const +{ + return m_key; +} + +void KoPicture::setKey(const KoPictureKey& key) +{ + m_key=key; +} + + +bool KoPicture::isNull(void) const +{ + if (m_sharedData) + return m_sharedData->isNull(); + return true; +} + +void KoPicture::draw(QPainter& painter, int x, int y, int width, int height, int sx, int sy, int sw, int sh, bool fastMode) +{ + if (m_sharedData) + m_sharedData->draw(painter, x, y, width, height, sx, sy, sw, sh, fastMode); + else + { + // Draw a white box + kdWarning(30003) << "Drawing white rectangle! (KoPicture::draw)" << endl; + painter.save(); + painter.setBrush(QColor(255, 255, 255)); + painter.drawRect(x,y,width,height); + painter.restore(); + } +} + +bool KoPicture::loadXpm(QIODevice* io) +{ + kdDebug(30003) << "KoPicture::loadXpm" << endl; + if (!io) + { + kdError(30003) << "No QIODevice!" << endl; + return false; + } + createSharedData(); + return m_sharedData->loadXpm(io); +} + +bool KoPicture::save(QIODevice* io) const +{ + if (!io) + return false; + if (m_sharedData) + return m_sharedData->save(io); + return false; +} + +bool KoPicture::saveAsBase64( KoXmlWriter& writer ) const +{ + if ( m_sharedData ) + return m_sharedData->saveAsBase64( writer ); + return false; +} + +void KoPicture::clear(void) +{ + unlinkSharedData(); +} + +void KoPicture::clearAndSetMode(const QString& newMode) +{ + createSharedData(); + m_sharedData->clearAndSetMode(newMode); +} + +QString KoPicture::getExtension(void) const +{ + if (m_sharedData) + return m_sharedData->getExtension(); + return "null"; // Just a dummy +} + +QString KoPicture::getMimeType(void) const +{ + if (m_sharedData) + return m_sharedData->getMimeType(); + return QString(NULL_MIME_TYPE); +} + +bool KoPicture::load(QIODevice* io, const QString& extension) +{ + kdDebug(30003) << "KoPicture::load(QIODevice*, const QString&) " << extension << endl; + createSharedData(); + + return m_sharedData->load(io,extension); +} + +bool KoPicture::loadFromFile(const QString& fileName) +{ + kdDebug(30003) << "KoPicture::loadFromFile " << fileName << endl; + createSharedData(); + return m_sharedData->loadFromFile(fileName); +} + +bool KoPicture::loadFromBase64( const QCString& str ) +{ + createSharedData(); + return m_sharedData->loadFromBase64( str ); +} + +QSize KoPicture::getOriginalSize(void) const +{ + if (m_sharedData) + return m_sharedData->getOriginalSize(); + return QSize(0,0); +} + +QPixmap KoPicture::generatePixmap(const QSize& size, bool smoothScale) +{ + if (m_sharedData) + return m_sharedData->generatePixmap(size, smoothScale); + return QPixmap(); +} + +bool KoPicture::setKeyAndDownloadPicture(const KURL& url, QWidget *window) +{ + bool result=false; + + QString tmpFileName; + if ( KIO::NetAccess::download(url, tmpFileName, window) ) + { + KoPictureKey key; + key.setKeyFromFile( tmpFileName ); + setKey( key ); + result=loadFromFile( tmpFileName ); + KIO::NetAccess::removeTempFile( tmpFileName ); + } + + return result; +} + +QDragObject* KoPicture::dragObject( QWidget *dragSource, const char *name ) +{ + if (m_sharedData) + return m_sharedData->dragObject( dragSource, name ); + return 0L; +} + +QImage KoPicture::generateImage(const QSize& size) +{ + if (m_sharedData) + return m_sharedData->generateImage( size ); + return QImage(); +} + +bool KoPicture::hasAlphaBuffer() const +{ + if (m_sharedData) + return m_sharedData->hasAlphaBuffer(); + return false; +} + +void KoPicture::setAlphaBuffer(bool enable) +{ + if (m_sharedData) + m_sharedData->setAlphaBuffer(enable); +} + +QImage KoPicture::createAlphaMask(int conversion_flags) const +{ + if (m_sharedData) + return m_sharedData->createAlphaMask(conversion_flags); + return QImage(); +} + +void KoPicture::clearCache(void) +{ + if (m_sharedData) + m_sharedData->clearCache(); +} diff --git a/lib/kofficecore/KoPicture.h b/lib/kofficecore/KoPicture.h new file mode 100644 index 00000000..e4ed1d5d --- /dev/null +++ b/lib/kofficecore/KoPicture.h @@ -0,0 +1,259 @@ +/* This file is part of the KDE project + Copyright (c) 2001 Simon Hausmann <hausmann@kde.org> + Copyright (C) 2002, 2003, 2004 Nicolas GOUTTE <goutte@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 __koPicture_h__ +#define __koPicture_h__ + +#include <qstring.h> +#include <qiodevice.h> +#include <qpixmap.h> +#include <koffice_export.h> + +#include "KoPictureKey.h" + +class KoXmlWriter; +class QPainter; +class QSize; +class QDragObject; +class KURL; + +class KoPictureShared; + +/** + * KoPicture is a container class for various types of pictures supported by %KOffice. + * + * @short A picture container class + */ +class KOFFICECORE_EXPORT KoPicture +{ +public: + /** + * Default constructor. + */ + KoPicture(void); + + /** + * Destructor. + */ + ~KoPicture(void); + + /** + * Copy constructor + */ + KoPicture(const KoPicture &other); + + /** + * Assignment operator + */ + KoPicture& operator=(const KoPicture& other); + + KoPictureType::Type getType(void) const; + + /** + * Retrieve the key structure describing the picture in a unique way. + */ + KoPictureKey getKey(void) const; + + /** + * Set the key structure describing the picture in a unique way + */ + void setKey(const KoPictureKey& key); + + /** + * Returns true if the picture is null. + */ + bool isNull(void) const; + + /** + * @brief Draw the picture in a painter. + * + * The parameter @p fastMode allows the picture to be re-sized and drawn quicker if possible + * + * The parameters @p width, @p height define the desired size for the picture + * + * The other parameters are very similar to QPainter::drawPixmap : + * (@p x, @p y) define the position in the painter, + * (@p sx, @p sy) specify the top-left point in picture that is to be drawn. The default is (0, 0). + * (@p sw, @p sh) specify the size of the picture that is to be drawn. The default, (-1, -1), means all the way to the bottom + * right of the pixmap. + * + */ + void draw(QPainter& painter, int x, int y, int width, int height, int sx = 0, int sy = 0, + int sw = -1, int sh = -1, bool fastMode = false); + + /** + * Create a dragobject containing this picture. + * @param dragSource must be 0 when copying to the clipboard + * @param name name for the QDragObject + * @return 0L if the picture is null, or if a dragobject for it isn't implemented [yet] + */ + QDragObject* dragObject( QWidget *dragSource = 0L, const char *name = 0L ); + + bool load(QIODevice* io, const QString& extension); + + /** + * Save picture into a QIODevice + * @param io QIODevice used for saving + */ + bool save(QIODevice* io) const; + + /** + * OASIS FlatXML support: + * Save picture as base64-encoded data into an XML writer. + * The caller will usually do something like + * @code + * writer.startElement( "office:binary-data" ); + * m_picture.saveAsBase64( writer ); + * writer.endElement(); + * @endcode + */ + bool saveAsBase64( KoXmlWriter& writer ) const; + + /** + * @return the image extension (e.g. png) + */ + QString getExtension(void) const; + + /** + * @return the image MIME type + */ + QString getMimeType(void) const; + + /** + * @return the original image size + */ + QSize getOriginalSize(void) const; + + /** + * Clear and set the mode of this KoPicture + * + * @param newMode a file extension (like "png") giving the wanted mode + */ + void clearAndSetMode(const QString& newMode); + + /** + * Reset the KoPicture (but not the key!) + */ + void clear(void); + + /** + * Load the picture from a file named @p fileName + */ + bool loadFromFile(const QString& fileName); + + /** + * Load the picture from base64-encoded data + */ + bool loadFromBase64(const QCString& str); + + /** + * Load a potentially broken XPM file (for old files of KPresenter) + */ + bool loadXpm(QIODevice* io); + + /** + * @deprecated To be replaced by @ref KoPicture::draw + * + * Returns a QPixmap from an image + * Returns an empty QPixmap if the KoPicture is not an image. + */ + QPixmap generatePixmap(const QSize& size, bool smoothScale = false); + + /** + * Download and set the key for a possibly remote file. + * + * @param url the url to download from + * @param window the parent widget for the download. You can pass + * NULL (0) if you absolutely cannot find a parent + * widget to use. + */ + bool setKeyAndDownloadPicture(const KURL& url, QWidget *window); + + /** + * Generate a QImage + * (always in slow mode) + * + * @param size the wanted size for the QImage + */ + QImage generateImage(const QSize& size); + + /** + * @return TRUE if the alpha channel processing has been enabled + */ + bool hasAlphaBuffer() const; + + /** + * Respect the image alpha buffer + */ + void setAlphaBuffer(bool enable); + + /** + * Creates an alpha mask for the picture + * (first you have to call @ref #setAlphaBuffer). + * + * @see hasAlphaBuffer() setAlphaBuffer() + */ + QImage createAlphaMask(int conversion_flags = 0) const; + + /** + * @brief Clear any cache + * + * This is used to avoid using too much memory + * especially if the application somehow also caches the KoPicture's output + */ + void clearCache(void); + + QString uniquePictureId() const; + void assignPictureId( uint _id); + +protected: + /** + * @internal + * Unregister shared data + */ + void unlinkSharedData(void); + /** + * @internal + * Register shared data + */ + void linkSharedData(void) const; + /** + * @internal + * Creare the shared data if needed + */ + void createSharedData(void); + QString uniqueName() const; + +protected: + /** + * @internal + * The key + */ + KoPictureKey m_key; + /** + * @internal + * The shared data + */ + KoPictureShared* m_sharedData; + static uint uniqueValue; + + QString m_uniqueName; +}; + +#endif /* __koPicture_h__ */ diff --git a/lib/kofficecore/KoPictureBase.cpp b/lib/kofficecore/KoPictureBase.cpp new file mode 100644 index 00000000..e608abc2 --- /dev/null +++ b/lib/kofficecore/KoPictureBase.cpp @@ -0,0 +1,142 @@ +/* This file is part of the KDE project + Copyright (c) 2001 Simon Hausmann <hausmann@kde.org> + Copyright (C) 2002, 2003 Nicolas GOUTTE <goutte@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 "KoPictureBase.h" + +#include <KoXmlWriter.h> + +#include <kdebug.h> +#include <kconfig.h> +#include <kglobal.h> + +#include <kmdcodec.h> +#include <qpainter.h> +#include <qpicture.h> +#include <qpixmap.h> +#include <qdragobject.h> + +static int s_useSlowResizeMode = -1; // unset + +KoPictureBase::KoPictureBase(void) +{ + // Slow mode can be very slow, especially at high zoom levels -> configurable + if ( s_useSlowResizeMode == -1 ) + { + KConfigGroup group( KGlobal::config(), "KOfficeImage" ); + s_useSlowResizeMode = group.readNumEntry( "HighResolution", 1 ); + kdDebug(30003) << "HighResolution = " << s_useSlowResizeMode << endl; + } +} + +KoPictureBase::~KoPictureBase(void) +{ +} + +KoPictureBase* KoPictureBase::newCopy(void) const +{ + return new KoPictureBase(*this); +} + +KoPictureType::Type KoPictureBase::getType(void) const +{ + return KoPictureType::TypeUnknown; +} + +bool KoPictureBase::isNull(void) const +{ + return true; // A KoPictureBase is always null. +} + +void KoPictureBase::draw(QPainter& painter, int x, int y, int width, int height, int, int, int, int, bool /*fastMode*/) +{ + // Draw a light red box (easier DEBUG) + kdWarning(30003) << "Drawing light red rectangle! (KoPictureBase::draw)" << endl; + painter.save(); + painter.setBrush(QColor(128,0,0)); + painter.drawRect(x,y,width,height); + painter.restore(); +} + +bool KoPictureBase::load(QIODevice* io, const QString& extension) +{ + return loadData(io->readAll(), extension); +} + +bool KoPictureBase::loadData(const QByteArray&, const QString&) +{ + // Nothing to load! + return false; +} + +bool KoPictureBase::save(QIODevice*) const +{ + // Nothing to save! + return false; +} + +bool KoPictureBase::saveAsBase64( KoXmlWriter& writer ) const +{ + QBuffer buffer; + buffer.open(IO_ReadWrite); + if ( !save( &buffer ) ) + return false; + QCString encoded = KCodecs::base64Encode( buffer.buffer() ); + writer.addTextNode( encoded ); + return true; +} + +QSize KoPictureBase::getOriginalSize(void) const +{ + return QSize(0,0); +} + +QPixmap KoPictureBase::generatePixmap(const QSize&, bool /*smoothScale*/) +{ + return QPixmap(); +} + +QString KoPictureBase::getMimeType(const QString&) const +{ + return QString(NULL_MIME_TYPE); +} + +bool KoPictureBase::isSlowResizeModeAllowed(void) const +{ + return s_useSlowResizeMode != 0; +} + +QDragObject* KoPictureBase::dragObject( QWidget * dragSource, const char * name ) +{ + QImage image (generateImage(getOriginalSize())); + if (image.isNull()) + return 0L; + else + return new QImageDrag( image, dragSource, name ); +} + +QImage KoPictureBase::generateImage(const QSize& size) +{ + return generatePixmap(size,true).convertToImage(); +} + +void KoPictureBase::clearCache(void) +{ + // Nothign to do! +} diff --git a/lib/kofficecore/KoPictureBase.h b/lib/kofficecore/KoPictureBase.h new file mode 100644 index 00000000..17a7df96 --- /dev/null +++ b/lib/kofficecore/KoPictureBase.h @@ -0,0 +1,128 @@ +/* This file is part of the KDE project + Copyright (c) 2001 Simon Hausmann <hausmann@kde.org> + Copyright (C) 2002, 2003, 2004 Nicolas GOUTTE <goutte@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 __koPictureBase_h__ +#define __koPictureBase_h__ + +#include "KoPictureKey.h" // for KoPictureType + +#include <qstring.h> +#include <qimage.h> + +class KoXmlWriter; +class QPainter; +class QSize; +class QIODevice; +class QDragObject; + +const char NULL_MIME_TYPE[]="application/x-zerosize"; + +// TODO: fix documentation + +/** + * @internal + * Base class for KoPictureImage, KoPictureClipart, KoPictureEps, KoPictureWmf... + */ +class KoPictureBase +{ +public: + /** + * Default constructor. + */ + KoPictureBase(); + + /** + * Destructor. + */ + virtual ~KoPictureBase(); + + virtual KoPictureType::Type getType(void) const; + + virtual KoPictureBase* newCopy(void) const; + + /** + * Returns true if the picture is null. + */ + virtual bool isNull(void) const; + + /** + * @brief Draw the picture in a painter. + * + * The parameter @p fastMode allows the picture to be re-sized and drawn quicker if possible + * + * The parameters @p width, @p height define the desired size for the image + * Note that the image is being scaled to that size using scale() - except when printing. + * This avoids scaling the image at each paint event. + * + * The other parameters are very similar to QPainter::drawPixmap : + * (@p x, @p y) define the position in the painter, + * (@p sx, @p sy) specify the top-left point in pixmap that is to be drawn. The default is (0, 0). + * (@p sw, @p sh) specify the size of the pixmap that is to be drawn. The default, (-1, -1), means all the way to the bottom + * right of the pixmap. + */ + virtual void draw(QPainter& painter, int x, int y, int width, int height, int sx = 0, int sy = 0, int sw = -1, int sh = -1, bool fastMode = false); + + /** + * Create a dragobject containing this picture. + * @param dragSource must be 0 when copying to the clipboard + * @return 0L if the picture is null! + */ + virtual QDragObject* dragObject( QWidget *dragSource = 0L, const char *name = 0L ); + + virtual bool load(QIODevice* io, const QString& extension); + + virtual bool loadData(const QByteArray& array, const QString& extension); + + /** + * save file + * @param io QIODevice used for saving + */ + virtual bool save(QIODevice* io) const; + + /** + * OASIS FlatXML support: + * Save picture as base64-encoded data into an XML writer. + */ + virtual bool saveAsBase64( KoXmlWriter& writer ) const; + + virtual QSize getOriginalSize(void) const; + + virtual QPixmap generatePixmap(const QSize& size, bool smoothScale = false); + + virtual QString getMimeType(const QString& extension) const; + + bool isSlowResizeModeAllowed(void) const; + + /** + * Generate a QImage + * (always in slow mode) + */ + virtual QImage generateImage(const QSize& size); + + virtual bool hasAlphaBuffer() const + { return false; } + virtual void setAlphaBuffer(bool /*enable*/) + { } + virtual QImage createAlphaMask(int /*conversion_flags*/ = 0) const + { return QImage(); } + + virtual void clearCache(void); +}; + +#endif /* __koPictureBase_h__ */ diff --git a/lib/kofficecore/KoPictureClipart.cpp b/lib/kofficecore/KoPictureClipart.cpp new file mode 100644 index 00000000..f4d94fd2 --- /dev/null +++ b/lib/kofficecore/KoPictureClipart.cpp @@ -0,0 +1,150 @@ +/* This file is part of the KDE project + Copyright (c) 2001 Simon Hausmann <hausmann@kde.org> + Copyright (c) 2001 David Faure <faure@kde.org> + Copyright (C) 2002 Nicolas GOUTTE <goutte@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 <qbuffer.h> +#include <qpainter.h> +#include <qpicture.h> +#include <qpixmap.h> + +#include <kdebug.h> +#include <kdeversion.h> +#if ! KDE_IS_VERSION( 3,1,90 ) +#include <kdebugclasses.h> +#endif + +#include "KoPictureKey.h" +#include "KoPictureBase.h" +#include "KoPictureClipart.h" + +KoPictureClipart::KoPictureClipart(void) : m_clipart(KoPictureType::formatVersionQPicture) +{ +} + +KoPictureClipart::~KoPictureClipart(void) +{ +} + +KoPictureBase* KoPictureClipart::newCopy(void) const +{ + return new KoPictureClipart(*this); +} + +KoPictureType::Type KoPictureClipart::getType(void) const +{ + return KoPictureType::TypeClipart; +} + +bool KoPictureClipart::isNull(void) const +{ + return m_clipart.isNull(); +} + +void KoPictureClipart::drawQPicture(QPicture& clipart, QPainter& painter, + int x, int y, int width, int height, int sx, int sy, int sw, int sh) +{ + kdDebug(30003) << "Drawing KoPictureClipart " << this << endl; + kdDebug(30003) << " x=" << x << " y=" << y << " width=" << width << " height=" << height << endl; + kdDebug(30003) << " sx=" << sx << " sy=" << sy << " sw=" << sw << " sh=" << sh << endl; + painter.save(); + // Thanks to Harri, Qt3 makes it much easier than Qt2 ;) + QRect br = clipart.boundingRect(); + kdDebug(30003) << " Bounding rect. " << br << endl; + + painter.translate(x,y); // Translating must be done before scaling! + if ( br.width() && br.height() ) + painter.scale(double(width)/double(br.width()),double(height)/double(br.height())); + else + kdWarning(30003) << "Null bounding rectangle: " << br.width() << " x " << br.height() << endl; + painter.drawPicture(0,0,clipart); + painter.restore(); +} + +void KoPictureClipart::draw(QPainter& painter, int x, int y, int width, int height, int sx, int sy, int sw, int sh, bool /*fastMode*/) +{ + drawQPicture(m_clipart, painter, x, y, width, height, sx, sy, sw, sh); +} + +bool KoPictureClipart::loadData(const QByteArray& array, const QString& extension) +{ + // Second, create the original clipart + kdDebug(30003) << "Trying to load clipart... (Size:" << m_rawData.size() << ")" << endl; + m_rawData=array; + QBuffer buffer(m_rawData); + buffer.open(IO_ReadOnly); + bool check = true; + if (extension=="svg") + { + if (!m_clipart.load(&buffer, "svg")) + { + kdWarning(30003) << "Loading SVG has failed! (KoPictureClipart::load)" << endl; + check = false; + } + } + else + { + if (!m_clipart.load(&buffer, NULL)) + { + kdWarning(30003) << "Loading QPicture has failed! (KoPictureClipart::load)" << endl; + check = false; + } + } + buffer.close(); + return check; +} + +bool KoPictureClipart::save(QIODevice* io) const +{ + // We save the raw data, as the SVG supposrt in QPicture is poor + Q_ULONG size=io->writeBlock(m_rawData); // WARNING: writeBlock returns Q_LONG but size() Q_ULONG! + return (size==m_rawData.size()); +} + +QSize KoPictureClipart::getOriginalSize(void) const +{ + return m_clipart.boundingRect().size(); +} + +QPixmap KoPictureClipart::generatePixmap(const QSize& size, bool /*smoothScale*/) +{ + // Not sure if it works, but it worked for KoPictureFilePreviewWidget::setClipart + QPixmap pixmap(size); + QPainter p; + + p.begin( &pixmap ); + p.setBackgroundColor( Qt::white ); + pixmap.fill( Qt::white ); + + QRect br = m_clipart.boundingRect(); + if ( br.width() && br.height() ) + p.scale( (double)pixmap.width() / (double)br.width(), (double)pixmap.height() / (double)br.height() ); + p.drawPicture( m_clipart ); + p.end(); + return pixmap; +} + +QString KoPictureClipart::getMimeType(const QString& extension) const +{ + if (extension=="svg") + return "image/svg+xml"; + else + return "image/x-vnd.trolltech.qpicture"; +} + diff --git a/lib/kofficecore/KoPictureClipart.h b/lib/kofficecore/KoPictureClipart.h new file mode 100644 index 00000000..9784c673 --- /dev/null +++ b/lib/kofficecore/KoPictureClipart.h @@ -0,0 +1,102 @@ +/* This file is part of the KDE project + Copyright (c) 2001 Simon Hausmann <hausmann@kde.org> + Copyright (C) 2002, 2004 Nicolas GOUTTE <goutte@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 __koPictureClipart_h__ +#define __koPictureClipart_h__ + +#include <qstring.h> +#include <qpicture.h> + +class QPainter; +class QSize; + +// TODO: fix documentation + +/** + * @internal + * KoPictureClipart is a container class for cliparts + */ +class KoPictureClipart : public KoPictureBase +{ +public: + /** + * Default constructor. + */ + KoPictureClipart(); + + /** + * Destructor. + */ + virtual ~KoPictureClipart(); + + virtual KoPictureType::Type getType(void) const; + + virtual KoPictureBase* newCopy(void) const; + + + /** + * Returns true if the picture is null. + */ + virtual bool isNull(void) const; + + /** + * @brief Draw the image in a painter. + * + * The parameter @p fastMode allows the picture to be re-sized and drawn quicker if possible + * + * The parameters @p width, @p height define the desired size for the image + * Note that the image is being scaled to that size using scale() - except when printing. + * This avoids scaling the image at each paint event. + * + * The other parameters are very similar to QPainter::drawPixmap : + * (@p x, @p y) define the position in the painter, + * (@p sx, @p sy) specify the top-left point in pixmap that is to be drawn. The default is (0, 0). + * (@p sw, @p sh) specify the size of the pixmap that is to be drawn. The default, (-1, -1), means all the way to the bottom + * right of the pixmap. + */ + virtual void draw(QPainter& painter, int x, int y, int width, int height, int sx = 0, int sy = 0, int sw = -1, int sh = -1, bool fastMode = false); + + virtual bool loadData(const QByteArray& array, const QString& extension); + + /** + * save file + * @param io QIODevice used for saving + */ + virtual bool save(QIODevice* io) const; + + virtual QSize getOriginalSize(void) const; + + virtual QPixmap generatePixmap(const QSize& size, bool smoothScale = false); + + virtual QString getMimeType(const QString& extension) const; + +protected: + QPixmap getPixmap(QImage& image); + /** + * @internal + * Draw a QPicture + */ + void drawQPicture(QPicture& clipart, QPainter& painter, + int x, int y, int width, int height, int sx, int sy, int sw, int sh); +protected: + QPicture m_clipart; ///< The clipart as QPicture + QByteArray m_rawData; ///< Copy of the loaded image file +}; + +#endif /* __koPictureClipart_h__ */ diff --git a/lib/kofficecore/KoPictureCollection.cpp b/lib/kofficecore/KoPictureCollection.cpp new file mode 100644 index 00000000..8941f32e --- /dev/null +++ b/lib/kofficecore/KoPictureCollection.cpp @@ -0,0 +1,324 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Reginald Stadlbauer <reggie@kde.org> + Copyright (c) 2001, 2004 David Faure <faure@kde.org> + Copyright (C) 2002, 2004 Nicolas GOUTTE <goutte@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 <qdom.h> + +#include <kdebug.h> +#include <kurl.h> + +#include <KoStoreDevice.h> +#include <KoXmlWriter.h> + +#include "KoPicture.h" +#include "KoPictureCollection.h" + +//#define DEBUG_PICTURES + +KoPicture KoPictureCollection::findPicture(const KoPictureKey& key) const +{ +#ifdef DEBUG_PICTURES + kdDebug(30003) << "KoPictureCollection::findPicture " << key.toString() << endl; +#endif + ConstIterator it = find( key ); + if ( it == end() ) + { + KoPicture picture; + picture.setKey(key); + return picture; + } + + return *it; +} + + +KoPicture KoPictureCollection::insertPicture(const KoPictureKey& key, const KoPicture& picture) +{ +#ifdef DEBUG_PICTURES + kdDebug(30003) << "KoPictureCollection::insertPicture " << key.toString() << endl; +#endif + KoPicture c = findPicture(key); + if (c.isNull()) + { +#ifdef DEBUG_PICTURES + kdDebug(30003) << "KoPictureCollection::insertPicture not found -> inserting" << endl; +#endif + c=picture; + c.setKey(key); // Be sure that the key is correctly set in the KoPicture! + insert(key, c); + } + return c; +} + +KoPicture KoPictureCollection::insertPicture(const KoPicture& picture) +{ + return insertPicture(picture.getKey(), picture); +} + +KoPicture KoPictureCollection::downloadPicture(const KURL& url, QWidget *window) +{ +#ifdef DEBUG_PICTURES + kdDebug(30003) << "KoPictureCollection::downloadPicture " << url.prettyURL() << endl; +#endif + + // If it is a local file, we can check the last modification date, so we should better use loadPicture + if (url.isLocalFile()) + return loadPicture(url.path()); + + + // We have really a remote file, so we cannot check the last modification date + // Therefore we have to always download the file + + KoPicture pic; +#ifdef DEBUG_PICTURES + kdDebug(30003) << "Trying to download picture from file " << url.prettyURL() << endl; +#endif + + if (pic.setKeyAndDownloadPicture(url, window)) + insertPicture(pic.getKey(), pic); + else + kdWarning(30003) << "Could not download KoPicture from " << url.prettyURL() << endl; + + return pic; +} + +KoPicture KoPictureCollection::loadPicture(const QString& fileName) +{ +#ifdef DEBUG_PICTURES + kdDebug(30003) << "KoPictureCollection::loadPicture " << fileName << endl; +#endif + // Check the last modified date of the file, as it is now. + KoPictureKey key; + key.setKeyFromFile(fileName); + + KoPicture c = findPicture(key); + if (c.isNull()) + { +#ifdef DEBUG_PICTURES + kdDebug(30003) << "Trying to load picture from file " << fileName << endl; +#endif + if (c.loadFromFile(fileName)) + insertPicture(key, c); + else + kdWarning(30003) << "Could not load KoPicture from " << fileName << endl; + } + return c; +} + +QString KoPictureCollection::getFileName(const Type pictureType, KoPicture& picture, int& counter) +{ + QString storeURL; + // ### TODO: remove "cliparts" when KPresenter is ready for it + if (pictureType==CollectionClipart) + storeURL="cliparts/clipart"; + else + storeURL="pictures/picture"; + storeURL+=QString::number(++counter); + storeURL+='.'; + storeURL+=picture.getExtension(); + return storeURL; +} + +QString KoPictureCollection::getOasisFileName(const KoPicture& picture) const +{ + QString storeURL( "Pictures/"); + if ( !picture.uniquePictureId().isEmpty() ) + storeURL+=picture.uniquePictureId(); + else + storeURL+=picture.getKey().toString(); + storeURL+='.'; + storeURL+=picture.getExtension(); + return storeURL; +} + +bool KoPictureCollection::saveToStore(const Type pictureType, KoStore *store, const QValueList<KoPictureKey>& keys) +{ + int counter=0; + QValueList<KoPictureKey>::const_iterator it = keys.begin(); + for ( ; it != keys.end(); ++it ) + { + KoPicture c = findPicture( *it ); + if (c.isNull()) + kdWarning(30003) << "Picture " << (*it).toString() << " not found in collection !" << endl; + else + { + QString storeURL=getFileName(pictureType, c, counter); + + if (store->open(storeURL)) + { + KoStoreDevice dev(store); + if ( ! c.save(&dev) ) + return false; // e.g. bad image? + if ( !store->close() ) + return false; // e.g. disk full + } + } + } + return true; +} + +bool KoPictureCollection::saveOasisToStore( KoStore *store, QValueList<KoPictureKey> keys, KoXmlWriter* manifestWriter ) +{ + QValueList<KoPictureKey>::Iterator it = keys.begin(); + for ( ; it != keys.end(); ++it ) + { + KoPicture c = findPicture( *it ); + if (c.isNull()) + kdWarning(30003) << "Picture " << (*it).toString() << " not found in collection !" << endl; + else + { + QString storeURL( getOasisFileName(c) ); + if (store->open(storeURL)) + { + KoStoreDevice dev(store); + if ( ! c.save(&dev) ) + return false; // e.g. bad image? + if ( !store->close() ) + return false; // e.g. disk full + manifestWriter->addManifestEntry( storeURL, c.getMimeType() ); + } + } + } + return true; +} + +QDomElement KoPictureCollection::saveXML(const Type pictureType, QDomDocument &doc, QValueList<KoPictureKey> keys) +{ + QString strElementName("PICTURES"); + if (pictureType==CollectionImage) + strElementName="PIXMAPS"; + else if (pictureType==CollectionClipart) + strElementName="CLIPARTS"; + QDomElement cliparts = doc.createElement( strElementName ); + int counter=0; + QValueList<KoPictureKey>::Iterator it = keys.begin(); + for ( ; it != keys.end(); ++it ) + { + KoPicture picture = findPicture( *it ); + if ( picture.isNull() ) + kdWarning(30003) << "Picture " << (*it).toString() << " not found in collection !" << endl; + else + { + QString pictureName=getFileName(pictureType, picture, counter); + QDomElement keyElem = doc.createElement( "KEY" ); + cliparts.appendChild(keyElem); + (*it).saveAttributes(keyElem); + keyElem.setAttribute("name", pictureName); + } + } + return cliparts; +} + +void KoPictureCollection::readXML( QDomElement& pixmapsElem, QMap <KoPictureKey, QString>& map ) +{ + for( QDomNode n = pixmapsElem.firstChild(); + !n.isNull(); + n = n.nextSibling() ) + { + QDomElement keyElement = n.toElement(); + if (keyElement.isNull()) continue; + if (keyElement.tagName()=="KEY") + { + KoPictureKey key; + key.loadAttributes(keyElement); + map.insert(key, keyElement.attribute("name")); + } + } +} + + +KoPictureCollection::StoreMap KoPictureCollection::readXML( QDomElement& pixmapsElem ) +{ + StoreMap map; + readXML(pixmapsElem, map); + return map; +} + +void KoPictureCollection::readFromStore( KoStore * store, const StoreMap & storeMap ) +{ +#ifdef DEBUG_PICTURES + kdDebug(30003) << "KoPictureCollection::readFromStore " << store << endl; +#endif + StoreMap::ConstIterator it = storeMap.begin(); + for ( ; it != storeMap.end(); ++it ) + { + KoPicture c = findPicture(it.key()); + if (!c.isNull()) + { + // Do not load a file that we already have! + //kdDebug(30003) << store << endl; + continue; + } + QString u(it.data()); + if (u.isEmpty()) + { + // old naming I suppose ? + u=it.key().toString(); + } + + KoPicture picture; + + if (!store->open( u )) + { + u.prepend( "file:" ); + if (!store->open( u )) + { + kdWarning(30003) << "Could load neither from store nor from file: " << it.data() << endl; + return; + } + } + + const int pos=u.findRev('.'); + if (pos==-1) + { + kdError(30003) << "File with no extension! Not supported!" << endl; + return; + } + const QString extension(u.mid(pos+1)); + + KoStoreDevice dev(store); + picture.load(&dev, extension); + store->close(); + + if (!picture.isNull()) + insertPicture(it.key(), picture); + } +} + +KoPicture KoPictureCollection::findOrLoad(const QString& fileName, const QDateTime& dateTime) +{ + // As now all KoPictureKey objects have a valid QDateTime, we must do it without a date/time check. + ConstIterator it = find( KoPictureKey ( fileName, dateTime ) ); + if ( it == end() ) + { + return loadPicture( fileName ); + } + return *it; +} + +void KoPictureCollection::assignUniqueIds() +{ + uint idx = 0; + Iterator it; + for ( it = begin(); it != end(); ++it, ++idx ) + { + it.data().assignPictureId(idx); + } +} diff --git a/lib/kofficecore/KoPictureCollection.h b/lib/kofficecore/KoPictureCollection.h new file mode 100644 index 00000000..22277bdd --- /dev/null +++ b/lib/kofficecore/KoPictureCollection.h @@ -0,0 +1,171 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Reginald Stadlbauer <reggie@kde.org> + Copyright (c) 2001 David Faure <faure@kde.org> + Copyright (C) 2002 Nicolas GOUTTE <goutte@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 koPictureCollection_h +#define koPictureCollection_h + +#include <qmap.h> +#include <qdom.h> +#include <qvaluelist.h> + +#include "KoPicture.h" + +class KURL; + +class KoStore; +class KoXmlWriter; + +/** + * A collection of pictures (a key and the picture itself). + * + */ +class KOFFICECORE_EXPORT KoPictureCollection : public QMap<KoPictureKey, KoPicture> +{ +public: + enum Type { + /// collection with mixed pictures + CollectionPicture=0, + /// collection with images only + CollectionImage, + /// collection with cliparts only + CollectionClipart + }; + + /** + * Looks for a clipart in the collection, returns a new KoPicture with that key if not found. + */ + KoPicture findPicture( const KoPictureKey &key ) const; + + /** + * Inserts a picture into the collection, if not already there + */ + KoPicture insertPicture( const KoPictureKey& key, const KoPicture& picture ); + + /** + * Inserts a picture into the collection, if not already there + * Same as above, but takes the key from the @p picture + */ + KoPicture insertPicture( const KoPicture& picture ); + + /** + * @brief Download a possibly remote file + * + * If this file is really remote, it is always downloaded. + * If the file is local, it acts as @ref #loadPicture. + * @param url the URL to download from + * @param window the parent window for the download. You can pass NULL (0) + * if you absolutely cannot find anything to use. + */ + KoPicture downloadPicture(const KURL& url, QWidget *window); + + /** + * @brief Load a clipart from a file (and insert into the collection). + * + * The modification date of the file is checked, to create the key + * for this clipart. If this key maps to an existing clipart in the + * collection, then this picture is returned, otherwise the file is loaded. + */ + KoPicture loadPicture( const QString &fileName ); + + /** + * Save the used picturess from the collection into the store + * Usually called from completeSaving(). + * + * @param pictureType type for the stored picture + * @param store the store in which to save the pictures + * @param keys the list of keys corresponding to the pictures to save + * @return true on success, false on failure (e.g. disk full) + * + * @todo Reduce lameness of dox for pictureType. + */ + bool saveToStore(const Type pictureType, KoStore * store, const QValueList<KoPictureKey>& keys ); + + /** + * Generate the <PICTURES>, <PIXMAPS> or <CLIPARTS> tag, that saves the key and the related + * relative path in the store (e.g. pictures/picture1.png) for each picture. + * + * @param pictureType the type of the collection + * @param doc the DOM document in which the tags are to be generated + * @param keys the list of keys + */ + QDomElement saveXML(const Type pictureType, QDomDocument &doc, + QValueList<KoPictureKey> keys ); + + bool saveOasisToStore( KoStore *store, QValueList<KoPictureKey> keys, KoXmlWriter* manifestWriter ); + + + typedef QMap<KoPictureKey, QString> StoreMap; + /** + * Read the <PICTURES>, <PIXMAPS> or <CLIPARTS> tag, and save the result (key<->store-filename associations) + * into the QMap. You may want to 'new' a QMap in loadXML, and to use and then delete + * it in completeLoading (to save memory). + * + * @param pixmapsElem the <PICTURES>, <PIXMAPS> or <CLIPARTS> element + */ + StoreMap readXML( QDomElement &pixmapsElem ); + + /** + * Helper method for @ref #readFromStore + */ + void readXML( QDomElement& pixmapsElem, QMap <KoPictureKey, QString>& map ); + + /** + * Read all pictures from the store, into this collection + * The map comes from @ref #readXML, and is used to find which pictures + * to load, and which key to associate them. + */ + void readFromStore( KoStore * store, const StoreMap & storeMap ); + + /** + * @deprecated + * KPresenter needs to use the same code for loading images from a collection and + * for loading images from disk. + * + * @param fileName the name of the file to read from disk if needed + * @param dateTime the date and time + * + * Formerly, an invalid date/time meant to read the file from disk. This is not the case anymore. + */ + KoPicture findOrLoad(const QString& fileName, const QDateTime& dateTime); + + /** + * Return filename as url for picture + * + */ + QString getOasisFileName(const KoPicture& picture) const; + + /** + * Call it before to save Oasis file + */ + void assignUniqueIds(); + + +private: + /** + * @internal + */ + QString getFileName(const Type pictureType, KoPicture& picture, int& counter); + + class Private; + Private* d; +}; + +#endif /* __koPictureCollection_h_- */ diff --git a/lib/kofficecore/KoPictureEps.cpp b/lib/kofficecore/KoPictureEps.cpp new file mode 100644 index 00000000..752e2a45 --- /dev/null +++ b/lib/kofficecore/KoPictureEps.cpp @@ -0,0 +1,447 @@ +/* This file is part of the KDE project + Copyright (c) 2001 Simon Hausmann <hausmann@kde.org> + Copyright (C) 2002, 2003, 2004 Nicolas GOUTTE <goutte@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 <unistd.h> +#include <stdio.h> + +#include <qbuffer.h> +#include <qpainter.h> +#include <qpaintdevicemetrics.h> +#include <qfile.h> +#include <qtextstream.h> +#include <qregexp.h> +#include <qimage.h> +#include <qpixmap.h> +#include <qapplication.h> +#include <qdragobject.h> + +#include <kglobal.h> +#include <kdebug.h> +#include <kdeversion.h> +#if ! KDE_IS_VERSION( 3,1,90 ) +#include <kdebugclasses.h> +#endif +#include <ktempfile.h> +#include <kprocess.h> + +#include "KoPictureKey.h" +#include "KoPictureBase.h" +#include "KoPictureEps.h" + + +KoPictureEps::KoPictureEps(void) : m_psStreamStart(0), m_psStreamLength(0), m_cacheIsInFastMode(true) +{ + // Forbid QPixmap to cache the X-Window resources (Yes, it is slower!) + m_cachedPixmap.setOptimization(QPixmap::MemoryOptim); +} + +KoPictureEps::~KoPictureEps(void) +{ +} + +KoPictureBase* KoPictureEps::newCopy(void) const +{ + return new KoPictureEps(*this); +} + +KoPictureType::Type KoPictureEps::getType(void) const +{ + return KoPictureType::TypeEps; +} + +bool KoPictureEps::isNull(void) const +{ + return m_rawData.isNull(); +} + +QImage KoPictureEps::scaleWithGhostScript(const QSize& size, const int resolutionx, const int resolutiony ) +{ + if (!m_boundingBox.width() || !m_boundingBox.height()) + { + kdDebug(30003) << "EPS image has a null size! (in KoPictureEps::scaleWithGhostScript)" << endl; + return QImage(); + } + + // ### TODO: do not call GhostScript up to three times for each re-scaling (one call of GhostScript should be enough to know which device is available: gs --help) + // png16m is better, but not always available -> fallback to bmp16m, then fallback to ppm (256 colors) + // ### TODO: pcx24b is also a true colour format + // ### TODO: support alpha (other gs devices needed) + + const char* deviceTable[] = { "png16m", "bmp16m", "ppm", 0 }; + + QImage img; + + for ( int i = 0; deviceTable[i]; ++i) + { + if ( tryScaleWithGhostScript( img, size, resolutionx, resolutiony, deviceTable[i] ) != -1 ) + { + return img; + } + + } + + kdError(30003) << "Image from GhostScript cannot be loaded (in KoPictureEps::scaleWithGhostScript)" << endl; + return img; +} + +// Helper method for scaleWithGhostScript. Returns 1 on success, 0 on error, -1 if nothing generated +// (in which case another 'output device' can be tried) +int KoPictureEps::tryScaleWithGhostScript(QImage &image, const QSize& size, const int resolutionx, const int resolutiony, const char* device ) +// Based on the code of the file kdelibs/kimgio/eps.cpp +{ + kdDebug(30003) << "Sampling with GhostScript, using device \"" << device << "\" (in KoPictureEps::tryScaleWithGhostScript)" << endl; + + KTempFile tmpFile; + tmpFile.setAutoDelete(true); + + if ( tmpFile.status() ) + { + kdError(30003) << "No KTempFile! (in KoPictureEps::tryScaleWithGhostScript)" << endl; + return 0; // error + } + + const int wantedWidth = size.width(); + const int wantedHeight = size.height(); + const double xScale = double(size.width()) / double(m_boundingBox.width()); + const double yScale = double(size.height()) / double(m_boundingBox.height()); + + // create GS command line + + QString cmdBuf ( "gs -sOutputFile=" ); + cmdBuf += KProcess::quote(tmpFile.name()); + cmdBuf += " -q -g"; + cmdBuf += QString::number( wantedWidth ); + cmdBuf += "x"; + cmdBuf += QString::number( wantedHeight ); + + if ( ( resolutionx > 0) && ( resolutiony > 0) ) + { +#if 0 + // Do not play with resolution for now. + // It brings more problems at print than solutions + cmdBuf += " -r"; + cmdBuf += QString::number( resolutionx ); + cmdBuf += "x"; + cmdBuf += QString::number( resolutiony ); +#endif + } + + cmdBuf += " -dSAFER -dPARANOIDSAFER -dNOPAUSE -sDEVICE="; + cmdBuf += device; + //cmdBuf += " -c 255 255 255 setrgbcolor fill 0 0 0 setrgbcolor"; + cmdBuf += " -"; + cmdBuf += " -c showpage quit"; + + // run ghostview + + FILE* ghostfd = popen (QFile::encodeName(cmdBuf), "w"); + + if ( ghostfd == 0 ) + { + kdError(30003) << "No connection to GhostScript (in KoPictureEps::tryScaleWithGhostScript)" << endl; + return 0; // error + } + + // The translation is needed as GhostScript (7.07) cannot handle negative values in the bounding box otherwise. + fprintf (ghostfd, "\n%d %d translate\n", -qRound(m_boundingBox.left()*xScale), -qRound(m_boundingBox.top()*yScale)); + fprintf (ghostfd, "%g %g scale\n", xScale, yScale); + + // write image to gs + + fwrite( m_rawData.data() + m_psStreamStart, sizeof(char), m_psStreamLength, ghostfd); + + pclose ( ghostfd ); + + // load image + if( !image.load (tmpFile.name()) ) + { + // It failed - maybe the device isn't supported by gs + return -1; + } + if ( image.size() != size ) // this can happen due to rounding problems + { + //kdDebug(30003) << "fixing size to " << size.width() << "x" << size.height() + // << " (was " << image.width() << "x" << image.height() << ")" << endl; + image = image.scale( size ); // hmm, smoothScale instead? + } + kdDebug(30003) << "Image parameters: " << image.width() << "x" << image.height() << "x" << image.depth() << endl; + return 1; // success +} + +void KoPictureEps::scaleAndCreatePixmap(const QSize& size, bool fastMode, const int resolutionx, const int resolutiony ) +{ + kdDebug(30003) << "KoPictureEps::scaleAndCreatePixmap " << size << " " << (fastMode?QString("fast"):QString("slow")) + << " resolutionx: " << resolutionx << " resolutiony: " << resolutiony << endl; + if ((size==m_cachedSize) + && ((fastMode) || (!m_cacheIsInFastMode))) + { + // The cached pixmap has already the right size + // and: + // - we are in fast mode (We do not care if the re-size was done slowly previously) + // - the re-size was already done in slow mode + kdDebug(30003) << "Already cached!" << endl; + return; + } + + // Slow mode can be very slow, especially at high zoom levels -> configurable + if ( !isSlowResizeModeAllowed() ) + { + kdDebug(30003) << "User has disallowed slow mode!" << endl; + fastMode = true; + } + + // We cannot use fast mode, if nothing was ever cached. + if ( fastMode && !m_cachedSize.isEmpty()) + { + kdDebug(30003) << "Fast scaling!" << endl; + // Slower than caching a QImage, but faster than re-sampling! + QImage image( m_cachedPixmap.convertToImage() ); + m_cachedPixmap=image.scale( size ); + m_cacheIsInFastMode=true; + m_cachedSize=size; + } + else + { + QTime time; + time.start(); + + QApplication::setOverrideCursor( Qt::waitCursor ); + m_cachedPixmap = scaleWithGhostScript( size, resolutionx, resolutiony ); + QApplication::restoreOverrideCursor(); + m_cacheIsInFastMode=false; + m_cachedSize=size; + + kdDebug(30003) << "Time: " << (time.elapsed()/1000.0) << " s" << endl; + } + kdDebug(30003) << "New size: " << size << endl; +} + +void KoPictureEps::draw(QPainter& painter, int x, int y, int width, int height, int sx, int sy, int sw, int sh, bool fastMode) +{ + if ( !width || !height ) + return; + + QSize screenSize( width, height ); + //kdDebug() << "KoPictureEps::draw screenSize=" << screenSize.width() << "x" << screenSize.height() << endl; + + QPaintDeviceMetrics metrics (painter.device()); + kdDebug(30003) << "Metrics: X: " << metrics.logicalDpiX() << " x Y: " << metrics.logicalDpiX() << " (in KoPictureEps::draw)" << endl; + + if ( painter.device()->isExtDev() ) // Is it an external device (i.e. printer) + { + kdDebug(30003) << "Drawing for a printer (in KoPictureEps::draw)" << endl; + // For printing, always re-sample the image, as a printer has never the same resolution than a display. + QImage image( scaleWithGhostScript( screenSize, metrics.logicalDpiX(), metrics.logicalDpiY() ) ); + // sx,sy,sw,sh is meant to be used as a cliprect on the pixmap, but drawImage + // translates it to the (x,y) point -> we need (x+sx, y+sy). + painter.drawImage( x + sx, y + sy, image, sx, sy, sw, sh ); + } + else // No, it is simply a display + { + scaleAndCreatePixmap(screenSize, fastMode, metrics.logicalDpiX(), metrics.logicalDpiY() ); + + // sx,sy,sw,sh is meant to be used as a cliprect on the pixmap, but drawPixmap + // translates it to the (x,y) point -> we need (x+sx, y+sy). + painter.drawPixmap( x + sx, y + sy, m_cachedPixmap, sx, sy, sw, sh ); + } +} + +bool KoPictureEps::extractPostScriptStream( void ) +{ + kdDebug(30003) << "KoPictureEps::extractPostScriptStream" << endl; + QDataStream data( m_rawData, IO_ReadOnly ); + data.setByteOrder( QDataStream::LittleEndian ); + Q_UINT32 magic, offset, length; + data >> magic; + data >> offset; + data >> length; + if ( !length ) + { + kdError(30003) << "Length of PS stream is zero!" << endl; + return false; + } + if ( offset+length>m_rawData.size() ) + { + kdError(30003) << "Data stream of the EPSF file is longer than file: " << offset << "+" << length << ">" << m_rawData.size() << endl; + return false; + } + m_psStreamStart = offset; + m_psStreamLength = length; + return true; +} + +QString KoPictureEps::readLine( const QByteArray& array, const uint start, const uint length, uint& pos, bool& lastCharWasCr ) +{ + QString strLine; + const uint finish = kMin( start + length, array.size() ); + for ( ; pos < finish; ++pos ) // We are starting at pos + { + const char ch = array[ pos ]; // Read one character + if ( ch == '\n' ) + { + if ( lastCharWasCr ) + { + // We have a line feed following a Carriage Return + // As the Carriage Return has already ended the previous line, + // discard this Line Feed. + lastCharWasCr = false; + } + else + { + // We have a normal Line Feed, therefore we end the line + break; + } + } + else if ( ch == '\r' ) + { + // We have a Carriage Return, therefore we end the line + lastCharWasCr = true; + break; + } + else if ( ch == char(12) ) // Form Feed + { // ### TODO: can a FF happen in PostScript? + // Ignore the form feed + continue; + } + else + { + strLine += ch; + lastCharWasCr = false; + } + } + return strLine; +} + + +bool KoPictureEps::loadData(const QByteArray& array, const QString& /* extension */ ) +{ + + kdDebug(30003) << "KoPictureEps::load" << endl; + // First, read the raw data + m_rawData=array; + + if (m_rawData.isNull()) + { + kdError(30003) << "No data was loaded!" << endl; + return false; + } + + if ( ( m_rawData[0]==char(0xc5) ) && ( m_rawData[1]==char(0xd0) ) + && ( m_rawData[2]==char(0xd3) ) && ( m_rawData[3]==char(0xc6) ) ) + { + // We have a so-called "MS-DOS EPS file", we have to extract the PostScript stream + if (!extractPostScriptStream()) // Changes m_rawData + return false; + } + else + { + m_psStreamStart = 0; + m_psStreamLength = m_rawData.size(); + } + + QString lineBox; // Line with the bounding box + bool lastWasCr = false; // Was the last character of the line a carriage return? + uint pos = m_psStreamStart; // We start to search the bounding box at the start of the PostScript stream + QString line( readLine( m_rawData, m_psStreamStart, m_psStreamLength, pos, lastWasCr ) ); + kdDebug(30003) << "Header: " << line << endl; + if (!line.startsWith("%!")) + { + kdError(30003) << "Not a PostScript file!" << endl; + return false; + } + QRect rect; + bool lineIsBoundingBox = false; // Does "line" has a %%BoundingBox line? + for(;;) + { + ++pos; // Get over the previous line end (CR or LF) + line = readLine( m_rawData, m_psStreamStart, m_psStreamLength, pos, lastWasCr ); + kdDebug(30003) << "Checking line: " << line << endl; + // ### TODO: it seems that the bounding box can be delayed with "(atend)" in the trailer (GhostScript 7.07 does not support it either.) + if (line.startsWith("%%BoundingBox:")) + { + lineIsBoundingBox = true; + break; + } + // ### TODO: also abort on %%EndComments + // ### TODO: %? , where ? is non-white-space printable, does not end the comment! + else if (!line.startsWith("%%")) + break; // Not a EPS comment anymore, so abort as we are not in the EPS header anymore + } + if ( !lineIsBoundingBox ) + { + kdError(30003) << "KoPictureEps::load: could not find a bounding box!" << endl; + return false; + } + // Floating point values are not allowed in a Bounding Box, but ther are many such files out there... + QRegExp exp("(\\-?[0-9]+\\.?[0-9]*)\\s(\\-?[0-9]+\\.?[0-9]*)\\s(\\-?[0-9]+\\.?[0-9]*)\\s(\\-?[0-9]+\\.?[0-9]*)"); + if ( exp.search(line) == -1 ) + { + // ### TODO: it might be an "(atend)" and the bounding box is in the trailer + // (but GhostScript 7.07 does not support a bounding box in the trailer.) + // Note: in Trailer, it is the last BoundingBox that counts not the first! + kdError(30003) << "Not standard bounding box: " << line << endl; + return false; + } + kdDebug(30003) << "Reg. Exp. Found: " << exp.capturedTexts() << endl; + rect.setLeft((int)exp.cap(1).toDouble()); + rect.setTop((int)exp.cap(2).toDouble()); + rect.setRight((int)exp.cap(3).toDouble()); + rect.setBottom((int)exp.cap(4).toDouble()); + m_boundingBox=rect; + m_originalSize=rect.size(); + kdDebug(30003) << "Rect: " << rect << " Size: " << m_originalSize << endl; + return true; +} + +bool KoPictureEps::save(QIODevice* io) const +{ + // We save the raw data, to avoid damaging the file by many load/save cycles + Q_ULONG size=io->writeBlock(m_rawData); // WARNING: writeBlock returns Q_LONG but size() Q_ULONG! + return (size==m_rawData.size()); +} + +QSize KoPictureEps::getOriginalSize(void) const +{ + return m_originalSize; +} + +QPixmap KoPictureEps::generatePixmap(const QSize& size, bool smoothScale) +{ + scaleAndCreatePixmap(size,!smoothScale, 0, 0); + return m_cachedPixmap; +} + +QString KoPictureEps::getMimeType(const QString&) const +{ + return "image/x-eps"; +} + +QImage KoPictureEps::generateImage(const QSize& size) +{ + // 0, 0 == resolution unknown + return scaleWithGhostScript(size, 0, 0); +} + +void KoPictureEps::clearCache(void) +{ + m_cachedPixmap.resize(0, 0); + m_cacheIsInFastMode=true; + m_cachedSize=QSize(); +} diff --git a/lib/kofficecore/KoPictureEps.h b/lib/kofficecore/KoPictureEps.h new file mode 100644 index 00000000..ba2a4cb9 --- /dev/null +++ b/lib/kofficecore/KoPictureEps.h @@ -0,0 +1,136 @@ +/* This file is part of the KDE project + Copyright (c) 2001 Simon Hausmann <hausmann@kde.org> + Copyright (C) 2002, 2003, 2004 Nicolas GOUTTE <goutte@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 __koPictureEps_h__ +#define __koPictureEps_h__ + +#include <qstring.h> + +class QPainter; +class QSize; +class KoPictureEpsPrivate; + +// TODO: fix documentation + +/** + * @internal + * KoPictureEps is a container class for a EPS picture + */ +class KoPictureEps : public KoPictureBase +{ +public: + /** + * Default constructor. + */ + KoPictureEps(); + + /** + * Destructor. + */ + virtual ~KoPictureEps(); + + KoPictureType::Type getType(void) const; + + KoPictureBase* newCopy(void) const; + + /** + * Returns true if the picture is null. + */ + virtual bool isNull(void) const; + + /** + * @brief Draw the image in a painter. + * + * No, this isn't as simple as painter.drawPixmap(). + * This method ensures that the best quality is used when printing, scaling the painter. + * + * The parameter @p fastMode allows the picture to be re-sized and drawn quicker if possible + * + * The parameters @p width, @p height define the desired size for the image + * Note that the image is being scaled to that size using scale() - except when printing. + * This avoids scaling the image at each paint event. + * + * The other parameters are very similar to QPainter::drawPixmap : + * (@p x, @p y) define the position in the painter, + * (@p sx, @p sy) specify the top-left point in pixmap that is to be drawn. The default is (0, 0). + * (@p sw, @p sh) specify the size of the pixmap that is to be drawn. The default, (-1, -1), means all the way to the bottom + * right of the pixmap. + */ + virtual void draw(QPainter& painter, int x, int y, int width, int height, int sx = 0, int sy = 0, int sw = -1, int sh = -1, bool fastMode = false); + + virtual bool loadData(const QByteArray& array, const QString& extension); + + virtual bool save(QIODevice* io) const; + + virtual QSize getOriginalSize(void) const; + + virtual QPixmap generatePixmap(const QSize& size, bool smoothScale = false); + + virtual QString getMimeType(const QString& extension) const; + + /** + * Generate a QImage + * (always in slow mode) + */ + virtual QImage generateImage(const QSize& size); + + virtual void clearCache(void); +protected: + QPixmap getPixmap(QImage& image); + void scaleAndCreatePixmap(const QSize& size, bool fastMode, const int resolutionx, const int resolutiony ); + QImage scaleWithGhostScript( const QSize& size, const int resolutionx, const int resolutiony ); + bool extractPostScriptStream( void ); + +private: + int tryScaleWithGhostScript(QImage &image, const QSize& size, const int resolutionx, const int resolutiony, const char* device ); + /** + * @brief Read a line from a PostScript stream in a QByteArray + * + * The PostScript stream is somewhere in the QByteArray and therefore has to be + * determined by the start and length of this stream and the current position + * + * @param array the array which is read + * @param start start position of the PostScript stream + * @param lenght lenght of the PostScript stream + * @param pos current position (which is also returned) + * @param lastCharWasCr defines if the previous line ended with a Carriage Return, + * to skip an eventual Line Feed at the start of this line + * @return the read line + */ + QString readLine( const QByteArray& array, const uint start, const uint length, uint& pos, bool& lastCharWasCr ); + +private: + /** + * Copy of the loaded EPS file + */ + QByteArray m_rawData; + QPixmap m_cachedPixmap; ///< Cached pixmap + QSize m_originalSize; ///< Original size of the EPS picture + QSize m_cachedSize; ///< size of the currently cached pixmap @see m_cachedPixmap + QRect m_boundingBox; ///< Bounding box, as read from the EPS file + uint m_psStreamStart; ///< Start position of the PostScript stream (like if it was a MS-DOS EPS file) + uint m_psStreamLength; ///< Lenght of the PostScript stream (like if it was a MS-DOS EPS file) + /** + * true, if the last cached image was done using fast mode. + * false, if the last cached image was done using slow mode. + */ + bool m_cacheIsInFastMode; +}; + +#endif /* __koPictureEps_h__ */ diff --git a/lib/kofficecore/KoPictureImage.cpp b/lib/kofficecore/KoPictureImage.cpp new file mode 100644 index 00000000..02110760 --- /dev/null +++ b/lib/kofficecore/KoPictureImage.cpp @@ -0,0 +1,194 @@ +/* This file is part of the KDE project + Copyright (c) 2001 Simon Hausmann <hausmann@kde.org> + Copyright (C) 2002, 2003 Nicolas GOUTTE <goutte@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 "KoPictureImage.h" +#include "KoPictureKey.h" + +#include <kdebug.h> +#include <kmimetype.h> + +#include <qbuffer.h> +#include <qpainter.h> +#include <qimage.h> +#include <qpixmap.h> +#include <qapplication.h> +#include <qdragobject.h> + +KoPictureImage::KoPictureImage(void) : m_cacheIsInFastMode(true) +{ + // Forbid QPixmap to cache the X-Window resources (Yes, it is slower!) + m_cachedPixmap.setOptimization(QPixmap::MemoryOptim); +} + +KoPictureImage::~KoPictureImage(void) +{ +} + +KoPictureBase* KoPictureImage::newCopy(void) const +{ + return new KoPictureImage(*this); +} + +KoPictureType::Type KoPictureImage::getType(void) const +{ + return KoPictureType::TypeImage; +} + +bool KoPictureImage::isNull(void) const +{ + return m_originalImage.isNull(); +} + +void KoPictureImage::scaleAndCreatePixmap(const QSize& size, bool fastMode) +{ + if ((size==m_cachedSize) + && ((fastMode) || (!m_cacheIsInFastMode))) + { + // The cached pixmap has already the right size + // and: + // - we are in fast mode (We do not care if the re-size was done slowly previously) + // - the re-size was already done in slow mode + return; + } + + // Slow mode can be very slow, especially at high zoom levels -> configurable + if ( !isSlowResizeModeAllowed() ) + { + kdDebug(30003) << "User has disallowed slow mode!" << endl; + fastMode = true; + } + + // Use QImage::scale if we have fastMode==true + if ( fastMode ) + { + m_cachedPixmap.convertFromImage(m_originalImage.scale( size ), QPixmap::Color); // Always color or else B/W can be reversed + m_cacheIsInFastMode=true; + } + else + { + m_cachedPixmap.convertFromImage(m_originalImage.smoothScale( size ), QPixmap::Color); // Always color or else B/W can be reversed + m_cacheIsInFastMode=false; + } + m_cachedSize=size; +} + +void KoPictureImage::draw(QPainter& painter, int x, int y, int width, int height, int sx, int sy, int sw, int sh, bool fastMode) +{ + //kdDebug() << "KoImage::draw currentSize:" << currentSize.width() << "x" << currentSize.height() << endl; + if ( !width || !height ) + return; + QSize origSize = getOriginalSize(); + const bool scaleImage = painter.device()->isExtDev() // we are printing + && ((width <= origSize.width()) || (height <= origSize.height())); + if( scaleImage ) + { + // use full resolution of image + double xScale = double(width) / double(origSize.width()); + double yScale = double(height) / double(origSize.height()); + + painter.save(); + painter.translate( x, y ); + painter.scale( xScale, yScale ); + // Note that sx, sy, sw and sh are unused in this case. Not a problem, since it's about printing. + // Note 2: we do not cache the QPixmap. As we are printing, the next time we will probably + // need again the screen version. + painter.drawImage(0, 0, m_originalImage); + painter.restore(); + } + else + { + QSize screenSize( width, height ); + //kdDebug() << "KoPictureImage::draw screenSize=" << screenSize.width() << "x" << screenSize.height() << endl; + + scaleAndCreatePixmap(screenSize, fastMode); + + // sx,sy,sw,sh is meant to be used as a cliprect on the pixmap, but drawPixmap + // translates it to the (x,y) point -> we need (x+sx, y+sy). + painter.drawPixmap( x + sx, y + sy, m_cachedPixmap, sx, sy, sw, sh ); + } +} + +bool KoPictureImage::loadData(const QByteArray& array, const QString& /* extension*/ ) +{ + m_rawData=array; + // Second, create the original image + QBuffer buffer(m_rawData); + buffer.open(IO_ReadWrite); + QImageIO imageIO(&buffer,NULL); + + if (!imageIO.read()) + { + buffer.close(); + kdError(30003) << "Image could not be loaded!" << endl; + return false; + } + buffer.close(); + m_originalImage=imageIO.image(); + + return true; +} + +bool KoPictureImage::save(QIODevice* io) const +{ + kdDebug() << k_funcinfo << "writing raw data. size=" << m_rawData.size() << endl; + // We save the raw data, to avoid damaging the file by many load/save cycles (especially for JPEG) + Q_ULONG size=io->writeBlock(m_rawData); // WARNING: writeBlock returns Q_LONG but size() Q_ULONG! + return (size==m_rawData.size()); +} + +QSize KoPictureImage::getOriginalSize(void) const +{ + return m_originalImage.size(); +} + +QPixmap KoPictureImage::generatePixmap(const QSize& size, bool smoothScale) +{ + scaleAndCreatePixmap(size,!smoothScale); + return m_cachedPixmap; +} + +QString KoPictureImage::getMimeType(const QString& extension) const +{ + QString fileName("/tmp/temp."); + fileName+=extension; + // Find the mimetype only by the extension, not by file content (as the file is empty!) + const QString mimetype( KMimeType::findByPath( fileName, 0 ,true )->name() ); + // ### TODO: use KMimeType::findByContent (but then the mimetype probably need to be cached) + kdDebug(30003) << "Image is mime type: " << mimetype << endl; + return mimetype; +} + +QDragObject* KoPictureImage::dragObject( QWidget *dragSource, const char *name ) +{ + return new QImageDrag( m_originalImage, dragSource, name ); +} + +QImage KoPictureImage::generateImage(const QSize& size) +{ + // We do not cache the image, as we will seldom need it again. + return m_originalImage.smoothScale( size ); +} + +void KoPictureImage::clearCache(void) +{ + m_cachedPixmap.resize(0, 0); + m_cacheIsInFastMode=true; + m_cachedSize=QSize(); +} diff --git a/lib/kofficecore/KoPictureImage.h b/lib/kofficecore/KoPictureImage.h new file mode 100644 index 00000000..98d7d588 --- /dev/null +++ b/lib/kofficecore/KoPictureImage.h @@ -0,0 +1,123 @@ +/* This file is part of the KDE project + Copyright (c) 2001 Simon Hausmann <hausmann@kde.org> + Copyright (C) 2002, 2003, 2004 Nicolas GOUTTE <goutte@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 __koPictureImage_h__ +#define __koPictureImage_h__ + +#include "KoPictureBase.h" +#include <qstring.h> + +class KoPictureImagePrivate; +// TODO: fix documentation + +/** + * @internal + * KoPictureImage is a container class for a QImage-based picture + * \todo remove private class, as the header is not installed + */ +class KoPictureImage : public KoPictureBase +{ +public: + /** + * Default constructor. + */ + KoPictureImage(); + + /** + * Destructor. + */ + virtual ~KoPictureImage(); + + KoPictureType::Type getType(void) const; + + KoPictureBase* newCopy(void) const; + + /** + * Returns true if the picture is null. + */ + virtual bool isNull(void) const; + + /** + * @brief Draw the image in a painter. + * + * No, this isn't as simple as painter.drawPixmap(). + * This method ensures that the best quality is used when printing, scaling the painter. + * + * The parameter @p fastMode allows the picture to be re-sized and drawn quicker if possible + * + * The parameters @p width, @p height define the desired size for the image + * Note that the image is being scaled to that size using scale() - except when printing. + * This avoids scaling the image at each paint event. + * + * The other parameters are very similar to QPainter::drawPixmap : + * (@p x, @p y) define the position in the painter, + * (@p sx, @p sy) specify the top-left point in pixmap that is to be drawn. The default is (0, 0). + * (@p sw, @p sh) specify the size of the pixmap that is to be drawn. The default, (-1, -1), means all the way to the bottom + * right of the pixmap. + */ + virtual void draw(QPainter& painter, int x, int y, int width, int height, int sx = 0, int sy = 0, int sw = -1, int sh = -1, bool fastMode = false); + + virtual QDragObject* dragObject( QWidget *dragSource = 0L, const char *name = 0L ); + + virtual bool loadData(const QByteArray& array, const QString& extension); + + virtual bool save(QIODevice* io) const; + + virtual QSize getOriginalSize(void) const; + + virtual QPixmap generatePixmap(const QSize& size, bool smoothScale = false); + + virtual QString getMimeType(const QString& extension) const; + + /** + * Generate a QImage + * (always in slow mode) + */ + virtual QImage generateImage(const QSize& size); + + virtual bool hasAlphaBuffer() const + { return m_originalImage.hasAlphaBuffer(); } + + virtual void setAlphaBuffer(bool enable) + { m_originalImage.setAlphaBuffer(enable); } + + virtual QImage createAlphaMask(int conversion_flags = 0) const + { return m_originalImage.createAlphaMask(conversion_flags); } + + virtual void clearCache(void); + +protected: + QPixmap getPixmap(QImage& image); + void scaleAndCreatePixmap(const QSize& size, bool fastMode=false); + +private: + QImage m_originalImage; ///< Image as Qimage + QByteArray m_rawData; ///< Binary copy of the loaded image file + QPixmap m_cachedPixmap; ///< Cached pixmap + QSize m_cachedSize; ///< size of the currently cached pixmap @see m_cachedPixmap + /** + * true, if the last cached image was done using fast mode. + * false, if the last cached image was done using slow mode. + */ + bool m_cacheIsInFastMode; + class Private; + Private* d; +}; + +#endif /* __koPictureImage_h__ */ diff --git a/lib/kofficecore/KoPictureKey.cpp b/lib/kofficecore/KoPictureKey.cpp new file mode 100644 index 00000000..ef60cfed --- /dev/null +++ b/lib/kofficecore/KoPictureKey.cpp @@ -0,0 +1,151 @@ + +/* This file is part of the KDE project + Copyright (c) 2001 Simon Hausmann <hausmann@kde.org> + Copyright 2002 Nicolas GOUTTE <goutte@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 <qdatetime.h> +#include <qfileinfo.h> +#include <qdom.h> + +#include <kdebug.h> + +#include "KoPictureKey.h" + +static void resetDateTimeToEpoch(QDateTime& dt) +{ + // set the time point to 1970-01-01 + dt.setDate(QDate(1970,1,1)); + dt.setTime(QTime(0,0)); + // Note: we cannot use QDateTime;;setTime_t as it makes a local time correction! (### TODO: not true anymore with recent Qt versions) +} + +KoPictureKey::KoPictureKey() +{ + resetDateTimeToEpoch(m_lastModified); +} + +KoPictureKey::KoPictureKey( const QString &fn, const QDateTime &mod ) + : m_filename( fn ), m_lastModified( mod ) +{ + if (!m_lastModified.isValid()) + { + // As we have an invalid date, set the time point to 1970-01-01 + resetDateTimeToEpoch(m_lastModified); + } +} + +KoPictureKey::KoPictureKey( const QString &fn ) + : m_filename( fn ) +{ + resetDateTimeToEpoch(m_lastModified); +} + +KoPictureKey::KoPictureKey( const KoPictureKey &key ) + : m_filename( key.m_filename ), m_lastModified( key.m_lastModified ) +{ +} + +KoPictureKey& KoPictureKey::operator=( const KoPictureKey &key ) +{ + m_filename = key.m_filename; + m_lastModified = key.m_lastModified; + return *this; +} + +bool KoPictureKey::operator==( const KoPictureKey &key ) const +{ + return ( key.m_filename == m_filename && + key.m_lastModified == m_lastModified ); +} + +bool KoPictureKey::operator<( const KoPictureKey &key ) const +{ + return key.toString() < toString(); +} + +void KoPictureKey::saveAttributes( QDomElement &elem ) const +{ + QDate date = m_lastModified.date(); + QTime time = m_lastModified.time(); + elem.setAttribute( "filename", m_filename ); + elem.setAttribute( "year", date.year() ); + elem.setAttribute( "month", date.month() ); + elem.setAttribute( "day", date.day() ); + elem.setAttribute( "hour", time.hour() ); + elem.setAttribute( "minute", time.minute() ); + elem.setAttribute( "second", time.second() ); + elem.setAttribute( "msec", time.msec() ); +} + +void KoPictureKey::loadAttributes( const QDomElement &elem ) +{ + // Default date/time is the *nix epoch: 1970-01-01 00:00:00,000 + int year=1970, month=1, day=1; + int hour=0, minute=0, second=0, msec=0; // We must initialize to zero, as not all compilers are C99-compliant + + if( elem.hasAttribute( "key" ) ) + { + // Note: the old KWord format (up to 1.1-beta2) has no date/time + m_filename=elem.attribute( "key" ); + } + else + { + // ### TODO: document which format is this? + m_filename=elem.attribute( "filename" ); + } + + if( elem.hasAttribute( "year" ) ) + year=elem.attribute( "year" ).toInt(); + if( elem.hasAttribute( "month" ) ) + month=elem.attribute( "month" ).toInt(); + if( elem.hasAttribute( "day" ) ) + day=elem.attribute( "day" ).toInt(); + if( elem.hasAttribute( "hour" ) ) + hour=elem.attribute( "hour" ).toInt(); + if( elem.hasAttribute( "minute" ) ) + minute=elem.attribute( "minute" ).toInt(); + if( elem.hasAttribute( "second" ) ) + second=elem.attribute( "second" ).toInt(); + if( elem.hasAttribute( "msec" ) ) + msec=elem.attribute( "msec" ).toInt(); + + m_lastModified.setDate( QDate( year, month, day ) ); + m_lastModified.setTime( QTime( hour, minute, second, msec ) ); + + if (!m_lastModified.isValid()) + { + // If the date/time is not valid, make it valid by force! + kdWarning(30003) << "Correcting invalid date/time: " << toString() << " (in KoPictureKey::loadAttributes)" << endl; + resetDateTimeToEpoch(m_lastModified); + } +} + +QString KoPictureKey::toString() const +{ + // We do not use the default QDateTime::toString has it does not show microseconds + return QString::fromLatin1( "%1 %2" ) + .arg( m_filename, m_lastModified.toString("yyyy-MM-dd hh:mm:ss.zzz") ); +} + +void KoPictureKey::setKeyFromFile (const QString& filename) +{ + QFileInfo inf(filename); + m_filename = filename; + m_lastModified = inf.lastModified(); +} diff --git a/lib/kofficecore/KoPictureKey.h b/lib/kofficecore/KoPictureKey.h new file mode 100644 index 00000000..d336215b --- /dev/null +++ b/lib/kofficecore/KoPictureKey.h @@ -0,0 +1,154 @@ +/* This file is part of the KDE project + Copyright (c) 2001 Simon Hausmann <hausmann@kde.org> + Copyright (C) 2002, 2004 Nicolas GOUTTE <goutte@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 __koPictureKey_h__ +#define __koPictureKey_h__ + +#include <qstring.h> +#include <qdatetime.h> +#include <koffice_export.h> +/** + * \file koPictureKey.h + * \todo correct documentation (for example: sed "s/image/picture/g") + */ + +class QDomElement; + +namespace KoPictureType +{ + /** + * QPicture version used by KoPictureClipart + * + * Possible values: + * \li 3 for Qt 2.1.x and later Qt 2.x + * \li 4 for Qt 3.0 + * \li 5 for Qt 3.1 and later Qt 3.x + * \li -1 for current Qt + */ + const int formatVersionQPicture=-1; + + enum Type + { + TypeUnknown = 0, ///< Unknown or not-an-image @see KoPictureBase + TypeImage, ///< Image, QImage-based @see KoPictureImage + TypeEps, ///< Encapsulated Postscript @see KoPictureEps + TypeClipart, ///< Clipart, QPicture-based @see KoPictureClipart + TypeWmf ///< WMF (Windows Meta File) @see KoPictureWmf + }; +} + +/** + * KoPictureKey is the structure describing a picture in a unique way. + * It currently includes the original path to the picture and the modification + * date. + * + * @short Structure describing a picture on disk + * + * @note We use the *nix epoch (1970-01-01) as a time base because it is a valid date. + * That way we do not depend on a behaviour of the current QDateTime that might change in future versions of Qt + * and we are also nice to non-Qt programs wanting to read KOffice's files. + * + * @note This behaviour is also needed for re-saving KWord files having \<FORMAT id="2"\>. When saving again, + * these files get a \<KEY\> element as child of \<PIXMAPS\> but not one as child of \<FORMAT\> and \<IMAGE\>. + * Therefore we need to be careful that the key remains compatible to default values + * (another good reason for the *NIX epoch) + * + * @note In case of a remote path, the "original path" is the name of the temporary file that was + * used to download the file. + */ +class KOFFICEUI_EXPORT KoPictureKey +{ +public: + /** + * Default constructor. Creates a null key + */ + KoPictureKey(); + + /** + * @brief Constructs a key, from a filename and a modification date + * + * Storing the modification date as part of the key allows the user + * to update the file and import it into the application again, without + * the application reusing the old copy from the collection. + */ + KoPictureKey( const QString &fn, const QDateTime &mod ); + + /** + * Constructs a key from a filename + * @note The modification date is set to 1970-01-01 + */ + KoPictureKey( const QString &fn ); + + /** + * Copy constructor + */ + KoPictureKey( const KoPictureKey &key ); + + /** + * Assignment operator + */ + KoPictureKey &operator=( const KoPictureKey &key ); + + /** + * Comparison operator + */ + bool operator==( const KoPictureKey &key ) const; + + /** + * Comparison operator + * @note Used for sorting in the collection's map + */ + bool operator<( const KoPictureKey &key ) const; + + /** + * Convert this key into a string representation of it + */ + QString toString() const; + + /** + * Save this key in XML (as %KOffice 1.3) + */ + void saveAttributes( QDomElement &elem ) const; + + /** + * Load this key from XML (as %KOffice 1.3) + */ + void loadAttributes( const QDomElement &elem ); + + /** + * First part of the key: the filename + */ + QString filename() const { return m_filename; } + + /** + * Second part of the key: the modification date + */ + QDateTime lastModified() const { return m_lastModified; } + + /** + * Sets the key according to @p filename, including modification time + */ + void setKeyFromFile (const QString& filename); + +protected: + QString m_filename; + QDateTime m_lastModified; +}; + +#endif /* __koPictureKey_h__ */ diff --git a/lib/kofficecore/KoPictureShared.cpp b/lib/kofficecore/KoPictureShared.cpp new file mode 100644 index 00000000..5c2ff2a2 --- /dev/null +++ b/lib/kofficecore/KoPictureShared.cpp @@ -0,0 +1,535 @@ +/* This file is part of the KDE project + Copyright (c) 2001 Simon Hausmann <hausmann@kde.org> + Copyright (C) 2002, 2003, 2004 Nicolas GOUTTE <goutte@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 <qpainter.h> +#include <qfile.h> + +#include <kdebug.h> +#include <kurl.h> +#include <kfilterdev.h> +#include <kio/netaccess.h> + +#include "KoPictureKey.h" +#include "KoPictureBase.h" +#include "KoPictureImage.h" +#include "KoPictureEps.h" +#include "KoPictureClipart.h" +#include "KoPictureWmf.h" +#include "KoPictureShared.h" +#include <kmdcodec.h> + + +KoPictureShared::KoPictureShared(void) : m_base(NULL) +{ +} + +void KoPictureShared::assignPictureId( uint _id) +{ + m_pictureId = _id; +} + +QString KoPictureShared::uniquePictureId() const +{ + return "Pictures"+ QString::number(m_pictureId); +} + +KoPictureShared::~KoPictureShared(void) +{ + delete m_base; +} + +KoPictureShared::KoPictureShared(const KoPictureShared &other) + : QShared() // Some compilers want it explicitly! +{ + // We need to use newCopy, because we want a real copy, not just a copy of the part of KoPictureBase + if (other.m_base) + m_base=other.m_base->newCopy(); + else + m_base=NULL; +} + +KoPictureShared& KoPictureShared::operator=( const KoPictureShared &other ) +{ + clear(); + kdDebug(30003) << "KoPictureShared::= before" << endl; + if (other.m_base) + m_base=other.m_base->newCopy(); + kdDebug(30003) << "KoPictureShared::= after" << endl; + return *this; +} + +KoPictureType::Type KoPictureShared::getType(void) const +{ + if (m_base) + return m_base->getType(); + return KoPictureType::TypeUnknown; +} + +bool KoPictureShared::isNull(void) const +{ + if (m_base) + return m_base->isNull(); + return true; +} + +void KoPictureShared::draw(QPainter& painter, int x, int y, int width, int height, int sx, int sy, int sw, int sh, bool fastMode) +{ + if (m_base) + m_base->draw(painter, x, y, width, height, sx, sy, sw, sh, fastMode); + else + { + // Draw a red box (easier DEBUG) + kdWarning(30003) << "Drawing red rectangle! (KoPictureShared::draw)" << endl; + painter.save(); + painter.setBrush(QColor(255,0,0)); + painter.drawRect(x,y,width,height); + painter.restore(); + } +} + +bool KoPictureShared::loadWmf(QIODevice* io) +{ + kdDebug(30003) << "KoPictureShared::loadWmf" << endl; + if (!io) + { + kdError(30003) << "No QIODevice!" << endl; + return false; + } + + clear(); + + // The extension .wmf was used (KOffice 1.1.x) for QPicture files + // For an extern file or in the storage, .wmf can mean a real Windows Meta File. + + QByteArray array ( io->readAll() ); + + if ((array[0]=='Q') && (array[1]=='P') &&(array[2]=='I') && (array[3]=='C')) + { + m_base=new KoPictureClipart(); + setExtension("qpic"); + } + else + { + m_base=new KoPictureWmf(); + setExtension("wmf"); + } + return m_base->loadData(array, m_extension); +} + +bool KoPictureShared::loadTmp(QIODevice* io) +// We have a temp file, probably from a downloaded file +// We must check the file type +{ + kdDebug(30003) << "KoPictureShared::loadTmp" << endl; + if (!io) + { + kdError(30003) << "No QIODevice!" << endl; + return false; + } + + // The extension .wmf was used (KOffice 1.1.x) for QPicture files + // For an extern file or in the storage, .wmf can mean a real Windows Meta File. + + QByteArray array ( io->readAll() ); + return identifyAndLoad( array ); +} + +bool KoPictureShared::identifyAndLoad( QByteArray array ) +{ + if ( array.size() < 5 ) + { + kdError(30003) << "Picture is less than 5 bytes long!" << endl; + return false; + } + + QString strExtension; + bool flag=false; + + // Try to find the file type by comparing magic on the first few bytes! + // ### TODO: could not QImageIO::imageFormat do it too? (At least most of them?) + if ((array[0]==char(0x89)) && (array[1]=='P') &&(array[2]=='N') && (array[3]=='G')) + { + strExtension="png"; + } + else if ((array[0]==char(0xff)) && (array[1]==char(0xd8)) &&(array[2]==char(0xff)) && (array[3]==char(0xe0))) + { + strExtension="jpeg"; + } + else if ((array[0]=='B') && (array[1]=='M')) + { + strExtension="bmp"; + } + else if ((array[0]==char(0xd7)) && (array[1]==char(0xcd)) &&(array[2]==char(0xc6)) && (array[3]==char(0x9a))) + { + strExtension="wmf"; + } + else if ((array[0]=='<') && (array[1]=='?') && ( array[2]=='x' ) && (array[3]=='m') && ( array[4]=='l' ) ) + { + strExtension="svg"; + } + else if ((array[0]=='Q') && (array[1]=='P') &&(array[2]=='I') && (array[3]=='C')) + { + strExtension="qpic"; + } + else if ((array[0]=='%') && (array[1]=='!') &&(array[2]=='P') && (array[3]=='S')) + { + strExtension="eps"; + } + else if ((array[0]==char(0xc5)) && (array[1]==char(0xd0)) && (array[2]==char(0xd3)) && (array[3]==char(0xc6))) + { + // So called "MS-DOS EPS file" + strExtension="eps"; + } + else if ((array[0]=='G') && (array[1]=='I') && (array[2]=='F') && (array[3]=='8')) + { + // GIF (87a or 89a) + strExtension="gif"; + } + else if ( ( array[0] == char( 0037 ) ) && ( array[1] == char( 0213 ) ) ) + { + // Gzip + QBuffer buffer(array); + buffer.open(IO_ReadOnly); + + const bool flag = loadCompressed( &buffer, "application/x-gzip", "tmp" ); + buffer.close(); + return flag; + } + else if ( ( array[0] == 'B' ) && ( array[1] == 'Z' ) && ( array[2] == 'h') ) + { + // BZip2 + QBuffer buffer(array); + buffer.open(IO_ReadOnly); + const bool flag = loadCompressed( &buffer, "application/x-bzip2", "tmp" ); + buffer.close(); + return flag; + } + else + { + kdDebug(30003) << "Cannot identify the type of temp file!" + << " Trying to convert to PNG! (in KoPictureShared::loadTmp" << endl; + + // Do not trust QBuffer and do not work directly on the QByteArray array + // DF: It would be faster to work on array here, and to create a completely + // different QBuffer for the writing code! + QBuffer buf( array.copy() ); + if (!buf.open(IO_ReadOnly)) + { + kdError(30003) << "Could not open read buffer!" << endl; + return false; + } + + QImageIO imageIO(&buf,NULL); + + if (!imageIO.read()) + { + kdError(30003) << "Could not read image!" << endl; + return false; + } + + buf.close(); + + if ( !buf.open( IO_WriteOnly | IO_Truncate ) ) + { + kdError(30003) << "Could not open write buffer!" << endl; + return false; + } + + imageIO.setIODevice(&buf); + imageIO.setFormat("PNG"); + + if (!imageIO.write()) + { + kdError(30003) << "Could not write converted image!" << endl; + return false; + } + buf.close(); + + array = buf.buffer(); + + strExtension="png"; + } + + kdDebug(30003) << "Temp file considered to be " << strExtension << endl; + + clearAndSetMode(strExtension); + if (m_base) + flag = m_base->loadData(array,strExtension); + setExtension(strExtension); + + return flag; +} + + + +bool KoPictureShared::loadXpm(QIODevice* io) +{ + kdDebug(30003) << "KoPictureShared::loadXpm" << endl; + if (!io) + { + kdError(30003) << "No QIODevice!" << endl; + return false; + } + + clear(); + + // Old KPresenter XPM files have char(1) instead of some " + // Therefore we need to treat XPM separately + + QByteArray array=io->readAll(); + + // As XPM files are normally only ASCII files, we can replace it without problems + + int pos=0; + + while ((pos=array.find(char(1),pos))!=-1) + { + array[pos]='"'; + } + + // Now that the XPM file is corrected, we need to load it. + + m_base=new KoPictureImage(); + + QBuffer buffer(array); + bool check = m_base->load(&buffer,"xpm"); + setExtension("xpm"); + return check; +} + +bool KoPictureShared::save(QIODevice* io) const +{ + if (!io) + return false; + if (m_base) + return m_base->save(io); + return false; +} + +bool KoPictureShared::saveAsBase64( KoXmlWriter& writer ) const +{ + if ( m_base ) + m_base->saveAsBase64( writer ); + return false; +} + +void KoPictureShared::clear(void) +{ + // Clear does not reset the key m_key! + delete m_base; + m_base=NULL; +} + +void KoPictureShared::clearAndSetMode(const QString& newMode) +{ + delete m_base; + m_base=NULL; + + const QString mode=newMode.lower(); + + if ((mode=="svg") || (mode=="qpic")) + { + m_base=new KoPictureClipart(); + } + else if (mode=="wmf") + { + m_base=new KoPictureWmf(); + } + else if ( (mode=="eps") || (mode=="epsi") || (mode=="epsf") ) + { + m_base=new KoPictureEps(); + } + else + { // TODO: test if QImageIO really knows the file format + m_base=new KoPictureImage(); + } +} + +QString KoPictureShared::getExtension(void) const +{ + return m_extension; +} + +void KoPictureShared::setExtension(const QString& extension) +{ + m_extension = extension; +} + +QString KoPictureShared::getMimeType(void) const +{ + if (m_base) + return m_base->getMimeType(m_extension); + return QString(NULL_MIME_TYPE); +} + + +bool KoPictureShared::loadFromBase64( const QCString& str ) +{ + clear(); + QByteArray data; + KCodecs::base64Decode( str, data ); + return identifyAndLoad( data ); +} + +bool KoPictureShared::load(QIODevice* io, const QString& extension) +{ + kdDebug(30003) << "KoPictureShared::load(QIODevice*, const QString&) " << extension << endl; + bool flag=false; + QString ext(extension.lower()); + if (ext=="wmf") + flag=loadWmf(io); + else if (ext=="tmp") // ### TODO: also remote scripts need this, don't they? + flag=loadTmp(io); + else if ( ext == "bz2" ) + { + flag = loadCompressed( io, "application/x-bzip2", "tmp" ); + } + else if ( ext == "gz" ) + { + flag = loadCompressed( io, "application/x-gzip", "tmp" ); + } + else if ( ext == "svgz" ) + { + flag = loadCompressed( io, "application/x-gzip", "svg" ); + } + else + { + clearAndSetMode(ext); + if (m_base) + flag = m_base->load(io, ext); + setExtension(ext); + } + if (!flag) + { + kdError(30003) << "File was not loaded! (KoPictureShared::load)" << endl; + } + return flag; +} + +bool KoPictureShared::loadFromFile(const QString& fileName) +{ + kdDebug(30003) << "KoPictureShared::loadFromFile " << fileName << endl; + if ( fileName.isEmpty() ) + { + kdError(30003) << "Cannot load file with empty name!" << endl; + return false; + } + QFile file(fileName); + if (!file.open(IO_ReadOnly)) + return false; + + bool flag = false; + const int pos=fileName.findRev('.'); + if (pos==-1) + { + kdDebug(30003) << "File with no extension!" << endl; + // As we have no extension, consider it like a temporary file + flag = loadTmp( &file ); + } + else + { + const QString extension( fileName.mid( pos+1 ) ); + // ### TODO: check if the extension if gz or bz2 and find the previous extension + flag = load( &file, extension ); + } + file.close(); + return flag; +} + +QSize KoPictureShared::getOriginalSize(void) const +{ + if (m_base) + return m_base->getOriginalSize(); + return QSize(0,0); +} + +QPixmap KoPictureShared::generatePixmap(const QSize& size, bool smoothScale) +{ + if (m_base) + return m_base->generatePixmap(size, smoothScale); + return QPixmap(); +} + +QDragObject* KoPictureShared::dragObject( QWidget *dragSource, const char *name ) +{ + if (m_base) + return m_base->dragObject( dragSource, name ); + return 0L; +} + +QImage KoPictureShared::generateImage(const QSize& size) +{ + if (m_base) + return m_base->generateImage( size ); + return QImage(); +} + +bool KoPictureShared::hasAlphaBuffer() const +{ + if (m_base) + return m_base->hasAlphaBuffer(); + return false; +} + +void KoPictureShared::setAlphaBuffer(bool enable) +{ + if (m_base) + m_base->setAlphaBuffer(enable); +} + +QImage KoPictureShared::createAlphaMask(int conversion_flags) const +{ + if (m_base) + return m_base->createAlphaMask(conversion_flags); + return QImage(); +} + +void KoPictureShared::clearCache(void) +{ + if (m_base) + m_base->clearCache(); +} + +bool KoPictureShared::loadCompressed( QIODevice* io, const QString& mimeType, const QString& extension ) +{ + // ### TODO: check that we do not have an endless recursion + QIODevice* in = KFilterDev::device( io, mimeType, false); + + if ( !in ) + { + kdError(30003) << "Cannot create device for uncompressing! Aborting!" << endl; + return false; + } + + + if ( !in->open( IO_ReadOnly ) ) + { + kdError(30003) << "Cannot open file for uncompressing! Aborting!" << endl; + delete in; + return false; + } + + const bool flag = load( in, extension ); + + in->close(); + delete in; + + return flag; +} diff --git a/lib/kofficecore/KoPictureShared.h b/lib/kofficecore/KoPictureShared.h new file mode 100644 index 00000000..da286368 --- /dev/null +++ b/lib/kofficecore/KoPictureShared.h @@ -0,0 +1,214 @@ +/* This file is part of the KDE project + Copyright (c) 2001 Simon Hausmann <hausmann@kde.org> + Copyright (C) 2002, 2003, 2004 Nicolas GOUTTE <goutte@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 __koPictureShared_h__ +#define __koPictureShared_h__ + +#include <qshared.h> +#include <qstring.h> +#include <qiodevice.h> +#include <qpixmap.h> + +#include "KoPictureKey.h" + +class KoXmlWriter; +class QPainter; +class QSize; + +class KURL; + +class KoPictureBase; + +/** + * @internal + * KoPictureShared is the class that contains the shared part for KoPicture + * + * @warning As with all QShared objects, the sharing is neither automatic nor transparent! + */ +class KoPictureShared : public QShared +{ +public: + /** + * Default constructor. + */ + KoPictureShared(void); + + /** + * Destructor. + */ + ~KoPictureShared(void); + + /** + * @brief Copy constructor + * + * This makes a deep copy. Do not use if you want to share! + */ + KoPictureShared(const KoPictureShared &other); + + /** + * @brief Assignment operator + * + * This makes a deep copy. Do not use if you want to share! + */ + KoPictureShared& operator=(const KoPictureShared& other); + + KoPictureType::Type getType(void) const; + + /** + * Returns true if the picture is null. + */ + bool isNull(void) const; + + /** + * @brief Draw the image in a painter. + * + * The parameter @p fastMode allows the picture to be re-sized and drawn quicker if possible + * + * The parameters @p width, @p height define the desired size for the picture + * + * The other parameters are very similar to QPainter::drawPixmap : + * (@p x, @p y) define the position in the painter, + * (@p sx, @p sy) specify the top-left point in pixmap that is to be drawn. The default is (0, 0). + * (@p sw, @p sh) specify the size of the pixmap that is to be drawn. The default, (-1, -1), means all the way to the bottom + * right of the pixmap. + */ + void draw(QPainter& painter, int x, int y, int width, int height, int sx = 0, int sy = 0, int sw = -1, int sh = -1, bool fastMode = false); + + /** + * Create a dragobject containing this picture. + * @param dragSource must be 0 when copying to the clipboard + * @return 0L if the picture is null! + */ + QDragObject* dragObject( QWidget *dragSource = 0L, const char *name = 0L ); + + bool load(QIODevice* io, const QString& extension); + bool loadFromBase64( const QCString& str ); + + /** + * Save picture into a QIODevice + * @param io QIODevice used for saving + */ + bool save(QIODevice* io) const; + + /** + * OASIS FlatXML support: + * Save picture as base64-encoded data into an XML writer. + */ + bool saveAsBase64( KoXmlWriter& writer ) const; + + void setExtension(const QString& extension); + + QString getExtension(void) const; + + QSize getOriginalSize(void) const; + + /** + * Clear and set the mode of this KoPictureShared + * + * @param newMode file extension (like "png") giving the wanted mode + */ + void clearAndSetMode(const QString& newMode); + + /** + * Reset the KoPictureShared (but not the key!) + */ + void clear(void); + + /** + * Load a file + * + * @param fileName the name of the file to load + */ + bool loadFromFile(const QString& fileName); + + /** + * Load a potentially broken XPM file (for KPresenter) + */ + bool loadXpm(QIODevice* io); + + /** + * @deprecated + * Returns a QPixmap from an image + * + * @param size the wanted size for the QPixmap + */ + QPixmap generatePixmap(const QSize& size, bool smoothScale = false); + + QString getMimeType(void) const; + + /** + * Generate a QImage + * + * (always in slow mode) + * + * @param size the wanted size for the QImage + */ + QImage generateImage(const QSize& size); + + bool hasAlphaBuffer() const; + void setAlphaBuffer(bool enable); + QImage createAlphaMask(int conversion_flags = 0) const; + + /** + * Clear any cache + * + * It is used to avoid using too much memory + * especially if the application somehow caches the KoPicture too. + */ + void clearCache(void); + + QString uniquePictureId() const; + void assignPictureId( uint _id); + +protected: + /** + * @internal + * Load a WMF file + * \note In %KOffice 1.1, a .wmf file was a QPicture file + */ + bool loadWmf(QIODevice* io); + + /** + * @internal + * Loads a temporary file, probably from a downloaded file + */ + bool loadTmp(QIODevice* io); + + /// Find type of image, create base accordingly, and load data + bool identifyAndLoad( QByteArray data ); + + /** + * @internal + * Loads a compressed file + * + * @warning risk of endless recurision, be careful when it is called from @see load + * + * @param io QIODevice of the compressed file/strea, + * @param mimeType mimetype of the (de-)compressor + * @param extension extension of the uncompressed file + */ + bool loadCompressed( QIODevice* io, const QString& mimeType, const QString& extension ); + +protected: + KoPictureBase* m_base; + QString m_extension; + uint m_pictureId; +}; + +#endif /* __koPictureShared_h__ */ diff --git a/lib/kofficecore/KoPictureWmf.cpp b/lib/kofficecore/KoPictureWmf.cpp new file mode 100644 index 00000000..96872af7 --- /dev/null +++ b/lib/kofficecore/KoPictureWmf.cpp @@ -0,0 +1,137 @@ +/* This file is part of the KDE project + Copyright (c) 2001 Simon Hausmann <hausmann@kde.org> + Copyright (c) 2001 David Faure <faure@kde.org> + Copyright (C) 2002 Nicolas GOUTTE <goutte@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 <qbuffer.h> +#include <qpainter.h> +#include <qpicture.h> +#include <qpixmap.h> + +#include <kdebug.h> +#include <kdeversion.h> +#if ! KDE_IS_VERSION( 3,1,90 ) +#include <kdebugclasses.h> +#endif + +#include <kowmfpaint.h> +#include "KoPictureKey.h" +#include "KoPictureBase.h" +#include "KoPictureWmf.h" + +KoPictureWmf::KoPictureWmf(void) : m_clipart(KoPictureType::formatVersionQPicture) +{ +} + +KoPictureWmf::~KoPictureWmf(void) +{ +} + +KoPictureBase* KoPictureWmf::newCopy(void) const +{ + return new KoPictureWmf(*this); +} + +KoPictureType::Type KoPictureWmf::getType(void) const +{ + return KoPictureType::TypeWmf; +} + +bool KoPictureWmf::isNull(void) const +{ + return m_clipart.isNull(); +} + +void KoPictureWmf::drawQPicture(QPicture& clipart, QPainter& painter, + int x, int y, int width, int height, int sx, int sy, int sw, int sh) +{ + kdDebug(30003) << "Drawing KoPictureWmf " << this << endl; + kdDebug(30003) << " x=" << x << " y=" << y << " width=" << width << " height=" << height << endl; + kdDebug(30003) << " sx=" << sx << " sy=" << sy << " sw=" << sw << " sh=" << sh << endl; + painter.save(); + // Thanks to Harri, Qt3 makes it much easier than Qt2 ;) + QRect br = clipart.boundingRect(); + kdDebug(30003) << " Bounding rect. " << br << endl; + + painter.translate(x,y); // Translating must be done before scaling! + if ( br.width() && br.height() ) + painter.scale(double(width)/double(br.width()),double(height)/double(br.height())); + else + kdWarning(30003) << "Null bounding rectangle: " << br.width() << " x " << br.height() << endl; + painter.drawPicture(0,0,clipart); + painter.restore(); +} + +void KoPictureWmf::draw(QPainter& painter, int x, int y, int width, int height, int sx, int sy, int sw, int sh, bool /*fastMode*/) +{ + drawQPicture(m_clipart, painter, x, y, width, height, sx, sy, sw, sh); +} + +bool KoPictureWmf::loadData(const QByteArray& array, const QString& /* extension */) +{ + // Second, create the original clipart + kdDebug(30003) << "Trying to load clipart... (Size:" << array.size() << ")" << endl; + m_rawData=array; + + KoWmfPaint wmf; + if (!wmf.load(array)) + { + kdWarning(30003) << "Loading WMF has failed! (KoPictureWmf::load)" << endl; + return false; + } + m_originalSize = wmf.boundingRect().size(); + // draw wmf file with relative coordinate + wmf.play(m_clipart, true); + + return true; +} + +bool KoPictureWmf::save(QIODevice* io) const +{ + // We save the raw data, as the SVG supposrt in QPicture is poor + Q_ULONG size=io->writeBlock(m_rawData); // WARNING: writeBlock returns Q_LONG but size() Q_ULONG! + return (size==m_rawData.size()); +} + +QSize KoPictureWmf::getOriginalSize(void) const +{ + return m_originalSize; +} + +QPixmap KoPictureWmf::generatePixmap(const QSize& size, bool /*smoothScale*/) +{ + // Not sure if it works, but it worked for KoPictureFilePreviewWidget::setClipart + QPixmap pixmap(size); + QPainter p; + + p.begin( &pixmap ); + p.setBackgroundColor( Qt::white ); + pixmap.fill( Qt::white ); + + if ( m_originalSize.width() && m_originalSize.height() ) + p.scale( (double)pixmap.width() / (double)m_originalSize.width(), (double)pixmap.height() / (double)m_originalSize.height() ); + p.drawPicture( m_clipart ); + p.end(); + return pixmap; +} + +QString KoPictureWmf::getMimeType(const QString& /* extension */) const +{ + return "image/x-wmf"; +} diff --git a/lib/kofficecore/KoPictureWmf.h b/lib/kofficecore/KoPictureWmf.h new file mode 100644 index 00000000..f1924d9c --- /dev/null +++ b/lib/kofficecore/KoPictureWmf.h @@ -0,0 +1,108 @@ +/* This file is part of the KDE project + Copyright (c) 2001 Simon Hausmann <hausmann@kde.org> + Copyright (C) 2002 Nicolas GOUTTE <goutte@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 __koPictureWmf_h__ +#define __koPictureWmf_h__ + +#include <qstring.h> +#include <qpicture.h> +#include <koffice_export.h> + +class QPainter; +class QSize; + +/** + * \file koPictureWmf.h + * \todo fix documentation + */ + +/** + * @internal + * KoPictureWmf is a container class for WMF pictures. + */ +class KOFFICECORE_EXPORT KoPictureWmf : public KoPictureBase +{ +public: + /** + * Default constructor. + */ + KoPictureWmf(); + + /** + * Destructor. + */ + virtual ~KoPictureWmf(); + + virtual KoPictureType::Type getType(void) const; + + virtual KoPictureBase* newCopy(void) const; + + + /** + * Returns true if the picture is null. + */ + virtual bool isNull(void) const; + + /** + * @brief Draw the image in a painter. + * + * The parameter @p fastMode allows the picture to be re-sized and drawn quicker if possible + * + * The parameters @p width, @p height define the desired size for the image + * Note that the image is being scaled to that size using scale() - except when printing. + * This avoids scaling the image at each paint event. + * + * The other parameters are very similar to QPainter::drawPixmap : + * (@p x, @p y) define the position in the painter, + * (@p sx, @p sy) specify the top-left point in pixmap that is to be drawn. The default is (0, 0). + * (@p sw, @p sh) specify the size of the pixmap that is to be drawn. The default, (-1, -1), means all the way to the bottom + * right of the pixmap. + */ + virtual void draw(QPainter& painter, int x, int y, int width, int height, int sx = 0, int sy = 0, int sw = -1, int sh = -1, bool fastMode = false); + + virtual bool loadData(const QByteArray& array, const QString& extension); + + /** + * save file + * @param io QIODevice used for saving + */ + virtual bool save(QIODevice* io) const; + + virtual QSize getOriginalSize(void) const; + + virtual QPixmap generatePixmap(const QSize& size, bool smoothScale = false); + + virtual QString getMimeType(const QString& extension) const; + +protected: + QPixmap getPixmap(QImage& image); + /** + * @internal + * Draw a QPicture + */ + void drawQPicture(QPicture& clipart, QPainter& painter, + int x, int y, int width, int height, int sx, int sy, int sw, int sh); +protected: + QPicture m_clipart; + QByteArray m_rawData; + QSize m_size; + QSize m_originalSize; +}; + +#endif /* __koPictureWmf_h__ */ diff --git a/lib/kofficecore/KoPoint.h b/lib/kofficecore/KoPoint.h new file mode 100644 index 00000000..5f39e6c6 --- /dev/null +++ b/lib/kofficecore/KoPoint.h @@ -0,0 +1,128 @@ +/* This file is part of the KDE project + Copyright (C) 2001 David Faure <faure@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 koPoint_h +#define koPoint_h + +#include <qwmatrix.h> +#include <math.h> + +/** + * A point whose coordinates are floating-point values ( "double"s ). + * The API isn't documented, it's a perfect mirror of QPoint. + */ +class KoPoint { + +public: + KoPoint() { m_x = 0; m_y = 0; } + KoPoint(const double &x, const double &y) : m_x(x), m_y(y) {} + explicit KoPoint(const QPoint & p) : m_x(p.x()), m_y(p.y()) {} + ~KoPoint() {} + + bool operator==(const KoPoint &rhs) const { return QABS(m_x-rhs.x()) < 1E-10 && QABS(m_y-rhs.y()) < 1E-10; } + bool operator!=(const KoPoint &rhs) const { return QABS(m_x-rhs.x()) > 1E-10 || QABS(m_y-rhs.y()) > 1E-10; } + + bool isNull() const { return m_x == 0 && m_y == 0; } + + double x() const { return m_x; } + double y() const { return m_y; } + void setX(const double &x) { m_x = x; } + void setY(const double &y) { m_y = y; } + + double &rx() { return m_x; } + double &ry() { return m_y; } + + KoPoint &operator=(const KoPoint &rhs) { m_x = rhs.x(); m_y = rhs.y(); return *this; } + KoPoint &operator+=( const KoPoint &rhs ) { m_x += rhs.x(); m_y += rhs.y(); return *this; } + KoPoint &operator-=( const KoPoint &rhs ) { m_x -= rhs.x(); m_y -= rhs.y(); return *this; } + KoPoint &operator*=( const double &c ) { m_x *= c; m_y *= c; return *this; } + + friend inline KoPoint operator+( const KoPoint &, const KoPoint & ); + friend inline KoPoint operator-( const KoPoint &, const KoPoint & ); + friend inline KoPoint operator*( const KoPoint &, const double & ); + friend inline KoPoint operator*( const double &, const KoPoint & ); + friend inline double operator*( const KoPoint &a, const KoPoint &b ); + + // Not in QPoint: + void setCoords(const double &x, const double &y) { m_x = x; m_y = y; } + KoPoint transform (const QWMatrix &m) const + { + double x, y; + m.map(m_x, m_y, &x, &y); + return KoPoint(x, y); + }; + + bool isNear(const KoPoint &p, double range) const + { + return (p.x() >= m_x - range && p.x() <= m_x + range && p.y() >= m_y - range && p.y() <= m_y + range); + } + + static double getAngle( const KoPoint& p1, const KoPoint& p2 ) { + double a = atan2( p2.x() - p1.x(), p2.y() - p1.y() ) + M_PI; + return ( ( - ( a * 360 ) / ( 2 * M_PI ) - 90 ) - 180 ); + } + + double manhattanLength() const + { + return QABS( m_x ) + QABS( m_y ); + } + + /// Convert to a QPoint - with precision loss! + QPoint toQPoint() const + { + return QPoint( qRound( m_x ), qRound( m_y ) ); + } + +private: + double m_x, m_y; +}; + +inline KoPoint operator+( const KoPoint &p1, const KoPoint &p2 ) +{ return KoPoint( p1.m_x+p2.m_x, p1.m_y+p2.m_y ); } + +inline KoPoint operator-( const KoPoint &p1, const KoPoint &p2 ) +{ return KoPoint( p1.m_x-p2.m_x, p1.m_y-p2.m_y ); } + +inline KoPoint operator*( const KoPoint &p, const double &c ) +{ return KoPoint( p.m_x*c, p.m_y*c ); } + +inline KoPoint operator*( const double &c, const KoPoint &p ) +{ return KoPoint( p.m_x*c, p.m_y*c ); } + +inline double operator*( const KoPoint &a, const KoPoint &b ) +{ return a.m_x * b.m_x + a.m_y * b.m_y; } + +/****************************** + kdDebug support +*******************************/ + +#include <kdebug.h> + +/** Show a floating point value with great precision (use within kdDebug) */ +#define DEBUGDOUBLE(d) QString::number( (d), 'g', 20 ) + +inline kdbgstream operator<<( kdbgstream str, const KoPoint & r ) { + // should this use DEBUGDOUBLE? + str << "(" << r.x() << ", " << r.y() << ")"; + return str; +} + +inline kndbgstream operator<<( kndbgstream str, const KoPoint & ) { return str; } + +#endif diff --git a/lib/kofficecore/KoQueryTrader.cpp b/lib/kofficecore/KoQueryTrader.cpp new file mode 100644 index 00000000..00f4379b --- /dev/null +++ b/lib/kofficecore/KoQueryTrader.cpp @@ -0,0 +1,214 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@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 <kparts/factory.h> + +#include <KoQueryTrader.h> +#include <KoDocument.h> +#include <KoFilter.h> +#include <ktrader.h> +#include <kservicetype.h> +#include <kdebug.h> + +#include <qfile.h> + +#include <limits.h> // UINT_MAX + +/** + * Port from KOffice Trader to KTrader/KActivator (kded) by Simon Hausmann + * (c) 1999 Simon Hausmann <hausmann@kde.org> + * Port to KService and simplifications by David Faure <faure@kde.org> + */ + + +/******************************************************************* + * + * KoDocumentEntry + * + *******************************************************************/ + +KoDocumentEntry::KoDocumentEntry( KService::Ptr service ) + : m_service( service ) +{ +} + +KoDocument* KoDocumentEntry::createDoc( KoDocument* parent, const char* name ) const +{ + KLibFactory* factory = KLibLoader::self()->factory( QFile::encodeName(m_service->library()) ); + + if( !factory ) { + kdWarning(30003) << KLibLoader::self()->lastErrorMessage() << endl; + return 0; + } + + QObject* obj; + if ( factory->inherits( "KParts::Factory" ) ) + obj = static_cast<KParts::Factory *>(factory)->createPart( 0L, "", parent, name, "KoDocument" ); + else { + kdWarning(30003) << "factory doesn't inherit KParts::Factory ! It is a " << factory->className() << endl; // This shouldn't happen... + obj = factory->create( parent, name, "KoDocument" ); + } + + if ( !obj || !obj->inherits( "KoDocument" ) ) + { + delete obj; + return 0; + } + + return static_cast<KoDocument*>(obj); +} + +KoDocumentEntry KoDocumentEntry::queryByMimeType( const QString & mimetype ) +{ + QString constr = QString::fromLatin1( "[X-KDE-NativeMimeType] == '%1' or '%2' in [X-KDE-ExtraNativeMimeTypes]" ).arg( mimetype ).arg( mimetype ); + + QValueList<KoDocumentEntry> vec = query( false,constr ); + if ( vec.isEmpty() ) + { + kdWarning(30003) << "Got no results with " << constr << endl; + // Fallback to the old way (which was probably wrong, but better be safe) + QString constr = QString::fromLatin1( "'%1' in ServiceTypes" ).arg( mimetype ); + vec = query( constr ); + if ( vec.isEmpty() ) + { + // Still no match. Either the mimetype itself is unknown, or we have no service for it. + // Help the user debugging stuff by providing some more diagnostics + if ( KServiceType::serviceType( mimetype ) == 0L ) + { + kdError(30003) << "Unknown KOffice MimeType " << mimetype << "." << endl; + kdError(30003) << "Check your installation (for instance, run 'kde-config --path mime' and check the result)." << endl; + } else + { + kdError(30003) << "Found no KOffice part able to handle " << mimetype << "!" << endl; + kdError(30003) << "Check your installation (does the desktop file have X-KDE-NativeMimeType and KOfficePart, did you install KOffice in a different prefix than KDE, without adding the prefix to /etc/kderc ?)" << endl; + } + return KoDocumentEntry(); + } + } + + return vec[0]; +} + +QValueList<KoDocumentEntry> KoDocumentEntry::query( const QString & _constr ) +{ + return query(true,_constr); +} + +QValueList<KoDocumentEntry> KoDocumentEntry::query( bool _onlyDocEmb, const QString & _constr ) +{ + + QValueList<KoDocumentEntry> lst; + QString constr; + if ( !_constr.isEmpty() ) { + constr = "("; + constr += _constr; + constr += ") and "; + } + constr += " exist Library"; + + // Query the trader + KTrader::OfferList offers = KTrader::self()->query( "KOfficePart", constr ); + + KTrader::OfferList::ConstIterator it = offers.begin(); + unsigned int max = offers.count(); + for( unsigned int i = 0; i < max; i++, ++it ) + { + //kdDebug(30003) << " desktopEntryPath=" << (*it)->desktopEntryPath() + // << " library=" << (*it)->library() << endl; + + if ( (*it)->noDisplay() ) + continue; + // Maybe this could be done as a trader constraint too. + if ( (!_onlyDocEmb) || ((*it)->property("X-KDE-NOTKoDocumentEmbeddable").toString()!="1") ) + { + KoDocumentEntry d( *it ); + // Append converted offer + lst.append( d ); + // Next service + } + } + + if ( lst.count() > 1 && !_constr.isEmpty() ) + kdWarning(30003) << "KoDocumentEntry::query " << constr << " got " << max << " offers!" << endl; + + return lst; +} + + + + +/******************************************************************* + * + * KoFilterEntry + * + *******************************************************************/ + +KoFilterEntry::KoFilterEntry( KService::Ptr service ) + : m_service( service ) +{ + import = service->property( "X-KDE-Import" ).toStringList(); + export_ = service->property( "X-KDE-Export" ).toStringList(); + int w = service->property( "X-KDE-Weight" ).toInt(); + weight = w < 0 ? UINT_MAX : static_cast<unsigned int>( w ); + available = service->property( "X-KDE-Available" ).toString(); +} + +QValueList<KoFilterEntry::Ptr> KoFilterEntry::query( const QString & _constr ) +{ + kdDebug(30500) << "KoFilterEntry::query( " << _constr << " )" << endl; + QValueList<KoFilterEntry::Ptr> lst; + + KTrader::OfferList offers = KTrader::self()->query( "KOfficeFilter", _constr ); + + KTrader::OfferList::ConstIterator it = offers.begin(); + unsigned int max = offers.count(); + //kdDebug(30500) << "Query returned " << max << " offers" << endl; + for( unsigned int i = 0; i < max; i++ ) + { + //kdDebug(30500) << " desktopEntryPath=" << (*it)->desktopEntryPath() + // << " library=" << (*it)->library() << endl; + // Append converted offer + lst.append( new KoFilterEntry( *it ) ); + // Next service + it++; + } + + return lst; +} + +KoFilter* KoFilterEntry::createFilter( KoFilterChain* chain, QObject* parent, const char* name ) +{ + KLibFactory* factory = KLibLoader::self()->factory( QFile::encodeName( m_service->library() ) ); + + if ( !factory ) { + kdWarning(30003) << KLibLoader::self()->lastErrorMessage() << endl; + return 0; + } + + QObject* obj = factory->create( parent, name, "KoFilter" ); + if ( !obj || !obj->inherits( "KoFilter" ) ) + { + delete obj; + return 0; + } + + KoFilter* filter = static_cast<KoFilter*>( obj ); + filter->m_chain = chain; + return filter; +} diff --git a/lib/kofficecore/KoQueryTrader.h b/lib/kofficecore/KoQueryTrader.h new file mode 100644 index 00000000..55ab8dbe --- /dev/null +++ b/lib/kofficecore/KoQueryTrader.h @@ -0,0 +1,178 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@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 __ko_query_trader_h__ +#define __ko_query_trader_h__ + +#include <kservice.h> +#include <ksharedptr.h> +#include <qvaluelist.h> +#include <koffice_export.h> + +class QObject; +class QStringList; +class KoDocument; +class KoFilter; +class KoFilterChain; + +/** + * Represents an available KOffice component + * that supports the document interface. + */ +class KOFFICECORE_EXPORT KoDocumentEntry +{ + +public: + KoDocumentEntry() { m_service = 0L; } // for QValueList + KoDocumentEntry( KService::Ptr service ); + ~KoDocumentEntry() { } + + KService::Ptr service() const { return m_service; } + + /** + * @return TRUE if the service pointer is null + */ + bool isEmpty() const { return m_service == 0L; } + + /** + * @return name of the associated service + */ + QString name() const { return m_service->name(); } + + /** + * Mimetypes (and other service types) which this document can handle. + */ + QStringList mimeTypes() const { return m_service->serviceTypes(); } + + /** + * @return TRUE if the document can handle the requested mimetype. + */ + bool supportsMimeType( const QString & _mimetype ) const + { return mimeTypes().contains( _mimetype ); } + + /** + * Uses the factory of the component to create + * a document. If that is not possible, 0 is returned. + */ + KoDocument* createDoc( KoDocument* parent = 0, const char* name = 0 ) const; + + /** + * This function will query ksycoca to find all available components. + * The result will only contain parts, which are embeddable into a document + * + * @param _constr is a constraint expression as used by KTrader. + * You can use it to set additional restrictions on the available + * components. + */ + static QValueList<KoDocumentEntry> query( const QString & _constr = QString::null ); + + /** + * This function will query the system to find all available filters. + * + * @param _onlyDocEmb specifies if only KOffice Parts should be listed which are + * embeddable into other koDocuments, or all (if false) + * (eg.: it makes no sense to embed Kexi into KWord, + * but it makes sense to embed it into KoShell) + * @param _constr is a constraint expression as used by KDEDs trader interface. + * You can use it to set additional restrictions on the available + * components. + */ + // ### TODO: MERGE WITH ABOVE METHODE WHEN BIC+SIC CHANGES ARE ALLOWED + static QValueList<KoDocumentEntry> query( bool _onlyDocEmb,const QString& _constr); + /* this is how the signature should be looking after merging + static QValueList<KoDocumentEntry> query( bool _onlyDocEmb =true, const QString& _constr = QString::null ); + or better: use an enum for the first arg. + */ + + + /** + * This is a convenience function. + * + * @return a document entry for the KOffice component that supports + * the requested mimetype and fits the user best. + */ + static KoDocumentEntry queryByMimeType( const QString & mimetype ); + +private: + KService::Ptr m_service; +}; + +/** + * Represents an available filter. + */ +class KoFilterEntry : public KShared +{ + +public: + typedef KSharedPtr<KoFilterEntry> Ptr; + + KoFilterEntry() : weight( 0 ) { m_service = 0L; } // for QValueList + KoFilterEntry( KService::Ptr service ); + ~KoFilterEntry() { } + + KoFilter* createFilter( KoFilterChain* chain, QObject* parent = 0, const char* name = 0 ); + + /** + * The imported mimetype(s). + */ + QStringList import; + + /** + * The exported mimetype(s). + */ + QStringList export_; + + /** + * The "weight" of this filter path. Has to be > 0 to be valid. + */ + unsigned int weight; + + /** + * Do we have to check during runtime? + */ + QString available; + + /** + * @return TRUE if the filter can import the requested mimetype. + */ + bool imports( const QString& _mimetype ) const + { return ( import.contains( _mimetype ) ); } + + /** + * @return TRUE if the filter can export the requested mimetype. + */ + bool exports( const QString& _m ) const + { return ( export_.contains( _m ) ); } + + /** + * This function will query KDED to find all available filters. + * + * @param _constr is a constraint expression as used by KDEDs trader interface. + * You can use it to set additional restrictions on the available + * components. + */ + static QValueList<KoFilterEntry::Ptr> query( const QString& _constr = QString::null ); + + KService::Ptr service() const { return m_service; } + +private: + KService::Ptr m_service; +}; + +#endif diff --git a/lib/kofficecore/KoRect.cpp b/lib/kofficecore/KoRect.cpp new file mode 100644 index 00000000..b93a577d --- /dev/null +++ b/lib/kofficecore/KoRect.cpp @@ -0,0 +1,274 @@ +/* This file is part of the KDE project + Copyright (C) 2001 David Faure <faure@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 "KoRect.h" + +KoRect KoRect::normalize() const +{ + KoRect r; + if ( right() < left() ) { // swap bad x values + r.m_tl.setX( right() ); + r.m_br.setX( left() ); + } else { + r.m_tl.setX( left() ); + r.m_br.setX( right() ); + } + if ( bottom() < top() ) { // swap bad y values + r.m_tl.setY( bottom() ); + r.m_br.setY( top() ); + } else { + r.m_tl.setY( top() ); + r.m_br.setY( bottom() ); + } + return r; +} + +void KoRect::setTopLeft(const KoPoint &topleft) +{ + m_tl = topleft; +} + +void KoRect::setBottomRight(const KoPoint &bottomright) +{ + m_br = bottomright; +} + +void KoRect::setTopRight(const KoPoint &topright) +{ + m_br.rx() = topright.x(); + m_tl.ry() = topright.y(); +} + +void KoRect::setBottomLeft(const KoPoint &bottomleft) +{ + m_tl.rx() = bottomleft.x(); + m_br.ry() = bottomleft.y(); +} + + +KoPoint KoRect::center() const +{ + return KoPoint((left() + right()) / 2, (top() + bottom()) / 2); +} + +void KoRect::moveTopLeft(const KoPoint &topleft) +{ + m_br.rx() += topleft.x() - m_tl.x(); + m_br.ry() += topleft.y() - m_tl.y(); + m_tl = topleft; +} + +void KoRect::moveBottomRight(const KoPoint &bottomright) +{ + m_tl.rx() += bottomright.x() - m_br.x(); + m_tl.ry() += bottomright.y() - m_br.y(); + m_br = bottomright; +} + +void KoRect::moveTopRight(const KoPoint &topright) +{ + m_tl.rx() += topright.x() - m_br.x(); + m_br.ry() += topright.y() - m_tl.y(); + m_br.rx() = topright.x(); + m_tl.ry() = topright.y(); +} + +void KoRect::moveBottomLeft(const KoPoint &bottomleft) +{ + m_br.rx() += bottomleft.x() - m_tl.x(); + m_tl.ry() += bottomleft.y() - m_br.y(); + m_tl.rx() = bottomleft.x(); + m_br.ry() = bottomleft.y(); +} + +void KoRect::moveBy(const double &dx, const double &dy) +{ + m_tl.rx() += dx; + m_tl.ry() += dy; + m_br.rx() += dx; + m_br.ry() += dy; +} + +void KoRect::setRect(const double &x, const double &y, const double &width, const double &height) +{ + m_tl.setCoords( x, y ); + m_br.setCoords( x + width, y + height ); +} + +void KoRect::setRect(const KoRect &rect) +{ + m_tl = rect.m_tl; + m_br = rect.m_br; +} + +void KoRect::setCoords(const double &x1, const double &y1, const double &x2, const double &y2) +{ + m_tl.setCoords( x1, y1 ); + m_br.setCoords( x2, y2 ); +} + +void KoRect::setSize(const KoSize &size) +{ + setWidth(size.width()); + setHeight(size.height()); +} + +KoSize KoRect::size() const +{ + return KoSize(width(), height()); +} + +KoRect &KoRect::operator|=(const KoRect &rhs) { + + if(rhs.isEmpty()) + return *this; + if(isEmpty()) + { + *this = rhs; + return *this; + } + if(m_tl.x() > rhs.left()) + m_tl.setX(rhs.left()); + if(m_tl.y() > rhs.top()) + m_tl.setY(rhs.top()); + if(m_br.x() < rhs.right()) + m_br.setX(rhs.right()); + if(m_br.y() < rhs.bottom()) + m_br.setY(rhs.bottom()); + return *this; +} + +KoRect &KoRect::operator&=(const KoRect &rhs) { + + if(m_tl.x() < rhs.left()) + m_tl.setX(rhs.left()); + if(m_tl.y() < rhs.top()) + m_tl.setY(rhs.top()); + if(m_br.x() > rhs.right()) + m_br.setX(rhs.right()); + if(m_br.y() > rhs.bottom()) + m_br.setY(rhs.bottom()); + return *this; +} + +bool KoRect::contains(const KoPoint &p) const { + return (p.x() >= m_tl.x() && p.x() <= m_br.x() && p.y() >= m_tl.y() && p.y() <= m_br.y()); +} + +bool KoRect::contains(const double &x, const double &y) const { + return (x >= m_tl.x() && x <= m_br.x() && y >= m_tl.y() && y <= m_br.y()); +} + +bool KoRect::contains(const KoRect &r) const { + return (r.left() >= m_tl.x() && r.right() <= m_br.x() && r.top() >= m_tl.y() && r.bottom() <= m_br.y()); +} + + +KoRect KoRect::unite(const KoRect &r) const { + return *this | r; +} + +KoRect KoRect::intersect(const KoRect &r) const { + return *this & r; +} + +bool KoRect::intersects(const KoRect &r) const { + return ( QMAX(m_tl.x(), r.left()) <= QMIN(m_br.x(), r.right()) && + QMAX(m_tl.y(), r.top()) <= QMIN(m_br.y(), r.bottom()) ); +} + +KoRect operator|(const KoRect &lhs, const KoRect &rhs) { + + if(lhs.isEmpty()) + return rhs; + if(rhs.isEmpty()) + return lhs; + KoRect tmp; + tmp.setCoords( (lhs.left() < rhs.left() ? lhs.left() : rhs.left()), + (lhs.top() < rhs.top() ? lhs.top() : rhs.top()), + (lhs.right() > rhs.right() ? lhs.right() : rhs.right()), + (lhs.bottom() > rhs.bottom() ? lhs.bottom() : rhs.bottom()) ); + return tmp; +} + +KoRect operator&(const KoRect &lhs, const KoRect &rhs) { + + KoRect tmp; + tmp.setCoords( (lhs.left() > rhs.left() ? lhs.left() : rhs.left()), + (lhs.top() > rhs.top() ? lhs.top() : rhs.top()), + (lhs.right() < rhs.right() ? lhs.right() : rhs.right()), + (lhs.bottom() < rhs.bottom() ? lhs.bottom() : rhs.bottom()) ); + return tmp; +} + +bool operator==(const KoRect &lhs, const KoRect &rhs) { + return ( lhs.topLeft()==rhs.topLeft() && + lhs.bottomRight()==rhs.bottomRight() ); +} + +bool operator!=(const KoRect &lhs, const KoRect &rhs) { + return ( lhs.topLeft()!=rhs.topLeft() || + lhs.bottomRight()!=rhs.bottomRight() ); +} + +KoRect KoRect::transform(const QWMatrix &m) const +{ + KoRect result; + if(m.m12() == 0.0F && m.m21() == 0.0F) + { + result = KoRect(topLeft().transform(m), bottomRight().transform(m)); + } + else + { + int i; + KoPoint p[4] = { KoPoint(m_tl.x(), m_tl.y()), KoPoint(m_tl.x(), m_br.x()), + KoPoint(m_br.x(), m_br.x()), KoPoint(m_br.x(), m_tl.y()) }; + for(i = 0; i < 4; i++) + p[i] = p[i].transform(m); + + result.setLeft(p[0].x()); + result.setTop(p[0].y()); + result.setRight(p[0].x()); + result.setBottom(p[0].y()); + + for(int i = 1; i < 4; i++) + { + result.setLeft(QMIN(p[i].x(), result.left())); + result.setTop(QMIN(p[i].y(), result.top())); + result.setRight(QMAX(p[i].x(), result.right())); + result.setBottom(QMAX(p[i].y(), result.bottom())); + } + } + return result; +} + +KoRect KoRect::translate(double dx, double dy) const +{ + return KoRect(left() + dx, top() + dy, width(), height()); +} + +QRect KoRect::toQRect() const +{ + return QRect( qRound( left() ), qRound( top() ), qRound( width() ), qRound( height() ) ); +} + +//static +KoRect KoRect::fromQRect( const QRect &rect ) +{ + return KoRect( rect.left(), rect.top(), rect.width(), rect.height() ); +} diff --git a/lib/kofficecore/KoRect.h b/lib/kofficecore/KoRect.h new file mode 100644 index 00000000..dfc63946 --- /dev/null +++ b/lib/kofficecore/KoRect.h @@ -0,0 +1,148 @@ +/* This file is part of the KDE project + Copyright (C) 2001 David Faure <faure@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 koRect_h +#define koRect_h + +#include "KoPoint.h" +#include "KoSize.h" +#include <qrect.h> +#include "koffice_export.h" + +/** + * A rect whose coordinates are floating-point values ( "double"s ). + * The API isn't documented, it's a perfect mirror of QRect. + */ +class KOFFICEUI_EXPORT KoRect { + +public: + KoRect() + : m_tl(), m_br() {} + KoRect(const KoPoint &topleft, const KoPoint &bottomright) + : m_tl(topleft), m_br(bottomright) {} + KoRect(const KoPoint &topleft, const KoSize &size) + {m_tl = topleft; setSize(size);} + KoRect(const double &left, const double &top, const double &width, const double &height) + : m_tl(left,top), m_br(left+width,top+height) {} + ~KoRect() {} + + bool isNull() const { return m_tl == m_br; } + // Like QRect, a null KoRect is empty. + bool isEmpty() const { return m_tl.x() > m_br.x() || m_tl.y() > m_br.y() || isNull(); } + // Unlike QRect, a null KoRect is valid (0-sized). + bool isValid() const { return m_tl.x() <= m_br.x() && m_tl.y() <= m_br.y(); } + KoRect normalize() const; + + double left() const { return m_tl.x(); } + double top() const { return m_tl.y(); } + double right() const { return m_br.x(); } + double bottom() const { return m_br.y(); } + + double& rLeft() { return m_tl.rx(); } + double& rTop() { return m_tl.ry(); } + double& rRight() { return m_br.rx(); } + double& rBottom() { return m_br.ry(); } + + double x() const { return left(); } + double y() const { return top(); } + + void setLeft(const double &left) { m_tl.setX(left); } + void setTop(const double &top) { m_tl.setY(top); } + void setRight(const double &right) { m_br.setX(right); } + void setBottom(const double &bottom) { m_br.setY(bottom); } + + void setX(const double &x) { m_tl.setX(x); } //same as setLeft() + void setY(const double &y) { m_tl.setY(y); } //same as setTop() + + KoPoint topLeft() const { return m_tl; } + KoPoint bottomRight() const { return m_br; } + KoPoint topRight() const { return KoPoint(m_br.x(), m_tl.y()); } + KoPoint bottomLeft() const { return KoPoint(m_tl.x(), m_br.y()); } + KoPoint center() const; + + void setTopLeft(const KoPoint &topleft); + void setBottomRight(const KoPoint &bottomright); + void setTopRight(const KoPoint &topright); + void setBottomLeft(const KoPoint &bottomleft); + + void moveTopLeft(const KoPoint &topleft); + void moveBottomRight(const KoPoint &bottomright); + void moveTopRight(const KoPoint &topright); + void moveBottomLeft(const KoPoint &bottomleft); + //void moveCenter(const KoPoint ¢er); + void moveBy(const double &dx, const double &dy); + + void setRect(const double &x, const double &y, const double &width, const double &height); + void setRect(const KoRect &rect); + void setCoords(const double &x1, const double &y1, const double &x2, const double &y2); + + KoSize size() const; + double width() const { return m_br.x()-m_tl.x(); } + double height() const { return m_br.y()-m_tl.y(); } + void setWidth(const double &width) { m_br.setX(m_tl.x()+width); } + void setHeight(const double &height) { m_br.setY(m_tl.y()+height); } + void setSize(const KoSize &size); + + KoRect &operator|=(const KoRect &rhs); + KoRect &operator&=(const KoRect &rhs); + /** + * Returns if the point is contained in the rect. + * @param p the point to test + * Will return true if the point is contained in the 2d area this rect represents, this means + * that it will return true on everthing from the topleft() to the bottomright(); + * Note that for KoRect(0, 0, 100, 100) the KoPoint(0, 0) as well as KoPoint(100, 100) are + * mathmatically contained in the rect, this in contrary to pixel based rectangles. + */ + bool contains(const KoPoint &p) const; + /// Helper function for the above function. + bool contains(const double &x, const double &y) const; + /// Helper function for the above function. + bool contains(const KoRect &r) const; + KoRect unite(const KoRect &r) const; + KoRect intersect(const KoRect &r) const; + bool intersects(const KoRect &r) const; + + KoRect transform(const QWMatrix &m) const; + KoRect translate(double dx, double dy) const; + + QRect toQRect() const; + static KoRect fromQRect( const QRect &rect ); + +private: + KoPoint m_tl, m_br; +}; + +KoRect operator|(const KoRect &lhs, const KoRect &rhs); +KoRect operator&(const KoRect &lhs, const KoRect &rhs); +KOFFICEUI_EXPORT bool operator==(const KoRect &lhs, const KoRect &rhs); +KOFFICEUI_EXPORT bool operator!=(const KoRect &lhs, const KoRect &rhs); + + +/** Show the position and size of a rectangle (use within kdDebug) */ +#define DEBUGRECT(rc) (rc).x() << "," << (rc).y() << " " << (rc).width() << "x" << (rc).height() + +//inline kdbgstream operator<<( kdbgstream str, const KoRect & r ) { str << "[" << r.left() << ", " << r.top() << " - " << r.right() << ", " << r.bottom() << "]"; return str; } +inline kdbgstream operator<<( kdbgstream str, const KoRect & r ) { str << "[" << r.left() << "," << r.top() << " " << r.width() << "x" << r.height() << "]"; return str; } +inline kndbgstream operator<<( kndbgstream str, const KoRect & ) { return str; } + +/** Show the rectangles that form a region (use as a standalone statement) */ +#define DEBUGREGION(reg) { QMemArray<QRect>rs=reg.rects(); for (int i=0;i<rs.size();++i) \ + kdDebug()<<" "<<DEBUGRECT(rs[i] )<<endl; } +// You can now use kdDebug() << theregion << endl; (kdebug.h) + +#endif diff --git a/lib/kofficecore/KoSetPropCommand.h b/lib/kofficecore/KoSetPropCommand.h new file mode 100644 index 00000000..53ee73bf --- /dev/null +++ b/lib/kofficecore/KoSetPropCommand.h @@ -0,0 +1,86 @@ +/* This file is part of the KDE project + Copyright (C) 2000 Werner Trobin <trobin@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 KOGENCOMMAND_H +#define KOGENCOMMAND_H + +#include <kcommand.h> + +/** + * Generic command to set a property on an object. + * This variant is for simple types, where the setter method takes a value. + */ +template<class Property, class Object, void (Object::* Function) (Property)> class KoSetBasicPropCommand : public KNamedCommand { + +public: + KoSetBasicPropCommand(Object *object, const QString &name) : KNamedCommand(name), m_object(object) {} + KoSetBasicPropCommand(Object *object, const QString &name, + const Property &oldProperty, const Property &newProperty) : KNamedCommand(name), + m_object(object), m_oldProperty(oldProperty), m_newProperty(newProperty) {} + virtual ~KoSetBasicPropCommand() {} + + virtual void execute() { if(m_object) (m_object->*Function)(m_newProperty); } + virtual void unexecute() { if(m_object) (m_object->*Function)(m_oldProperty); } + + void setOldProperty(const Property &oldProperty) { m_oldProperty=oldProperty; } + const Property &oldProperty() const { return m_oldProperty; } + void setNewProperty(const Property &newProperty) { m_newProperty=newProperty; } + const Property &newProperty() const { return m_newProperty; } + +private: + // Don't copy or assign this stuff + KoSetBasicPropCommand(const KoSetBasicPropCommand<Property, Object, Function> &rhs); + KoSetBasicPropCommand &operator=(const KoSetBasicPropCommand<Property, Object, Function> &rhs); + + Object *m_object; + Property m_oldProperty, m_newProperty; +}; + +/** + * Generic command to set a property on an object. + * This variant is for non-trivial types, where the setter method takes a const reference. + */ +template<class Property, class Object, void (Object::* Function) (const Property &)> class KoSetPropCommand : public KNamedCommand { + +public: + KoSetPropCommand(Object *object, const QString &name) : KNamedCommand(name), m_object(object) {} + KoSetPropCommand(Object *object, const QString &name, + const Property &oldProperty, const Property &newProperty) : KNamedCommand(name), + m_object(object), m_oldProperty(oldProperty), m_newProperty(newProperty) {} + virtual ~KoSetPropCommand() {} + + virtual void execute() { if(m_object) (m_object->*Function)(m_newProperty); } + virtual void unexecute() { if(m_object) (m_object->*Function)(m_oldProperty); } + + void setOldProperty(const Property &oldProperty) { m_oldProperty=oldProperty; } + const Property &oldProperty() const { return m_oldProperty; } + void setNewProperty(const Property &newProperty) { m_newProperty=newProperty; } + const Property &newProperty() const { return m_newProperty; } + +private: + // Don't copy or assign this stuff + KoSetPropCommand(const KoSetPropCommand<Property, Object, Function> &rhs); + KoSetPropCommand &operator=(const KoSetPropCommand<Property, Object, Function> &rhs); + + Object *m_object; + Property m_oldProperty, m_newProperty; +}; + +#endif /* KOGENCOMMAND_H */ + diff --git a/lib/kofficecore/KoSize.h b/lib/kofficecore/KoSize.h new file mode 100644 index 00000000..3cdc30bc --- /dev/null +++ b/lib/kofficecore/KoSize.h @@ -0,0 +1,230 @@ +/* This file is part of the KDE project + Copyright (C) 2001 Lukas Tinkl <lukas@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. + + This file borrows from the QSize class; + Copyright (C) 1992-2000 Trolltech AS. All rights reserved. +*/ + +#ifndef koSize_h +#define koSize_h + +#include <qsize.h> +#include <qtl.h> + +/** + * A size whose coordinates are floating-point values ( "double"s ). + * The API isn't documented, it's a perfect mirror of QSize. + */ +class KoSize +{ +public: + KoSize(); + KoSize( double w, double h ); + + bool isNull() const; + bool isEmpty() const; + bool isValid() const; + + double width() const; + double height() const; + void setWidth( double w ); + void setHeight( double h ); + + KoSize expandedTo( const KoSize & ) const; + KoSize boundedTo( const KoSize & ) const; + + double &rwidth(); + double &rheight(); + + KoSize &operator+=( const KoSize & ); + KoSize &operator-=( const KoSize & ); + KoSize &operator*=( int c ); + KoSize &operator*=( double c ); + KoSize &operator/=( int c ); + KoSize &operator/=( double c ); + + friend inline bool operator==( const KoSize &, const KoSize & ); + friend inline bool operator!=( const KoSize &, const KoSize & ); + friend inline const KoSize operator+( const KoSize &, const KoSize & ); + friend inline const KoSize operator-( const KoSize &, const KoSize & ); + friend inline const KoSize operator*( const KoSize &, int ); + friend inline const KoSize operator*( int, const KoSize & ); + friend inline const KoSize operator*( const KoSize &, double ); + friend inline const KoSize operator*( double, const KoSize & ); + friend inline const KoSize operator/( const KoSize &, int ); + friend inline const KoSize operator/( const KoSize &, double ); + + inline QSize toQSize() const; + static KoSize fromQSize( const QSize &size ) + { + return KoSize(size.width(), size.height()); + } + + void transpose() + { + qSwap(wd, ht); + } + +private: + static void warningDivByZero() + { +#if defined(QT_CHECK_MATH) + qWarning( "KoSize: Division by zero error" ); +#endif + } + + double wd; + double ht; +}; + + +/***************************************************************************** + KoSize inline functions + *****************************************************************************/ + +inline KoSize::KoSize() +{ wd = ht = -1.0; } + +inline KoSize::KoSize( double w, double h ) +{ wd=w; ht=h; } + +inline bool KoSize::isNull() const +{ return wd==0.0 && ht==0.0; } + +inline bool KoSize::isEmpty() const +{ return wd<=0.0 || ht<=0.0; } + +inline bool KoSize::isValid() const +{ return wd>=0.0 && ht>=0.0; } + +inline double KoSize::width() const +{ return wd; } + +inline double KoSize::height() const +{ return ht; } + +inline void KoSize::setWidth( double w ) +{ wd=w; } + +inline void KoSize::setHeight( double h ) +{ ht=h; } + +inline double &KoSize::rwidth() +{ return wd; } + +inline double &KoSize::rheight() +{ return ht; } + +inline KoSize &KoSize::operator+=( const KoSize &s ) +{ wd+=s.wd; ht+=s.ht; return *this; } + +inline KoSize &KoSize::operator-=( const KoSize &s ) +{ wd-=s.wd; ht-=s.ht; return *this; } + +inline KoSize &KoSize::operator*=( int c ) +{ wd*=c; ht*=c; return *this; } + +inline KoSize &KoSize::operator*=( double c ) +{ wd=wd*c; ht=ht*c; return *this; } + +inline bool operator==( const KoSize &s1, const KoSize &s2 ) +{ return s1.wd == s2.wd && s1.ht == s2.ht; } + +inline bool operator!=( const KoSize &s1, const KoSize &s2 ) +{ return s1.wd != s2.wd || s1.ht != s2.ht; } + +inline const KoSize operator+( const KoSize & s1, const KoSize & s2 ) +{ return KoSize(s1.wd+s2.wd, s1.ht+s2.ht); } + +inline const KoSize operator-( const KoSize &s1, const KoSize &s2 ) +{ return KoSize(s1.wd-s2.wd, s1.ht-s2.ht); } + +inline const KoSize operator*( const KoSize &s, int c ) +{ return KoSize(s.wd*c, s.ht*c); } + +inline const KoSize operator*( int c, const KoSize &s ) +{ return KoSize(s.wd*c, s.ht*c); } + +inline const KoSize operator*( const KoSize &s, double c ) +{ return KoSize(s.wd*c, s.ht*c); } + +inline const KoSize operator*( double c, const KoSize &s ) +{ return KoSize(s.wd*c, s.ht*c); } + +inline KoSize &KoSize::operator/=( int c ) +{ +#if defined(QT_CHECK_MATH) + if ( c == 0 ) + warningDivByZero(); +#endif + wd/=c; ht/=c; + return *this; +} + +inline KoSize &KoSize::operator/=( double c ) +{ +#if defined(QT_CHECK_MATH) + if ( c == 0.0 ) + warningDivByZero(); +#endif + wd=wd/c; ht=ht/c; + return *this; +} + +inline const KoSize operator/( const KoSize &s, int c ) +{ +#if defined(QT_CHECK_MATH) + if ( c == 0 ) + KoSize::warningDivByZero(); +#endif + return KoSize(s.wd/c, s.ht/c); +} + +inline const KoSize operator/( const KoSize &s, double c ) +{ +#if defined(QT_CHECK_MATH) + if ( c == 0.0 ) + KoSize::warningDivByZero(); +#endif + return KoSize(s.wd/c, s.ht/c); +} + +inline KoSize KoSize::expandedTo( const KoSize & otherSize ) const +{ + return KoSize( QMAX(wd,otherSize.wd), QMAX(ht,otherSize.ht) ); +} + +inline KoSize KoSize::boundedTo( const KoSize & otherSize ) const +{ + return KoSize( QMIN(wd,otherSize.wd), QMIN(ht,otherSize.ht) ); +} + +inline QSize KoSize::toQSize() const +{ + return QSize(qRound(wd), qRound(ht)); +} + +/****************************** + kdDebug support +*******************************/ +#include <kdebug.h> + +inline kdbgstream operator<<( kdbgstream str, const KoSize & sz ) { str << "[" << sz.width() << "x" << sz.height() << "]"; return str; } +inline kndbgstream operator<<( kndbgstream str, const KoSize & ) { return str; } + +#endif diff --git a/lib/kofficecore/KoSpeaker.cpp b/lib/kofficecore/KoSpeaker.cpp new file mode 100644 index 00000000..db975623 --- /dev/null +++ b/lib/kofficecore/KoSpeaker.cpp @@ -0,0 +1,602 @@ +/* +* This file is part of the KDE/KOffice project. +* Copyright (C) 2005, Gary Cramblitt <garycramblitt@comcast.net> +* +* @author Gary Cramblitt <garycramblitt@comcast.net> +* @since KOffice 1.5 +* +* 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. +*/ + +// Qt includes. +#include <qtimer.h> +#include <qcursor.h> +#include <qtooltip.h> +#include <qwhatsthis.h> +#include <qmenubar.h> +#include <qlabel.h> +#include <qbutton.h> +#include <qcombobox.h> +#include <qtabbar.h> +#include <qgroupbox.h> +#include <qlineedit.h> +#include <qtextedit.h> +#include <qlistview.h> +#include <qlistbox.h> +#include <qiconview.h> +#include <qtable.h> +#include <qgridview.h> +#include <qregexp.h> +#include <qstylesheet.h> + +// KDE includes. +#include <kapplication.h> +#include <klocale.h> +#include <kglobal.h> +#include <dcopclient.h> +#include <kconfig.h> +#include <ktrader.h> +#include <kdebug.h> + +// KoSpeaker includes. +#include "KoSpeaker.h" +#include "KoSpeaker.moc" + +// ------------------ KoSpeakerPrivate ------------------------ + +class KoSpeakerPrivate +{ +public: + KoSpeakerPrivate() : + m_versionChecked(false), + m_enabled(false), + m_speakFlags(0), + m_timeout(600), + m_timer(0), + m_prevPointerWidget(0), + m_prevPointerId(-1), + m_prevFocusWidget(0), + m_prevFocusId(-1), + m_prevWidget(0), + m_prevId(-1), + m_cancelSpeakWidget(false) + {} + + // List of text jobs. + QValueList<uint> m_jobNums; + // Whether the version of KTTSD has been requested from the daemon. + bool m_versionChecked; + // KTTSD version string. + QString m_kttsdVersion; + // Language code of last spoken text. + QString m_langCode; + // Word used before speaking an accelerator letter. + QString m_acceleratorPrefix; + // Whether TTS service is available or not. + bool m_enabled; + // TTS options. + uint m_speakFlags; + // Timer which implements the polling interval. + int m_timeout; + QTimer* m_timer; + // Widget and part of widget for 1) last widget under mouse pointer, 2) last widget with focus, and + // last widget spoken. + QWidget* m_prevPointerWidget; + int m_prevPointerId; + QWidget* m_prevFocusWidget; + int m_prevFocusId; + QWidget* m_prevWidget; + int m_prevId; + // True when cancelSpeakWidget has been called in response to customSpeakWidget signal. + bool m_cancelSpeakWidget; +}; + +// ------------------ KoSpeaker ------------------------------- + +KoSpeaker* KoSpeaker::KSpkr = 0L; + +KoSpeaker::KoSpeaker() +{ + Q_ASSERT(!KSpkr); + KSpkr = this; + d = new KoSpeakerPrivate(); + readConfig(KGlobal::config()); +} + +KoSpeaker::~KoSpeaker() +{ + if (d->m_jobNums.count() > 0) { + for (int i = d->m_jobNums.count() - 1; i >= 0; i--) + removeText(d->m_jobNums[i]); + d->m_jobNums.clear(); + } + delete d; + KSpkr = 0; +} + +bool KoSpeaker::isEnabled() const { return d->m_enabled; } + +void KoSpeaker::probe() +{ + d->m_timer->stop(); + QWidget* w; + QPoint pos; + bool spoke = false; + if ( d->m_speakFlags & SpeakFocusWidget ) { + w = kapp->focusWidget(); + if (w) { + spoke = maybeSayWidget(w); + if (!spoke) + emit customSpeakWidget(w, pos, d->m_speakFlags); + } + } + if ( !spoke && d->m_speakFlags & SpeakPointerWidget ) { + pos = QCursor::pos(); + w = kapp->widgetAt(pos, true); + if (w) { + if (!maybeSayWidget(w, pos)) + emit customSpeakWidget(w, pos, d->m_speakFlags); + } + } + d->m_timer->start(d->m_timeout); +} + +void KoSpeaker::queueSpeech(const QString& msg, const QString& langCode /*= QString()*/, bool first /*= true*/) +{ + if (!startKttsd()) return; + int jobCount = d->m_jobNums.count(); + if (first && jobCount > 0) { + for (int i = jobCount - 1; i >= 0; i--) + removeText(d->m_jobNums[i]); + d->m_jobNums.clear(); + jobCount = 0; + } + QString s = msg.stripWhiteSpace(); + if (s.isEmpty()) return; + // kdDebug() << "KoSpeaker::queueSpeech: s = [" << s << "]" << endl; + // If no language code given, assume desktop setting. + QString languageCode = langCode; + if (langCode.isEmpty()) + languageCode = KGlobal::locale()->language(); + // kdDebug() << "KoSpeaker::queueSpeech:languageCode = " << languageCode << endl; + // If KTTSD version is 0.3.5 or later, we can use the appendText method to submit a + // single, multi-part text job. Otherwise, must submit separate text jobs. + // If language code changes, then must also start a new text job so that it will + // be spoken in correct talker. + if (getKttsdVersion().isEmpty()) + d->m_jobNums.append(setText(s, languageCode)); + else { + if ((jobCount == 0) || (languageCode != d->m_langCode)) + d->m_jobNums.append(setText(s, languageCode)); + else + appendText(s, d->m_jobNums[jobCount-1]); + } + d->m_langCode = languageCode; +} + +void KoSpeaker::startSpeech() +{ + for (uint i = 0; i < d->m_jobNums.count(); i++) + startText(d->m_jobNums[i]); +} + +void KoSpeaker::readConfig(KConfig* config) +{ + delete d->m_timer; + d->m_timer = 0; + config->setGroup("TTS"); + d->m_speakFlags = 0; + if (config->readBoolEntry("SpeakPointerWidget", false)) d->m_speakFlags |= SpeakPointerWidget; + if (config->readBoolEntry("SpeakFocusWidget", false)) d->m_speakFlags |= SpeakFocusWidget; + if (config->readBoolEntry("SpeakTooltips", true)) d->m_speakFlags |= SpeakTooltip; + if (config->readBoolEntry("SpeakWhatsThis", false)) d->m_speakFlags |= SpeakWhatsThis; + if (config->readBoolEntry("SpeakDisabled", true)) d->m_speakFlags |= SpeakDisabled; + if (config->readBoolEntry("SpeakAccelerators", true)) d->m_speakFlags |= SpeakAccelerator; + d->m_timeout = config->readNumEntry("PollingInterval", 600); + d->m_acceleratorPrefix = config->readEntry("AcceleratorPrefixWord", i18n("Accelerator")); + if (d->m_speakFlags & (SpeakPointerWidget | SpeakFocusWidget)) { + if (startKttsd()) { + d->m_timer = new QTimer( this ); + connect( d->m_timer, SIGNAL(timeout()), this, SLOT(probe()) ); + d->m_timer->start( d->m_timeout ); + } + } +} + +bool KoSpeaker::maybeSayWidget(QWidget* w, const QPoint& pos /*=QPoint()*/) +{ + if (!w) return false; + + int id = -1; + QString text; + + if (w->inherits("QViewportWidget")) { + w = w->parentWidget(); + if (!w) return false; + } + + // Handle widgets that have multiple parts. + + if ( w->inherits("QMenuBar") ) { + QMenuBar* menuBar = dynamic_cast<QMenuBar *>(w); + if (pos == QPoint()) { + for (uint i = 0; i < menuBar->count(); ++i) + if (menuBar->isItemActive(menuBar->idAt(i))) { + id = menuBar->idAt(i); + break; + } + } + // TODO: This doesn't work. Need way to figure out the QMenuItem underneath mouse pointer. + // id = menuBarItemAt(menuBar, pos); + if ( id != -1 ) + text = menuBar->text(id); + } + else + if (w->inherits("QPopupMenu")) { + QPopupMenu* popupMenu = dynamic_cast<QPopupMenu *>(w); + if (pos == QPoint()) { + for (uint i = 0; i < popupMenu->count(); ++i) + if (popupMenu->isItemActive(popupMenu->idAt(i))) { + id = popupMenu->idAt(i); + break; + } + } else + id = popupMenu->idAt(popupMenu->mapFromGlobal(pos)); + if ( id != -1 ) + text = popupMenu->text(id); + } + else + if (w->inherits("QTabBar")) { + QTabBar* tabBar = dynamic_cast<QTabBar *>(w); + QTab* tab = 0; + if (pos == QPoint()) + tab = tabBar->tabAt(tabBar->currentTab()); + else + tab = tabBar->selectTab(tabBar->mapFromGlobal(pos)); + if (tab) { + id = tab->identifier(); + text = tab->text(); + } + } + else + if (w->inherits("QListView")) { + QListView* lv = dynamic_cast<QListView *>(w); + QListViewItem* item = 0; + if (pos == QPoint()) + item = lv->currentItem(); + else + item = lv->itemAt(lv->viewport()->mapFromGlobal(pos)); + if (item) { + id = lv->itemPos(item); + text = item->text(0); + for (int col = 1; col < lv->columns(); ++col) + if (!item->text(col).isEmpty()) text += ". " + item->text(col); + } + } + else + if (w->inherits("QListBox")) { + QListBox* lb = dynamic_cast<QListBox *>(w); + // qt docs say coordinates are in "on-screen" coordinates. What does that mean? + QListBoxItem* item = 0; + if (pos == QPoint()) + item = lb->item(lb->currentItem()); + else + item = lb->itemAt(lb->mapFromGlobal(pos)); + if (item) { + id = lb->index(item); + text = item->text(); + } + } + else + if (w->inherits("QIconView")) { + QIconView* iv = dynamic_cast<QIconView *>(w); + QIconViewItem* item = 0; + if (pos == QPoint()) + item = iv->currentItem(); + else + item = iv->findItem(iv->viewportToContents(iv->viewport()->mapFromGlobal(pos))); + if (item) { + id = item->index(); + text = item->text(); + } + } + else + if (w->inherits("QTable")) { + QTable* tbl = dynamic_cast<QTable *>(w); + int row = -1; + int col = -1; + if (pos == QPoint()) { + row = tbl->currentRow(); + col = tbl->currentColumn(); + } else { + QPoint p = tbl->viewportToContents(tbl->viewport()->mapFromGlobal(pos)); + row = tbl->rowAt(p.y()); + col = tbl->columnAt(p.x()); + } + if (row >= 0 && col >= 0) { + id = (row * tbl->numCols()) + col; + text = tbl->text(row, col); + } + } + else + if (w->inherits("QGridView")) { + QGridView* gv = dynamic_cast<QGridView *>(w); + // TODO: QGridView does not have a "current" row or column. Don't think they can even get focus? + int row = -1; + int col = -1; + if (pos != QPoint()) { + QPoint p = gv->viewportToContents(gv->viewport()->mapFromGlobal(pos)); + row = gv->rowAt(p.y()); + col = gv->columnAt(p.x()); + } + if (row >= 0 && col >= 0) + id = (row * gv->numCols()) + col; + } + + if (pos == QPoint()) { + if ( w == d->m_prevFocusWidget && id == d->m_prevFocusId) return false; + d->m_prevFocusWidget = w; + d->m_prevFocusId = id; + } else { + if ( w == d->m_prevPointerWidget && id == d->m_prevPointerId) return false; + d->m_prevPointerWidget = w; + d->m_prevPointerId = id; + } + if (w == d->m_prevWidget && id == d->m_prevId) return false; + d->m_prevWidget = w; + d->m_prevId = id; + + // kdDebug() << " w = " << w << endl; + + d->m_cancelSpeakWidget = false; + emit customSpeakNewWidget(w, pos, d->m_speakFlags); + if (d->m_cancelSpeakWidget) return true; + + // Handle simple, single-part widgets. + if ( w->inherits("QButton") ) + text = dynamic_cast<QButton *>(w)->text(); + else + if (w->inherits("QComboBox")) + text = dynamic_cast<QComboBox *>(w)->currentText(); + else + if (w->inherits("QLineEdit")) + text = dynamic_cast<QLineEdit *>(w)->text(); + else + if (w->inherits("QTextEdit")) + text = dynamic_cast<QTextEdit *>(w)->text(); + else + if (w->inherits("QLabel")) + text = dynamic_cast<QLabel *>(w)->text(); + else + if (w->inherits("QGroupBox")) { + // TODO: Should calculate this number from font size? + if (w->mapFromGlobal(pos).y() < 30) + text = dynamic_cast<QGroupBox *>(w)->title(); + } +// else +// if (w->inherits("QWhatsThat")) { +// text = dynamic_cast<QWhatsThat *>(w)->text(); +// } + + text = text.stripWhiteSpace(); + if (!text.isEmpty()) { + if (text.right(1) == ".") + text += " "; + else + text += ". "; + } + if (d->m_speakFlags & SpeakTooltip || text.isEmpty()) { + // kdDebug() << "pos = " << pos << endl; + // QPoint p = w->mapFromGlobal(pos); + // kdDebug() << "p = " << p << endl; + QString t = QToolTip::textFor(w, pos); + t = t.stripWhiteSpace(); + if (!t.isEmpty()) { + if (t.right(1) != ".") t += "."; + text += t + " "; + } + } + + if (d->m_speakFlags & SpeakWhatsThis || text.isEmpty()) { + QString t = QWhatsThis::textFor(w, pos); + t = t.stripWhiteSpace(); + if (!t.isEmpty()) { + if (t.right(1) != ".") t += "."; + text += t + " "; + } + } + + if (d->m_speakFlags & SpeakDisabled) { + if (!w->isEnabled()) + text += i18n("A grayed widget", "Disabled. "); + } + + return sayWidget(text); +} + +bool KoSpeaker::sayWidget(const QString& msg) +{ + QString s = msg; + if (d->m_speakFlags & SpeakAccelerator) { + int amp = s.find("&"); + if (amp >= 0) { + QString acc = s.mid(++amp,1); + acc = acc.stripWhiteSpace(); + if (!acc.isEmpty()) + s += ". " + d->m_acceleratorPrefix + " " + acc + "."; + } + } + s.remove("&"); + if (QStyleSheet::mightBeRichText(s)) { + // kdDebug() << "richtext" << endl; + s.replace(QRegExp("</?[pbius]>"), ""); + s.replace(QRegExp("</?h\\d>"), ""); + s.replace(QRegExp("<(br|hr)>"), " "); + s.replace(QRegExp( + "</?(qt|center|li|pre|div|span|em|strong|big|small|sub|sup|code|tt|font|nobr|ul|ol|dl|dt)>"), ""); + s.replace(QRegExp("</?(table|tr|th|td).*>"), ""); + s.replace(QRegExp("</?a\\s.+>"), ""); + // Replace <img source="small|frame_text"> with "small frame_text image. " + s.replace(QRegExp("<img\\s.*(?:source=|src=)\"([^|\"]+)[|]?([^|\"]*)\">"), "\\1 \\2 image. "); + } + if (s.isEmpty()) return false; + s.replace("Ctrl+", i18n("control plus ")); + s.replace("Alt+", i18n("alt plus ")); + s.replace("+", i18n(" plus ")); + sayScreenReaderOutput(s, ""); + return true; +} + +// This doesn't work. Anybody know how to find the menu item underneath mouse pointer +// in a QMenuBar? +// int KoSpeaker::menuBarItemAt(QMenuBar* m, const QPoint& p) +// { +// for (uint i = 0; i < m->count(); i++) { +// int id = m->idAt(i); +// QMenuItem* mi = m->findItem(id); +// QWidget* w = mi->widget(); +// if (w->rect().contains(w->mapFromGlobal(p))) return id; +// } +// return -1; +// } + +/*static*/ bool KoSpeaker::isKttsdInstalled() +{ + KTrader::OfferList offers = KTrader::self()->query("DCOP/Text-to-Speech", "Name == 'KTTSD'"); + return (offers.count() > 0); +} + +bool KoSpeaker::startKttsd() +{ + DCOPClient *client = kapp->dcopClient(); + // If KTTSD not running, start it. + if (!client->isApplicationRegistered("kttsd")) + { + QString error; + if (kapp->startServiceByDesktopName("kttsd", QStringList(), &error)) { + kdDebug() << "KoSpeaker::startKttsd: error starting KTTSD service: " << error << endl; + d->m_enabled = false; + } else + d->m_enabled = true; + } else + d->m_enabled = true; + return d->m_enabled; +} + +QString KoSpeaker::getKttsdVersion() +{ + // Determine which version of KTTSD is running. Note that earlier versions of KSpeech interface + // did not support version() method, so we must manually marshall this call ourselves. + if (d->m_enabled) { + if (!d->m_versionChecked) { + DCOPClient *client = kapp->dcopClient(); + QByteArray data; + QCString replyType; + QByteArray replyData; + if ( client->call("kttsd", "KSpeech", "version()", data, replyType, replyData, true) ) { + QDataStream arg(replyData, IO_ReadOnly); + arg >> d->m_kttsdVersion; + kdDebug() << "KoSpeaker::startKttsd: KTTSD version = " << d->m_kttsdVersion << endl; + } + d->m_versionChecked = true; + } + } + return d->m_kttsdVersion; +} + +void KoSpeaker::sayScreenReaderOutput(const QString &msg, const QString &talker) +{ + if (msg.isEmpty()) return; + DCOPClient *client = kapp->dcopClient(); + QByteArray data; + QCString replyType; + QByteArray replyData; + QDataStream arg(data, IO_WriteOnly); + arg << msg << talker; + if ( !client->call("kttsd", "KSpeech", "sayScreenReaderOutput(QString,QString)", + data, replyType, replyData, true) ) { + kdDebug() << "KoSpeaker::sayScreenReaderOutput: failed" << endl; + } +} + +uint KoSpeaker::setText(const QString &text, const QString &talker) +{ + if (text.isEmpty()) return 0; + DCOPClient *client = kapp->dcopClient(); + QByteArray data; + QCString replyType; + QByteArray replyData; + QDataStream arg(data, IO_WriteOnly); + arg << text << talker; + uint jobNum = 0; + if ( !client->call("kttsd", "KSpeech", "setText(QString,QString)", + data, replyType, replyData, true) ) { + kdDebug() << "KoSpeaker::sayText: failed" << endl; + } else { + QDataStream arg2(replyData, IO_ReadOnly); + arg2 >> jobNum; + } + return jobNum; +} + +int KoSpeaker::appendText(const QString &text, uint jobNum /*=0*/) +{ + if (text.isEmpty()) return 0; + DCOPClient *client = kapp->dcopClient(); + QByteArray data; + QCString replyType; + QByteArray replyData; + QDataStream arg(data, IO_WriteOnly); + arg << text << jobNum; + int partNum = 0; + if ( !client->call("kttsd", "KSpeech", "appendText(QString,uint)", + data, replyType, replyData, true) ) { + kdDebug() << "KoSpeaker::appendText: failed" << endl; + } else { + QDataStream arg2(replyData, IO_ReadOnly); + arg2 >> partNum; + } + return partNum; +} + +void KoSpeaker::startText(uint jobNum /*=0*/) +{ + DCOPClient *client = kapp->dcopClient(); + QByteArray data; + QCString replyType; + QByteArray replyData; + QDataStream arg(data, IO_WriteOnly); + arg << jobNum; + if ( !client->call("kttsd", "KSpeech", "startText(uint)", + data, replyType, replyData, true) ) { + kdDebug() << "KoSpeaker::startText: failed" << endl; + } +} + +void KoSpeaker::removeText(uint jobNum /*=0*/) +{ + DCOPClient *client = kapp->dcopClient(); + QByteArray data; + QCString replyType; + QByteArray replyData; + QDataStream arg(data, IO_WriteOnly); + arg << jobNum; + if ( !client->call("kttsd", "KSpeech", "removeText(uint)", + data, replyType, replyData, true) ) { + kdDebug() << "KoSpeaker::removeText: failed" << endl; + } +} + diff --git a/lib/kofficecore/KoSpeaker.h b/lib/kofficecore/KoSpeaker.h new file mode 100644 index 00000000..6f8b430f --- /dev/null +++ b/lib/kofficecore/KoSpeaker.h @@ -0,0 +1,204 @@ +/** @file +* This file is part of the KDE/KOffice project. +* Copyright (C) 2005, Gary Cramblitt <garycramblitt@comcast.net> +* +* @author Gary Cramblitt <garycramblitt@comcast.net> +* @since KOffice 1.5 +* +* 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 KOSPEAKER_H +#define KOSPEAKER_H + +// Qt includes. +#include <qobject.h> +#include <qstring.h> + +// KDE includes. +#include <ksharedptr.h> + +// KOffice includes. +#include <koffice_export.h> + +class QWidget; +class QPoint; +class KConfig; +class KoSpeakerPrivate; + +#define kospeaker KoSpeaker::koSpeaker() + +/** KoSpeaker is a singleton object that provides Text-to-Speech services for KOffice applications. + * When activated, it will speak the text of widgets under the mouse pointer and/or the + * the widget with focus. + * + * It also provides some methods for speaking text from documents. + * + * IMPORTANT: This class will be removed from KOffice when KOffice is converted to KDE4. + * It will be replaced with a proper screen reading capability using the AT-SPI. + * + * This is quite a hack and doesn't work reliably. The following are current problems: + * 1. Cannot speak menu items in a QMenuBar (top menu of app). + * 2. Doesn't understand every possible widget. + * + * This capability is @em not intended for completely blind users. Such users cannot use + * KDE 3.x anyway, since it lacks a screen reader. Instead, this capability is intended as + * an aid to users with other vision disabilities. + * + * KOffice applications can access this object using the kospeaker global. + */ +class KOFFICECORE_EXPORT KoSpeaker : public QObject, public KShared +{ + Q_OBJECT +public: + KoSpeaker(); + ~KoSpeaker(); + + /** Speech Options */ + enum SpeakFlags { + SpeakFocusWidget = 0x0001 /**< Speak widget with focus */, + SpeakPointerWidget = 0x0002 /**< Speak widget under mouse pointer */, + SpeakWhatsThis = 0x0004 /**< Speak Whats This if available */, + SpeakTooltip = 0x0008 /**< Speak tooltip if available */, + SpeakAccelerator = 0x0010 /**< Speak accelerator */, + SpeakDisabled = 0x0020 /**< Say 'disabled' if not enabled */ + }; + + /** + * Returns true if TTS services are available. If KTTSD daemon is not running, it is started. + * Will return false if: + * -- KTTSD daemon is not installed, or + * -- Was not able to start KTTSD daemon for some reason. + */ + bool isEnabled() const; + + /** + * Reads configuration options from @p config object and starts TTS if screen reader + * capability is requested. + * If KTTSD daemon is not installed, @ref isEnabled will return false. + * If screen reader is requested and KTTSD is installed, but not running, it will be started. + */ + void readConfig(KConfig* config); + + /** + * Given a widget @p w and its @p pos screen coordinates, tries to extract the text of the widget + * and speak it. If @p pos is not specified, and the widget has multiple parts (such as + * a QListView), uses the current part. + * Call @ref isEnabled to ensure TTS is available before calling this method. + */ + bool maybeSayWidget(QWidget* w, const QPoint& pos = QPoint()); + + /** + * Speak a @p msg that came from a widget, such as the widget's text label, tool tip, etc. + * Speaks using ScreenReaderOutput, which has highest priority, and therefore, should only be + * be used in very time-sensitive contexts and for short messages. + * Certain standard substitutions are performed on the message. For example, "Ctrl+" becomes + * "control plus". "Qt" markup is stripped. + * @returns true if anything is actually spoken. + * Call @ref isEnabled to ensure TTS is available before calling this method. + */ + bool sayWidget(const QString& msg); + + /** + * Cancels speaking of widget. Usually called by slots that receive @ref customSpeakNewWidget + * signal when they wish to speak the widget themselves. + */ + void cancelSpeakWidget(); + + /** + * Queue a @p msg as a speech text job. The text is encoded in the @p langCode language. + * Examples "en", "es", "en_US". If not specified, defaults to current desktop setting. + * If @p first is true and a job is already speaking, cancel it. + * If @p first is false, appends to the already queued job. + * If the KTTSD daemon is not already running, it is started. + */ + void queueSpeech(const QString& msg, const QString& langCode = QString(), bool first = true); + + /** + * Start speaking queued text job (if any). + */ + void startSpeech(); + + /** + * Returns whether the KTTSD deamon is installed in the system. If not, apps should disable + * or hide options/commands to speak. + */ + static bool isKttsdInstalled(); + + /** + * Returns the KoSpeaker object singleton. Apps should use "kospeaker" rather than this function + * directly. + */ + static KoSpeaker* koSpeaker() { return KSpkr; } + +signals: + /** + * This signal is emitted whenever a new widget has received focus or the mouse pointer + * has moved to a new widget. If a receiver wishes to handle speaking of the widget itself, + * it should call @ref cancelSpeakWidget() . + * @param w The widget. + * @param p Mouse pointer global coordinates, or in the case of a focus change (0,0). + * @param flags Speech options. @ref SpeakFlags. + * + * IMPORTANT: This signal is emitted from the @ref maybeSayWidget method. Slots who + * call maybeSayWidget should take care to avoid infinite recursion. + */ + void customSpeakNewWidget(QWidget* w, const QPoint& p, uint flags); + + /** + * This signal is emitted each polling interval when KoSpeaker did not speak the widget + * (either because it did not think the widget was a new one or because it did not + * understand the widget). If both mouse pointer and focus flags are set, it may + * emit twice per polling interval. + * @param w The widget. + * @param p Mouse pointer global coordinates, or in the case of a focus change (0,0). + * @param flags Speech options. @ref SpeakFlags. + * + * IMPORTANT: This signal is emitted frequently. Receivers should be coded efficiently. + */ + void customSpeakWidget(QWidget* w, const QPoint& p, uint flags); + +protected: + static KoSpeaker* KSpkr; + +private slots: + /** + * Tells the class to do it's stuff - ie. figure out + * which widget is under the mouse pointer or which has focus and speak it. + */ + void probe(); + +private: + // int menuBarItemAt(QMenuBar* m, const QPoint& p); + + // Start the KTTSD daemon if not already running. + bool startKttsd(); + // Return the KTTSD daemon version string. + QString getKttsdVersion(); + + // These methods correspond to dcop interface in kdelibs/interfaces/kspeech/kspeech.h. + // They use manual marshalling, instead of using kspeech_stub, because KOffice + // supports KDE 3.3 and above and kspeech.h didn't appear until 3.4. + void sayScreenReaderOutput(const QString &msg, const QString &talker); + uint setText(const QString &text, const QString &talker); + int appendText(const QString &text, uint jobNum=0); + void startText(uint jobNum=0); + void removeText(uint jobNum=0); + + KoSpeakerPrivate* d; +}; + +#endif // H_KOSPEAKER diff --git a/lib/kofficecore/KoStyleStack.cpp b/lib/kofficecore/KoStyleStack.cpp new file mode 100644 index 00000000..4b5b807b --- /dev/null +++ b/lib/kofficecore/KoStyleStack.cpp @@ -0,0 +1,296 @@ +/* This file is part of the KDE project + Copyright (c) 2003 Lukas Tinkl <lukas@kde.org> + Copyright (c) 2003 David Faure <faure@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 "KoStyleStack.h" +#include "KoUnit.h" +#include "KoDom.h" +#include "KoXmlNS.h" + +#include <kdebug.h> + +//#define DEBUG_STYLESTACK + +KoStyleStack::KoStyleStack() + : m_styleNSURI( KoXmlNS::style ), m_foNSURI( KoXmlNS::fo ) +{ + clear(); +} + +KoStyleStack::KoStyleStack( const char* styleNSURI, const char* foNSURI ) + : m_propertiesTagName( "properties" ), m_styleNSURI( styleNSURI ), m_foNSURI( foNSURI ) +{ + clear(); +} + +KoStyleStack::~KoStyleStack() +{ +} + +void KoStyleStack::clear() +{ + m_stack.clear(); +#ifdef DEBUG_STYLESTACK + kdDebug(30003) << "clear!" << endl; +#endif +} + +void KoStyleStack::save() +{ + m_marks.push( m_stack.count() ); +#ifdef DEBUG_STYLESTACK + kdDebug(30003) << "save (level " << m_marks.count() << ") -> index " << m_stack.count() << endl; +#endif +} + +void KoStyleStack::restore() +{ + Q_ASSERT( !m_marks.isEmpty() ); + int toIndex = m_marks.pop(); +#ifdef DEBUG_STYLESTACK + kdDebug(30003) << "restore (level " << m_marks.count()+1 << ") -> to index " << toIndex << endl; +#endif + Q_ASSERT( toIndex > -1 ); + Q_ASSERT( toIndex <= (int)m_stack.count() ); // If equal, nothing to remove. If greater, bug. + for ( int index = (int)m_stack.count() - 1; index >= toIndex; --index ) + m_stack.pop_back(); +} + +void KoStyleStack::pop() +{ + Q_ASSERT( !m_stack.isEmpty() ); + m_stack.pop_back(); +#ifdef DEBUG_STYLESTACK + kdDebug(30003) << "pop -> count=" << m_stack.count() << endl; +#endif +} + +void KoStyleStack::push( const QDomElement& style ) +{ + m_stack.append( style ); +#ifdef DEBUG_STYLESTACK + kdDebug(30003) << "pushed " << style.attributeNS( m_styleNSURI, "name", QString::null ) << " -> count=" << m_stack.count() << endl; +#endif +} + +bool KoStyleStack::hasAttribute( const QString& name, const QString& detail ) const +{ + QString fullName( name ); + if ( !detail.isEmpty() ) + { + fullName += '-'; + fullName += detail; + } + QValueList<QDomElement>::ConstIterator it = m_stack.end(); + while ( it != m_stack.begin() ) + { + --it; + QDomElement properties = (*it).namedItem( "style:"+m_propertiesTagName ).toElement(); + if ( properties.hasAttribute( name ) || + ( !detail.isEmpty() && properties.hasAttribute( fullName ) ) ) + return true; + } + return false; +} + +QString KoStyleStack::attribute( const QString& name, const QString& detail ) const +{ + QString fullName( name ); + if ( !detail.isEmpty() ) + { + fullName += '-'; + fullName += detail; + } + QValueList<QDomElement>::ConstIterator it = m_stack.end(); + while ( it != m_stack.begin() ) + { + --it; + QDomElement properties = (*it).namedItem( "style:"+m_propertiesTagName ).toElement(); + if ( properties.hasAttribute( name ) ) + return properties.attribute( name ); + if ( !detail.isEmpty() && properties.hasAttribute( fullName ) ) + return properties.attribute( fullName ); + } + return QString::null; +} + +QString KoStyleStack::attributeNS( const char* nsURI, const char* name, const char* detail ) const +{ + QString fullName( name ); + if ( detail ) + { + fullName += '-'; + fullName += detail; + } + QValueList<QDomElement>::ConstIterator it = m_stack.end(); + while ( it != m_stack.begin() ) + { + --it; + QDomElement properties = KoDom::namedItemNS( *it, m_styleNSURI, m_propertiesTagName ); + if ( properties.hasAttributeNS( nsURI, name ) ) + return properties.attributeNS( nsURI, name, QString::null ); + if ( detail && properties.hasAttributeNS( nsURI, fullName ) ) + return properties.attributeNS( nsURI, fullName, QString::null ); + } + return QString::null; +} + +bool KoStyleStack::hasAttributeNS( const char* nsURI, const char* name, const char* detail ) const +{ + QString fullName( name ); + if ( detail ) + { + fullName += '-'; + fullName += detail; + } + QValueList<QDomElement>::ConstIterator it = m_stack.end(); + while ( it != m_stack.begin() ) + { + --it; + QDomElement properties = KoDom::namedItemNS( *it, m_styleNSURI, m_propertiesTagName ); + if ( properties.hasAttributeNS( nsURI, name ) || + ( detail && properties.hasAttributeNS( nsURI, fullName ) ) ) + return true; + } + return false; +} + +// Font size is a bit special. "115%" applies to "the fontsize of the parent style". +// This can be generalized though (hasAttributeThatCanBePercentOfParent() ? :) +// Although, if we also add support for fo:font-size-rel here then it's not general anymore. +double KoStyleStack::fontSize() const +{ + const QString name = "font-size"; + double percent = 1; + QValueList<QDomElement>::ConstIterator it = m_stack.end(); // reverse iterator + + while ( it != m_stack.begin() ) + { + --it; + QDomElement properties = KoDom::namedItemNS( *it, m_styleNSURI, m_propertiesTagName ).toElement(); + if ( properties.hasAttributeNS( m_foNSURI, name ) ) { + const QString value = properties.attributeNS( m_foNSURI, name, QString::null ); + if ( value.endsWith( "%" ) ) + percent *= value.left( value.length() - 1 ).toDouble() / 100.0; + else + return percent * KoUnit::parseValue( value ); // e.g. 12pt + } + } + return 0; +} + +bool KoStyleStack::hasChildNode(const QString & name) const +{ + QValueList<QDomElement>::ConstIterator it = m_stack.end(); + while ( it != m_stack.begin() ) + { + --it; + QDomElement properties = (*it).namedItem( "style:"+m_propertiesTagName ).toElement(); + if ( !properties.namedItem( name ).isNull() ) + return true; + } + + return false; +} + +QDomElement KoStyleStack::childNode(const QString & name) const +{ + QValueList<QDomElement>::ConstIterator it = m_stack.end(); + + while ( it != m_stack.begin() ) + { + --it; + QDomElement properties = (*it).namedItem( "style:"+m_propertiesTagName ).toElement(); + if ( !properties.namedItem( name ).isNull() ) + return properties.namedItem( name ).toElement(); + } + + return QDomElement(); // a null element +} + +bool KoStyleStack::hasChildNodeNS( const char* nsURI, const char* localName ) const +{ + QValueList<QDomElement>::ConstIterator it = m_stack.end(); + while ( it != m_stack.begin() ) + { + --it; + QDomElement properties = KoDom::namedItemNS( *it, m_styleNSURI, m_propertiesTagName ); + if ( !KoDom::namedItemNS( properties, nsURI, localName ).isNull() ) + return true; + } + + return false; +} + +QDomElement KoStyleStack::childNodeNS( const char* nsURI, const char* localName) const +{ + QValueList<QDomElement>::ConstIterator it = m_stack.end(); + + while ( it != m_stack.begin() ) + { + --it; + QDomElement properties = KoDom::namedItemNS( *it, m_styleNSURI, m_propertiesTagName ); + QDomElement e = KoDom::namedItemNS( properties, nsURI, localName ); + if ( !e.isNull() ) + return e; + } + + return QDomElement(); // a null element +} + +bool KoStyleStack::isUserStyle( const QDomElement& e, const QString& family ) const +{ + if ( e.attributeNS( m_styleNSURI, "family", QString::null ) != family ) + return false; + const QDomElement parent = e.parentNode().toElement(); + //kdDebug(30003) << k_funcinfo << "tagName=" << e.tagName() << " parent-tagName=" << parent.tagName() << endl; + return parent.localName() == "styles" /*&& parent.namespaceURI() == KoXmlNS::office*/; +} + +QString KoStyleStack::userStyleName( const QString& family ) const +{ + QValueList<QDomElement>::ConstIterator it = m_stack.end(); + while ( it != m_stack.begin() ) + { + --it; + //kdDebug(30003) << k_funcinfo << (*it).attributeNS( m_styleNSURI, "name", QString::null) << endl; + if ( isUserStyle( *it, family ) ) + return (*it).attributeNS( m_styleNSURI, "name", QString::null ); + } + // Can this ever happen? + return "Standard"; +} + +QString KoStyleStack::userStyleDisplayName( const QString& family ) const +{ + QValueList<QDomElement>::ConstIterator it = m_stack.end(); + while ( it != m_stack.begin() ) + { + --it; + //kdDebug(30003) << k_funcinfo << (*it).attributeNS( m_styleNSURI, "display-name") << endl; + if ( isUserStyle( *it, family ) ) + return (*it).attributeNS( m_styleNSURI, "display-name", QString::null ); + } + return QString::null; // no display name, this can happen since it's optional +} + +void KoStyleStack::setTypeProperties( const char* typeProperties ) +{ + m_propertiesTagName = typeProperties == 0 ? QCString( "properties" ) : ( QCString( typeProperties ) + "-properties" ); +} diff --git a/lib/kofficecore/KoStyleStack.h b/lib/kofficecore/KoStyleStack.h new file mode 100644 index 00000000..f3c5faf1 --- /dev/null +++ b/lib/kofficecore/KoStyleStack.h @@ -0,0 +1,201 @@ +/* This file is part of the KDE project + Copyright (c) 2003 Lukas Tinkl <lukas@kde.org> + Copyright (c) 2003 David Faure <faure@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 KOSTYLESTACK_H +#define KOSTYLESTACK_H + + +#include <qvaluelist.h> +#include <qdom.h> +#include <qvaluestack.h> + +#include <kdemacros.h> + +#include "koffice_export.h" + +/** + * @brief This class implements a stack for the different styles of an object. + * + * There can be several styles that are valid for one object. For example + * a textobject on a page has styles 'pr3' and 'P7' and a paragraph in + * that textobject has styles 'P1' and 'T3'. And some styles even have + * parent-styles... + * + * If you want to know if there is, for example, the attribute 'fo:font-family' + * for this paragraph, you have to look into style 'T3', 'P1', 'P7' and 'pr3'. + * When you find this attribute in one style you have to stop processing the list + * and take the found attribute for this object. + * + * This is what this class does. You can push styles on the stack while walking + * through the xml-tree to your object and then ask the stack if any of the styles + * provides a certain attribute. The stack will search from top to bottom, i.e. + * in our example from 'T3' to 'pr3' and return the first occurrence of the wanted + * attribute. + * + * So this is some sort of inheritance where the styles on top of the stack overwrite + * the same attribute of a lower style on the stack. + * + * In general though, you wouldn't use push/pop directly, but KoOasisLoadingContext::fillStyleStack + * or KoOasisLoadingContext::addStyles to automatically push a style and all its + * parent styles onto the stack. + */ +class KOFFICECORE_EXPORT KoStyleStack +{ +public: + /** + * Create a OASIS style stack + */ + KoStyleStack(); + /** + * Create a style stack based on other namespaces than OASIS - used for OOo-1.1 import. + */ + KoStyleStack( const char* styleNSURI, const char* foNSURI ); + virtual ~KoStyleStack(); + + /** + * Clears the complete stack. + */ + void clear(); + + /** + * Save the current state of the stack. Any items added between + * this call and its corresponding restore() will be removed when calling restore(). + */ + void save(); + + /** + * Restore the stack to the state it was at the corresponding save() call. + */ + void restore(); + + /** + * Removes the style on top of the stack. + */ + void pop(); + + /** + * Pushes the new style onto the stack. + */ + void push( const QDomElement& style ); + + /** + * Check if any of the styles on the stack has an attribute called 'name'-'detail' + * where detail is e.g. left, right, top or bottom. + * This allows to also find 'name' alone (e.g. padding implies padding-left, padding-right etc.) + */ + bool hasAttribute( const QString& name, const QString& detail = QString::null ) const KDE_DEPRECATED; + + /** + * Search for the attribute called 'name', starting on top of the stack, + * and return it. + */ + QString attribute( const QString& name, const QString& detail = QString::null ) const KDE_DEPRECATED; + + /** + * Check if any of the styles on the stack has an attribute called 'name'-'detail' + * where detail is e.g. left, right, top or bottom. + * This allows to also find 'name' alone (e.g. padding implies padding-left, padding-right etc.) + */ + bool hasAttributeNS( const char* nsURI, const char* localName, const char* detail = 0 ) const; + + /** + * Search for the attribute called 'name', starting on top of the stack, + * and return it. + */ + QString attributeNS( const char* nsURI, const char* localName, const char* detail = 0 ) const; + + /** + * Check if any of the styles on the stack has a child node called 'name'. + */ + bool hasChildNode( const QString & name ) const KDE_DEPRECATED; + + /** + * Search for a child node called 'name', starting on top of the stack, + * and return it. + */ + QDomElement childNode( const QString & name ) const KDE_DEPRECATED; + + /** + * Check if any of the styles on the stack has a child element called 'localName' in the namespace 'nsURI'. + */ + bool hasChildNodeNS( const char* nsURI, const char* localName ) const; + + /** + * Search for a child element which has a child element called 'localName' + * in the namespace 'nsURI' starting on top of the stack, + * and return it. + */ + QDomElement childNodeNS( const char* nsURI, const char* localName ) const; + + /** + * Special case for the current font size, due to special handling of fo:font-size="115%". + */ + double fontSize() const; + + /** + * Return the name of the style specified by the user, + * i.e. not an auto style. + * This is used to know e.g. which user-style is associated with the current paragraph. + * There could be none though. + */ + QString userStyleName( const QString& family ) const; + + /** + * Return the display name of the style specified by the user, + * i.e. not an auto style + */ + QString userStyleDisplayName( const QString& family ) const; + + /** + * Set the type of properties that will be looked for. + * For instance setTypeProperties("paragraph") will make hasAttribute() and attribute() + * look into "paragraph-properties". + * If @p typeProperties is 0, the stylestack is resetted to look for "properties" + * as it does by default. + */ + void setTypeProperties( const char* typeProperties ); + +private: + bool isUserStyle( const QDomElement& e, const QString& family ) const; + +private: + /// For save/restore: stack of "marks". Each mark is an index in m_stack. + QValueStack<int> m_marks; + + /** + * We use QValueList instead of QValueStack because we need access to all styles + * not only the top one. + */ + QValueList<QDomElement> m_stack; + + QCString m_propertiesTagName; + + const char* m_styleNSURI; + const char* m_foNSURI; + + class KoStyleStackPrivate; + KoStyleStackPrivate *d; + + // forbidden + void operator=( const KoStyleStack& ); + KoStyleStack( const KoStyleStack& ); +}; + +#endif /* KOSTYLESTACK_H */ diff --git a/lib/kofficecore/KoTemplates.cpp b/lib/kofficecore/KoTemplates.cpp new file mode 100644 index 00000000..7fbf3a99 --- /dev/null +++ b/lib/kofficecore/KoTemplates.cpp @@ -0,0 +1,379 @@ +/* This file is part of the KDE project + Copyright (C) 2000 Werner Trobin <trobin@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 <KoTemplates.h> + +#include <qdir.h> +#include <qimage.h> +#include <qprinter.h> + +#include <kdesktopfile.h> +#include <ksimpleconfig.h> +#include <kdebug.h> +#include <kdeversion.h> +#include <kinstance.h> +#include <ksavefile.h> +#include <kstandarddirs.h> +#include <kiconloader.h> +#include <kio/netaccess.h> +#include <klocale.h> + +#include <stdlib.h> + + +KoTemplate::KoTemplate(const QString &name, const QString &description, const QString &file, + const QString &picture, const QString &fileName, const QString &_measureSystem, + bool hidden, bool touched) : + m_name(name), m_descr(description), m_file(file), m_picture(picture), m_fileName(fileName), + m_hidden(hidden), m_touched(touched), m_cached(false), m_measureSystem(_measureSystem) +{ +} + +const QPixmap &KoTemplate::loadPicture( KInstance* instance ) { + + if(m_cached) + return m_pixmap; + m_cached=true; + if ( m_picture[ 0 ] == '/' ) + { + // ### TODO: use the class KoPicture instead of QImage to support non-image pictures + QImage img( m_picture ); + if (img.isNull()) { + kdWarning() << "Couldn't find icon " << m_picture << endl; + m_pixmap=QPixmap(); + return m_pixmap; + } + const int maxHeightWidth = 128; // ### TODO: some people would surely like to have 128x128 + if (img.width() > maxHeightWidth || img.height() > maxHeightWidth) { + img = img.smoothScale( maxHeightWidth, maxHeightWidth, QImage::ScaleMax ); + } + m_pixmap.convertFromImage(img); + return m_pixmap; + } else { // relative path + m_pixmap = instance->iconLoader()->loadIcon( m_picture, KIcon::Desktop, 128 ); + return m_pixmap; + } +} + + +KoTemplateGroup::KoTemplateGroup(const QString &name, const QString &dir, + int _sortingWeight, bool touched) : + m_name(name), m_touched(touched), m_sortingWeight(_sortingWeight) +{ + m_dirs.append(dir); + m_templates.setAutoDelete(true); +} + +bool KoTemplateGroup::isHidden() const { + + QPtrListIterator<KoTemplate> it(m_templates); + bool hidden=true; + while(it.current()!=0L && hidden) { + hidden=it.current()->isHidden(); + ++it; + } + return hidden; +} + +void KoTemplateGroup::setHidden(bool hidden) const { + + QPtrListIterator<KoTemplate> it(m_templates); + for( ; it.current()!=0L; ++it) + it.current()->setHidden(hidden); + m_touched=true; +} + +bool KoTemplateGroup::add(KoTemplate *t, bool force, bool touch) { + + KoTemplate *myTemplate=find(t->name()); + if(myTemplate==0L) { + m_templates.append(t); + m_touched=touch; + return true; + } + else if(myTemplate && force) { + //kdDebug() << "removing :" << myTemplate->fileName() << endl; + QFile::remove( myTemplate->fileName() ); + QFile::remove( myTemplate->picture() ); + QFile::remove( myTemplate->file() ); + m_templates.removeRef(myTemplate); + m_templates.append(t); + m_touched=touch; + return true; + } + return false; +} + +KoTemplate *KoTemplateGroup::find(const QString &name) const { + + QPtrListIterator<KoTemplate> it(m_templates); + while(it.current() && it.current()->name()!=name) + ++it; + return it.current(); +} + + +KoTemplateTree::KoTemplateTree(const QCString &templateType, + KInstance *instance, bool readTree) : + m_templateType(templateType), m_instance(instance), m_defaultGroup(0L), + m_defaultTemplate(0L) { + + m_groups.setAutoDelete(true); + if(readTree) + readTemplateTree(); +} + +void KoTemplateTree::readTemplateTree() { + + readGroups(); + readTemplates(); +} + +void KoTemplateTree::writeTemplateTree() { + QString localDir=m_instance->dirs()->saveLocation(m_templateType); + + for(KoTemplateGroup *group=m_groups.first(); group!=0L; group=m_groups.next()) { + //kdDebug() << "---------------------------------" << endl; + //kdDebug() << "group: " << group->name() << endl; + + bool touched=false; + for(KoTemplate *t=group->first(); t!=0L && !touched && !group->touched(); t=group->next()) + touched=t->touched(); + + if(group->touched() || touched) { + //kdDebug() << "touched" << endl; + if(!group->isHidden()) { + //kdDebug() << "not hidden" << endl; + KStandardDirs::makeDir(localDir+group->name()); // create the local group dir + } + else { + //kdDebug() << "hidden" << endl; + if(group->dirs().count()==1 && !group->dirs().grep(localDir).isEmpty()) { + //kdDebug() << "local only" << endl; + KIO::NetAccess::del(group->dirs().first(), 0); + //kdDebug() << "removing: " << group->dirs().first() << endl; + } + else { + //kdDebug() << "global" << endl; + KStandardDirs::makeDir(localDir+group->name()); + } + } + } + for(KoTemplate *t=group->first(); t!=0L; t=group->next()) { + if(t->touched()) { + //kdDebug() << "++template: " << t->name() << endl; + writeTemplate(t, group, localDir); + } + if(t->isHidden() && t->touched() ) { + //kdDebug() << "+++ delete local template ##############" << endl; + writeTemplate(t, group, localDir); + QFile::remove(t->file()); + QFile::remove(t->picture()); + } + } + } +} + +void KoTemplateTree::add(KoTemplateGroup *g) { + + KoTemplateGroup *group=find(g->name()); + if(group==0L) + m_groups.append(g); + else + group->addDir(g->dirs().first()); // "...there can be only one..." (Queen) +} + +KoTemplateGroup *KoTemplateTree::find(const QString &name) const { + + QPtrListIterator<KoTemplateGroup> it(m_groups); + while(it.current() && it.current()->name()!=name) + ++it; + return it.current(); +} + +void KoTemplateTree::readGroups() { + + QStringList dirs = m_instance->dirs()->resourceDirs(m_templateType); + for(QStringList::ConstIterator it=dirs.begin(); it!=dirs.end(); ++it) { + //kdDebug() << "dir: " << *it << endl; + QDir dir(*it); + // avoid the annoying warning + if(!dir.exists()) + continue; + dir.setFilter(QDir::Dirs); + QStringList templateDirs=dir.entryList(); + for(QStringList::ConstIterator tdirIt=templateDirs.begin(); tdirIt!=templateDirs.end(); ++tdirIt) { + if(*tdirIt=="." || *tdirIt=="..") // we don't want to check those dirs :) + continue; + QDir templateDir(*it+*tdirIt); + QString name=*tdirIt; + QString defaultTab; + int sortingWeight = 1000; + if(templateDir.exists(".directory")) { + KSimpleConfig config(templateDir.absPath()+"/.directory", true); + config.setDesktopGroup(); + name=config.readEntry("Name"); + defaultTab=config.readEntry("X-KDE-DefaultTab"); + sortingWeight=config.readNumEntry("X-KDE-SortingWeight", 1000); + //kdDebug() << "name: " << name <<endl; + } + KoTemplateGroup *g=new KoTemplateGroup(name, *it+*tdirIt+QChar('/'), sortingWeight); + add(g); + if(defaultTab=="true") + m_defaultGroup=g; + } + } +} + +void KoTemplateTree::readTemplates() { + QString dontShow = "imperial"; + + if(KGlobal::locale()->pageSize() == QPrinter::Letter) { + dontShow = "metric"; + } + + QPtrListIterator<KoTemplateGroup> groupIt(m_groups); + for( ; groupIt.current()!=0L; ++groupIt) { + QStringList dirs=groupIt.current()->dirs(); + for(QStringList::ConstIterator it=dirs.begin(); it!=dirs.end(); ++it) { + QDir d(*it); + if( !d.exists() ) + continue; + QStringList files=d.entryList( QDir::Files | QDir::Readable, QDir::Name ); + for(unsigned int i=0; i<files.count(); ++i) { + QString filePath = *it + files[i]; + //kdDebug() << "filePath: " << filePath << endl; + QString icon; + QString text; + QString description; + QString hidden_str; + QString fileName; + bool hidden=false; + bool defaultTemplate = false; + QString templatePath; + QString measureSystem; + // If a desktop file, then read the name from it. + // Otherwise (or if no name in it?) use file name + if (KDesktopFile::isDesktopFile(filePath)) { + KSimpleConfig config(filePath, true); + config.setDesktopGroup(); + if (config.readEntry("Type")=="Link") { + text=config.readEntry("Name"); + fileName=filePath; + description=config.readEntry("Comment"); + //kdDebug() << "name: " << text << endl; + icon=config.readEntry("Icon"); + if(icon[0]!='/' && // allow absolute paths for icons + QFile::exists(*it+icon)) // allow icons from icontheme + icon=*it+icon; + //kdDebug() << "icon2: " << icon << endl; + hidden=config.readBoolEntry("X-KDE-Hidden", false); + defaultTemplate = config.readBoolEntry("X-KDE-DefaultTemplate", false); + measureSystem=config.readEntry("X-KDE-MeasureSystem").lower(); + + // Don't add a template that is for the wrong measure system + if(measureSystem == dontShow) + continue; + + //kdDebug() << "hidden: " << hidden_str << endl; + templatePath=config.readPathEntry("URL"); + //kdDebug() << "Link to : " << templatePath << endl; + if(templatePath[0]!='/') { + if(templatePath.left(6)=="file:/") // I doubt this will happen + templatePath=templatePath.right(templatePath.length()-6); + //else + // kdDebug() << "dirname=" << *it << endl; + templatePath=*it+templatePath; + //kdDebug() << "templatePath: " << templatePath << endl; + } + } else + continue; // Invalid + } + // The else if and the else branch are here for compat. with the old system + else if ( files[i].right(4) != ".png" ) + // Ignore everything that is not a PNG file + continue; + else { + // Found a PNG file - the template must be here in the same dir. + icon = filePath; + QFileInfo fi(filePath); + text = fi.baseName(); + templatePath = filePath; // Note that we store the .png file as the template ! + // That's the way it's always been done. Then the app replaces the extension... + } + KoTemplate *t=new KoTemplate(text, description, templatePath, icon, fileName, + measureSystem, hidden); + groupIt.current()->add(t, false, false); // false -> we aren't a "user", false -> don't + // "touch" the group to avoid useless + // creation of dirs in .kde/blah/... + if ( defaultTemplate ) + m_defaultTemplate = t; + } + } + } +} + +void KoTemplateTree::writeTemplate(KoTemplate *t, KoTemplateGroup *group, + const QString &localDir) { + QString fileName; + if ( t->isHidden() ) + { + fileName = t->fileName(); + // try to remove the file + if ( QFile::remove(fileName) || !QFile::exists(fileName) ) + { + QFile::remove( t->name() ); + QFile::remove( t->picture() ); + return; + } + } + // be sure that the template's file name is unique so we don't overwrite an other + QString const path = localDir + group->name() + '/'; + QString const name = KoTemplates::stripWhiteSpace( t->name() ); + fileName = path + name + ".desktop"; + if ( t->isHidden() && QFile::exists(fileName) ) + return; + QString fill; + while ( KIO::NetAccess::exists( fileName, true, 0 ) ) + { + fill += '_'; + fileName = path + fill + name + ".desktop"; + } + + KSimpleConfig config( fileName ); + config.setDesktopGroup(); + config.writeEntry("Type", "Link"); + config.writePathEntry("URL", t->file()); + config.writeEntry("Name", t->name()); + config.writeEntry("Icon", t->picture()); + config.writeEntry("X-KDE-Hidden", t->isHidden()); +} + +namespace KoTemplates { +QString stripWhiteSpace(const QString &string) { + + QString ret; + for(unsigned int i=0; i<string.length(); ++i) { + QChar tmp(string[i]); + if(!tmp.isSpace()) + ret+=tmp; + } + return ret; +} +} diff --git a/lib/kofficecore/KoTemplates.h b/lib/kofficecore/KoTemplates.h new file mode 100644 index 00000000..4c9e3133 --- /dev/null +++ b/lib/kofficecore/KoTemplates.h @@ -0,0 +1,150 @@ +/* + This file is part of the KDE project + Copyright (C) 2000 Werner Trobin <trobin@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 koTemplates_h +#define koTemplates_h + +#include <qptrlist.h> +#include <qstringlist.h> +#include <qpixmap.h> +#include <koffice_export.h> + +class KInstance; + +/** @internal */ +class KOFFICECORE_EXPORT KoTemplate { + +public: + KoTemplate(const QString &name, + const QString &description=QString::null, + const QString &file=QString::null, + const QString &picture=QString::null, + const QString &fileName=QString::null, + const QString &_measureSystem=QString::null, + bool hidden=false, bool touched=false); + ~KoTemplate() {} + + QString name() const { return m_name; } + QString description() const { return m_descr; }; + QString file() const { return m_file; } + QString picture() const { return m_picture; } + QString fileName() const { return m_fileName; } + const QPixmap &loadPicture( KInstance* instance ); + + bool isHidden() const { return m_hidden; } + void setHidden(bool hidden=true) { m_hidden=hidden; m_touched=true; } + + bool touched() const { return m_touched; } + + QString measureSystem() const { return m_measureSystem; } + void setMeasureSystem(const QString& system) { m_measureSystem = system; } + +private: + QString m_name, m_descr, m_file, m_picture, m_fileName; + bool m_hidden; + mutable bool m_touched; + bool m_cached; + QPixmap m_pixmap; + QString m_measureSystem; +}; + + +class KOFFICECORE_EXPORT KoTemplateGroup { + +public: + KoTemplateGroup(const QString &name, + const QString &dir=QString::null, + int _sortingWeight=0, + bool touched=false); + ~KoTemplateGroup() {} + + QString name() const { return m_name; } + QStringList dirs() const { return m_dirs; } + void addDir(const QString &dir) { m_dirs.append(dir); m_touched=true; } + int sortingWeight() const { return m_sortingWeight; } + void setSortingWeight(int weight) { m_sortingWeight = weight; } + /// If all children are hidden, we are hidden too + bool isHidden() const; + /// if we should hide, we hide all the children + void setHidden(bool hidden=true) const; + + KoTemplate *first() { return m_templates.first(); } + KoTemplate *next() { return m_templates.next(); } + KoTemplate *last() { return m_templates.last(); } + KoTemplate *prev() { return m_templates.prev(); } + KoTemplate *current() { return m_templates.current(); } + + bool add(KoTemplate *t, bool force=false, bool touch=true); + KoTemplate *find(const QString &name) const; + + bool touched() const { return m_touched; } + +private: + QString m_name; + QStringList m_dirs; + QPtrList<KoTemplate> m_templates; + mutable bool m_touched; + int m_sortingWeight; +}; + + +class KoTemplateTree { + +public: + KoTemplateTree(const QCString &templateType, KInstance *instance, + bool readTree=false); + ~KoTemplateTree() {} + + QCString templateType() const { return m_templateType; } + KInstance *instance() const { return m_instance; } + void readTemplateTree(); + void writeTemplateTree(); + + KoTemplateGroup *first() { return m_groups.first(); } + KoTemplateGroup *next() { return m_groups.next(); } + KoTemplateGroup *last() { return m_groups.last(); } + KoTemplateGroup *prev() { return m_groups.prev(); } + KoTemplateGroup *current() { return m_groups.current(); } + + void add(KoTemplateGroup *g); + KoTemplateGroup *find(const QString &name) const; + + KoTemplateGroup *defaultGroup() const { return m_defaultGroup; } + KoTemplate *defaultTemplate() const { return m_defaultTemplate; } + +private: + void readGroups(); + void readTemplates(); + void writeTemplate(KoTemplate *t, KoTemplateGroup *group, + const QString &localDir); + + QCString m_templateType; + KInstance *m_instance; + QPtrList<KoTemplateGroup> m_groups; + KoTemplateGroup *m_defaultGroup; + KoTemplate *m_defaultTemplate; +}; + + +namespace KoTemplates { +QString stripWhiteSpace(const QString &string); +} + +#endif diff --git a/lib/kofficecore/KoUnit.cpp b/lib/kofficecore/KoUnit.cpp new file mode 100644 index 00000000..fc459c51 --- /dev/null +++ b/lib/kofficecore/KoUnit.cpp @@ -0,0 +1,215 @@ +/* This file is part of the KDE project + Copyright (C) 2001 David Faure <faure@kde.org> + Copyright (C) 2004, Nicolas GOUTTE <goutte@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 <KoGlobal.h> +#include "KoUnit.h" +#include <KoXmlWriter.h> + +#include <klocale.h> +#include <kglobal.h> +#include <kdebug.h> + +#include <qregexp.h> +#include <qdom.h> + +QStringList KoUnit::listOfUnitName() +{ + QStringList lst; + for ( uint i = 0 ; i <= KoUnit::U_LASTUNIT ; ++i ) + { + KoUnit::Unit unit = static_cast<KoUnit::Unit>( i ); + lst.append( KoUnit::unitDescription( unit ) ); + } + return lst; +} + +QString KoUnit::unitDescription( Unit _unit ) +{ + switch ( _unit ) + { + case KoUnit::U_MM: + return i18n("Millimeters (mm)"); + case KoUnit::U_CM: + return i18n("Centimeters (cm)"); + case KoUnit::U_DM: + return i18n("Decimeters (dm)"); + case KoUnit::U_INCH: + return i18n("Inches (in)"); + case KoUnit::U_PI: + return i18n("Pica (pi)"); + case KoUnit::U_DD: + return i18n("Didot (dd)"); + case KoUnit::U_CC: + return i18n("Cicero (cc)"); + case KoUnit::U_PT: + return i18n("Points (pt)" ); + default: + return i18n("Error!"); + } +} + +double KoUnit::toUserValue( double ptValue, Unit unit ) +{ + switch ( unit ) { + case U_MM: + return toMM( ptValue ); + case U_CM: + return toCM( ptValue ); + case U_DM: + return toDM( ptValue ); + case U_INCH: + return toInch( ptValue ); + case U_PI: + return toPI( ptValue ); + case U_DD: + return toDD( ptValue ); + case U_CC: + return toCC( ptValue ); + case U_PT: + default: + return toPoint( ptValue ); + } +} + +double KoUnit::ptToUnit( const double ptValue, const Unit unit ) +{ + switch ( unit ) + { + case U_MM: + return POINT_TO_MM( ptValue ); + case U_CM: + return POINT_TO_CM( ptValue ); + case U_DM: + return POINT_TO_DM( ptValue ); + case U_INCH: + return POINT_TO_INCH( ptValue ); + case U_PI: + return POINT_TO_PI( ptValue ); + case U_DD: + return POINT_TO_DD( ptValue ); + case U_CC: + return POINT_TO_CC( ptValue ); + case U_PT: + default: + return ptValue; + } +} + +QString KoUnit::toUserStringValue( double ptValue, Unit unit ) +{ + return KGlobal::locale()->formatNumber( toUserValue( ptValue, unit ) ); +} + +double KoUnit::fromUserValue( double value, Unit unit ) +{ + switch ( unit ) { + case U_MM: + return MM_TO_POINT( value ); + case U_CM: + return CM_TO_POINT( value ); + case U_DM: + return DM_TO_POINT( value ); + case U_INCH: + return INCH_TO_POINT( value ); + case U_PI: + return PI_TO_POINT( value ); + case U_DD: + return DD_TO_POINT( value ); + case U_CC: + return CC_TO_POINT( value ); + case U_PT: + default: + return value; + } +} + +double KoUnit::fromUserValue( const QString& value, Unit unit, bool* ok ) +{ + return fromUserValue( KGlobal::locale()->readNumber( value, ok ), unit ); +} + +double KoUnit::parseValue( QString value, double defaultVal ) +{ + value.simplifyWhiteSpace(); + value.remove( ' ' ); + + if( value.isEmpty() ) + return defaultVal; + + int index = value.find( QRegExp( "[a-z]+$" ) ); + if ( index == -1 ) + return value.toDouble(); + + QString unit = value.mid( index ); + value.truncate ( index ); + double val = value.toDouble(); + + if ( unit == "pt" ) + return val; + + bool ok; + Unit u = KoUnit::unit( unit, &ok ); + if( ok ) + return fromUserValue( val, u ); + + if( unit == "m" ) + return fromUserValue( val * 10.0, U_DM ); + else if( unit == "km" ) + return fromUserValue( val * 10000.0, U_DM ); + kdWarning() << "KoUnit::parseValue: Unit " << unit << " is not supported, please report." << endl; + + // TODO : add support for mi/ft ? + return defaultVal; +} + +KoUnit::Unit KoUnit::unit( const QString &_unitName, bool* ok ) +{ + if ( ok ) + *ok = true; + if ( _unitName == QString::fromLatin1( "mm" ) ) return U_MM; + if ( _unitName == QString::fromLatin1( "cm" ) ) return U_CM; + if ( _unitName == QString::fromLatin1( "dm" ) ) return U_DM; + if ( _unitName == QString::fromLatin1( "in" ) + || _unitName == QString::fromLatin1("inch") /*compat*/ ) return U_INCH; + if ( _unitName == QString::fromLatin1( "pi" ) ) return U_PI; + if ( _unitName == QString::fromLatin1( "dd" ) ) return U_DD; + if ( _unitName == QString::fromLatin1( "cc" ) ) return U_CC; + if ( _unitName == QString::fromLatin1( "pt" ) ) return U_PT; + if ( ok ) + *ok = false; + return U_PT; +} + +QString KoUnit::unitName( Unit _unit ) +{ + if ( _unit == U_MM ) return QString::fromLatin1( "mm" ); + if ( _unit == U_CM ) return QString::fromLatin1( "cm" ); + if ( _unit == U_DM ) return QString::fromLatin1( "dm" ); + if ( _unit == U_INCH ) return QString::fromLatin1( "in" ); + if ( _unit == U_PI ) return QString::fromLatin1( "pi" ); + if ( _unit == U_DD ) return QString::fromLatin1( "dd" ); + if ( _unit == U_CC ) return QString::fromLatin1( "cc" ); + return QString::fromLatin1( "pt" ); +} + +void KoUnit::saveOasis(KoXmlWriter* settingsWriter, Unit _unit) +{ + settingsWriter->addConfigItem( "unit", unitName(_unit) ); +} diff --git a/lib/kofficecore/KoUnit.h b/lib/kofficecore/KoUnit.h new file mode 100644 index 00000000..07638215 --- /dev/null +++ b/lib/kofficecore/KoUnit.h @@ -0,0 +1,175 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Reginald Stadlbauer <reggie@kde.org>, Torben Weis <weis@kde.org> + Copyright (C) 2004, Nicolas GOUTTE <goutte@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 kounit_h +#define kounit_h +#include <qstring.h> +#include <qstringlist.h> +#include <math.h> // for floor +#include <koffice_export.h> + +class KoXmlWriter; +class QDomElement; + +// 1 inch ^= 72 pt +// 1 inch ^= 25.399956 mm (-pedantic ;p) +// 1 pt = 1/12 pi +// 1 pt ^= 0.0077880997 cc +// 1 cc = 12 dd +// Note: I don't use division but multiplication with the inverse value +// because it's faster ;p (Werner) +#define POINT_TO_MM(px) ((px)*0.352777167) +#define MM_TO_POINT(mm) ((mm)*2.83465058) +#define POINT_TO_CM(px) ((px)*0.0352777167) +#define CM_TO_POINT(cm) ((cm)*28.3465058) +#define POINT_TO_DM(px) ((px)*0.00352777167) +#define DM_TO_POINT(dm) ((dm)*283.465058) +#define POINT_TO_INCH(px) ((px)*0.01388888888889) +#define INCH_TO_POINT(inch) ((inch)*72.0) +#define MM_TO_INCH(mm) ((mm)*0.039370147) +#define INCH_TO_MM(inch) ((inch)*25.399956) +#define POINT_TO_PI(px)((px)*0.083333333) +#define POINT_TO_DD(px)((px)*0.006490083) +#define POINT_TO_CC(px)((px)*0.077880997) +#define PI_TO_POINT(pi)((pi)*12) +#define DD_TO_POINT(dd)((dd)*154.08124) +#define CC_TO_POINT(cc)((cc)*12.840103) +/** + * %KOffice stores everything in pt (using "double") internally. + * When displaying a value to the user, the value is converted to the user's unit + * of choice, and rounded to a reasonable precision to avoid 0.999999 + */ +class KOFFICECORE_EXPORT KoUnit +{ +public: + /** Length units supported by KOffice. */ + enum Unit { + U_MM = 0, + U_PT = 1, + U_INCH = 2, + U_CM = 3, + U_DM = 4, + U_PI = 5, // pica + U_DD = 6, // didot + U_CC = 7, // cicero + U_LASTUNIT = U_CC // update when adding a new unit + // when adding a new unit, make sure to implement support for it in koRuler too + }; + + /// Prepare ptValue to be displayed in pt + static double toPoint( double ptValue ) { + // No conversion, only rounding (to 0.001 precision) + return floor( ptValue * 1000.0 ) / 1000.0; + } + + /// Prepare ptValue to be displayed in mm + static double toMM( double ptValue ) { + // "mm" values are rounded to 0.0001 millimeters + return floor( POINT_TO_MM( ptValue ) * 10000.0 ) / 10000.0; + } + + /// Prepare ptValue to be displayed in cm + static double toCM( double ptValue ) { + return floor( POINT_TO_CM( ptValue ) * 10000.0 ) / 10000.0; + } + + /// Prepare ptValue to be displayed in dm + static double toDM( double ptValue ) { + return floor( POINT_TO_DM( ptValue ) * 10000.0 ) / 10000.0; + } + + /// Prepare ptValue to be displayed in inch + static double toInch( double ptValue ) { + // "in" values are rounded to 0.00001 inches + return floor( POINT_TO_INCH( ptValue ) * 100000.0 ) / 100000.0; + } + + /// Prepare ptValue to be displayed in pica + static double toPI( double ptValue ) { + // "pi" values are rounded to 0.00001 inches + return floor( POINT_TO_PI( ptValue ) * 100000.0 ) / 100000.0; + } + + /// Prepare ptValue to be displayed in didot + static double toDD( double ptValue ) { + // "dd" values are rounded to 0.00001 inches + return floor( POINT_TO_DD( ptValue ) * 100000.0 ) / 100000.0; + } + + /// Prepare ptValue to be displayed in cicero + static double toCC( double ptValue ) { + // "cc" values are rounded to 0.00001 inches + return floor( POINT_TO_CC( ptValue ) * 100000.0 ) / 100000.0; + } + + /** + * This method is the one to use to display a value in a dialog + * \return the value @p ptValue converted to @p unit and rounded, ready to be displayed + * Old name: ptToUnit + */ + static double toUserValue( double ptValue, Unit unit ); + + /** + * Convert the value @p ptValue to a given unit @p unit + * Unlike KoUnit::ptToUnit the return value remains unrounded, so that it can be used in complex calculation + * \return the converted value + * Old name: ptToUnitUnrounded + */ + static double ptToUnit( const double ptValue, const Unit unit ); + + /// This method is the one to use to display a value in a dialog + /// @return the value @p ptValue converted to @p unit and rounded, ready to be displayed + /// Old name: userValue + static QString toUserStringValue( double ptValue, Unit unit ); + + /// This method is the one to use to read a value from a dialog + /// @return the value in @p unit, converted to points for internal use + /// Old name: ptFromUnit + static double fromUserValue( double value, Unit unit ); + + /// This method is the one to use to read a value from a dialog + /// @param value value entered by the user + /// @param unit unit type selected by the user + /// @param ok if set, the pointed bool is set to true if the value could be + /// converted to a double, and to false otherwise. + /// @return the value in @p unit, converted to points for internal use + static double fromUserValue( const QString& value, Unit unit, bool* ok = 0 ); + + /// Convert a unit name into a Unit enum + /// @param _unitName name to convert + /// @param ok if set, it will be true if the unit was known, false if unknown + static Unit unit( const QString &_unitName, bool* ok = 0 ); + /// Get the name of a unit + static QString unitName( Unit _unit ); + /// Get the full (translated) description of a unit + static QString unitDescription( Unit _unit ); + static QStringList listOfUnitName(); + + /// parse common %KOffice and OO values, like "10cm", "5mm" to pt + static double parseValue( QString value, double defaultVal = 0.0 ); + // Note: the above method doesn't take a const ref, since it modifies the arg. + + /// Save a unit in OASIS format + static void saveOasis(KoXmlWriter* settingsWriter, Unit _unit); + +}; + + +#endif diff --git a/lib/kofficecore/KoView.cpp b/lib/kofficecore/KoView.cpp new file mode 100644 index 00000000..9650192c --- /dev/null +++ b/lib/kofficecore/KoView.cpp @@ -0,0 +1,868 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@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 <KoView.h> +#include <KoDocument.h> +#include <KoMainWindow.h> +#include <KoFrame.h> +#include <KoViewIface.h> +#include <KoDocumentChild.h> + +#include <klocale.h> +#include <kglobal.h> +#include <kdebug.h> +#include <kparts/partmanager.h> +#include <kparts/event.h> +#include <kcursor.h> +#include <assert.h> +#include <kstatusbar.h> +#include <kapplication.h> +#include <qtimer.h> + +class KoViewPrivate +{ +public: + KoViewPrivate() + { + m_inOperation = false; + m_zoom = 1.0; + m_children.setAutoDelete( true ); + m_manager = 0L; + m_tempActiveWidget = 0L; + m_dcopObject = 0; + m_registered=false; + m_documentDeleted=false; + } + ~KoViewPrivate() + { + } + + QGuardedPtr<KoDocument> m_doc; // our KoDocument + QGuardedPtr<KParts::PartManager> m_manager; + double m_zoom; + QPtrList<KoViewChild> m_children; + QWidget *m_tempActiveWidget; + KoViewIface *m_dcopObject; + bool m_registered; // are we registered at the part manager? + bool m_documentDeleted; // true when m_doc gets deleted [can't use m_doc==0 + // since this only happens in ~QObject, and views + // get deleted by ~KoDocument]. + QTimer *m_scrollTimer; + + // Hmm sorry for polluting the private class with such a big inner class. + // At the beginning it was a little struct :) + class StatusBarItem { + public: + StatusBarItem() // for QValueList + : m_widget(0), m_visible(false) + {} + StatusBarItem( QWidget * widget, int stretch, bool permanent ) + : m_widget(widget), m_stretch(stretch), m_permanent(permanent), m_visible(false) + {} + + QWidget * widget() const { return m_widget; } + + void ensureItemShown( KStatusBar * sb ) + { + if ( !m_visible ) + { + sb->addWidget( m_widget, m_stretch, m_permanent ); + m_visible = true; + m_widget->show(); + } + } + void ensureItemHidden( KStatusBar * sb ) + { + if ( m_visible ) + { + sb->removeWidget( m_widget ); + m_visible = false; + m_widget->hide(); + } + } + private: + QWidget * m_widget; + int m_stretch; + bool m_permanent; + bool m_visible; // true when the item has been added to the statusbar + }; + QValueList<StatusBarItem> m_statusBarItems; // Our statusbar items + bool m_inOperation; //in the middle of an operation (no screen refreshing)? +}; + +KoView::KoView( KoDocument *document, QWidget *parent, const char *name ) + : QWidget( parent, name ) +{ + Q_ASSERT( document ); + + //kdDebug(30003) << "KoView::KoView " << this << endl; + d = new KoViewPrivate; + d->m_doc = document; + KParts::PartBase::setPartObject( this ); + + setFocusPolicy( StrongFocus ); + + setMouseTracking( true ); + + connect( d->m_doc, SIGNAL( childChanged( KoDocumentChild * ) ), + this, SLOT( slotChildChanged( KoDocumentChild * ) ) ); + + connect( d->m_doc, SIGNAL( sigBeginOperation() ), + this, SLOT( beginOperation() ) ); + + connect( d->m_doc, SIGNAL( sigEndOperation() ), + this, SLOT( endOperation() ) ); + + + actionCollection()->setWidget( this ); + setupGlobalActions(); + KActionCollection *coll = actionCollection(); + /**** not needed anymore, according to David (Werner) + QValueList<KAction*> docActions = document->actionCollection()->actions(); + QValueList<KAction*>::ConstIterator it = docActions.begin(); + QValueList<KAction*>::ConstIterator end = docActions.end(); + for (; it != end; ++it ) + coll->insert( *it ); + */ + + KStatusBar * sb = statusBar(); + if ( sb ) // No statusbar in e.g. konqueror + { + coll->setHighlightingEnabled( true ); + connect( coll, SIGNAL( actionStatusText( const QString & ) ), + this, SLOT( slotActionStatusText( const QString & ) ) ); + connect( coll, SIGNAL( clearStatusText() ), + this, SLOT( slotClearStatusText() ) ); + + connect( d->m_doc, SIGNAL( sigStatusBarMessage( const QString& ) ), + this, SLOT( slotActionStatusText( const QString& ) ) ); + connect( d->m_doc, SIGNAL( sigClearStatusBarMessage() ), + this, SLOT( slotClearStatusText() ) ); + } + d->m_doc->setCurrent(); + + d->m_scrollTimer = new QTimer( this ); + connect (d->m_scrollTimer, SIGNAL( timeout() ), this, SLOT( slotAutoScroll() ) ); +} + +KoView::~KoView() +{ + kdDebug(30003) << "KoView::~KoView " << this << endl; + delete d->m_scrollTimer; + delete d->m_dcopObject; + if (!d->m_documentDeleted) + { + if ( koDocument() && !koDocument()->isSingleViewMode() ) + { + if ( d->m_manager && d->m_registered ) // if we aren't registered we mustn't unregister :) + d->m_manager->removePart( koDocument() ); + d->m_doc->removeView(this); + d->m_doc->setCurrent( false ); + } + } + delete d; +} + +KoDocument *KoView::koDocument() const +{ + return d->m_doc; +} + +void KoView::setDocumentDeleted() +{ + d->m_documentDeleted = true; +} + +bool KoView::documentDeleted() const +{ + return d->m_documentDeleted; +} + +bool KoView::hasDocumentInWindow( KoDocument *doc ) +{ + return child( doc ) != 0L; +} + +void KoView::setPartManager( KParts::PartManager *manager ) +{ + d->m_manager = manager; + if ( !koDocument()->isSingleViewMode() && + manager->parts()->containsRef( koDocument() ) == 0 ) // is there another view registered? + { + d->m_registered = true; // no, so we have to register now and ungregister again in the DTOR + manager->addPart( koDocument(), false ); + } + else + d->m_registered = false; // There is already another view registered for that part... +} + +KParts::PartManager *KoView::partManager() const +{ + return d->m_manager; +} + +KAction *KoView::action( const QDomElement &element ) const +{ + static const QString &attrName = KGlobal::staticQString( "name" ); + QString name = element.attribute( attrName ); + + KAction *act = KXMLGUIClient::action( name.utf8() ); + + if ( !act ) + act = d->m_doc->KXMLGUIClient::action( name.utf8() ); + + return act; +} + +KoDocument *KoView::hitTest( const QPoint &viewPos ) +{ + KoViewChild *viewChild; + + QPoint pos = reverseViewTransformations( viewPos ); + + KoDocumentChild *docChild = selectedChild(); + if ( docChild ) + { + if ( ( viewChild = child( docChild->document() ) ) ) + { + if ( viewChild->frameRegion().contains( pos ) ) + return 0; + } + else + if ( docChild->frameRegion().contains( pos ) ) + return 0; + } + + docChild = activeChild(); + if ( docChild ) + { + if ( ( viewChild = child( docChild->document() ) ) ) + { + if ( viewChild->frameRegion().contains( pos ) ) + return 0; + } + else + if ( docChild->frameRegion().contains( pos ) ) + return 0; + } + + return koDocument()->hitTest( pos ); +} + +int KoView::leftBorder() const +{ + return 0; +} + +int KoView::rightBorder() const +{ + return 0; +} + +int KoView::topBorder() const +{ + return 0; +} + +int KoView::bottomBorder() const +{ + return 0; +} + +void KoView::setZoom( double zoom ) +{ + d->m_zoom = zoom; + update(); +} + +double KoView::zoom() const +{ + return d->m_zoom; +} + +QWidget *KoView::canvas() const +{ + //dfaure: since the view plays two roles in this method (the const means "you can modify the canvas + // but not the view", it's just coincidence that the view is the canvas by default ;) + return const_cast<KoView *>(this); +} + +int KoView::canvasXOffset() const +{ + return 0; +} + +int KoView::canvasYOffset() const +{ + return 0; +} + +void KoView::canvasAddChild( KoViewChild * ) +{ +} + +void KoView::customEvent( QCustomEvent *ev ) +{ + if ( KParts::PartActivateEvent::test( ev ) ) + partActivateEvent( (KParts::PartActivateEvent *)ev ); + else if ( KParts::PartSelectEvent::test( ev ) ) + partSelectEvent( (KParts::PartSelectEvent *)ev ); + else if( KParts::GUIActivateEvent::test( ev ) ) + guiActivateEvent( (KParts::GUIActivateEvent*)ev ); +} + +void KoView::partActivateEvent( KParts::PartActivateEvent *event ) +{ + if ( event->part() != (KParts::Part *)koDocument() ) + { + assert( event->part()->inherits( "KoDocument" ) ); + + KoDocumentChild *child = koDocument()->child( (KoDocument *)event->part() ); + if ( child && event->activated() ) + { + if ( child->isRectangle() && !child->isTransparent() ) + { + KoViewChild *viewChild = new KoViewChild( child, this ); + d->m_children.append( viewChild ); + + QApplication::setOverrideCursor(waitCursor); + // This is the long operation (due to toolbar layout stuff) + d->m_manager->setActivePart( child->document(), viewChild->frame()->view() ); + QApplication::restoreOverrideCursor(); + + // Now we can move the frame to the right place + viewChild->setInitialFrameGeometry(); + } + else + { + emit regionInvalidated( child->frameRegion( matrix() ), true ); + } + emit childActivated( child ); + } + else if ( child ) + { + emit regionInvalidated( child->frameRegion( matrix() ), true ); + emit childDeactivated( child ); + } + else + emit invalidated(); + } + else + emit activated( event->activated() ); +} + +void KoView::partSelectEvent( KParts::PartSelectEvent *event ) +{ + if ( event->part() != (KParts::Part *)koDocument() ) + { + assert( event->part()->inherits( "KoDocument" ) ); + + KoDocumentChild *child = koDocument()->child( (KoDocument *)event->part() ); + + if ( child && event->selected() ) + { + QRegion r = child->frameRegion( matrix() ); + r.translate( - canvasXOffset(), - canvasYOffset() ); + emit regionInvalidated( r, true ); + emit childSelected( child ); + } + else if ( child ) + { + QRegion r = child->frameRegion( matrix() ); + r.translate( - canvasXOffset(), - canvasYOffset() ); + emit regionInvalidated( r, true ); + emit childUnselected( child ); + } + else + emit invalidated(); + } + else + emit selected( event->selected() ); +} + +void KoView::guiActivateEvent( KParts::GUIActivateEvent * ev ) +{ + showAllStatusBarItems( ev->activated() ); +} + +void KoView::showAllStatusBarItems( bool show ) +{ + KStatusBar * sb = statusBar(); + if ( !sb ) + return; + QValueListIterator<KoViewPrivate::StatusBarItem> it = d->m_statusBarItems.begin(); + for ( ; it != d->m_statusBarItems.end() ; ++it ) + if ( show ) + (*it).ensureItemShown( sb ); + else + (*it).ensureItemHidden( sb ); +} + +void KoView::addStatusBarItem( QWidget * widget, int stretch, bool permanent ) +{ + KoViewPrivate::StatusBarItem item( widget, stretch, permanent ); + d->m_statusBarItems.append(item); + QValueListIterator<KoViewPrivate::StatusBarItem> it = d->m_statusBarItems.fromLast(); + KStatusBar * sb = statusBar(); + Q_ASSERT(sb); + if (sb) + (*it).ensureItemShown( sb ); +} + +void KoView::removeStatusBarItem( QWidget * widget ) +{ + KStatusBar * sb = statusBar(); + QValueListIterator<KoViewPrivate::StatusBarItem> it = d->m_statusBarItems.begin(); + for ( ; it != d->m_statusBarItems.end() ; ++it ) + if ( (*it).widget() == widget ) + { + if ( sb ) + (*it).ensureItemHidden( sb ); + d->m_statusBarItems.remove( it ); + break; + } + if ( it == d->m_statusBarItems.end() ) + kdWarning() << "KoView::removeStatusBarItem. Widget not found : " << widget << endl; +} + +KoDocumentChild *KoView::selectedChild() +{ + if ( !d->m_manager ) + return 0L; + + KParts::Part *selectedPart = d->m_manager->selectedPart(); + + if ( !selectedPart || !selectedPart->inherits( "KoDocument" ) ) + return 0L; + + return koDocument()->child( (KoDocument *)selectedPart ); +} + +KoDocumentChild *KoView::activeChild() +{ + if ( !d->m_manager ) + return 0L; + + KParts::Part *activePart = d->m_manager->activePart(); + + if ( !activePart || !activePart->inherits( "KoDocument" ) ) + return 0L; + + return koDocument()->child( (KoDocument *)activePart ); +} + +void KoView::enableAutoScroll( ) +{ + d->m_scrollTimer->start( 50 ); +} + +void KoView::disableAutoScroll( ) +{ + d->m_scrollTimer->stop(); +} + +void KoView::paintEverything( QPainter &painter, const QRect &rect, bool transparent ) +{ + koDocument()->paintEverything( painter, rect, transparent, this ); +} + +KoViewChild *KoView::child( KoView *view ) +{ + QPtrListIterator<KoViewChild> it( d->m_children ); + for (; it.current(); ++it ) + if ( it.current()->frame()->view() == view ) + return it.current(); + + return 0L; +} + +KoViewChild *KoView::child( KoDocument *doc ) +{ + QPtrListIterator<KoViewChild> it( d->m_children ); + for (; it.current(); ++it ) + if ( it.current()->documentChild()->document() == doc ) + return it.current(); + + return 0L; +} + +QWMatrix KoView::matrix() const +{ + QWMatrix m; + m.scale( zoom(), zoom() ); + //m.translate( canvasXOffset() , canvasYOffset() ); + return m; +} + +void KoView::slotChildActivated( bool a ) +{ + // Only interested in deactivate events + if ( a ) + return; + + KoViewChild* ch = child( (KoView*)sender() ); + if ( !ch ) + return; + + KoView* view = ch->frame()->view(); + + QWidget *activeWidget = view->d->m_tempActiveWidget; + + if ( d->m_manager->activeWidget() ) + activeWidget = d->m_manager->activeWidget(); + + if ( !activeWidget || !activeWidget->inherits( "KoView" ) ) + return; + + // Is the new active view a child of this one ? + // In this case we may not delete! + // QObject *n = d->m_manager->activeWidget(); + QObject *n = activeWidget; + while( n ) + if ( n == (QObject *)view ) + return; + else + n = n->parent(); + + + d->m_tempActiveWidget = activeWidget; + QApplication::setOverrideCursor(waitCursor); + d->m_manager->setActivePart( 0L ); + + QGuardedPtr<KoDocumentChild> docChild = ch->documentChild(); + QGuardedPtr<KoFrame> chFrame = ch->frame(); + if ( docChild && chFrame && chFrame->view() ) + { + docChild->setContentsPos( chFrame->view()->canvasXOffset(), + chFrame->view()->canvasYOffset() ); + docChild->document()->setViewBuildDocument( chFrame->view(), chFrame->view()->xmlguiBuildDocument() ); + } + + d->m_children.remove( ch ); + + d->m_manager->addPart( docChild->document(), false ); // the destruction of the view removed the part from the partmanager. re-add it :) + + QApplication::restoreOverrideCursor(); + + // #### HACK + // We want to delete as many views as possible and this + // trick is used to go upwards in the view-tree. + emit activated( FALSE ); +} + +void KoView::slotChildChanged( KoDocumentChild *child ) +{ + QRegion region( child->oldPointArray( matrix() ) ); + emit regionInvalidated( child->frameRegion( matrix(), true ).unite( region ), true ); +} + +int KoView::autoScrollAcceleration( int offset ) const +{ + if(offset < 40) + return offset; + else + return offset*offset/40; +} + +void KoView::slotAutoScroll( ) +{ + QPoint scrollDistance; + bool actuallyDoScroll = false; + QPoint pos( mapFromGlobal( QCursor::pos() ) ); + + //Provide progressive scrolling depending on the mouse position + if ( pos.y() < topBorder() ) + { + scrollDistance.setY ((int) - autoScrollAcceleration( - pos.y() + topBorder() )); + actuallyDoScroll = true; + } + else if ( pos.y() > height() - bottomBorder() ) + { + scrollDistance.setY ((int) autoScrollAcceleration(pos.y() - height() + bottomBorder() )); + actuallyDoScroll = true; + } + + if ( pos.x() < leftBorder() ) + { + scrollDistance.setX ((int) - autoScrollAcceleration( - pos.x() + leftBorder() )); + actuallyDoScroll = true; + } + else if ( pos.x() > width() - rightBorder() ) + { + scrollDistance.setX ((int) autoScrollAcceleration( pos.x() - width() + rightBorder() )); + actuallyDoScroll = true; + } + + if ( actuallyDoScroll ) + { + int state=0; +#if KDE_IS_VERSION(3,4,0) + state = kapp->keyboardMouseState(); +#endif + + pos = canvas()->mapFrom(this, pos); + QMouseEvent * event = new QMouseEvent(QEvent::MouseMove, pos, 0, state); + + QApplication::postEvent( canvas(), event ); + emit autoScroll( scrollDistance ); + } +} + + +void KoView::setupGlobalActions() { + actionNewView = new KAction( i18n( "&New View" ), "window_new", 0, + this, SLOT( newView() ), + actionCollection(), "view_newview" ); +} + +void KoView::setupPrinter( KPrinter & ) +{ + kdDebug() << "KoView::setupPrinter not implemented by the application!" << endl; +} + +void KoView::print( KPrinter & ) +{ + kdDebug() << "KoView::print not implemented by the application!" << endl; +} + +void KoView::newView() { + assert( ( d!=0L && d->m_doc ) ); + + KoDocument *thisDocument = d->m_doc; + KoMainWindow *shell = new KoMainWindow( thisDocument->instance() ); + shell->setRootDocument(thisDocument); + shell->show(); +} + +bool KoView::isInOperation() const +{ + return d->m_inOperation; +} + +void KoView::beginOperation() +{ + d->m_inOperation = true; + canvas()->setUpdatesEnabled(FALSE); +} + +void KoView::endOperation() +{ + canvas()->setUpdatesEnabled(TRUE); + d->m_inOperation = false; + +// canvas()->update(); +} + +KoMainWindow * KoView::shell() const +{ + return dynamic_cast<KoMainWindow *>( topLevelWidget() ); +} + +KMainWindow * KoView::mainWindow() const +{ + return dynamic_cast<KMainWindow *>( topLevelWidget() ); +} + +KStatusBar * KoView::statusBar() const +{ + KoMainWindow *mw = shell(); + return mw ? mw->statusBar() : 0L; +} + +void KoView::slotActionStatusText( const QString &text ) +{ + KStatusBar *sb = statusBar(); + if ( sb ) + sb->message( text ); +} + +void KoView::slotClearStatusText() +{ + KStatusBar *sb = statusBar(); + if ( sb ) + sb->clear(); +} + +DCOPObject *KoView::dcopObject() +{ + if ( !d->m_dcopObject ) + d->m_dcopObject = new KoViewIface( this ); + return d->m_dcopObject; +} + +class KoViewChild::KoViewChildPrivate +{ +public: + KoViewChildPrivate() + { + } + ~KoViewChildPrivate() + { + } +}; + +KoViewChild::KoViewChild( KoDocumentChild *child, KoView *_parentView ) +{ + d = new KoViewChildPrivate; + m_parentView = _parentView; + m_child = child; + + m_frame = new KoFrame( parentView()->canvas() ); + KoView *view = child->document()->createView( m_frame ); + view->setXMLGUIBuildDocument( child->document()->viewBuildDocument( view ) ); + + view->setPartManager( parentView()->partManager() ); + + // hack? (Werner) + view->setZoom( parentView()->zoom() * QMAX(child->xScaling(), child->yScaling()) ); + + m_frame->setView( view ); + m_frame->show(); + m_frame->raise(); + + parentView()->canvasAddChild( this ); + + + /* + KoViewChild has basically three geometries to keep in sync. + - The KoDocumentChild geometry (i.e. the embedded object's geometry, unzoomed) + - Its own geometry (used for hit-test etc.) + - The KoFrame geometry (the graphical widget for moving the object when active) + + So we need to subtract the scrollview's offset for the frame geometry, since it's a widget. + + The rules are + (R1) frameGeometry = viewGeometry(childGeometry) "+" m_frame->{left|right|top|bottom}Border() - scrollview offset, + (R2) frameGeometry = myGeometry "+" active_frame_border - scrollview offset. + + So: (R3, unused) myGeometry = viewGeometry(childGeometry) "+" m_frame->{left|right|top|bottom}Border() "-" active_frame_border + + Notes: active_frame_border is m_frame->border() (0 when inactive, 5 when active). + {left|right|top|bottom}Border are the borders used in kspread (0 when inactive, big when active). + "+" border means we add a border, so it's a subtraction on x, y and an addition on width, height. + + viewGeometry() applies the zoom as well as any other translation the app might want to do + */ + + // Setting the frameGeometry is done in setInitialFrameGeometry, which is + // also called right after activation. + + connect( view, SIGNAL( activated( bool ) ), + parentView(), SLOT( slotChildActivated( bool ) ) ); +} + +KoViewChild::~KoViewChild() +{ + if ( m_frame ) + { + slotFrameGeometryChanged(); + delete static_cast<KoFrame *>( m_frame ); + } + delete d; +} + +void KoViewChild::slotFrameGeometryChanged() +{ + // Set our geometry from the frame geometry (R2 reversed) + QRect geom = m_frame->geometry(); + int b = m_frame->border(); + QRect borderRect( geom.x() + b + parentView()->canvasXOffset(), + geom.y() + b + parentView()->canvasYOffset(), + geom.width() - b * 2, + geom.height() - b * 2 ); + setGeometry( borderRect ); + + if(m_child) + { + // Set the child geometry from the frame geometry (R1 reversed) + QRect borderLessRect( geom.x() + m_frame->leftBorder() + parentView()->canvasXOffset(), + geom.y() + m_frame->topBorder() + parentView()->canvasYOffset(), + geom.width() - m_frame->leftBorder() - m_frame->rightBorder(), + geom.height() - m_frame->topBorder() - m_frame->bottomBorder() ); + + // We don't want to trigger slotDocGeometryChanged again + lock(); + QRect childGeom = parentView()->reverseViewTransformations( borderLessRect ); + kdDebug() << "KoChild::slotFrameGeometryChanged child geometry " + << ( geometry() == childGeom ? "already " : "set to " ) + << childGeom << endl; + m_child->setGeometry( childGeom ); + unlock(); + } +} + +void KoViewChild::slotDocGeometryChanged() +{ + if ( locked() ) + return; + // Set frame geometry from child geometry (R1) + // The frame's resizeEvent will call slotFrameGeometryChanged. + QRect geom = parentView()->applyViewTransformations( m_child->geometry() ); + QRect borderRect( geom.x() - m_frame->leftBorder() - parentView()->canvasXOffset(), + geom.y() - m_frame->topBorder() - parentView()->canvasYOffset(), + geom.width() + m_frame->leftBorder() + m_frame->rightBorder(), + geom.height() + m_frame->topBorder() + m_frame->bottomBorder() ); + kdDebug() << "KoViewChild::slotDocGeometryChanged frame geometry " + << ( m_frame->geometry() == borderRect ? "already " : "set to " ) + << borderRect << endl; + + m_frame->setGeometry( borderRect ); +} + +QPoint KoView::applyViewTransformations( const QPoint& p ) const +{ + return QPoint( qRound( p.x() * zoom() ), qRound( p.y() * zoom() ) ); +} + +QPoint KoView::reverseViewTransformations( const QPoint& v ) const +{ + return QPoint( qRound( v.x() / zoom() ), qRound( v.y() / zoom() ) ); +} + +QRect KoView::applyViewTransformations( const QRect& r ) const +{ + return QRect( applyViewTransformations( r.topLeft() ), + applyViewTransformations( r.bottomRight() ) ); +} + +QRect KoView::reverseViewTransformations( const QRect& r ) const +{ + return QRect( reverseViewTransformations( r.topLeft() ), + reverseViewTransformations( r.bottomRight() ) ); +} + +void KoViewChild::setInitialFrameGeometry() +{ + kdDebug() << k_funcinfo << endl; + + // Connect only now, so that the GUI building doesn't move us around. + connect( m_frame, SIGNAL( geometryChanged() ), + this, SLOT( slotFrameGeometryChanged() ) ); + connect( m_child, SIGNAL( changed( KoChild * ) ), + this, SLOT( slotDocGeometryChanged() ) ); + + // Set frameGeometry from childGeometry + slotDocGeometryChanged(); + // Set myGeometry from frameGeometry + slotFrameGeometryChanged(); + +} + +#include "KoView.moc" + diff --git a/lib/kofficecore/KoView.h b/lib/kofficecore/KoView.h new file mode 100644 index 00000000..c2be52ec --- /dev/null +++ b/lib/kofficecore/KoView.h @@ -0,0 +1,502 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@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 __koView_h__ +#define __koView_h__ + +#include <qwidget.h> +#include <qguardedptr.h> + +#include <kparts/part.h> + +#include <KoChild.h> +#include <koffice_export.h> + +class KAction; +class KActionCollection; +class QCustomEvent; +class KoDocument; +class KoMainWindow; +class KMainWindow; +class KoViewPrivate; +class KoViewChild; +class KoFrame; +class KStatusBar; +class KInstance; +class KPrinter; +class KoDocumentChild; +class DCOPObject; + +namespace KParts +{ + class PartManager; + class PartActivateEvent; + class PartSelectEvent; +} + +/** + * This class is used to display a @ref KoDocument. + * + * Multiple views can be attached to one document at a time. + */ +class KOFFICECORE_EXPORT KoView : public QWidget, public KParts::PartBase +{ + Q_OBJECT +public: + /** + * Creates a new view for the document. Usually you don't create views yourself + * since the KOffice components come with their own view classes which inherit + * KoView. + * + * The standard way to retrieve a KoView is to call @ref KoDocument::createView. + * + * @param document is the document which should be displayed in this view. This pointer + * must not be zero. + * @param parent parent widget for this view. + * @param name Name of the view. The name is used in DCOP, so the name should + * match the pattern [A-Za-z_][A-Za-z_0-9]*. + * + */ + KoView( KoDocument *document, QWidget *parent = 0, const char *name = 0 ); + /** + * Destroys the view and unregisters at the document. + */ + virtual ~KoView(); + + /** + * Retrieves the document object of this view. + */ + KoDocument *koDocument() const; + + /** + * Tells this view that its document has got deleted (called internally) + */ + void setDocumentDeleted(); + /** + * @return true if the document has already got deleted. + * This can be useful for the view destructor to know if it can + * access the document or not. + */ + bool documentDeleted() const; + + virtual void setPartManager( KParts::PartManager *manager ); + virtual KParts::PartManager *partManager() const; + + /** + * Returns the action described action object. In fact only the "name" attribute + * of @p element is of interest here. The method searches in the + * KActionCollection of this view. + * + * Please notice that KoView indirectly inherits KXMLGUIClient. + * + * @see KXMLGUIClient + * @see KXMLGUIClient::actionCollection + * @see KoDocument::action + */ + virtual KAction *action( const QDomElement &element ) const; + + /** + * Retrieves the document that is hit. This can be an embedded document. + * + * The default implementation asks @ref KoDocument::hitTest. This + * will iterate over all child documents to detect a hit. + * + * If your koffice component has multiple pages, like for example KSpread, then the hittest + * may not succeed for a child that is not on the visible page. In those + * cases you need to reimplement this method. + */ + virtual KoDocument *hitTest( const QPoint &pos ); + + /** + * Retrieves the left border width that is displayed around the content if + * the view is active. + * + * In a spread sheet this border is for example used to display the + * rows, while a top border is used to display the names of the cells + * and a right and bottom border is used to display scrollbars. If the view + * becomes inactive, then this stuff is not displayed anymore. + * + * @ref KoFrame uses this border information. If an embedded document becomes active + * then it is resized so that it has enough space to display the borders and to + * display the same content as before the activation. + * So if for example all of your borders are 20 pixels, then activating the embedded + * document causes the KoView to move 20 pixels up/left and the size and width increases + * by 20+20 pixels each. + * + * The default border is 0. + */ + virtual int leftBorder() const; + /** + * @see #leftBorder + */ + virtual int rightBorder() const; + /** + * @see #leftBorder + */ + virtual int topBorder() const; + /** + * @see #leftBorder + */ + virtual int bottomBorder() const; + + /** + * Scales the view on the content. This does not affect the contents + * data structures. You can use this mechanism to implement a zoom + * for example. + * + * The method calls QWidget::update so that the scaled content + * is automatically displayed. + * + * The default scaling is 1.0 in both orientations. + */ + virtual void setZoom( double zoom ); + /** + * @return the current scaling factor (zoom level) + * + * @see #setZoom + */ + virtual double zoom() const; + + /** + * Overload this function if the content will be displayed + * on some child widget instead of the view directly. + * + * By default this function returns a pointer to the view. + */ + virtual QWidget *canvas() const; + + /** + * Overload this function if the content will be displayed + * with an offset relative to the upper left corner + * of the canvas widget. + * + * By default this function returns 0. + */ + virtual int canvasXOffset() const; + + /** + * Overload this function if the content will be displayed + * with an offset relative to the upper left corner + * of the canvas widget. + * + * By default this function returns 0. + */ + virtual int canvasYOffset() const; + + /** + * Overload this function if you need to perform some actions + * after KoView (the part widget) is inserted into canvas. + * You should call for example addChild(QWidget*) method + * of QScrollView here, if canvas is a viewport of QScrollView. + * + * By default this function does nothing. + */ + virtual void canvasAddChild( KoViewChild *child ); + + /** + * @return the selected child. The function returns 0 if + * no direct child is currently selected. + */ + virtual KoDocumentChild *selectedChild(); + + /** + * @return the active child. The function returns 0 if + * no direct child is currently active. + */ + virtual KoDocumentChild *activeChild(); + + /** + * Sets up so that autoScroll signals are emitted when the mouse pointer is outside the view + */ + void enableAutoScroll(); + + /** + * Stops the emitting of autoScroll signals + */ + void disableAutoScroll(); + + /** + * calls KoDocument::paintEverything() + */ + virtual void paintEverything( QPainter &painter, const QRect &rect, bool transparent = false ); + + /** + * @return TRUE if the document @p doc is represented in this view by + * some KoViewChild. + * + * This is just a convenience function for @ref #child. + */ + bool hasDocumentInWindow( KoDocument *doc ); + + /** + * Returns the matrix which is used by the view to transform the content. + * Currently only scaling is supported. + * + * The matrix changes when calling @ref #setZoom. + * + * @deprecated, use applyViewTransformations / reverseViewTransformations instead. + */ + virtual QWMatrix matrix() const KDE_DEPRECATED; + + /** + * Apply the transformations that the view makes to its contents. + * This is used for embedded objects. + * By default this simply applies the zoom(). + * Reimplement to add some translation if needed (e.g. to center the page) + */ + virtual QPoint applyViewTransformations( const QPoint& ) const; + + /** + * Reverse the transformations that the view makes to its contents, + * i.e. undo the transformations done by applyViewTransformations(). + * This is used for embedded objects. + * By default this simply unzooms the point. + * Reimplement to add some translation if needed (e.g. to center the page) + */ + virtual QPoint reverseViewTransformations( const QPoint& ) const; + + /** + * Overload for QRect, usually it's not needed to reimplement this one. + */ + virtual QRect applyViewTransformations( const QRect& ) const; + + /** + * Overload for QRect, usually it's not needed to reimplement this one. + */ + virtual QRect reverseViewTransformations( const QRect& ) const; + + /** + * @return the KoViewChild which is responsible for the @p view or 0. + * + * This method does no recursion. + */ + KoViewChild *child( KoView *view ); + /** + * A convenience function which returns the KoViewChild which in turn holds the + * @ref KoView that in turn holds the @p document. + */ + KoViewChild *child( KoDocument *document ); + + /** + * Return a DCOP interface for this view + * KOffice Applications are strongly recommended to reimplement this method, + * so that their dcop interface provides more functionality than the basic KoViewIface + */ + virtual DCOPObject * dcopObject(); + + /** + * Overload this method to setup KPrinter before the actual printing. + * + * @see #print + */ + virtual void setupPrinter( KPrinter &printer ); + + // BCI: make it return a bool, so that aborting doesn't still fire up the print preview afterwards + /** + * Overload this method with your own printing code. + */ + virtual void print( KPrinter &printer ); + + /** + * @return the KoMainWindow in which this view is currently. + * WARNING: this could be 0L, if the main window isn't a koffice main window. + * (e.g. it can be any KParts application). + */ + KoMainWindow * shell() const; + + /** + * @return the KMainWindow in which this view is currently. + * This one should never return 0L, in a KDE app. + */ + KMainWindow* mainWindow() const; + + /** + * @return the statusbar of the KoMainWindow in which this view is currently. + * WARNING: this could be 0L, if the main window isn't a koffice main window. + * (e.g. it can be any KParts application). + */ + KStatusBar * statusBar() const; + + /** + * This adds a widget to the statusbar for this view. + * If you use this method instead of using statusBar() directly, + * KoView will take care of removing the items when the view GUI is deactivated + * and readding them when it is reactivated. + * The parameters are the same as QStatusBar::addWidget(). + * + * Note that you can't use KStatusBar methods (inserting text items by id). + * But you can create a KStatusBarLabel with a dummy id instead, and use + * it directly, to get the same look and feel. + */ + void addStatusBarItem( QWidget * widget, int stretch = 0, bool permanent = false ); + + /** + * Remove a widget from the statusbar for this view. + */ + void removeStatusBarItem( QWidget * widget ); + + /** + * Show or hide all statusbar items. Used by KoMainWindow during saving. + */ + void showAllStatusBarItems( bool show ); + + /** + * You have to implement this method and disable/enable certain functionality (actions for example) in + * your view to allow/disallow editing of the document. + */ + virtual void updateReadWrite( bool readwrite ) = 0; + + /** + * Check to see if the view is currently in the middle of an operation which means + * that there will be no screen refreshes until a signal from the document hits + * the @ref #endOperation slot + */ + bool isInOperation() const; + +public slots: + /** + * Slot to create a new view around the contained @ref #koDocument. + */ + virtual void newView(); + + /** + * Slot to allow code to signal the beginning of an operation where the screen should + * not update until it is done. + * + * @see #endOperation + */ + virtual void beginOperation(); + + /** + * Slot to allow code to signal the end of an operation where the screen should + * not have been updating. So now it will update. + * + * @see #beginOperation + */ + virtual void endOperation(); + + /** + * Display a message in the status bar (calls QStatusBar::message()) + * @todo rename to something more generic + */ + void slotActionStatusText( const QString &text ); + + /** + * End of the message in the status bar (calls QStatusBar::clear()) + * @todo rename to something more generic + */ + void slotClearStatusText(); + +protected: + /** + * This method handles three events: KParts::PartActivateEvent, KParts::PartSelectEvent + * and KParts::GUIActivateEvent. + * The respective handlers are called if such an event is found. + */ + virtual void customEvent( QCustomEvent *ev ); + + /** + * Handles the event KParts::PartActivateEvent. + */ + virtual void partActivateEvent( KParts::PartActivateEvent *event ); + /** + * Handles the event KParts::PartSelectEvent. + */ + virtual void partSelectEvent( KParts::PartSelectEvent *event ); + /** + * Handles the event KParts::GUIActivateEvent. + */ + virtual void guiActivateEvent( KParts::GUIActivateEvent * ); + +signals: + void activated( bool active ); + void selected( bool select ); + + void autoScroll(const QPoint &scrollDistance); + + void childSelected( KoDocumentChild *child ); + void childUnselected( KoDocumentChild *child ); + + void childActivated( KoDocumentChild *child ); + void childDeactivated( KoDocumentChild *child ); + + void regionInvalidated( const QRegion ®ion, bool erase ); + + void invalidated(); + +// KDE invents public signals :) +#undef signals +#define signals public +signals: + + /** + * Make it possible for plugins to request + * the embedding of an image into the current + * document. Used e.g. by the scan-plugin + */ + void embeddImage(const QString &filename); + +#undef signals +#define signals protected + +protected slots: + virtual void slotChildActivated( bool a ); + virtual void slotChildChanged( KoDocumentChild *child ); + virtual void slotAutoScroll( ); + +private: + KAction *actionNewView; + virtual void setupGlobalActions( void ); + KoViewPrivate *d; + int autoScrollAcceleration( int offset ) const; +}; + +/** + * This class represents an active embedded document. + */ +class KoViewChild : public KoChild +{ + Q_OBJECT +public: + KoViewChild( KoDocumentChild *child, KoView *_parentView ); + virtual ~KoViewChild(); + + KoDocumentChild *documentChild() const { return m_child; } + KoView *parentView() const { return m_parentView; } + KoFrame *frame() const { return m_frame; } + + void setInitialFrameGeometry(); + +public slots: + + // Call this when the view transformations change + void reposition() { slotDocGeometryChanged(); } + +private slots: + void slotFrameGeometryChanged(); + void slotDocGeometryChanged(); + +private: + QGuardedPtr<KoDocumentChild> m_child; + QGuardedPtr<KoFrame> m_frame; + QGuardedPtr<KoView> m_parentView; + class KoViewChildPrivate; + KoViewChildPrivate *d; +}; + +#endif diff --git a/lib/kofficecore/KoViewIface.cc b/lib/kofficecore/KoViewIface.cc new file mode 100644 index 00000000..378db1e6 --- /dev/null +++ b/lib/kofficecore/KoViewIface.cc @@ -0,0 +1,76 @@ +/* This file is part of the KDE project + Copyright (c) 2000 Simon Hausmann <hausmann@kde.org> + + $Id: KoViewIface.cc 508787 2006-02-12 18:28:12Z ingwa $ + + 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 "KoViewIface.h" + +#include "KoView.h" + +#include <kapplication.h> +#include <dcopclient.h> +#include <kdcopactionproxy.h> +#include <kaction.h> + +//static +QCString KoViewIface::newIfaceName() +{ + static int s_viewIFNumber = 0; + QCString name; name.setNum( s_viewIFNumber++ ); name.prepend("View-"); + return name; +} + +KoViewIface::KoViewIface( KoView *view ) + : DCOPObject( newIfaceName() ) +{ + m_actionProxy = new KDCOPActionProxy( view->actionCollection(), this ); +} + +KoViewIface::KoViewIface( const char *name, KoView *view ) + : DCOPObject( name ) +{ + m_pView = view; + m_actionProxy = new KDCOPActionProxy( view->actionCollection(), this ); +} + +KoViewIface::~KoViewIface() +{ + delete m_actionProxy; +} + +DCOPRef KoViewIface::action( const QCString &name ) +{ + return DCOPRef( kapp->dcopClient()->appId(), m_actionProxy->actionObjectId( name ) ); +} + +QCStringList KoViewIface::actions() +{ + QCStringList res; + QValueList<KAction *> lst = m_actionProxy->actions(); + QValueList<KAction *>::ConstIterator it = lst.begin(); + QValueList<KAction *>::ConstIterator end = lst.end(); + for (; it != end; ++it ) + res.append( (*it)->name() ); + + return res; +} + +QMap<QCString,DCOPRef> KoViewIface::actionMap() +{ + return m_actionProxy->actionMap(); +} diff --git a/lib/kofficecore/KoViewIface.h b/lib/kofficecore/KoViewIface.h new file mode 100644 index 00000000..1d01af64 --- /dev/null +++ b/lib/kofficecore/KoViewIface.h @@ -0,0 +1,56 @@ +/* This file is part of the KDE project + Copyright (c) 2000 Simon Hausmann <hausmann@kde.org> + + $Id: KoViewIface.h 466447 2005-10-02 17:54:10Z zander $ + + 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 __KoViewIface_h__ +#define __KoViewIface_h__ + +#include <dcopobject.h> +#include <dcopref.h> + +#include "koffice_export.h" + +class KDCOPActionProxy; +class KoView; + +class KOFFICECORE_EXPORT KoViewIface : public DCOPObject +{ + K_DCOP +public: + KoViewIface( KoView *view ); + KoViewIface( const char *name, KoView *view ); + // TODO same args order as KoDocumentIface + + virtual ~KoViewIface(); + + // Generate a name for this interface. Automatically used if + // the first constructor is used. + static QCString newIfaceName(); + +k_dcop: + DCOPRef action( const QCString &name ); + QCStringList actions(); + QMap<QCString,DCOPRef> actionMap(); + +protected: + KoView *m_pView; + KDCOPActionProxy *m_actionProxy; +}; + +#endif diff --git a/lib/kofficecore/KoXmlNS.cpp b/lib/kofficecore/KoXmlNS.cpp new file mode 100644 index 00000000..769ee653 --- /dev/null +++ b/lib/kofficecore/KoXmlNS.cpp @@ -0,0 +1,44 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <faure@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 "KoXmlNS.h" + +const char* const KoXmlNS::office = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; +const char* const KoXmlNS::meta = "urn:oasis:names:tc:opendocument:xmlns:meta:1.0"; +const char* const KoXmlNS::config = "urn:oasis:names:tc:opendocument:xmlns:config:1.0"; +const char* const KoXmlNS::text = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; +const char* const KoXmlNS::table = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"; +const char* const KoXmlNS::draw = "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"; +const char* const KoXmlNS::presentation = "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0"; +const char* const KoXmlNS::dr3d = "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0"; +const char* const KoXmlNS::chart = "urn:oasis:names:tc:opendocument:xmlns:chart:1.0"; +const char* const KoXmlNS::form = "urn:oasis:names:tc:opendocument:xmlns:form:1.0"; +const char* const KoXmlNS::script = "urn:oasis:names:tc:opendocument:xmlns:script:1.0"; +const char* const KoXmlNS::style = "urn:oasis:names:tc:opendocument:xmlns:style:1.0"; +const char* const KoXmlNS::number = "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"; +const char* const KoXmlNS::manifest = "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"; + +const char* const KoXmlNS::math = "http://www.w3.org/1998/Math/MathML"; +const char* const KoXmlNS::svg = "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"; +const char* const KoXmlNS::fo = "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"; +const char* const KoXmlNS::dc = "http://purl.org/dc/elements/1.1/"; +const char* const KoXmlNS::xlink = "http://www.w3.org/1999/xlink"; +// const char* const KoXmlNS::smil = "urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0"; + +const char* const KoXmlNS::koffice = "http://www.koffice.org/2005/"; diff --git a/lib/kofficecore/KoXmlNS.h b/lib/kofficecore/KoXmlNS.h new file mode 100644 index 00000000..410519b8 --- /dev/null +++ b/lib/kofficecore/KoXmlNS.h @@ -0,0 +1,55 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <faure@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 KOXMLNS_H +#define KOXMLNS_H + +#include <koffice_export.h> +/** + * Repository of XML namespaces used for OASIS documents. + * Note: if we have code which needs often those as QStrings, then maybe + * we need static const QString& versions of them too. Needs an init() though. + */ +class KOFFICECORE_EXPORT KoXmlNS { +public: + static const char* const office; + static const char* const meta; + static const char* const config; + static const char* const text; + static const char* const table; + static const char* const draw; + static const char* const presentation; + static const char* const dr3d; + static const char* const chart; + static const char* const form; + static const char* const script; + static const char* const style; + static const char* const number; + static const char* const manifest; + + static const char* const math; + static const char* const svg; + static const char* const fo; + static const char* const dc; + static const char* const xlink; + + static const char* const koffice; +}; + +#endif /* KOXMLNS_H */ diff --git a/lib/kofficecore/KoXmlReader.cpp b/lib/kofficecore/KoXmlReader.cpp new file mode 100644 index 00000000..ae1e2ae6 --- /dev/null +++ b/lib/kofficecore/KoXmlReader.cpp @@ -0,0 +1,1568 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Ariya Hidayat <ariya@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. +*/ + +/* + This is a memory-efficient DOM implementation for KOffice. See the API + documentation for details. + + IMPORTANT ! + + * When you change this stuff, make sure it DOES NOT BREAK the test suite. + Build tests/koxmlreadertest.cpp and verify it. Many sleepless nights + have been sacrificed for this piece of code, do not let those precious + hours wasted! + + * Run testdom.cpp WITH Valgrind's memcheck tool and make sure NO illegal + memory read/write and any type of leak occurs. If you are not familiar + with Valgrind then RTFM first and come back again later on. + + * The public API shall remain as compatible as QDom. + + * All QDom-compatible methods should behave the same. All QDom-compatible + functions should return the same result. In case of doubt, run + koxmlreadertest.cpp but uncomment KOXML_USE_QDOM in koxmlreader.h + so that the tests are performed with standard QDom. + + Some differences compared to QDom: + + - DOM tree in KoXmlDocument is read-only, you can not modify it. This is + sufficient for KOffice since the tree is only accessed when loading + a document to the application. For saving the document to XML file, + use KoXmlWriter. + + - Comment node (like QDomComment) is not implemented as comments are + simply ignored. + + - DTD, entity and entity reference are not handled. Thus, the associated + nodes (like QDomDocumentType, QDomEntity, QDomEntityReference) are also + not implemented. + + - Attribute mapping node is not implemented. But of course, functions to + query attributes of an element are available. + + + */ + +#include "KoXmlReader.h" + +#ifndef KOXML_USE_QDOM + +#include <qxml.h> +#include <qdom.h> + +#include <qmap.h> +#include <qcstring.h> + +// double QString, used for hashing against namespace and qualified name pair +class DQString +{ +public: + DQString() { } + DQString( const QString& s1, const QString& s2 ){ str1 = s1; str2 = s2; } + DQString( const DQString& b ) { str1 = b.str1; str2 = b.str2; } + DQString& operator=( const DQString& b ){ str1 = b.str1; str2 = b.str2; return *this; } + bool operator==( const DQString& b ) const { return (str1==b.str1) && (str2==b.str2); } + bool operator!=( const DQString& b ) const { return (str1!=b.str1) || (str2!=b.str2); } + bool operator<( const DQString& b ) const + { return ( str1 < b.str1 ) ? true : ( str1==b.str1 ) ? str2<b.str2 : false; } + QString s1() const { return str1; } + QString s2() const { return str2; } +private: + QString str1, str2; +}; + +// just for completeness, this is the self-test for DQString above +#if 0 + DQString b1; + DQString b2; + CHECK( b1==b2, true ); + CHECK( b1!=b2, false ); + + b1 = DQString( "sweet","princess" ); + b2 = DQString( "sweet","princess" ); + CHECK( b1==b2, true ); + CHECK( b1!=b2, false ); + + b1 = DQString( "sweet","princess" ); + b2 = DQString( "bad","prince" ); + CHECK( b1==b2, false ); + CHECK( b1!=b2, true ); +#endif + +class KoXmlStream +{ +public: + KoXmlStream(){ saveData = true; data.reserve(1024); pos = 0; } + QString stringData() const { return data; } + void setSaveData( bool s ){ saveData = s; } + int at() const { return pos; } + KoXmlStream& operator<<( const QString& str ) + { if(saveData) data.append(str); pos+=str.length(); return *this; } + KoXmlStream& appendEscape( const QString& str ); + +private: + bool saveData; + QString data; + int pos; +}; + +KoXmlStream& KoXmlStream::appendEscape( const QString& str ) +{ + unsigned len = str.length(); + + if( saveData ) + { + data.reserve( data.length() + len ); + for( unsigned c=0; c<len; c++ ) + if( str[c]=='<' ){ data.append( "<"); pos += 4; } else + if( str[c]=='>'){ data.append( ">"); pos+= 4; } else + if( str[c]=='"'){ data.append( """); pos += 6; } else + if( str[c]=='&'){ data.append( "&"); pos += 5; } else + { data.append( str[c] ); pos++; } + } + else + { + pos += len; + for( unsigned c=0; c<len; c++ ) + if( str[c]=='<' ) pos += 3; else // "<" + if( str[c]=='>') pos+= 3; else // ">" + if( str[c]=='"') pos += 5; else // """ + if( str[c]=='&') pos += 4; // "&" + } + + return *this; +} + +class KoXmlNodeData +{ +public: + + KoXmlNodeData(); + virtual ~KoXmlNodeData(); + + // generic properties + KoXmlNode::NodeType nodeType; + QString tagName; + QString namespaceURI; + QString prefix; + QString localName; + + // reference counting + unsigned long count; + void ref() { count++; } + void unref() { --count; if( !count ) delete this; } + + // type information + virtual const char* typeInfo() const { return "Node"; } + QString nodeName() const; + + // for tree and linked-list + KoXmlNodeData* parent; + KoXmlNodeData* prev; + KoXmlNodeData* next; + KoXmlNodeData* first; + KoXmlNodeData* last; + + QString text(); + + // node manipulation + void appendChild( KoXmlNodeData* child ); + virtual void clear(); + KoXmlNodeData* ownerDocument(); + + // attributes + void setAttribute( const QString& name, const QString& value ); + QString attribute( const QString& name ); + bool hasAttribute( const QString& name ); + void setAttributeNS( const QString& nsURI, const QString& name, const QString& value ); + QString attributeNS( const QString& nsURI, const QString& name ); + bool hasAttributeNS( const QString& nsURI, const QString& name ); + + // for text and CDATA + QString data() const; + void setData( const QString& data ); + + // for document node + QXmlSimpleReader* xmlReader; + QString buffer; + bool setContent( QXmlInputSource* source, QXmlReader* reader, + QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0 ); + + // used when doing on-demand (re)parse + bool loaded; + unsigned startPos, endPos; + void loadChildren( int depth=1 ); + void unloadChildren(); + bool fastLoading; + +private: + QMap<QString,QString> attr; + QMap<DQString,QString> attrNS; + QString textData; + friend class KoXmlHandler; +}; + +class KoXmlHandler : public QXmlDefaultHandler +{ +public: + KoXmlHandler( KoXmlNodeData*, bool processNamespace ); + ~KoXmlHandler(); + + void setMaxDepth( int d ){ maxDepth = d; } + void setInitialOffset( int ofs ){ parseOffset = ofs; } + + // content handler + bool startDocument(); + bool endDocument(); + + bool startElement( const QString& nsURI, const QString& localName, + const QString& qName, const QXmlAttributes& atts ); + bool endElement( const QString& nsURI, const QString& localName, + const QString& qName ); + + bool characters( const QString& ch ); + bool processingInstruction( const QString& target, const QString& data ); + bool skippedEntity( const QString& name ); + + // lexical handler + bool startCDATA(); + bool endCDATA(); + bool startEntity( const QString & ); + bool endEntity( const QString & ); + bool startDTD( const QString& name, const QString& publicId, + const QString& systemId ); + bool comment( const QString& ch ); + + // decl handler + bool externalEntityDecl( const QString &name, const QString &publicId, + const QString &systemId ) ; + + // DTD handler + bool notationDecl( const QString & name, const QString & publicId, + const QString & systemId ); + bool unparsedEntityDecl( const QString &name, const QString &publicId, + const QString &systemId, const QString ¬ationName ) ; + + // error handler + bool fatalError( const QXmlParseException& exception ); + + QString errorMsg; + int errorLine; + int errorColumn; + +private: + bool processNamespace; + KoXmlNodeData* rootNode; + KoXmlNodeData* currentNode; + QString entityName; + bool cdata; + int parseOffset; + KoXmlStream bufferStream; + int elementDepth; + int maxDepth; +}; + +// ================================================================== +// +// KoXmlNodeData +// +// ================================================================== + +KoXmlNodeData::KoXmlNodeData() +{ + nodeType = KoXmlNode::NullNode; + + tagName = QString::null; + prefix = QString::null; + localName = QString::null; + namespaceURI = QString::null; + textData = QString::null; + + count = 1; + parent = 0; + prev = next = 0; + first = last = 0; + + xmlReader = 0; + startPos = endPos = 0; + + fastLoading = false; + + // assume true, it will be set to false by XML parser when this node + // apparently has children AND the children are not loaded + loaded = true; +} + +KoXmlNodeData::~KoXmlNodeData() +{ + clear(); +} + +void KoXmlNodeData::clear() +{ + if( first ) + for( KoXmlNodeData* node = first; node ; ) + { + KoXmlNodeData* next = node->next; + node->unref(); + node = next; + } + + nodeType = KoXmlNode::NullNode; + tagName = QString::null; + prefix = QString::null; + namespaceURI = QString::null; + textData = QString::null; + + attr.clear(); + attrNS.clear(); + + parent = 0; + prev = next = 0; + first = last = 0; + + delete xmlReader; + xmlReader = 0; + buffer = QString::null; +} + +QString KoXmlNodeData::text() +{ + QString t( "" ); + + loadChildren(); + + KoXmlNodeData* node = first; + while ( node ) + { + switch( node->nodeType ) + { + case KoXmlNode::ElementNode: + t += node->text(); break; + case KoXmlNode::TextNode: + t += node->data(); break; + case KoXmlNode::CDATASectionNode: + t += node->data(); break; + default: break; + } + node = node->next; + } + + return t; +} + +QString KoXmlNodeData::nodeName() const +{ + QString n; + + switch( nodeType ) + { + case KoXmlNode::ElementNode: + n = tagName; + if (!prefix.isEmpty()) n.prepend(":").prepend(prefix); break; + case KoXmlNode::TextNode: return QString("#text"); + case KoXmlNode::CDATASectionNode: return QString("#cdata-section"); + case KoXmlNode::DocumentNode: return QString("#document"); + default: break; + } + + return n; +} + +KoXmlNodeData* KoXmlNodeData::ownerDocument() +{ + KoXmlNodeData* owner = this; + + while( owner->parent ) + owner = owner->parent; + + return (owner->nodeType==KoXmlNode::DocumentNode) ? owner : 0; +} + +void KoXmlNodeData::appendChild( KoXmlNodeData* node ) +{ + node->parent = this; + if( !last ) + first = last = node; + else + { + last->next = node; + node->prev = last; + node->next = 0; + last = node; + } +} + +void KoXmlNodeData::setAttribute( const QString& name, const QString& value ) +{ + attr[ name ] = value; +} + +QString KoXmlNodeData::attribute( const QString& name ) +{ + return attr[ name ]; +} + +bool KoXmlNodeData::hasAttribute( const QString& name ) +{ + return attr.contains( name ); +} + +void KoXmlNodeData::setAttributeNS( const QString& nsURI, +const QString& name, const QString& value ) +{ + QString prefix; + QString localName = name; + int i = name.find( ':' ); + if( i != -1 ) + { + localName = name.mid( i + 1 ); + prefix = name.left( i ); + } + + if( prefix.isNull() ) return; + + DQString key( nsURI, localName ); + attrNS[ key ] = value; +} + +QString KoXmlNodeData::attributeNS( const QString& nsURI, const QString& name ) +{ + DQString key( nsURI, name ); + return attrNS[ key ]; +} + +bool KoXmlNodeData::hasAttributeNS( const QString& nsURI, const QString& name ) +{ + DQString key( nsURI, name ); + return attrNS.contains( key ); +} + +QString KoXmlNodeData::data() const +{ + return textData; +} + +void KoXmlNodeData::setData( const QString& d ) +{ + textData = d; +} + +bool KoXmlNodeData::setContent( QXmlInputSource* source, +QXmlReader* reader, QString* errorMsg, int* errorLine, int* errorColumn ) +{ + if( nodeType != KoXmlNode::DocumentNode ) + return false; + + clear(); + nodeType = KoXmlNode::DocumentNode; + + // sanity checks + if( !source ) return false; + if( !reader ) return false; + + // copy the reader for later on-demand loading + // FIXME this is a workaround because no copy is possible with QXmlReader + char* features[] = + { + "http://xml.org/sax/features/namespaces", + "http://xml.org/sax/features/namespace-prefixes", + "http://trolltech.com/xml/features/report-whitespace-only-CharData", + "http://trolltech.com/xml/features/report-start-end-entity" + }; + xmlReader = new QXmlSimpleReader; + for( int fi=0; fi<4; fi++ ) + xmlReader->setFeature( features[fi], reader->feature( features[fi] ) ); + + bool processNamespace = + reader->feature( "http://xml.org/sax/features/namespaces" ) && + !reader->feature( "http://xml.org/sax/features/namespace-prefixes" ); + + KoXmlHandler handler( this, processNamespace ); + reader->setContentHandler( &handler ); + reader->setErrorHandler( &handler ); + reader->setLexicalHandler( &handler ); + reader->setDeclHandler( &handler ); + reader->setDTDHandler( &handler ); + + if( !fastLoading ) + handler.setMaxDepth( 4 ); + + if( !reader->parse( source ) ) + { + // parsing error has occurred + if( errorMsg ) *errorMsg = handler.errorMsg; + if( errorLine ) *errorLine = handler.errorLine; + if( errorColumn ) *errorColumn = handler.errorColumn; + return false; + } + + return true; +} + +void KoXmlNodeData::loadChildren( int depth ) +{ + // for more than 1 level, force reloading anyway + if( ( depth== 1 ) && loaded ) return; + + KoXmlNodeData* doc = ownerDocument(); + if( !doc ) return; + + // fast loading? then this on-demand loading makes no sense + if( doc->fastLoading ) return; + + unloadChildren(); + + bool nsProcess = + doc->xmlReader->feature( "http://xml.org/sax/features/namespaces" ) && + !doc->xmlReader->feature( "http://xml.org/sax/features/namespace-prefixes" ); + + + // XML snippet for the children, including this element + QString snippet = doc->buffer.mid( startPos, endPos-startPos+1 ); + + // now parse all subnodes + KoXmlHandler handler( this, nsProcess ); + handler.setMaxDepth( depth ); + handler.setInitialOffset( startPos ); + doc->xmlReader->setContentHandler( &handler ); + doc->xmlReader->setErrorHandler( &handler ); + doc->xmlReader->setLexicalHandler( &handler ); + doc->xmlReader->setDeclHandler( &handler ); + doc->xmlReader->setDTDHandler( &handler ); + + QXmlInputSource source; + source.setData( snippet ); + if( !doc->xmlReader->parse( source ) ) + { + // parsing error has occurred, which should not happen + // nothing we can do except... + loaded = false; + qWarning( "On-demand loading triggers parse error!" ); + } + else + loaded = true; +} + +void KoXmlNodeData::unloadChildren() +{ + if( !loaded ) return; + + if( first ) + for( KoXmlNodeData* node = first; node ; ) + { + KoXmlNodeData* next = node->next; + node->unloadChildren(); + node->unref(); + node = next; + } + + loaded = false; + first = last = 0; +} + + +// ================================================================== +// +// KoXmlHandler +// +// ================================================================== + +KoXmlHandler::KoXmlHandler( KoXmlNodeData* n, bool ns ): +QXmlDefaultHandler() +{ + processNamespace = ns; + + rootNode = n; + currentNode = n; + cdata = false; + entityName = QString::null; + + errorMsg = QString::null; + errorLine = 0; + errorColumn = 0; + + parseOffset = 0; + elementDepth = -1; + maxDepth = 999; + + bufferStream.setSaveData( rootNode->nodeType == KoXmlNode::DocumentNode ); +} + +KoXmlHandler::~KoXmlHandler() +{ +} + +bool KoXmlHandler::startDocument() +{ + // just for sanity + currentNode = rootNode; + cdata = false; + entityName = QString::null; + elementDepth = -1; + + return true; +} + +bool KoXmlHandler::endDocument() +{ + // just for sanity + if( rootNode->nodeType == KoXmlNode::DocumentNode ) + if( currentNode!=rootNode ) + return false; + + if( rootNode->nodeType == KoXmlNode::DocumentNode ) + rootNode->buffer = bufferStream.stringData(); + + return true; +} + +bool KoXmlHandler::startDTD( const QString& name, const QString& publicId, +const QString& systemId ) +{ + Q_UNUSED( name ); + Q_UNUSED( publicId ); + Q_UNUSED( systemId ); + + // we skip DTD + return true; +} + +bool KoXmlHandler::startElement( const QString& nsURI, const QString& localName, +const QString& name, const QXmlAttributes& atts ) +{ + Q_UNUSED( localName ); + + // sanity check + if( !currentNode ) + return false; + + // we are going one level deeper + elementDepth++; + + QString nodePrefix, nodeLocalName, nodeTagName; + KoXmlNodeData* element = 0; + + if( processNamespace ) + { + // parse, using namespace + nodeTagName = name; + nodeLocalName = name; + nodePrefix = nsURI.isNull() ? QString::null : QString(""); + int i = name.find( ':' ); + if( i != -1 ) + { + nodeTagName = name.mid( i + 1 ); + nodeLocalName = nodeTagName; + nodePrefix = name.left( i ); + } + + if( elementDepth <= maxDepth ) + { + // construct a new element + element = new KoXmlNodeData; + element->nodeType = KoXmlNode::ElementNode; + element->parent = currentNode; + element->namespaceURI = nsURI; + element->prefix = nodePrefix; + element->localName = nodeLocalName; + element->tagName = nodeTagName; + + // Note: endPos will be later fixed in endElement + element->endPos = element->startPos = parseOffset + bufferStream.at(); + + // handle the attributes + for( int c=0; c<atts.length(); c++ ) + { + QString prefix; + QString qName; // with prefix + QString name; // without prefix, i.e. local name + + name = qName = atts.qName(c); + int i = qName.find( ':' ); + if( i != -1 ) prefix = qName.left( i ); + if( i != -1 ) name = qName.mid( i + 1 ); + element->setAttributeNS( atts.uri(c), qName, atts.value(c) ); + element->setAttribute( name, atts.value(c) ); + } + } + + // save in buffer for later on-demand loading + if( ( rootNode->nodeType != KoXmlNode::DocumentNode ) || !rootNode->fastLoading ) + { + bufferStream << "<"; + if( !nodePrefix.isEmpty() ) + bufferStream << nodePrefix << ":"; + bufferStream << localName; + bufferStream << " xmlns"; + if( !nodePrefix.isEmpty() ) + bufferStream << ":" << nodePrefix; + bufferStream << "=\""; + bufferStream.appendEscape( nsURI ); + bufferStream << "\""; + for( int c=0; c<atts.length(); c++ ) + { + QString prefix; + QString name = atts.qName(c); // qName contains the prefix + int i = name.find( ':' ); + if( i != -1 ) prefix = name.left( i ); + if( i != -1 ) name = atts.qName(c).mid( i + 1 ); + if( !atts.uri(c).isEmpty() ) + bufferStream << " xmlns:" << prefix << "=\"" << atts.uri(c) << "\""; + bufferStream << " "; + if( !prefix.isEmpty() ) bufferStream << prefix << ":"; + bufferStream << name << "=\""; + bufferStream.appendEscape( atts.value(c) ); + bufferStream << "\""; + } + bufferStream << ">"; + } + } + else + { + // parse, without using namespace + nodeTagName = name; + + if( elementDepth <= maxDepth ) + { + // construct a new element + element = new KoXmlNodeData; + element->nodeType = KoXmlNode::ElementNode; + element->parent = currentNode; + element->namespaceURI = QString::null; + element->prefix = QString::null; + element->localName = QString::null; + element->tagName = nodeTagName; + + if( rootNode->nodeType == KoXmlNode::DocumentNode ) + element->fastLoading = rootNode->fastLoading; + + // Note: endPos will be later fixed in endElement + element->endPos = element->startPos = parseOffset + bufferStream.at(); + + // handle the attributes + for( int c=0; c<atts.length(); c++ ) + element->setAttribute( atts.qName(c), atts.value(c) ); + } + + // save in buffer for later on-demand loading + if( ( rootNode->nodeType != KoXmlNode::DocumentNode ) || !rootNode->fastLoading ) + { + bufferStream << "<"; + bufferStream << nodeTagName; + for( int c=0; c<atts.length(); c++ ) + { + bufferStream << " " << atts.qName(c) << "=\""; + bufferStream.appendEscape( atts.value(c) ); + bufferStream << "\""; + } + bufferStream << ">"; + } + + } + + // if we do not parse a complete document, the first element is ignored + // this feature is used in on-demand loading + // e.g. "<ul><li><b>bold items</b></li></ul>", if root node points to + // <ul> tag, then the first <ul> is skipped so that next <li> element + // becomes the child of it. + if( elementDepth == 0 ) + if( rootNode->nodeType != KoXmlNode::DocumentNode ) + { + delete element; + return true; + } + + if( element ) + { + // add as the child and traverse to it + currentNode->loaded = true; + currentNode->appendChild( element ); + currentNode = element; + } + else + currentNode->loaded = false; + + return true; +} + +bool KoXmlHandler::endElement( const QString& nsURI, const QString& localName, +const QString& qName ) +{ + Q_UNUSED( nsURI ); + Q_UNUSED( localName ); + + // sanity check + if( !currentNode ) return false; + if( !currentNode->parent ) return false; + + // see comments in startElement about first element and on-demand loading + if( rootNode->nodeType == KoXmlNode::DocumentNode ) + if( currentNode == rootNode ) + return false; + + // buffer for on-demand loading + if( ( rootNode->nodeType != KoXmlNode::DocumentNode ) || !rootNode->fastLoading ) + bufferStream << "</" << qName << ">"; + + // fix up the pointer + currentNode->endPos = parseOffset + bufferStream.at() - 1; + + // go up one level + if( elementDepth <= maxDepth ) + currentNode = currentNode->parent; + + // we are going up one level + elementDepth--; + + return true; +} + +bool KoXmlHandler::characters( const QString& str ) +{ + // sanity check + if( rootNode->nodeType == KoXmlNode::DocumentNode ) + if( currentNode == rootNode ) + return false; + + // are we inside entity ? + if( !entityName.isEmpty() ) + { + // we do not handle entity but need to keep track of it + // because we want to skip it alltogether + return true; + } + + if( cdata ) + { + // are we inside CDATA section ? + if( elementDepth <= maxDepth ) + { + KoXmlNodeData* cdata = new KoXmlNodeData; + cdata->nodeType = KoXmlNode::CDATASectionNode; + cdata->parent = currentNode; + cdata->setData( str ); + currentNode->appendChild( cdata ); + } + + if( ( rootNode->nodeType != KoXmlNode::DocumentNode ) || !rootNode->fastLoading ) + bufferStream << "<![CDATA[" << str << "]]>"; // no escape for CDATA + } + else + { + // this must be normal text node + if( elementDepth <= maxDepth ) + { + KoXmlNodeData* text = new KoXmlNodeData; + text->nodeType = KoXmlNode::TextNode; + text->parent = currentNode; + text->setData( str ); + currentNode->appendChild( text ); + } + + // save it for later use + if( ( rootNode->nodeType != KoXmlNode::DocumentNode ) || !rootNode->fastLoading ) + bufferStream.appendEscape( str ); + } + + return true; +} + +bool KoXmlHandler::processingInstruction( const QString& target, +const QString& data ) +{ + // sanity check + if( !currentNode ) + return false; + + KoXmlNodeData* instruction = new KoXmlNodeData; + instruction->nodeType = KoXmlNode::ProcessingInstructionNode; + instruction->parent = currentNode; + instruction->tagName = target; + instruction->setData( data ); + + currentNode->appendChild( instruction ); + + return true; +} + +bool KoXmlHandler::skippedEntity( const QString& name ) +{ + Q_UNUSED( name ); + + // we skip entity + return true; +} + +bool KoXmlHandler::startCDATA() +{ + cdata = true; + return true; +} + +bool KoXmlHandler::endCDATA() +{ + cdata = false; + return true; +} + +bool KoXmlHandler::startEntity( const QString& name ) +{ + entityName = name; + return true; +} + +bool KoXmlHandler::endEntity( const QString& name ) +{ + Q_UNUSED( name ); + entityName = QString::null; + return true; +} + +bool KoXmlHandler::comment( const QString& comment ) +{ + Q_UNUSED( comment ); + + // we skip comment + return true; +} + +bool KoXmlHandler::unparsedEntityDecl( const QString &name, +const QString &publicId, const QString &systemId, const QString ¬ationName ) +{ + Q_UNUSED( name ); + Q_UNUSED( publicId ); + Q_UNUSED( systemId ); + Q_UNUSED( notationName ); + + // we skip entity + return true; +} + +bool KoXmlHandler::externalEntityDecl( const QString &name, +const QString &publicId, const QString &systemId ) +{ + Q_UNUSED( name ); + Q_UNUSED( publicId ); + Q_UNUSED( systemId ); + + // we skip entity + return true; +} + +bool KoXmlHandler::notationDecl( const QString & name, +const QString & publicId, const QString & systemId ) +{ + Q_UNUSED( name ); + Q_UNUSED( publicId ); + Q_UNUSED( systemId ); + + // we skip notation node + return true; +} + +bool KoXmlHandler::fatalError( const QXmlParseException& exception ) +{ + errorMsg = exception.message(); + errorLine = exception.lineNumber(); + errorColumn = exception.columnNumber(); + return QXmlDefaultHandler::fatalError( exception ); +} + +// ================================================================== +// +// KoXmlNode +// +// ================================================================== + +// Creates a null node +KoXmlNode::KoXmlNode() +{ + d = new KoXmlNodeData; +} + +// Destroys this node +KoXmlNode::~KoXmlNode() +{ + if( d ) d->unref(); +} + +// Creates a copy of another node +KoXmlNode::KoXmlNode( const KoXmlNode& node ) +{ + d = node.d; + d->ref(); +} + +// Creates a node for specific implementation +KoXmlNode::KoXmlNode( KoXmlNodeData* data ) +{ + d = data; + data->ref(); +} + +// Creates a shallow copy of another node +KoXmlNode& KoXmlNode::operator=( const KoXmlNode& node ) +{ + d->unref(); + d = node.d; + d->ref(); + return *this; +} + +// Note: two null nodes are always equal +bool KoXmlNode::operator==( const KoXmlNode& node ) const +{ + if( isNull() && node.isNull() ) return true; + return( d==node.d ); +} + +// Note: two null nodes are always equal +bool KoXmlNode::operator!=( const KoXmlNode& node ) const +{ + if( isNull() && !node.isNull() ) return true; + if( !isNull() && node.isNull() ) return true; + if( isNull() && node.isNull() ) return false; + return( d!=node.d ); +} + +KoXmlNode::NodeType KoXmlNode::nodeType() const +{ + return d->nodeType; +} + +bool KoXmlNode::isElement() const +{ + return d->nodeType == ElementNode; +} + +bool KoXmlNode::isText() const +{ + return (d->nodeType == TextNode) || isCDATASection(); +} + +bool KoXmlNode::isCDATASection() const +{ + return d->nodeType == CDATASectionNode; +} + +bool KoXmlNode::isDocument() const +{ + return d->nodeType == DocumentNode; +} + +bool KoXmlNode::isNull() const +{ + return d->nodeType == NullNode; +} + +void KoXmlNode::clear() +{ + d->unref(); + d = new KoXmlNodeData; +} + +QString KoXmlNode::nodeName() const +{ + return d->nodeName(); +} + +QString KoXmlNode::prefix() const +{ + return isElement() ? d->prefix : QString::null; +} + +QString KoXmlNode::namespaceURI() const +{ + return isElement() ? d->namespaceURI : QString::null; +} + +QString KoXmlNode::localName() const +{ + return isElement() ? d->localName : QString::null; +} + +KoXmlDocument KoXmlNode::ownerDocument() const +{ + KoXmlNodeData* node = d; + while( node->parent ) node = node->parent; + + if( node->nodeType != DocumentNode ) return KoXmlDocument(); + return KoXmlDocument( node ); +} + +KoXmlNode KoXmlNode::parentNode() const +{ + return d->parent ? KoXmlNode( d->parent ) : KoXmlNode(); +} + +bool KoXmlNode::hasChildNodes() const +{ + d->loadChildren(); + return d->first!=0 ; +} + +KoXmlNode KoXmlNode::firstChild() const +{ + if( !d->fastLoading ) + d->loadChildren(); + return d->first ? KoXmlNode( d->first ) : KoXmlNode(); +} + +KoXmlNode KoXmlNode::lastChild() const +{ + if( !d->fastLoading ) + d->loadChildren(); + return d->last ? KoXmlNode( d->last ) : KoXmlNode(); +} + +KoXmlNode KoXmlNode::nextSibling() const +{ + return d->next ? KoXmlNode( d->next ) : KoXmlNode(); +} + +KoXmlNode KoXmlNode::previousSibling() const +{ + return d->prev ? KoXmlNode( d->prev ) : KoXmlNode(); +} + +KoXmlNode KoXmlNode::namedItem( const QString& name ) const +{ + if( !d->fastLoading ) + d->loadChildren(); + + KoXmlNodeData* node = d->first; + while ( node ) + { + if( node->nodeName() == name ) + return KoXmlNode( node ); + node = node->next; + } + + // not found + return KoXmlNode(); +} + +KoXmlNode KoXmlNode::namedItemNS( const QString& nsURI, const QString& name ) const +{ + if( !d->fastLoading ) + d->loadChildren(); + + KoXmlNodeData* node = d->first; + while ( node ) + { + if( !node->prefix.isNull() ) + if( node->namespaceURI == nsURI ) + if( node->localName == name ) + return KoXmlNode( node ); + node = node->next; + } + + // not found + return KoXmlNode(); +} + +KoXmlElement KoXmlNode::toElement() +{ + return isElement() ? KoXmlElement( d ) : KoXmlElement(); +} + +KoXmlText KoXmlNode::toText() +{ + return isText() ? KoXmlText( d ) : KoXmlText(); +} + +KoXmlCDATASection KoXmlNode::toCDATASection() +{ + return isCDATASection() ? KoXmlCDATASection( (KoXmlNodeData*)d ) : + KoXmlCDATASection(); +} + +KoXmlDocument KoXmlNode::toDocument() +{ + return isDocument() ? KoXmlDocument( d ) : KoXmlDocument(); +} + +void KoXmlNode::load( int depth ) +{ + d->loadChildren( depth ); +} + +void KoXmlNode::unload() +{ + d->unloadChildren(); +} + +// ================================================================== +// +// KoXmlElement +// +// ================================================================== + +// Creates an empty element +KoXmlElement::KoXmlElement(): KoXmlNode() +{ + d->unref(); + d = new KoXmlNodeData; +} + +KoXmlElement::~KoXmlElement() +{ + d->unref(); + d = 0; +} + +// Creates a shallow copy of another element +KoXmlElement::KoXmlElement( const KoXmlElement& element ): KoXmlNode() +{ + d->unref(); + d = element.d; + d->ref(); +} + +KoXmlElement::KoXmlElement( KoXmlNodeData* data ): KoXmlNode() +{ + d->unref(); + d = data; + d->ref(); +} + +// Copies another element +KoXmlElement& KoXmlElement::operator=( const KoXmlElement& element ) +{ + KoXmlNode::operator=( element ); + return *this; +} + +bool KoXmlElement::operator== ( const KoXmlElement& element ) const +{ + if( isNull() || element.isNull() ) return false; + return (d==element.d); +} + +bool KoXmlElement::operator!= ( const KoXmlElement& element ) const +{ + if( isNull() && element.isNull() ) return false; + if( isNull() || element.isNull() ) return true; + return (d!=element.d); +} + +QString KoXmlElement::tagName() const +{ + return isElement() ? ((KoXmlNodeData*)d)->tagName: QString::null; +} + +QString KoXmlElement::text() const +{ + return d->text(); +} + +bool KoXmlElement::isElement() const +{ + return true; +} + +QString KoXmlElement::attribute( const QString& name ) const +{ + return attribute( name, QString::null ); +} + +QString KoXmlElement::attribute( const QString& name, +const QString& defaultValue ) const +{ + if( !isElement() ) + return defaultValue; + + if( !hasAttribute( name ) ) + return defaultValue; + + return ((KoXmlNodeData*)d)->attribute( name ); +} + +QString KoXmlElement::attributeNS( const QString& namespaceURI, +const QString& localName, const QString& defaultValue ) const +{ + if( !isElement() ) + return defaultValue; + + if( !hasAttributeNS( namespaceURI,localName ) ) + return defaultValue; + + return ((KoXmlNodeData*)d)->attributeNS( namespaceURI,localName ); +} + +bool KoXmlElement::hasAttribute( const QString& name ) const +{ + return isElement() ? ((KoXmlNodeData*)d)->hasAttribute( name ) : false; +} + +bool KoXmlElement::hasAttributeNS( const QString& namespaceURI, +const QString& localName ) const +{ + return isElement() ? ((KoXmlNodeData*)d)->hasAttributeNS( + namespaceURI, localName ) : false;; +} + +// ================================================================== +// +// KoXmlText +// +// ================================================================== + +KoXmlText::KoXmlText(): KoXmlNode() +{ + d->unref(); + d = new KoXmlNodeData; + d->nodeType = TextNode; +} + +KoXmlText::~KoXmlText() +{ + if( d ) d->unref(); + d = 0; +} + +KoXmlText::KoXmlText( const KoXmlText& text ): KoXmlNode() +{ + d->unref(); + d = (KoXmlNodeData*) text.d; + d->ref(); +} + +KoXmlText::KoXmlText( KoXmlNodeData* data ): KoXmlNode() +{ + d->unref(); + d = data; + d->ref(); +} + +bool KoXmlText::isText() const +{ + return true; +} + +QString KoXmlText::data() const +{ + return ((KoXmlNodeData*)d)->data(); +} + +KoXmlText& KoXmlText::operator=( const KoXmlText& element ) +{ + KoXmlNode::operator=( element ); + return *this; +} + +// ================================================================== +// +// KoXmlCDATASection +// +// ================================================================== + +KoXmlCDATASection::KoXmlCDATASection(): KoXmlText() +{ + d->unref(); + d = new KoXmlNodeData; + d->nodeType = KoXmlNode::CDATASectionNode; +} + +KoXmlCDATASection::~KoXmlCDATASection() +{ + d->unref(); + d = 0; +} + +KoXmlCDATASection::KoXmlCDATASection( const KoXmlCDATASection& cdata ): +KoXmlText() +{ + d->unref(); + d = (KoXmlNodeData*) cdata.d; + d->ref(); +} + +KoXmlCDATASection::KoXmlCDATASection( KoXmlNodeData* cdata ): +KoXmlText() +{ + d->unref(); + d = cdata; + d->ref(); +} + +bool KoXmlCDATASection::isCDATASection() const +{ + return true; +} + +KoXmlCDATASection& KoXmlCDATASection::operator=( const KoXmlCDATASection& cdata ) +{ + KoXmlNode::operator=( cdata ); + return *this; +} + +// ================================================================== +// +// KoXmlDocument +// +// ================================================================== + +KoXmlDocument::KoXmlDocument(): KoXmlNode() +{ + d->unref(); + d = new KoXmlNodeData; + d->nodeType = KoXmlNode::DocumentNode; +} + +KoXmlDocument::~KoXmlDocument() +{ + d->unref(); + d = 0; +} + +KoXmlDocument::KoXmlDocument( KoXmlNodeData* data ): KoXmlNode() +{ + d->unref(); + d = data; + d->ref(); +} + +// Creates a copy of another document +KoXmlDocument::KoXmlDocument( const KoXmlDocument& doc ): KoXmlNode() +{ + d->unref(); + d = doc.d; + d->ref(); +} + +// Creates a shallow copy of another document +KoXmlDocument& KoXmlDocument::operator=( const KoXmlDocument& doc ) +{ + KoXmlNode::operator=( doc ); + return *this; +} + +// Checks if this document and doc are equals +bool KoXmlDocument::operator==( const KoXmlDocument& doc ) const +{ + return( d==doc.d ); +} + +// Checks if this document and doc are not equals +bool KoXmlDocument::operator!=( const KoXmlDocument& doc ) const +{ + return( d!=doc.d ); +} + +bool KoXmlDocument::isDocument() const +{ + return true; +} + +KoXmlElement KoXmlDocument::documentElement() const +{ + for( KoXmlNodeData* node=d->first; node; ) + if( node->nodeType==KoXmlNode::ElementNode ) + return KoXmlElement( node ); + else node = node->next; + + return KoXmlElement(); +} + +void KoXmlDocument::setFastLoading( bool f ) +{ + d->fastLoading = f; +} + +bool KoXmlDocument::fastLoading() const +{ + return d->fastLoading; +} + +bool KoXmlDocument::setContent( QXmlInputSource *source, QXmlReader *reader, + QString* errorMsg, int* errorLine, int* errorColumn ) +{ + if( d->nodeType != KoXmlNode::DocumentNode ) + return false; + + return d->setContent( source, reader, errorMsg, errorLine, errorColumn ); +} + +// no namespace processing +bool KoXmlDocument::setContent( QIODevice* device, QString* errorMsg, +int* errorLine, int* errorColumn ) +{ + return setContent( device, false, errorMsg, errorLine, errorColumn ); +} + +bool KoXmlDocument::setContent( QIODevice* device, bool namespaceProcessing, +QString* errorMsg, int* errorLine, int* errorColumn ) +{ + if( d->nodeType != KoXmlNode::DocumentNode ) + return false; + + QXmlSimpleReader reader; + reader.setFeature( "http://xml.org/sax/features/namespaces", namespaceProcessing ); + reader.setFeature( "http://xml.org/sax/features/namespace-prefixes", !namespaceProcessing ); + reader.setFeature( "http://trolltech.com/xml/features/report-whitespace-only-CharData", false ); + + // FIXME this hack is apparently private + //reader.setUndefEntityInAttrHack(true); + + QXmlInputSource source( device ); + return d->setContent( &source, &reader, errorMsg, errorLine, errorColumn ); +} + +#endif + +KoXmlElement KoXml::namedItemNS( const KoXmlNode& node, const char* nsURI, +const char* localName ) +{ +#ifdef KOXML_USE_QDOM + // David's solution for namedItemNS, only for QDom stuff + KoXmlNode n = node.firstChild(); + for ( ; !n.isNull(); n = n.nextSibling() ) { + if ( n.isElement() && n.localName() == localName && + n.namespaceURI() == nsURI ) + return n.toElement(); + } + return KoXmlElement(); +#else + return node.namedItemNS( nsURI, localName).toElement(); +#endif +} + +void KoXml::load( KoXmlNode& node, int depth ) +{ +#ifdef KOXML_USE_QDOM + // do nothing, QDom has no on-demand loading + Q_UNUSED( node ); + Q_UNUSED( depth ); +#else + node.load( depth ); +#endif +} + + +void KoXml::unload( KoXmlNode& node ) +{ +#ifdef KOXML_USE_QDOM + // do nothing, QDom has no on-demand unloading + Q_UNUSED( node ); +#else + node.unload(); +#endif +} diff --git a/lib/kofficecore/KoXmlReader.h b/lib/kofficecore/KoXmlReader.h new file mode 100644 index 00000000..df2f10c9 --- /dev/null +++ b/lib/kofficecore/KoXmlReader.h @@ -0,0 +1,320 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Ariya Hidayat <ariya@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 KOFFICE_XMLREADER +#define KOFFICE_XMLREADER + +// use standard QDom, useful to test KoXml classes against Qt's QDom +//#define KOXML_USE_QDOM + +#include <qdom.h> +#include <koffice_export.h> + +#ifdef KOXML_USE_QDOM + +#define KoXmlNode QDomNode +#define KoXmlElement QDomElement +#define KoXmlText QDomText +#define KoXmlCDATASection QDomCDATASection +#define KoXmlDocument QDomDocument + +#else + +class QIODevice; +class QString; +class QTextStream; +class QXmlReader; +class QXmlInputSource; + +class KoXmlElement; +class KoXmlText; +class KoXmlCDATASection; +class KoXmlDocument; +class KoXmlNodeData; + +/** + * KoXmlNode represents a node in a DOM tree. + * + * KoXmlNode is a base class for KoXmlElement, KoXmlText. + * Often, these subclasses are used for getting the data instead of KoXmlNode. + * However, as base class, KoXmlNode is very helpful when for example iterating + * all child nodes within one parent node. + * + * KoXmlNode implements an explicit sharing, a node shares its data with + * other copies (if exist). + * + * @author Ariya Hidayat <ariya@kde.org> + */ +class KOFFICECORE_EXPORT KoXmlNode +{ +public: + + enum NodeType + { + NullNode = 0, + ElementNode, + TextNode, + CDATASectionNode, + ProcessingInstructionNode, + DocumentNode + }; + + KoXmlNode(); + KoXmlNode( const KoXmlNode& node ); + KoXmlNode& operator=( const KoXmlNode& node ); + bool operator== ( const KoXmlNode& ) const; + bool operator!= ( const KoXmlNode& ) const; + virtual ~KoXmlNode(); + + virtual KoXmlNode::NodeType nodeType() const; + virtual bool isNull() const; + virtual bool isElement() const; + virtual bool isText() const; + virtual bool isCDATASection() const; + virtual bool isDocument() const; + + void clear(); + KoXmlElement toElement(); + KoXmlText toText(); + KoXmlCDATASection toCDATASection(); + KoXmlDocument toDocument(); + + virtual QString nodeName() const; + virtual QString namespaceURI() const; + virtual QString prefix() const; + virtual QString localName() const; + + KoXmlDocument ownerDocument() const; + KoXmlNode parentNode() const; + + bool hasChildNodes() const; + KoXmlNode firstChild() const; + KoXmlNode lastChild() const; + KoXmlNode nextSibling() const; + KoXmlNode previousSibling() const; + + KoXmlNode namedItem( const QString& name ) const; + KoXmlNode namedItemNS( const QString& nsURI, const QString& name ) const; + + /** + * Loads all child nodes (if any) of this node. Normally you do not need + * to call this function as the child nodes will be automatically + * loaded when necessary. + */ + void load( int depth=1 ); + + /** + * Releases all child nodes of this node. + */ + void unload(); + +protected: + KoXmlNodeData* d; + KoXmlNode( KoXmlNodeData* ); +}; + +/** + * KoXmlElement represents a tag element in a DOM tree. + * + * KoXmlElement holds information about an XML tag, along with its attributes. + * + * @author Ariya Hidayat <ariya@kde.org> + */ + +class KOFFICECORE_EXPORT KoXmlElement: public KoXmlNode +{ +public: + KoXmlElement(); + KoXmlElement( const KoXmlElement& element ); + KoXmlElement& operator=( const KoXmlElement& element ); + virtual ~KoXmlElement(); + bool operator== ( const KoXmlElement& ) const; + bool operator!= ( const KoXmlElement& ) const; + + QString tagName() const; + QString text() const; + virtual bool isElement() const; + + QString attribute( const QString& name ) const; + QString attribute( const QString& name, const QString& defaultValue ) const; + QString attributeNS( const QString& namespaceURI, const QString& localName, + const QString& defaultValue ) const; + bool hasAttribute( const QString& name ) const; + bool hasAttributeNS( const QString& namespaceURI, const QString& localName ) const; + +private: + friend class KoXmlNode; + friend class KoXmlDocument; + KoXmlElement( KoXmlNodeData* ); +}; + +/** + * KoXmlText represents a text in a DOM tree. + * @author Ariya Hidayat <ariya@kde.org> + */ +class KOFFICECORE_EXPORT KoXmlText: public KoXmlNode +{ +public: + KoXmlText(); + KoXmlText( const KoXmlText& text ); + KoXmlText& operator=( const KoXmlText& text ); + virtual ~KoXmlText(); + + QString data() const; + void setData( const QString& data ); + virtual bool isText() const; + +private: + friend class KoXmlNode; + friend class KoXmlDocument; + KoXmlText( KoXmlNodeData* ); +}; + +/** + * KoXmlCDATASection represents a CDATA section in a DOM tree. + * @author Ariya Hidayat <ariya@kde.org> + */ +class KOFFICECORE_EXPORT KoXmlCDATASection: public KoXmlText +{ +public: + KoXmlCDATASection(); + KoXmlCDATASection( const KoXmlCDATASection& cdata ); + KoXmlCDATASection& operator=( const KoXmlCDATASection& cdata ); + virtual ~KoXmlCDATASection(); + + virtual bool isCDATASection() const; + +private: + friend class KoXmlNode; + friend class KoXmlDocument; + KoXmlCDATASection( KoXmlNodeData* ); +}; + +/** + * KoXmlDocument represents an XML document, structured in a DOM tree. + * + * KoXmlDocument is designed to be memory efficient. Unlike QDomDocument from + * Qt's XML module, KoXmlDocument does not store all nodes in the DOM tree. + * Some nodes will be loaded and parsed on-demand only. + * + * KoXmlDocument is read-only, you can not modify its content. + * + * @author Ariya Hidayat <ariya@kde.org> + */ + +class KOFFICECORE_EXPORT KoXmlDocument: public KoXmlNode +{ +public: + KoXmlDocument(); + KoXmlDocument( const KoXmlDocument& node ); + KoXmlDocument& operator=( const KoXmlDocument& node ); + bool operator==( const KoXmlDocument& ) const; + bool operator!=( const KoXmlDocument& ) const; + virtual ~KoXmlDocument(); + + virtual bool isDocument() const; + + KoXmlElement documentElement() const; + + void setFastLoading( bool f ); + bool fastLoading() const; + + bool setContent( QIODevice* device, bool namespaceProcessing, + QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0 ); + bool setContent( QIODevice* device, + QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0 ); + bool setContent( QXmlInputSource *source, QXmlReader *reader, + QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0 ); + +// bool setContent( const QCString& text, bool namespaceProcessing, QString *errorMsg=0, int *errorLine=0, int *errorColumn=0 ); +// bool setContent( const QByteArray& text, bool namespaceProcessing, QString *errorMsg=0, int *errorLine=0, int *errorColumn=0 ); +// bool setContent( const QString& text, bool namespaceProcessing, QString *errorMsg=0, int *errorLine=0, int *errorColumn=0 ); +// bool setContent( QIODevice* dev, bool namespaceProcessing, QString *errorMsg=0, int *errorLine=0, int *errorColumn=0 ); +// bool setContent( const QCString& text, QString *errorMsg=0, int *errorLine=0, int *errorColumn=0 ); +// bool setContent( const QByteArray& text, QString *errorMsg=0, int *errorLine=0, int *errorColumn=0 ); +// bool setContent( const QString& text, QString *errorMsg=0, int *errorLine=0, +// int *errorColumn=0 ); + + +private: + friend class KoXmlNode; + KoXmlDocument( KoXmlNodeData* ); +}; + +#endif // KOXML_USE_QDOM + +/** + * This namespace contains a few convenience functions to simplify code using QDom + * (when loading OASIS documents, in particular). + * + * To find the child element with a given name, use KoXml::namedItemNS. + * + * To find all child elements with a given name, use + * QDomElement e; + * forEachElement( e, parent ) + * { + * if ( e.localName() == "..." && e.namespaceURI() == KoXmlNS::... ) + * { + * ... + * } + * } + * Note that this means you don't ever need to use QDomNode nor toElement anymore! + * Also note that localName is the part without the prefix, this is the whole point + * of namespace-aware methods. + * + * To find the attribute with a given name, use QDomElement::attributeNS. + * + * Do not use getElementsByTagNameNS, it's recursive (which is never needed in KOffice). + * Do not use tagName() or nodeName() or prefix(), since the prefix isn't fixed. + * + * @author David Faure <faure@kde.org> + */ +namespace KoXml { + + /** + * A namespace-aware version of QDomNode::namedItem(), + * which also takes care of casting to a QDomElement. + * Use this when a domelement is known to have only *one* child element + * with a given tagname. + * + * Note: do *NOT* use getElementsByTagNameNS, it's recursive! + */ + KOFFICECORE_EXPORT KoXmlElement namedItemNS( const KoXmlNode& node, + const char* nsURI, const char* localName ); + + /** + * Explicitly load child nodes of specified node, up to given depth. + * This function has no effect if QDom is used. + */ + KOFFICECORE_EXPORT void load( KoXmlNode& node, int depth = 1 ); + + /** + * Unload child nodes of specified node. + * This function has no effect if QDom is used. + */ + KOFFICECORE_EXPORT void unload( KoXmlNode& node ); + +} + +#define forEachElement( elem, parent ) \ + for ( KoXmlNode _node = parent.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) \ + if ( !( elem = _node.toElement() ).isNull() ) + + +#endif // KOFFICE_XMLREADER diff --git a/lib/kofficecore/Koversiondialog.cpp b/lib/kofficecore/Koversiondialog.cpp new file mode 100644 index 00000000..b21ae52f --- /dev/null +++ b/lib/kofficecore/Koversiondialog.cpp @@ -0,0 +1,143 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Laurent Montel <montel@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 <qlabel.h> +#include <qlayout.h> +#include <qmultilineedit.h> +#include <qpushbutton.h> +#include <qtoolbutton.h> +#include <qapplication.h> +#include <qlayout.h> +#include <kiconloader.h> +#include <kbuttonbox.h> +#include <kdebug.h> +#include <kmessagebox.h> +#include <klocale.h> +#include <klistview.h> +#include <kdebug.h> + +#include <qmultilineedit.h> + +#include "Koversiondialog.h" + + +KoVersionDialog::KoVersionDialog( QWidget* parent, const char* name ) + : KDialogBase( parent, name, true, i18n("Version"), Ok|Cancel ) +{ + QWidget* page = new QWidget( this ); + setMainWidget( page ); + + QGridLayout *grid1 = new QGridLayout( page,10,3,KDialog::marginHint(), KDialog::spacingHint()); + + list=new KListView(page, "versionlist"); + list->addColumn(i18n("Date & Time")); + list->addColumn(i18n("Saved By")); + list->addColumn(i18n("Comment")); + + grid1->addMultiCellWidget(list,0,8,0,0); + + m_pAdd=new QPushButton(i18n("&Add"),page); + grid1->addWidget(m_pAdd,1,2); + + m_pRemove=new QPushButton(i18n("&Remove"),page); + grid1->addWidget(m_pRemove,2,2); + + m_pModify=new QPushButton(i18n("&Modify"),page); + grid1->addWidget(m_pModify,3,2); + + m_pOpen=new QPushButton(i18n("&Open"),page); + grid1->addWidget(m_pOpen,4,2); + + + connect( m_pRemove, SIGNAL( clicked() ), this, SLOT( slotRemove() ) ); + connect( m_pAdd, SIGNAL( clicked() ), this, SLOT( slotAdd() ) ); + connect( m_pOpen, SIGNAL( clicked() ), this, SLOT( slotOpen() ) ); + connect( m_pModify, SIGNAL( clicked() ), this, SLOT( slotModify() ) ); + + updateButton(); + + resize( 600, 250 ); + +} + +KoVersionDialog::~KoVersionDialog() +{ +} + +void KoVersionDialog::updateButton() +{ +#if 0 + bool state = ( list->currentItem() >= 0 ); + m_pRemove->setEnabled( state ); +#endif +} + +void KoVersionDialog::slotAdd() +{ + //TODO create entry +} + +void KoVersionDialog::slotRemove() +{ + //TODO remove entry +} + +void KoVersionDialog::slotModify() +{ + KoVersionModifyDialog * dlg = new KoVersionModifyDialog( this /*, const QString &_comment*/ /*TODO add*/ ); + if ( dlg->exec() ) + { + //TODO + kdDebug()<<" comment :"<<dlg->comment()<<endl; + } + delete dlg; + +} + +void KoVersionDialog::slotOpen() +{ + //TODO open file +} + +void KoVersionDialog::slotOk() +{ + accept(); +} + +KoVersionModifyDialog::KoVersionModifyDialog( QWidget* parent, const QString &/*comment*/, const char* name ) + : KDialogBase( parent, name, true, i18n("Comment"), Ok|Cancel ) +{ + QWidget* page = new QWidget( this ); + setMainWidget( page ); + + QHBoxLayout *grid1 = new QHBoxLayout( page,KDialog::marginHint(), KDialog::spacingHint()); + + m_multiline=new QMultiLineEdit(page, "multiline"); + grid1->addWidget( m_multiline ); + +} + +QString KoVersionModifyDialog::comment() const +{ + return m_multiline->text(); +} + + +#include "Koversiondialog.moc" diff --git a/lib/kofficecore/Koversiondialog.h b/lib/kofficecore/Koversiondialog.h new file mode 100644 index 00000000..6a8c7fb6 --- /dev/null +++ b/lib/kofficecore/Koversiondialog.h @@ -0,0 +1,68 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Laurent Montel <montel@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 __VERSION_DIALOG__ +#define __VERSION_DIALOG__ + +#include <kdialogbase.h> + +class QPushButton; +class QToolButton; +class KListView; +class QMultiLineEdit; + +class KoVersionDialog : public KDialogBase +{ + Q_OBJECT +public: + KoVersionDialog( QWidget* parent, const char* name = 0L ); + ~KoVersionDialog(); + +public slots: + virtual void slotOk(); + void slotRemove(); + void slotAdd(); + void slotOpen(); + void slotModify(); + +protected: + + void init(); + void updateButton(); + + KListView * list; + QPushButton* m_pRemove; + QPushButton* m_pAdd; + QPushButton* m_pOpen; + QPushButton* m_pModify; +}; + +class KoVersionModifyDialog : public KDialogBase +{ + Q_OBJECT +public: + KoVersionModifyDialog( QWidget* parent, const QString &_comment = QString::null , const char* name = 0L ); + + QString comment() const; + +private: + QMultiLineEdit *m_multiline; +}; + +#endif diff --git a/lib/kofficecore/Makefile.am b/lib/kofficecore/Makefile.am new file mode 100644 index 00000000..df7cdfa8 --- /dev/null +++ b/lib/kofficecore/Makefile.am @@ -0,0 +1,70 @@ +####### General stuff + +SUBDIRS = . tests +KDE_CXXFLAGS = $(USE_RTTI) $(WOVERLOADED_VIRTUAL) +INCLUDES= $(KSTORE_INCLUDES) $(KWMF_INCLUDES) $(all_includes) +libkofficecore_la_LIBADD = $(LIB_KSTORE) $(LIB_KOWMF) $(LIB_KPARTS) \ + $(LIB_KDEPRINT) $(LIB_KABC) $(LIB_KWMF) + +####### Files + +lib_LTLIBRARIES = libkofficecore.la +kde_module_LTLIBRARIES = kodocinfopropspage.la + +libkofficecore_la_SOURCES = KoDocument.cpp \ + KoGlobal.cpp KoUnit.cpp KoFilterManager.cpp \ + KoMainWindow.cpp \ + KoApplication.cpp KoQueryTrader.cpp \ + KoFilter.cpp KoFilterChain.cpp \ + KoPictureKey.cpp KoPictureBase.cpp KoPicture.cpp KoPictureShared.cpp \ + KoPictureImage.cpp KoPictureClipart.cpp KoPictureCollection.cpp \ + KoPictureEps.cpp KoPictureWmf.cpp \ + KoDocumentInfo.cpp \ + KoView.cpp KoFrame.cpp KoContainerHandler.cpp KoDocumentChild.cpp \ + KoDocumentInfoDlg.cpp KoFactory.cpp KoChild.cpp \ + koDocumentInfoAboutWidget.ui koDocumentInfoAuthorWidget.ui \ + koDocumentInfoUserMetadataWidget.ui \ + KoApplicationIface.cc KoApplicationIface.skel \ + KoDocumentIface.cc KoDocumentIface.skel KoViewIface.cc KoViewIface.skel \ + KoMainWindowIface.cc KoMainWindowIface.skel kofficeversion.cc KoOasisStyles.cpp \ + KoStyleStack.cpp KoGenStyles.cpp KoOasisSettings.cpp KoPageLayout.cpp \ + KoFileDialog.cpp KoXmlNS.cpp KoDom.cpp Koversiondialog.cpp KoOasisStore.cpp \ + kkbdaccessextensions.cpp koDetailsPaneBase.ui koOpenPaneBase.ui KoOpenPane.cpp \ + KoTemplates.cpp KoDetailsPane.cpp \ + KoSpeaker.cpp KoOasisLoadingContext.cpp \ + KoRect.cpp + +libkofficecore_la_LDFLAGS = $(all_libraries) -version-info 3:0:0 -no-undefined + +include_HEADERS = KoContainerHandler.h \ + KoFilter.h KoFilterChain.h \ + KoGlobal.h KoUnit.h KoDocument.h \ + KoMainWindow.h \ + KoApplication.h KoQueryTrader.h \ + KoFilterManager.h \ + KoDocumentInfo.h \ + KoView.h KoFrame.h KoDocumentChild.h \ + KoDocumentInfoDlg.h KoFactory.h KoChild.h \ + KoApplicationIface.h KoDocumentIface.h KoViewIface.h KoMainWindowIface.h \ + KoPictureKey.h KoPicture.h KoPictureCollection.h kofficeversion.h KoOasisStyles.h \ + KoStyleStack.h KoGenStyles.h KoOasisSettings.h KoPageLayout.h KoXmlNS.h KoDom.h Koversiondialog.h \ + kkbdaccessextensions.h \ + koffice_export.h KoOpenPane.h \ + KoSpeaker.h KoOasisLoadingContext.h \ + KoPoint.h + +noinst_HEADERS = KoDocument_p.h KoFilterManager_p.h \ + KoPictureShared.h KoPictureBase.h KoPictureImage.h KoPictureClipart.h \ + KoPictureEps.h KoPictureWmf.h + +kodocinfopropspage_la_SOURCES = KoDocInfoPropsFactory.cpp +kodocinfopropspage_la_LIBADD = ./libkofficecore.la +kodocinfopropspage_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +METASOURCES = AUTO + +rcdir = $(kde_datadir)/koffice +rc_DATA = koffice_shell.rc + +service_DATA = kodocinfopropspage.desktop +servicedir = $(kde_servicesdir) diff --git a/lib/kofficecore/THOUGHTS b/lib/kofficecore/THOUGHTS new file mode 100644 index 00000000..f0121aa9 --- /dev/null +++ b/lib/kofficecore/THOUGHTS @@ -0,0 +1,164 @@ +------------------------------------------------------------------------------ +- Bugs and missing features in the KOffice Library - +- some initial brainstroming... comments? flames? - +------------------------------------------------------------------------------ + +1) Accurate Coordinates: +IMHO it's a must for KOffice applications to store reasonably accurate +coordinates. If zooming is supported I think we should try to make it work +up to zoom factors of ten (1000%) or so. This means you need some kind +of "spare precision". Another related issue is getting KOffice applications +support 1:1 sizes on the screen (i.e. that an A4 paper is A4 also on the +screen). Here the problem is that in X we can have a wide range of +resolutions (from 75 or so up to 120 dpi). We also can have different +resolutions in X and Y direction, but as Qt only honors one of them (the +Y resolution) I think it's safe to ignore the X resolution (see +QPaintDevice::x11AppDpiY()). +In lib/kofficeui we have some classes replacing QPoint/QRect (they use double +precision floating point numbers to store coordinates). + +2) Embedding: +2.1) "Plain" KOffice embedding (paintContent): +We finally added two double values for the X and Y zoom factor. This means +using these values and the QRect argument we can "ask" the part to draw +certain areas of itself. One problem I still see with this method is, that we +only have a QRect there. It's not that problematic there because we can +agree on some hacks if it's not accurate enough (e.g. values * 100.0), or we +just agree that this QRect represents screen pixels at the given zoom value(s) +and the app can calculate the requested area to be painted itself. This should +solve zooming and "scrolling" problems. +One ugly problem also is what we will do about performance problems on +repainting a part. Some time ago we got a mail on KOffice where some guy +asked what to do about making that embedded painting faster. The reason is +that we *always* repaint the whole area, no matter what. This can easily be +optimized to a WNorthWestGravity like behavior, because you can't edit those +embedded parts anyway, so even kchart and other non-WNG apps support that. +( we have to translate the painter, of course). + +### Zooming Special (copy from DESIGN_zooming in kofficecore): +There are two kinds of zooming: + +1) View Zooming: +This kind of zooming is only done by full KoViews, regardless +whether they are the root views (not embedded), or they are +*active* embedded views. There only has to be a KoView object. + +There is *one* zoom factor for both axes (x/y) and the whole +content (text, objects, children,...) is zoomed accordingly. + +This kind of zooming is like the zoom support you know from +other applications. + + +2) Child Zooming: +Imagine you'd like to show a certain part of an embedded +document, but you have to fill a specified area. At the +moment this is "impossible" (we know that it's kind of +possible, but it's not straightforward) because when you +resize the child's frame, the contents resize, too. Due +to that you see more of the child's content :) + +The solution will be: If you simply resize a frame, the +child document will resize, too, and show more contents. +If you press Alt/Meta during resizing, the child's +content will stay the same, but it will be zoomed to +fit the rectangle you specify. +Pressing Shift gives you a constant width/height ratio +during resizing. +Pressing Alt+Shift is allowed, too, if you can do that :p + +As you already know we'll support a zoom factor for +each axe here. (i.e. x/y zoom factors) + +Related to that is child-panning. Indepenent of the zoom factor +the child shouldn't just show the left/top corner, but it should +"remember" the position inside the document when it's repainted +as inactive child. +### + +If a part doesn't support zooming natively we have to fall back to WMartix +hacks (don't know how we can "ask" a part about that, but I'm sure we find +some BC way). + +2.2) Widget embedding: +That's a tough topic... but let's see... There are a few different possibilities +we have to treat differently: +a) plain KParts: +We can only embed the widgets obviously. Printing will look horribly, but +I think we should support that nonetheless. Imagine a KPresenter (screen) +presentation with an embedded video player part on some page :)) +I'd say when printing those files the parts aren't printed at all by default... +makes more sense to me than redirecting an ugly 96dpi printout to some +QPrinter... well. It should at least be possible to print that stuff and get +some crappy output (but at least some output, e.g. an empty frame with some +text information about the part in it). Of course the user should be warned +before embedding such parts :) +b) embedding special KOffice parts as widgets: +Here we can simply add one entry "X-KDE-EmbedAsWidget" or so to our +.desktop files for KOffice parts. This will guarantee then that this special +part wants to be embedded as a widget. This surely makes sense, but still +we have some problems on printing... no idea how to solve that one. Maybe +we should use the widget for viewing on the screen and fall back to a plain +paintContents when printing. + +General embedding stuff: +Do we support the "transparent" flag in paintContent with all parts we have? +IMHO it's a nice feature and we sould keep it, but if no part supports it... +well :] +Maybe we'll have to add a X-KDE-DoNotEmbed flag to the .desktop files +at some point. These parts should be excluded in the part select dia, then. + +3) Handling of embedded parts: +This is one of the most annoying things in KOffice. Every application +handles embedded parts different. Of course this makes sense for most +of the cases, but maybe we can make that a bit more consistent at least +among "object-based" applications like KPresenter, Kivio, and KIllustrator. + +4) Printing: +Here the problem is that even Qt has enough problems with printing :( +We definitely need some magic here, because right now we don't take +any advantage of the better resolution of the printer. This will be a hard +job (and an ugly hack) even if we have accurate coordinates and so +on. Lars told me that QPainter will have some setResolution call for +printing in 3.0... let's see. As a temporary solution he suggested to scale +the painter by 0.1 and print 10 times as big... don't know if that works. + +5) Shell menus: +The Help -> About entry should be the one for the active part. + +6) Image handling: +We need one class (IMVHO a part is too much overhead) which properly +handles all kinds of images. Internal ones, external ones, maybe thumbnails, +proper rescaling w/o any quality loss on rescaling again and again (read: +keep the original around),... Fortunately Simon already implemented most of it. +He also suggested that we need a very tiny KOffice part which just can +display images. That way we can finally support images in KSpread and other +non-object based apps. + +7) Colors: +What about RGB-CMYK-HSV-LAB-... should this be solved in the KOffice libs? +I see some code for this in krayon, and maybe Matthias Elter or gis can help +us with that one. + +8) General configuration options: +Do we need a KOffice kcm? What to configure there? +e.g.: +- start default template (which one :) (start without any template dialog) +- default page size +- default unit +- ... + +9) Make it possible for components to provide their own about dialog. + What's currently needed to acomplish this is an awful addShell() + hack, like in kivio_doc.cpp . + + Possible solution: + Make a slot in KoMainWindow call a virtual method in KoDocument which + calls back the real show-about method. That would give components the + ability to re-implement that very method and be done. + + Disadvantage: breaks binary compatibility. + Possible workaround for the BC breakage: Don't call a virtual method + but call/connect-to a slot in the document only if it exists and + call the default show-about method otherwise. + diff --git a/lib/kofficecore/document-info.dtd b/lib/kofficecore/document-info.dtd new file mode 100644 index 00000000..bff7a00b --- /dev/null +++ b/lib/kofficecore/document-info.dtd @@ -0,0 +1,80 @@ +<!-- $Id: document-info.dtd 466019 2005-10-01 11:07:55Z mlaurent $ + +This is an XML document type definition (DTD) for the documentinfo.xml files which +are used by all KOffice-1.1 and 1.1.1 programs, to store information about the +document (e.g. title and abstract) and its author. + +ChangeLog: +Initial version (for koffice-1.1.1) written by David Faure <faure@kde.org>. +--> + +<!-- + The document-info for a KOffice document contains one main element for each tab of +the "Document Info" dialog. As of KOffice-1.1, this includes: author and about. +A third page, "log" is planned for the future. +--> +<!ELEMENT document-info + (author?, about?, log?)> + +<!-- + The "author" element stores information about the author of the document. +--> +<!ELEMENT author + (full-name?, initial?, title?, company?, email?, telephone?, telephone-work?, fax?, country?, postal-code?, city?, street?, position?)> + +<!ELEMENT full-name + (#PCDATA)> +<!ELEMENT initial + (#PCDATA)> +<!ELEMENT title + (#PCDATA)> +<!ELEMENT company + (#PCDATA)> +<!ELEMENT email + (#PCDATA)> +<!ELEMENT telephone + (#PCDATA)> +<!ELEMENT telephone-work + (#PCDATA)> +<!ELEMENT fax + (#PCDATA)> +<!ELEMENT country + (#PCDATA)> +<!ELEMENT postal-code + (#PCDATA)> +<!ELEMENT city + (#PCDATA)> +<!ELEMENT street + (#PCDATA)> +<!ELEMENT position + (#PCDATA)> + +<!-- + The "about" element contains information about the document itself. + This includes an optional title for the document, and an optional abstract. +--> +<!ELEMENT about + (abstract?, title?, keyword?, subject?, initial-creator?, + editing-cycles?, creation-date?, date?)> +<!ELEMENT abstract + (#PCDATA)> +<!--title already defined above--> +<!ELEMENT keyword + (#PCDATA)> +<!ELEMENT subject + (#PCDATA)> +<!ELEMENT initial-creator + (#PCDATA)> +<!ELEMENT editing-cycles + (#PCDATA)> +<!ELEMENT creation-date + (#PCDATA)> +<!ELEMENT date + (#PCDATA)> + +<!-- + The "log" element is unused at the moment. +--> +<!ELEMENT log + EMPTY> + diff --git a/lib/kofficecore/kkbdaccessextensions.cpp b/lib/kofficecore/kkbdaccessextensions.cpp new file mode 100644 index 00000000..aae37203 --- /dev/null +++ b/lib/kofficecore/kkbdaccessextensions.cpp @@ -0,0 +1,667 @@ +/* This file is part of the KDE project + Copyright (C) 2005, Gary Cramblitt <garycramblitt@comcast.net> + + 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. +*/ + +// Qt includes +#include <qsplitter.h> +#include <qdockwindow.h> +#include <qdockarea.h> +#include <qevent.h> +#include <qcursor.h> +#include <qobjectlist.h> +#include <qwidgetlist.h> +#include <qlabel.h> +#include <qtooltip.h> + +// KDE includes +#include <klocale.h> +#include <kglobal.h> +#include <kapplication.h> +#include <kmainwindow.h> +#include <kaction.h> +#include <kdebug.h> + +// KKbdAccessExtensions includes +#include "kkbdaccessextensions.h" +// TODO: See eventFilter method. +//#include "kkbdaccessextensions.moc" + +class KPanelKbdSizerIcon : public QCursor +{ + public: + KPanelKbdSizerIcon() : + QCursor(Qt::SizeAllCursor), + isActive(false) + { + currentPos = QPoint(-1, -1); + } + + ~KPanelKbdSizerIcon() + { + hide(); + } + + void show(const QPoint p) { + if (!isActive) { + originalPos = QCursor::pos(); + kapp->setOverrideCursor(*this); + isActive = true; + } + if (p != pos()) + setPos(p); + currentPos = p; + } + + void hide() { + if (isActive) { + kapp->restoreOverrideCursor(); + QCursor::setPos(originalPos); + } + isActive = false; + } + + void setShape(int shayp) + { + if (shayp != shape()) { + // Must restore and override to get the icon to refresh. + if (isActive) kapp->restoreOverrideCursor(); + QCursor::setShape(shayp); + if (isActive) kapp->setOverrideCursor(*this); + } + } + + // Return the difference between a position and where icon is supposed to be. + QSize delta(const QPoint p) + { + QPoint d = p - currentPos; + return QSize(d.x(), d.y()); + } + + // Return the difference between where the icon is currently positioned and where + // it is supposed to be. + QSize delta() { return delta(pos()); } + + // True if the sizing icon is visible. + bool isActive; + + private: + // Icon's current position. + QPoint currentPos; + // Mouse cursor's original position when icon is shown. + QPoint originalPos; +}; + +class KKbdAccessExtensionsPrivate +{ + public: + KKbdAccessExtensionsPrivate() : + fwdAction(0), + revAction(0), + accessKeysAction(0), + panel(0), + handleNdx(0), + icon(0), + stepSize(10), + accessKeyLabels(0) {}; + + ~KKbdAccessExtensionsPrivate() + { + delete icon; + // TODO: This crashes, but should delete in the event that KMainWindow is not deleted. + if (accessKeyLabels) { + accessKeyLabels->setAutoDelete(false); + delete accessKeyLabels; + } + } + + // Action that starts panel sizing (defaults to F8), forward and reverse; + KAction* fwdAction; + KAction* revAction; + + // Action that starts access keys. + KAction* accessKeysAction; + + // The splitter or dockwindow currently being sized. If 0, sizing is not in progress. + QWidget* panel; + + // Index of current handle of the panel. When panel is a QDockWindow: + // 1 = size horizontally + // 2 = size vertically + uint handleNdx; + + // Sizing icon. + KPanelKbdSizerIcon* icon; + + // Sizing increment. + int stepSize; + + // List of the access key QLabels. If not 0, access keys are onscreen. + QPtrList<QLabel>* accessKeyLabels; + + // Pointer to the KMainWindow. + KMainWindow* mainWindow; +}; + +KKbdAccessExtensions::KKbdAccessExtensions(KMainWindow* parent, const char* name) : + QObject(parent, name) +{ + // kdDebug() << "KKbdAccessExtensions::KKbdAccessExtensions: running." << endl; + d = new KKbdAccessExtensionsPrivate; + d->mainWindow = parent; + d->fwdAction = new KAction(i18n("Resize Panel Forward"), KShortcut("F8"), + 0, 0, parent->actionCollection(), "resize_panel_forward"); + d->revAction = new KAction(i18n("Resize Panel Reverse"), KShortcut("Shift+F8"), + 0, 0, parent->actionCollection(), "resize_panel_reverse"); + d->accessKeysAction = new KAction(i18n("Access Keys"), KShortcut("Alt+F8"), + 0, 0, parent->actionCollection(), "access_keys"); + // "Disable" the shortcuts so we can see them in eventFilter. + d->fwdAction->setEnabled(false); + d->revAction->setEnabled(false); + d->accessKeysAction->setEnabled(false); + d->icon = new KPanelKbdSizerIcon(); + kapp->installEventFilter(this); +} + +KKbdAccessExtensions::~KKbdAccessExtensions() +{ + kapp->removeEventFilter(this); + if (d->panel) exitSizing(); + delete d; +} + +int KKbdAccessExtensions::stepSize() const { return d->stepSize; } + +void KKbdAccessExtensions::setStepSize(int s) { d->stepSize = s; } + +bool KKbdAccessExtensions::eventFilter( QObject *o, QEvent *e ) +{ + if ( e->type() == QEvent::KeyPress ) { + // TODO: This permits only a single-key shortcut. For example, Alt+S,R would not work. + // If user configures a multi-key shortcut, it is undefined what will happen here. + // It would be better to handle these as KShortcut activate() signals, but the problem + // is that once a QDockWindow is undocked and has focus, the KShortcut activate() signals + // don't fire anymore. + KShortcut fwdSc = d->fwdAction->shortcut(); + KShortcut revSc = d->revAction->shortcut(); + KShortcut accessKeysSc = d->accessKeysAction->shortcut(); + QKeyEvent* kev = dynamic_cast<QKeyEvent *>(e); + KKey k = KKey(kev); + KShortcut sc = KShortcut(k); + // kdDebug() << "KKbdAccessExtensions::eventFilter: Key press " << sc << endl; + if (!d->accessKeyLabels) { + if (sc == fwdSc) { + nextHandle(); + return true; + } + if (sc == revSc) { + prevHandle(); + return true; + } + } + if (d->panel) { + if (k == KKey(Key_Escape)) + exitSizing(); + else + resizePanelFromKey(kev->key(), kev->state()); + // Eat the key. + return true; + } + if (sc == accessKeysSc && !d->panel) { + if (d->accessKeyLabels) { + delete d->accessKeyLabels; + d->accessKeyLabels = 0; + } else + displayAccessKeys(); + return true; + } + if (d->accessKeyLabels) { + if (k == KKey(Key_Escape)) { + delete d->accessKeyLabels; + d->accessKeyLabels = 0; + } else + handleAccessKey(kev); + return true; + } + return false; + } + else if (d->icon->isActive && e->type() == QEvent::MouseButtonPress) { + exitSizing(); + return true; + } + else if (d->accessKeyLabels && e->type() == QEvent::MouseButtonPress) { + delete d->accessKeyLabels; + d->accessKeyLabels = 0; + return true; + } +/* else if (e->type() == QEvent::MouseMove && d->icon->isActive) { + // Lock mouse cursor down. + showIcon(); + dynamic_cast<QMouseEvent *>(e)->accept(); + return true; + }*/ + else if (e->type() == QEvent::MouseMove && d->icon->isActive && d->panel) { + // Resize according to mouse movement. + QMouseEvent* me = dynamic_cast<QMouseEvent *>(e); + QSize s = d->icon->delta(); + int dx = s.width(); + int dy = s.height(); + resizePanel(dx, dy, me->state()); + me->accept(); + showIcon(); + return true; + } + else if (e->type() == QEvent::Resize && d->panel && o == d->panel) { + // TODO: This doesn't always work. + showIcon(); + } + return false; +} + +QWidgetList* KKbdAccessExtensions::getAllPanels() +{ + QWidgetList* allWidgets = kapp->allWidgets(); + QWidgetList* allPanels = new QWidgetList; + QWidget* widget = allWidgets->first(); + while (widget) { + if (widget->isVisible()) { + if (::qt_cast<QSplitter*>( widget )) { + // Only size QSplitters with at least two handles (there is always one hidden). + if (dynamic_cast<QSplitter *>(widget)->sizes().count() >= 2) + allPanels->append(widget); + } else if (::qt_cast<QDockWindow*>( widget )) { + if (dynamic_cast<QDockWindow *>(widget)->isResizeEnabled()) { + // kdDebug() << "KKbdAccessExtensions::getAllPanels: QDockWindow = " << widget->name() << endl; + allPanels->append(widget); + } + } + } + widget = allWidgets->next(); + } + delete allWidgets; + return allPanels; +} + +void KKbdAccessExtensions::nextHandle() +{ + QWidget* panel = d->panel; + // See if current panel has another handle. If not, find next panel. + if (panel) { + bool advance = true; + d->handleNdx++; + if (::qt_cast<QSplitter*>( panel )) + advance = (d->handleNdx >= dynamic_cast<QSplitter *>(panel)->sizes().count()); + else + // Undocked windows have only one "handle" (center). + advance = (d->handleNdx > 2 || !dynamic_cast<QDockWindow *>(panel)->area()); + if (advance) { + QWidgetList* allWidgets = getAllPanels(); + allWidgets->findRef(panel); + panel = 0; + if (allWidgets->current()) panel = allWidgets->next(); + delete allWidgets; + d->handleNdx = 1; + } + } else { + // Find first panel. + QWidgetList* allWidgets = getAllPanels(); + panel = allWidgets->first(); + delete allWidgets; + d->handleNdx = 1; + } + d->panel = panel; + if (panel) + showIcon(); + else + exitSizing(); +} + +void KKbdAccessExtensions::prevHandle() +{ + QWidget* panel = d->panel; + // See if current panel has another handle. If not, find previous panel. + if (panel) { + bool rewind = true; + d->handleNdx--; + rewind = (d->handleNdx < 1); + if (rewind) { + QWidgetList* allWidgets = getAllPanels(); + allWidgets->findRef(panel); + panel = 0; + if (allWidgets->current()) panel = allWidgets->prev(); + delete allWidgets; + if (panel) { + if (::qt_cast<QSplitter*>( panel )) + d->handleNdx = dynamic_cast<QSplitter *>(panel)->sizes().count() - 1; + else { + if (dynamic_cast<QDockWindow *>(panel)->area()) + d->handleNdx = 2; + else + d->handleNdx = 1; + } + } + } + } else { + // Find last panel. + QWidgetList* allWidgets = getAllPanels(); + panel = allWidgets->last(); + delete allWidgets; + if (panel) { + if (::qt_cast<QSplitter*>( panel )) + d->handleNdx = dynamic_cast<QSplitter *>(panel)->sizes().count() - 1; + else { + if (dynamic_cast<QDockWindow *>(panel)->area()) + d->handleNdx = 2; + else + d->handleNdx = 1; + } + } + } + d->panel = panel; + if (panel) + showIcon(); + else + exitSizing(); +} + +void KKbdAccessExtensions::exitSizing() +{ + // kdDebug() << "KKbdAccessExtensions::exiting sizing mode." << endl; + hideIcon(); + d->handleNdx = 0; + d->panel = 0; +} + +void KKbdAccessExtensions::showIcon() +{ + if (!d->panel) return; + QPoint p; + // kdDebug() << "KKbdAccessExtensions::showIcon: topLevelWidget = " << d->panel->topLevelWidget()->name() << endl; + if (::qt_cast<QSplitter*>( d->panel )) { + QSplitter* splitter = dynamic_cast<QSplitter *>(d->panel); + int handleNdx = d->handleNdx - 1; + QValueList<int> sizes = splitter->sizes(); + // kdDebug() << "KKbdAccessExtensions::showIcon: sizes = " << sizes << endl; + if (splitter->orientation() == Qt::Horizontal) { + d->icon->setShape(Qt::SizeHorCursor); + p.setX(sizes[handleNdx] + (splitter->handleWidth() / 2)); + p.setY(splitter->height() / 2); + } else { + d->icon->setShape(Qt::SizeVerCursor); + p.setX(splitter->width() / 2); + p.setY(sizes[handleNdx] + (splitter->handleWidth() / 2)); + } + // kdDebug() << "KKbdAccessExtensions::showIcon: p = " << p << endl; + p = splitter->mapToGlobal(p); + // kdDebug() << "KKbdAccessExtensions::showIcon: mapToGlobal = " << p << endl; + } else { + QDockWindow* dockWindow = dynamic_cast<QDockWindow *>(d->panel); + p = dockWindow->pos(); + if (dockWindow->area()) { + // kdDebug() << "KKbdAccessExtensions::showIcon: pos = " << p << " of window = " << dockWindow->parentWidget()->name() << endl; + p = dockWindow->parentWidget()->mapTo(dockWindow->topLevelWidget(), p); + // kdDebug() << "KKbdAccessExtensions::showIcon: mapTo = " << p << " of window = " << dockWindow->topLevelWidget()->name() << endl; + // TODO: How to get the handle width? + if (d->handleNdx == 1) { + d->icon->setShape(Qt::SizeHorCursor); + if (dockWindow->area()->orientation() == Qt::Vertical) { + if (dockWindow->area()->handlePosition() == QDockArea::Normal) + // Handle is to the right of the dock window. + p.setX(p.x() + dockWindow->width()); + // else Handle is to the left of the dock window. + } else + // Handle is to the right of the dock window. + p.setX(p.x() + dockWindow->width()); + p.setY(p.y() + (dockWindow->height() / 2)); + } else { + d->icon->setShape(Qt::SizeVerCursor); + p.setX(p.x() + (dockWindow->width() / 2)); + if (dockWindow->area()->orientation() == Qt::Vertical) + // Handle is below the dock window. + p.setY(p.y() + dockWindow->height()); + else { + if (dockWindow->area()->handlePosition() == QDockArea::Normal) + // Handle is below the dock window. + p.setY(p.y() + dockWindow->height()); + // else Handle is above the dock window. + } + } + p = dockWindow->topLevelWidget()->mapToGlobal(p); + } else { + d->icon->setShape(Qt::SizeAllCursor); + p = QPoint(dockWindow->width() / 2, dockWindow->height() / 2); + p = dockWindow->mapToGlobal(p); // Undocked. Position in center of window. + } + } + // kdDebug() << "KKbdAccessExtensions::showIcon: show(p) = " << p << endl; + d->icon->show(p); +} + +void KKbdAccessExtensions::hideIcon() +{ + d->icon->hide(); +} + +void KKbdAccessExtensions::resizePanel(int dx, int dy, int state) +{ + int adj = dx + dy; + if (adj == 0) return; + // kdDebug() << "KKbdAccessExtensions::resizePanel: panel = " << d->panel->name() << endl; + if (::qt_cast<QSplitter*>( d->panel )) { + QSplitter* splitter = dynamic_cast<QSplitter *>(d->panel); + int handleNdx = d->handleNdx - 1; + QValueList<int> sizes = splitter->sizes(); + // kdDebug() << "KKbdAccessExtensions::resizePanel: before sizes = " << sizes << endl; + sizes[handleNdx] = sizes[handleNdx] + adj; + // kdDebug() << "KKbdAccessExtensions::resizePanel: setSizes = " << sizes << endl; + splitter->setSizes(sizes); + QApplication::postEvent(splitter, new QEvent(QEvent::LayoutHint)); + } else { + // TODO: How to get the handle width? + QDockWindow* dockWindow = dynamic_cast<QDockWindow *>(d->panel); + if (dockWindow->area()) { + // kdDebug() << "KKbdAccessExtensions::resizePanel: fixedExtent = " << dockWindow->fixedExtent() << endl; + QSize fe = dockWindow->fixedExtent(); + if (d->handleNdx == 1) { + // When vertically oriented and dock area is on right side of screen, pressing + // left arrow increases size. + if (dockWindow->area()->orientation() == Qt::Vertical && + dockWindow->area()->handlePosition() == QDockArea::Reverse) adj = -adj; + int w = fe.width(); + if (w < 0) w = dockWindow->width(); + w = w + adj; + if (w > 0 ) dockWindow->setFixedExtentWidth(w); + } else { + // When horizontally oriented and dock area is at bottom of screen, + // pressing up arrow increases size. + if (dockWindow->area()->orientation() == Qt::Horizontal && + dockWindow->area()->handlePosition() == QDockArea::Reverse) adj = -adj; + int h = fe.height(); + if (h < 0) h = dockWindow->height(); + h = h + adj; + if (h > 0) dockWindow->setFixedExtentHeight(h); + } + dockWindow->updateGeometry(); + QApplication::postEvent(dockWindow->area(), new QEvent(QEvent::LayoutHint)); + // kdDebug() << "KKbdAccessExtensions::resizePanel: fixedExtent = " << dockWindow->fixedExtent() << endl; + } else { + if (state == Qt::ShiftButton) { + QSize s = dockWindow->size(); + s.setWidth(s.width() + dx); + s.setHeight(s.height() + dy); + dockWindow->resize(s); + } else { + QPoint p = dockWindow->pos(); + p.setX(p.x() + dx); + p.setY(p.y() + dy); + dockWindow->move(p); + } + } + } +} + +void KKbdAccessExtensions::resizePanelFromKey(int key, int state) +{ + // kdDebug() << "KPanelKdbSizer::resizePanelFromKey: key = " << key << " state = " << state << endl; + if (!d->panel) return; + int dx = 0; + int dy = 0; + int stepSize = d->stepSize; + switch (key) { + case Qt::Key_Left: dx = -stepSize; break; + case Qt::Key_Right: dx = stepSize; break; + case Qt::Key_Up: dy = -stepSize; break; + case Qt::Key_Down: dy = stepSize; break; + case Qt::Key_Prior: dy = -5 * stepSize; break; + case Qt::Key_Next: dy = 5 * stepSize; break; + } + int adj = dx + dy; + // kdDebug() << "KKbdAccessExtensions::resizePanelFromKey: adj = " << adj << endl; + if (adj != 0) + resizePanel(dx, dy, state); + else { + if (key == Qt::Key_Enter && ::qt_cast<QDockWindow*>( d->panel )) { + QDockWindow* dockWindow = dynamic_cast<QDockWindow *>(d->panel); + if (dockWindow->area()) + dockWindow->undock(); + else + dockWindow->dock(); + } + } + showIcon(); +} + +void KKbdAccessExtensions::displayAccessKeys() +{ + // Build a list of valid access keys that don't collide with shortcuts. + QString availableAccessKeys = "ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890"; + QPtrList<KXMLGUIClient> allClients = d->mainWindow->factory()->clients(); + QPtrListIterator<KXMLGUIClient> it( allClients ); + KXMLGUIClient *client; + while( (client=it.current()) !=0 ) + { + ++it; + KActionPtrList actions = client->actionCollection()->actions(); + for (int j = 0; j < (int)actions.count(); j++) { + KAction* action = actions[j]; + KShortcut sc = action->shortcut(); + for (int i = 0; i < (int)sc.count(); i++) { + KKeySequence seq = sc.seq(i); + if (seq.count() == 1) { + QString s = seq.toString(); + if (availableAccessKeys.contains(s)) + availableAccessKeys.remove(s); + } + } + } + } + // Find all visible, focusable widgets and create a QLabel for each. Don't exceed + // available list of access keys. + QWidgetList* allWidgets = kapp->allWidgets(); + QWidget* widget = allWidgets->first(); + int accessCount = 0; + int maxAccessCount = availableAccessKeys.length(); + int overlap = 20; + QPoint prevGlobalPos = QPoint(-overlap, -overlap); + while (widget && (accessCount < maxAccessCount)) { + if (widget->isVisible() && widget->isFocusEnabled() ) { + QRect r = widget->rect(); + QPoint p(r.x(), r.y()); + // Don't display an access key if within overlap pixels of previous one. + QPoint globalPos = widget->mapToGlobal(p); + QPoint diffPos = globalPos - prevGlobalPos; + if (diffPos.manhattanLength() > overlap) { + accessCount++; + QLabel* lab=new QLabel(widget, "", widget, 0, Qt::WDestructiveClose); + lab->setPalette(QToolTip::palette()); + lab->setLineWidth(2); + lab->setFrameStyle(QFrame::Box | QFrame::Plain); + lab->setMargin(3); + lab->adjustSize(); + lab->move(p); + if (!d->accessKeyLabels) { + d->accessKeyLabels = new QPtrList<QLabel>; + d->accessKeyLabels->setAutoDelete(true); + } + d->accessKeyLabels->append(lab); + prevGlobalPos = globalPos; + } + } + widget = allWidgets->next(); + } + if (accessCount > 0) { + // Sort the access keys from left to right and down the screen. + QValueList<KSortedLabel> sortedLabels; + for (int i = 0; i < accessCount; i++) + sortedLabels.append(KSortedLabel(d->accessKeyLabels->at(i))); + qHeapSort( sortedLabels ); + // Assign access key labels. + for (int i = 0; i < accessCount; i++) { + QLabel* lab = sortedLabels[i].label(); + QChar s = availableAccessKeys[i]; + lab->setText(s); + lab->adjustSize(); + lab->show(); + } + } +} + +// Handling of the HTML accesskey attribute. +bool KKbdAccessExtensions::handleAccessKey( const QKeyEvent* ev ) +{ +// Qt interprets the keyevent also with the modifiers, and ev->text() matches that, +// but this code must act as if the modifiers weren't pressed + if (!d->accessKeyLabels) return false; + QChar c; + if( ev->key() >= Key_A && ev->key() <= Key_Z ) + c = 'A' + ev->key() - Key_A; + else if( ev->key() >= Key_0 && ev->key() <= Key_9 ) + c = '0' + ev->key() - Key_0; + else { + // TODO fake XKeyEvent and XLookupString ? + // This below seems to work e.g. for eacute though. + if( ev->text().length() == 1 ) + c = ev->text()[ 0 ]; + } + if( c.isNull()) + return false; + + QLabel* lab = d->accessKeyLabels->first(); + while (lab) { + if (lab->text() == c) { + lab->buddy()->setFocus(); + delete d->accessKeyLabels; + d->accessKeyLabels = 0; + return true; + } + lab = d->accessKeyLabels->next(); + } + return false; +} + +KSortedLabel::KSortedLabel(QLabel* l) : + m_l(l) { } + +KSortedLabel::KSortedLabel() : + m_l(0) { } + +bool KSortedLabel::operator<( KSortedLabel l ) +{ + QPoint p1 = m_l->mapToGlobal(m_l->pos()); + QPoint p2 = l.label()->mapToGlobal(l.label()->pos()); + return (p1.y() < p2.y() || (p1.y() == p2.y() && p1.x() < p2.x())); +} diff --git a/lib/kofficecore/kkbdaccessextensions.h b/lib/kofficecore/kkbdaccessextensions.h new file mode 100644 index 00000000..acca057c --- /dev/null +++ b/lib/kofficecore/kkbdaccessextensions.h @@ -0,0 +1,175 @@ +/** @file +* This file is part of the KDE/KOffice project. +* Copyright (C) 2005, Gary Cramblitt <garycramblitt@comcast.net> +* +* @author Gary Cramblitt <garycramblitt@comcast.net> +* @since KOffice 1.5 +* +* 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 __KKBDACCESSEXTENSIONS_H__ +#define __KKBDACCESSEXTENSIONS_H__ + +// Qt includes. +#include <qobject.h> + +// KOffice includes. +#include <koffice_export.h> + +class KKbdAccessExtensionsPrivate; +class QWidgetList; +class KMainWindow; + +/** KKbdAccessExtensions is an object that improves accessibility for motor impaired users +* who may not be able to easily use a mouse. It adds two new capabilities using the keyboard: +* +* - Resizing and positioning of panel widgets derived from QSplitter and QDockWindow. +* - Setting focus to any widget that accepts focus. +* +* @section sizing_mode Sizing Mode +* +* Users may press F8 or Shift-F8 (defaults) to enter sizing mode. A sizing icon appears on the first +* QSplitter or QDockWindow handle found in the application (F8) or the last such handle (Shift+F8). +* (A "handle" is the divider bar that appears to the left, right, above, or below each panel +* of a QSplitter or QDockArea.) +* +* Once in sizing mode, the following functions are available via the keyboard: +* +* - F8 Moves to the next sizing handle. After the last handle, exits sizing mode. +* - Shift+F8 Moves to the previous sizing handle. After the first handle, exits sizing mode. +* - Esc Exits sizing mode. +* - LeftArrow When on a vertical sizing handle, moves the handle to the left. +* When on a horizontal sizing handle, moves the handle up. +* - RightArrow When on a vertical sizing handle, moves the handle to the right. +* When on a horizontal sizing handle, moves the handle down. +* - UpArrow When on a vertical sizing handle, moves the handle to the left. +* When on a horizontal sizing handle, moves the handle up. +* - DownArrow When on a vertical sizing handle, moves the handle to the right. +* When on a horizontal sizing handle, moves the handle down. +* - PgUp Like LeftArrow or UpArrow, but moves the handle 5X farther to the left or up. +* - PgDn Like RightArrow or DownArrow, but moves the handle 5X farther to the right or down. +* - Enter (On numeric keypad). When on the handle of a QDockWindow, undocks or docks +* the widget. Ignored when on the handle of a QSplitter. +* +* The default step size for each arrow key press is 10 pixels. +* +* When a QDockWindow is undocked, the sizing icon appears in the center of the window. +* The arrow keys and PgUp/PgDn move the undocked window on the screen. Shifted arrow keys +* and PgUp/PgDn decrease/increase the size of the undocked window. +* +* When the sizing icon is on a sizing handle, the mouse may also be used to move the handle +* without having to click and drag. When moving the mouse while sizing icon is on an undocked +* QDockWindow, the window moves with the mouse. Holding Shift down while moving the mouse +* sizes the QDockWindow. +* +* @note Users can also move and size undocked windows using the Window Operations Menu (Alt+F3). +* +* Clicking any mouse button exits sizing mode. +* +* When entering sizing mode, the position of the mouse cursor is saved and restored when +* exiting sizing mode. +* +* For a QSplitter or QDockWindow to be found, it must be in the kapp::allWidgets() list. +* +* @section focus_setting Focus Setting +* +* Users can press Alt-F8. A small box appears in the upperleft corner of each visible widget +* on the screen that can accept focus. Each box is assigned a single letter or digit. +* User can press the corresponding key to set focus to the widget. +* +* At most 36 such shortcuts are possible. If any application shortcuts are single letters +* or digits, those shortcuts are not in any of the boxes. +* +* Clicking any mouse button exits Focus Setting mode. +* +* @section notes Notes +* +* The F8, Shift+F8, and Alt+F8 keys are KShortcuts and therefore user may choose different keys in +* the application's Configure Shortcuts dialog. +* +* @note At present, these shortcuts may not be multi-key. If user sets multi-key +* shortcuts, they will not work. +* +* F8/Shift+F8 are the default shortcuts because these are the keys used for similar +* functionality in GNOME and Java SWT. +*/ +class KOFFICECORE_EXPORT KKbdAccessExtensions : public QObject +{ + // TODO: A .moc isn't really needed right now, but see TODO in eventFilter method. + // Q_PROPERTY(int stepSize READ stepSize WRITE setStepSize) + + public: + /** Constructor. + * @param parent KMainWindow of the application. Required. + * @param name (optional) Name of this object. + */ + KKbdAccessExtensions(KMainWindow* parent, const char* name = 0); + + /** Destructor. */ + virtual ~KKbdAccessExtensions(); + + /** Returns number of pixels panel is sized for each arrow key pressed. Default is 10. */ + int stepSize() const; + /** Sets number of pixels panel is sized for each arrow key pressed. */ + void setStepSize(int s); + + protected: + /** Event filter installed on kapp object. */ + bool eventFilter( QObject *o, QEvent *e ); + + /** Retrieves a list of all Splitter and DockArea widgets in the application. */ + QWidgetList* getAllPanels(); + /** Advances to the next Panel handle. If not currently in resizing mode, + turns it on. */ + void nextHandle(); + /** Moves to the previous Panel handle. If not currently in resizing mode, + turns it on. */ + void prevHandle(); + /** Exits Sizing mode. */ + void exitSizing(); + /** Moves panel handle based on key pressed. */ + void resizePanelFromKey(int key, int state); + /** Moves panel handle based on deltaX and deltaY and state of keyboard modifier keys. */ + void resizePanel(int dx, int dy, int state); + /** Displays the sizer icon. */ + void showIcon(); + /** Hides the sizer icon. */ + void hideIcon(); + + /** Displays the access keys. */ + void displayAccessKeys(); + /** Handles an access keypress. */ + bool handleAccessKey( const QKeyEvent* ev ); + + private: + KKbdAccessExtensionsPrivate* d; +}; + +/** Provides a way to sort QLabelss using a QValueList based on their screen position. */ +class KSortedLabel +{ +public: + KSortedLabel(QLabel* l); + KSortedLabel(); // default constructor + bool operator<( KSortedLabel l); + QLabel* label() { return m_l; } + +private: + QLabel* m_l; +}; + +#endif // __KKBDACCESSEXTENSIONS_H__ diff --git a/lib/kofficecore/koDetailsPaneBase.ui b/lib/kofficecore/koDetailsPaneBase.ui new file mode 100644 index 00000000..282e8bb6 --- /dev/null +++ b/lib/kofficecore/koDetailsPaneBase.ui @@ -0,0 +1,238 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>KoDetailsPaneBase</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KoDetailsPaneBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>528</width> + <height>549</height> + </rect> + </property> + <property name="caption"> + <string>DetailsPaneBase</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QSplitter" row="0" column="0"> + <property name="name"> + <cstring>m_splitter</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <widget class="KListView"> + <column> + <property name="text"> + <string></string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>false</bool> + </property> + </column> + <property name="name"> + <cstring>m_documentList</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>30</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="resizeMode"> + <enum>LastColumn</enum> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout2</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="KTextBrowser" row="5" column="0" rowspan="1" colspan="5"> + <property name="name"> + <cstring>m_detailsLabel</cstring> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + </widget> + <widget class="QLabel" row="0" column="0" rowspan="1" colspan="5"> + <property name="name"> + <cstring>m_previewLabel</cstring> + </property> + <property name="text"> + <string></string> + </property> + <property name="alignment"> + <set>AlignCenter</set> + </property> + </widget> + <spacer row="3" column="0"> + <property name="name"> + <cstring>spacer3_2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>0</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="Line" row="4" column="0" rowspan="1" colspan="5"> + <property name="name"> + <cstring>line1</cstring> + </property> + <property name="frameShape"> + <enum>HLine</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + </widget> + <widget class="KPushButton" row="2" column="2"> + <property name="name"> + <cstring>m_openButton</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string></string> + </property> + <property name="autoDefault"> + <bool>true</bool> + </property> + <property name="default"> + <bool>true</bool> + </property> + </widget> + <widget class="QCheckBox" row="3" column="1" rowspan="1" colspan="3"> + <property name="name"> + <cstring>m_alwaysUseCheckBox</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Always use this template</string> + </property> + <property name="toolTip" stdset="0"> + <string>Always use this template at application start up</string> + </property> + </widget> + <spacer row="3" column="4"> + <property name="name"> + <cstring>spacer3</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>0</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QLabel" row="1" column="0" rowspan="1" colspan="5"> + <property name="name"> + <cstring>m_titleLabel</cstring> + </property> + <property name="font"> + <font> + <bold>1</bold> + </font> + </property> + <property name="text"> + <string></string> + </property> + <property name="alignment"> + <set>AlignCenter</set> + </property> + </widget> + <spacer row="2" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>spacer8</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>0</width> + <height>20</height> + </size> + </property> + </spacer> + <spacer row="2" column="3" rowspan="1" colspan="2"> + <property name="name"> + <cstring>spacer8_2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>0</width> + <height>20</height> + </size> + </property> + </spacer> + </grid> + </widget> + </widget> + </grid> +</widget> +<customwidgets> +</customwidgets> +<tabstops> + <tabstop>m_documentList</tabstop> + <tabstop>m_openButton</tabstop> + <tabstop>m_alwaysUseCheckBox</tabstop> + <tabstop>m_detailsLabel</tabstop> +</tabstops> +<layoutdefaults spacing="6" margin="11"/> +<layoutfunctions spacing="KDialog::spacingHint" margin="KDialog::marginHint"/> +<includehints> + <includehint>klistview.h</includehint> + <includehint>ktextbrowser.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/lib/kofficecore/koDocumentInfoAboutWidget.ui b/lib/kofficecore/koDocumentInfoAboutWidget.ui new file mode 100644 index 00000000..29070e54 --- /dev/null +++ b/lib/kofficecore/koDocumentInfoAboutWidget.ui @@ -0,0 +1,352 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>KoDocumentInfoAboutWidget</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KoDocumentInfoAboutWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>448</width> + <height>557</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout8</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>pixmapLabel</cstring> + </property> + <property name="minimumSize"> + <size> + <width>56</width> + <height>56</height> + </size> + </property> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>leDocFile</cstring> + </property> + <property name="text"> + <string></string> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </hbox> + </widget> + <widget class="Line"> + <property name="name"> + <cstring>line1</cstring> + </property> + <property name="frameShape"> + <enum>HLine</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout3</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KLineEdit" row="2" column="1"> + <property name="name"> + <cstring>leDocKeywords</cstring> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>textLabel1_2</cstring> + </property> + <property name="text"> + <string>Subject:</string> + </property> + </widget> + <widget class="QLabel" row="2" column="0"> + <property name="name"> + <cstring>textLabel2</cstring> + </property> + <property name="text"> + <string>Keywords:</string> + </property> + </widget> + <widget class="QLayoutWidget" row="3" column="0"> + <property name="name"> + <cstring>layout14</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel3</cstring> + </property> + <property name="text"> + <string>Abstract:</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer2_2</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>31</height> + </size> + </property> + </spacer> + </vbox> + </widget> + <widget class="KLineEdit" row="1" column="1"> + <property name="name"> + <cstring>leDocSubject</cstring> + </property> + </widget> + <widget class="KLineEdit" row="0" column="1"> + <property name="name"> + <cstring>leDocTitle</cstring> + </property> + </widget> + <widget class="KTextEdit" row="3" column="1"> + <property name="name"> + <cstring>meDocAbstract</cstring> + </property> + <property name="tabChangesFocus"> + <bool>true</bool> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>textLabel1_2_2</cstring> + </property> + <property name="text"> + <string>Title:</string> + </property> + </widget> + </grid> + </widget> + <widget class="Line"> + <property name="name"> + <cstring>line2</cstring> + </property> + <property name="frameShape"> + <enum>HLine</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout11</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer row="0" column="2" rowspan="4" colspan="2"> + <property name="name"> + <cstring>spacer3</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>90</width> + <height>70</height> + </size> + </property> + </spacer> + <spacer row="4" column="2"> + <property name="name"> + <cstring>spacer5</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QLabel" row="2" column="0"> + <property name="name"> + <cstring>textLabel5</cstring> + </property> + <property name="text"> + <string>Modified:</string> + </property> + </widget> + <widget class="QLabel" row="3" column="1"> + <property name="name"> + <cstring>labelLastPrinted</cstring> + </property> + </widget> + <widget class="QLabel" row="2" column="1"> + <property name="name"> + <cstring>labelModified</cstring> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>textLabel4</cstring> + </property> + <property name="text"> + <string>Created:</string> + </property> + </widget> + <widget class="QLabel" row="3" column="0"> + <property name="name"> + <cstring>textLabel4_2</cstring> + </property> + <property name="text"> + <string>Last printed:</string> + </property> + </widget> + <widget class="QLabel" row="5" column="1"> + <property name="name"> + <cstring>labelRevision</cstring> + </property> + </widget> + <widget class="QLabel" row="5" column="0"> + <property name="name"> + <cstring>textLabel7</cstring> + </property> + <property name="text"> + <string>Revision number:</string> + </property> + </widget> + <widget class="QLabel" row="4" column="1"> + <property name="name"> + <cstring>labelEditing</cstring> + </property> + </widget> + <widget class="QLabel" row="4" column="0"> + <property name="name"> + <cstring>textLabel6</cstring> + </property> + <property name="text"> + <string>Total editing time:</string> + </property> + </widget> + <widget class="KPushButton" row="4" column="3"> + <property name="name"> + <cstring>pbReset</cstring> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>24</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>32767</width> + <height>24</height> + </size> + </property> + <property name="text"> + <string>Reset</string> + </property> + </widget> + <widget class="QLabel" row="1" column="1"> + <property name="name"> + <cstring>labelCreated</cstring> + </property> + </widget> + <widget class="QLabel" row="0" column="1"> + <property name="name"> + <cstring>labelType</cstring> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>Type:</string> + </property> + </widget> + <spacer row="5" column="3"> + <property name="name"> + <cstring>spacer4</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Minimum</enum> + </property> + <property name="sizeHint"> + <size> + <width>50</width> + <height>16</height> + </size> + </property> + </spacer> + </grid> + </widget> + </vbox> +</widget> +<tabstops> + <tabstop>leDocFile</tabstop> + <tabstop>leDocTitle</tabstop> + <tabstop>leDocSubject</tabstop> + <tabstop>leDocKeywords</tabstop> + <tabstop>meDocAbstract</tabstop> +</tabstops> +<layoutdefaults spacing="6" margin="11"/> +<layoutfunctions spacing="KDialog::spacingHint" margin="KDialog::marginHint"/> +<includehints> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>ktextedit.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/lib/kofficecore/koDocumentInfoAuthorWidget.ui b/lib/kofficecore/koDocumentInfoAuthorWidget.ui new file mode 100644 index 00000000..e442a1e8 --- /dev/null +++ b/lib/kofficecore/koDocumentInfoAuthorWidget.ui @@ -0,0 +1,311 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>KoDocumentInfoAuthorWidget</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KoDocumentInfoAuthorWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>363</width> + <height>492</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>labelAuthor</cstring> + </property> + <property name="minimumSize"> + <size> + <width>56</width> + <height>56</height> + </size> + </property> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>leFullName</cstring> + </property> + </widget> + </hbox> + </widget> + <widget class="Line"> + <property name="name"> + <cstring>line1</cstring> + </property> + <property name="frameShape"> + <enum>HLine</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout16</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel" row="9" column="0"> + <property name="name"> + <cstring>textLabel10</cstring> + </property> + <property name="text"> + <string>Postal code:</string> + </property> + </widget> + <widget class="QLabel" row="6" column="0"> + <property name="name"> + <cstring>textLabel7</cstring> + </property> + <property name="text"> + <string>Telephone (work):</string> + </property> + </widget> + <widget class="QLabel" row="4" column="0"> + <property name="name"> + <cstring>textLabel5</cstring> + </property> + <property name="text"> + <string>Email:</string> + </property> + </widget> + <widget class="KLineEdit" row="3" column="1"> + <property name="name"> + <cstring>leCompany</cstring> + </property> + </widget> + <widget class="KLineEdit" row="9" column="1"> + <property name="name"> + <cstring>lePostalCode</cstring> + </property> + </widget> + <widget class="QLabel" row="10" column="0"> + <property name="name"> + <cstring>textLabel11</cstring> + </property> + <property name="text"> + <string>City:</string> + </property> + </widget> + <widget class="KLineEdit" row="2" column="1"> + <property name="name"> + <cstring>leAuthorPosition</cstring> + </property> + </widget> + <widget class="KLineEdit" row="8" column="1"> + <property name="name"> + <cstring>leStreet</cstring> + </property> + </widget> + <widget class="KLineEdit" row="7" column="1"> + <property name="name"> + <cstring>leFax</cstring> + </property> + </widget> + <widget class="KLineEdit" row="5" column="1"> + <property name="name"> + <cstring>leTelephoneHome</cstring> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>Initials:</string> + </property> + </widget> + <widget class="QLabel" row="7" column="0"> + <property name="name"> + <cstring>textLabel8</cstring> + </property> + <property name="text"> + <string>Fax:</string> + </property> + </widget> + <widget class="KLineEdit" row="4" column="1"> + <property name="name"> + <cstring>leEmail</cstring> + </property> + </widget> + <widget class="KLineEdit" row="1" column="1"> + <property name="name"> + <cstring>leAuthorTitle</cstring> + </property> + </widget> + <widget class="QLabel" row="3" column="0"> + <property name="name"> + <cstring>textLabel4</cstring> + </property> + <property name="text"> + <string>Company:</string> + </property> + </widget> + <widget class="KLineEdit" row="6" column="1"> + <property name="name"> + <cstring>leTelephoneWork</cstring> + </property> + </widget> + <widget class="KLineEdit" row="0" column="1"> + <property name="name"> + <cstring>leInitial</cstring> + </property> + </widget> + <widget class="QLabel" row="11" column="0"> + <property name="name"> + <cstring>textLabel12</cstring> + </property> + <property name="text"> + <string>Country:</string> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>textLabel2</cstring> + </property> + <property name="text"> + <string>Title:</string> + </property> + </widget> + <widget class="QLabel" row="2" column="0"> + <property name="name"> + <cstring>textLabel3</cstring> + </property> + <property name="text"> + <string>Position:</string> + </property> + </widget> + <widget class="QLabel" row="8" column="0"> + <property name="name"> + <cstring>textLabel9</cstring> + </property> + <property name="text"> + <string>Street:</string> + </property> + </widget> + <widget class="QLabel" row="5" column="0"> + <property name="name"> + <cstring>textLabel6</cstring> + </property> + <property name="text"> + <string>Telephone (home):</string> + </property> + </widget> + <widget class="KLineEdit" row="11" column="1"> + <property name="name"> + <cstring>leCountry</cstring> + </property> + </widget> + <widget class="KLineEdit" row="10" column="1"> + <property name="name"> + <cstring>leCity</cstring> + </property> + </widget> + </grid> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton"> + <property name="name"> + <cstring>pbLoadKABC</cstring> + </property> + <property name="text"> + <string>&Load From Address Book</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>pbDelete</cstring> + </property> + <property name="text"> + <string>Delete Personal Data</string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + </hbox> + </widget> + <spacer> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>16</height> + </size> + </property> + </spacer> + </vbox> +</widget> +<tabstops> + <tabstop>leFullName</tabstop> + <tabstop>leInitial</tabstop> + <tabstop>leAuthorTitle</tabstop> + <tabstop>leAuthorPosition</tabstop> + <tabstop>leCompany</tabstop> + <tabstop>leEmail</tabstop> + <tabstop>leTelephoneHome</tabstop> + <tabstop>leTelephoneWork</tabstop> + <tabstop>leFax</tabstop> + <tabstop>leStreet</tabstop> + <tabstop>lePostalCode</tabstop> + <tabstop>leCity</tabstop> + <tabstop>leCountry</tabstop> + <tabstop>pbLoadKABC</tabstop> +</tabstops> +<layoutdefaults spacing="6" margin="11"/> +<layoutfunctions spacing="KDialog::spacingHint" margin="KDialog::marginHint"/> +<includehints> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/lib/kofficecore/koDocumentInfoUserMetadataWidget.ui b/lib/kofficecore/koDocumentInfoUserMetadataWidget.ui new file mode 100644 index 00000000..fd91dd5e --- /dev/null +++ b/lib/kofficecore/koDocumentInfoUserMetadataWidget.ui @@ -0,0 +1,233 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>KoDocumentInfoUserMetadataWidget</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KoDocumentInfoUserMetadataWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>558</width> + <height>484</height> + </rect> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="KListView" row="0" column="0"> + <property name="name"> + <cstring>metaListView</cstring> + </property> + </widget> + <widget class="QLayoutWidget" row="0" column="1"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton"> + <property name="name"> + <cstring>kPushButton2</cstring> + </property> + <property name="minimumSize"> + <size> + <width>130</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Add...</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>kPushButton4</cstring> + </property> + <property name="text"> + <string>Delete</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer5</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>101</height> + </size> + </property> + </spacer> + </vbox> + </widget> + <widget class="QButtonGroup" row="1" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>buttonGroup1</cstring> + </property> + <property name="title"> + <string>Type</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QRadioButton" row="1" column="0"> + <property name="name"> + <cstring>rbNumber</cstring> + </property> + <property name="text"> + <string>Number:</string> + </property> + <property name="checked"> + <bool>false</bool> + </property> + </widget> + <widget class="KDoubleSpinBox" row="1" column="1"> + <property name="name"> + <cstring>wNumber</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + <widget class="QRadioButton" row="0" column="0"> + <property name="name"> + <cstring>rString</cstring> + </property> + <property name="text"> + <string>String:</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + <widget class="KLineEdit" row="0" column="1"> + <property name="name"> + <cstring>wString</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + </widget> + <widget class="QRadioButton" row="2" column="0"> + <property name="name"> + <cstring>rbBoolean</cstring> + </property> + <property name="text"> + <string>Boolean:</string> + </property> + </widget> + <widget class="KComboBox" row="2" column="1"> + <item> + <property name="text"> + <string>true</string> + </property> + </item> + <item> + <property name="text"> + <string>false</string> + </property> + </item> + <property name="name"> + <cstring>wBoolean</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + <widget class="QRadioButton" row="3" column="0"> + <property name="name"> + <cstring>rbTime</cstring> + </property> + <property name="text"> + <string>Time:</string> + </property> + </widget> + <widget class="KTimeWidget" row="3" column="1"> + <property name="name"> + <cstring>wTime</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + <widget class="QRadioButton" row="4" column="0"> + <property name="name"> + <cstring>rbDate</cstring> + </property> + <property name="text"> + <string>Date:</string> + </property> + </widget> + <widget class="KDateTimeWidget" row="4" column="1"> + <property name="name"> + <cstring>wDate</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </grid> + </widget> + </grid> +</widget> +<connections> + <connection> + <sender>rbNumber</sender> + <signal>toggled(bool)</signal> + <receiver>wNumber</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>rbDate</sender> + <signal>toggled(bool)</signal> + <receiver>wDate</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>rbTime</sender> + <signal>toggled(bool)</signal> + <receiver>wTime</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>rbBoolean</sender> + <signal>toggled(bool)</signal> + <receiver>wBoolean</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>rString</sender> + <signal>toggled(bool)</signal> + <receiver>wString</receiver> + <slot>setEnabled(bool)</slot> + </connection> +</connections> +<layoutdefaults spacing="6" margin="11"/> +<layoutfunctions spacing="KDialog::spacingHint" margin="KDialog::marginHint"/> +<includehints> + <includehint>klistview.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>knuminput.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kcombobox.h</includehint> + <includehint>ktimewidget.h</includehint> + <includehint>kdatetimewidget.h</includehint> + <includehint>kdatewidget.h</includehint> + <includehint>ktimewidget.h</includehint> +</includehints> +</UI> diff --git a/lib/kofficecore/koOpenPaneBase.ui b/lib/kofficecore/koOpenPaneBase.ui new file mode 100644 index 00000000..64a083a5 --- /dev/null +++ b/lib/kofficecore/koOpenPaneBase.ui @@ -0,0 +1,144 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>KoOpenPaneBase</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KoOpenPaneBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>534</width> + <height>482</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QSplitter"> + <property name="name"> + <cstring>m_splitter</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="KListView"> + <column> + <property name="text"> + <string></string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>false</bool> + </property> + </column> + <property name="name"> + <cstring>m_sectionList</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="allColumnsShowFocus"> + <bool>true</bool> + </property> + <property name="resizeMode"> + <enum>LastColumn</enum> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_openExistingButton</cstring> + </property> + <property name="text"> + <string></string> + </property> + </widget> + </vbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>m_headerLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>4</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <bold>1</bold> + </font> + </property> + <property name="text"> + <string>No Header</string> + </property> + </widget> + <widget class="Line"> + <property name="name"> + <cstring>line2</cstring> + </property> + <property name="frameShape"> + <enum>HLine</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + </widget> + <widget class="QWidgetStack"> + <property name="name"> + <cstring>m_widgetStack</cstring> + </property> + <widget class="QWidget"> + <property name="name"> + <cstring>WStackPage</cstring> + </property> + <attribute name="id"> + <number>0</number> + </attribute> + </widget> + </widget> + </vbox> + </widget> + </widget> + </vbox> +</widget> +<customwidgets> +</customwidgets> +<layoutdefaults spacing="6" margin="11"/> +<layoutfunctions spacing="KDialog::spacingHint" margin="KDialog::marginHint"/> +<includehints> + <includehint>klistview.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/lib/kofficecore/kodocinfopropspage.desktop b/lib/kofficecore/kodocinfopropspage.desktop new file mode 100644 index 00000000..8ef8c958 --- /dev/null +++ b/lib/kofficecore/kodocinfopropspage.desktop @@ -0,0 +1,68 @@ +[Desktop Entry] +Type=Service +Name=KOffice Document Info Properties Page +Name[af]=Koffice Dokument Inligting Eienskappe Bladsy +Name[ar]=صفحة الخصائص المعلوماتيّة الخاصة بمُستند KOffice +Name[az]=KOffice Sənəd Xüsusiyyətləri Səhifəsi +Name[bg]=Информация за документ на KOffice +Name[br]=Pajenn Titouriñ Perzhioù Teul KOffice +Name[bs]=Stranica sa osobinama KOffice dokumenta +Name[ca]=Pàgina d'informació de propietats de documents KOffice +Name[cs]=Stránka s informacemi o dokumentu KOffice +Name[cy]=Tudalen Priodweddau Gwybodaeth Dogfen KWord +Name[da]=Egenskabsside for KOffice-dokumentinformation +Name[de]=KOffice Dokumentinfo-Eigenschaftenseite +Name[el]=Σελίδα ιδιοτήτων των πληροφοριών εγγράφου του KOffice +Name[eo]=KOficeja Dokumentinformo-eco-paĝo +Name[es]=Página de propiedades de información de documentos de KOffice +Name[et]=KOffice'i dokumendiinfo omaduste lehekülg +Name[eu]=KOffice-en dokumentuen informazio-propietateen orria +Name[fa]=صفحۀ ویژگیهای اطلاعات سند KOffice +Name[fr]=Page des propriétés d'un document KOffice +Name[fy]=KOffice dokumint ynfo eigenskippen side +Name[gl]=Páxina de Información das Propiedades do Documento de KOffice +Name[he]=דף מאפיינים לגבי מידע מסמך של KOffice +Name[hi]=के-ऑफ़िस दस्तावेज़ जानकारी गुण पृष्ठ +Name[hr]=Stranica s podacima o svojstvima KOffice dokumenta +Name[hu]=KOffice dokumentumjellemzők tulajdonságlap +Name[id]=Halaman Informasi Properties KOffice +Name[is]=Eiginleikar KOffice skjals +Name[it]=Pagina proprietà informazioni documento KOffice +Name[ja]=KOffice ドキュメント情報プロパティページ +Name[km]=ទំព័រលក្ខណៈសម្បត្តិព័ត៌មានឯកសារ KOffice +Name[lo]=ຫນ້າຂໍ້ມູນຄຸນສົມບັດຂອງເອກະສານ ໂປຣແກມຊຸດສໍານັກງານ K +Name[lt]=KOffice Dokumento informacijos savybių puslapis +Name[lv]=KOffice dokumenta informācijas īpašību lapa +Name[mk]=Информации и параметри на KOffice документ +Name[ms]=Halaman Ciri Maklumat Dokumen KOffice +Name[mt]=Paġna ta' Informazzjoni dwar Dokumenti KOffice +Name[nb]=KOffice dokumentinformasjon +Name[nds]=Dokmentinformatschonen-Egenschappensiet för KOffice +Name[ne]=केडीई कार्यालय कागजात सूचना गुणहरूको पृष्ठ +Name[nl]=KOffice Document Info Eigenschappen Pagina +Name[nn]=KOffice Dokumentinformasjon +Name[pl]=Strona właściwości dokumentu KOffice +Name[pt]=Página de Informações sobre um Documento do KOffice +Name[pt_BR]=Página de Propriedades de Informações do Documento do KOffice +Name[ro]=Pagină proprietăţi document KOffice +Name[ru]=Страница сведений о документе KOffice +Name[se]=KOffice-dokumeantadieđut +Name[sk]=Popis vlastností dokumentu KOffice +Name[sl]=Stran z lastnostmi dokumenta za KOffice +Name[sr]=KOffice-ова страна са информацијама о документу +Name[sr@Latn]=KOffice-ova strana sa informacijama o dokumentu +Name[sv]=Koffice-sida med dokumentinformation +Name[tg]=KOffice Ҳуҷҷати Ахбороти Саҳифаи Ҳосиятҳо +Name[th]=หน้าข้อมูลคุณสมบัติของเอกสารโปรแกรมชุดสำนักงาน K +Name[tr]=KOffice Belge Özellikler Sayfası +Name[uk]=Сторінка властивостей документу KOffice +Name[uz]=KOffice hujjati haqida maʼlumot +Name[uz@cyrillic]=KOffice ҳужжати ҳақида маълумот +Name[ven]=Siatari la mafhungo a manwalwa a ofisi ya K +Name[wa]=Pådje des prôpietés des informåcions do documint KOffice +Name[xh]=Iphepha Lezinto zobumnini Zexwebhu lwe KOffice +Name[zh_CN]=KOffice 文档基本信息和属性 +Name[zh_TW]=KOffice 文件資訊和屬性 +Name[zu]=KOffice iphepha ledokhumenti Iphepha Eliqukethe Ulwazi +X-KDE-Library=kodocinfopropspage +ServiceTypes=KPropsDlg/Plugin,application/x-kspread,application/x-kword,application/x-kpresenter,application/x-kchart diff --git a/lib/kofficecore/koffice_export.h b/lib/kofficecore/koffice_export.h new file mode 100644 index 00000000..37a9e8f7 --- /dev/null +++ b/lib/kofficecore/koffice_export.h @@ -0,0 +1,210 @@ +/* + This file is part of kofficecore + Copyright (c) 2005 KOffice Team + + 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 _KOFFICE_EXPORT_H +#define _KOFFICE_EXPORT_H + +#include <kdeversion.h> + +#ifdef Q_WS_WIN + +/* workaround for KDElibs < 3.2 on !win32 */ +#ifndef KDE_EXPORT +# define KDE_EXPORT +#endif + +#ifndef KOFFICECORE_EXPORT +# ifdef MAKE_KOFFICECORE_LIB +# define KOFFICECORE_EXPORT KDE_EXPORT +# elif KDE_MAKE_LIB +# define KOFFICECORE_EXPORT KDE_IMPORT +# else +# define KOFFICECORE_EXPORT +# endif +#endif + +#ifndef KOFFICEUI_EXPORT +# ifdef MAKE_KOFFICEUI_LIB +# define KOFFICEUI_EXPORT KDE_EXPORT +# elif KDE_MAKE_LIB +# define KOFFICEUI_EXPORT KDE_IMPORT +# else +# define KOFFICEUI_EXPORT +# endif +#endif + +#ifdef MAKE_KOSTORE_LIB +# define KOSTORE_EXPORT KDE_EXPORT +#elif KDE_MAKE_LIB +# define KOSTORE_EXPORT KDE_IMPORT +#else +# define KOSTORE_EXPORT +#endif + +#ifdef MAKE_KOPALETTE_LIB +# define KOPALETTE_EXPORT KDE_EXPORT +#elif KDE_MAKE_LIB +# define KOPALETTE_EXPORT KDE_IMPORT +#else +# define KOPALETTE_EXPORT +#endif + +#ifdef MAKE_KOWMF_LIB +# define KOWMF_EXPORT KDE_EXPORT +#elif KDE_MAKE_LIB +# define KOWMF_EXPORT KDE_IMPORT +#else +# define KOWMF_EXPORT +#endif + +#ifdef MAKE_KWMF_LIB +# define KWMF_EXPORT KDE_EXPORT +#elif KDE_MAKE_LIB +# define KWMF_EXPORT KDE_IMPORT +#else +# define KWMF_EXPORT +#endif + +#ifdef MAKE_KOTEXT_LIB +# define KOTEXT_EXPORT KDE_EXPORT +#elif KDE_MAKE_LIB +# define KOTEXT_EXPORT KDE_IMPORT +#else +# define KOTEXT_EXPORT +#endif + +#ifdef MAKE_KOFORMULA_LIB +# define KOFORMULA_EXPORT KDE_EXPORT +#elif KDE_MAKE_LIB +# define KOFORMULA_EXPORT KDE_IMPORT +#else +# define KOFORMULA_EXPORT +#endif + +#ifdef MAKE_KOPAINTER_LIB +# define KOPAINTER_EXPORT KDE_EXPORT +#elif KDE_MAKE_LIB +# define KOPAINTER_EXPORT KDE_IMPORT +#else +# define KOPAINTER_EXPORT +#endif + +#ifdef MAKE_KWORD_LIB +# define KWORD_EXPORT KDE_EXPORT +#elif KDE_MAKE_LIB +# define KWORD_EXPORT KDE_IMPORT +#else +# define KWORD_EXPORT +#endif + +#ifdef MAKE_KWMAILMERGE_LIB +# define KWMAILMERGE_EXPORT KDE_EXPORT +#elif KDE_MAKE_LIB +# define KWMAILMERGE_EXPORT KDE_IMPORT +#else +# define KWMAILMERGE_EXPORT +#endif + +#ifndef KOPROPERTY_EXPORT +# ifdef MAKE_KOPROPERTY_LIB +# define KOPROPERTY_EXPORT KDE_EXPORT +# elif KDE_MAKE_LIB +# define KOPROPERTY_EXPORT KDE_IMPORT +# else +# define KOPROPERTY_EXPORT +# endif +#endif + +#define KPRESENTER_EXPORT KDE_EXPORT +#define KCHART_EXPORT KDE_EXPORT +#define KDCHART_EXPORT KDE_EXPORT +#define KARBONCOMMON_EXPORT KDE_EXPORT +#define KARBONBASE_EXPORT KDE_EXPORT +#define KARBONCOMMAND_EXPORT KDE_EXPORT +#define KSPREAD_EXPORT KDE_EXPORT +#define KOSHELL_EXPORT KDE_EXPORT +#define KPLATO_EXPORT KDE_EXPORT +#define KPLATOCHART_EXPORT KDE_EXPORT +#define KUGAR_EXPORT KDE_EXPORT +#define KUGARDESIGNER_EXPORT KDE_EXPORT +#define KOFFICETOOLS_EXPORT KDE_EXPORT +#define KOFFICEFILTER_EXPORT KDE_EXPORT +#define KOCHARTINTERFACE_EXPORT KDE_EXPORT +#define KIVIOPLUGINS_EXPORT KDE_EXPORT +#define KIVIO_EXPORT KDE_EXPORT +#define KRITA_EXPORT KDE_EXPORT +#define KRITAUI_EXPORT KDE_EXPORT +#define KRITACORE_EXPORT KDE_EXPORT +#define KRITATOOL_EXPORT KDE_EXPORT +#define KRITAPAINT_EXPORT KDE_EXPORT +#define EXAMPLE_EXPORT KDE_EXPORT +#else // not windows + +#if KDE_VERSION >= KDE_MAKE_VERSION(3,3,90) +#define KOFFICE_EXPORT KDE_EXPORT +#else +#define KOFFICE_EXPORT +#endif + +/* kdemacros is OK, we can use gcc visibility macros */ +#define KOFFICECORE_EXPORT KOFFICE_EXPORT +#define KOFFICEUI_EXPORT KOFFICE_EXPORT +#define KOPALETTE_EXPORT KOFFICE_EXPORT +#define KOTEXT_EXPORT KOFFICE_EXPORT +#define KOFORMULA_EXPORT KOFFICE_EXPORT +#define KOSTORE_EXPORT KOFFICE_EXPORT +#define KOWMF_EXPORT KOFFICE_EXPORT +#define KOSCRIPT_EXPORT KOFFICE_EXPORT +#define KOPAINTER_EXPORT KOFFICE_EXPORT +#define KSPREAD_EXPORT KOFFICE_EXPORT +#define KFORMULA_EXPORT KOFFICE_EXPORT +#define KWORD_EXPORT KOFFICE_EXPORT +#define KWORD_MAILMERGE_EXPORT KOFFICE_EXPORT +#define KPRESENTER_EXPORT KOFFICE_EXPORT +#define KCHART_EXPORT KOFFICE_EXPORT +#define KDCHART_EXPORT KOFFICE_EXPORT +#define KARBONCOMMON_EXPORT KOFFICE_EXPORT +#define KARBONBASE_EXPORT KOFFICE_EXPORT +#define KARBONCOMMAND_EXPORT KOFFICE_EXPORT +#define KOSHELL_EXPORT KOFFICE_EXPORT +#define KPLATO_EXPORT KOFFICE_EXPORT +#define KPLATOCHART_EXPORT KOFFICE_EXPORT +#define KUGAR_EXPORT KOFFICE_EXPORT +#define KUGARDESIGNER_EXPORT KOFFICE_EXPORT +#define KOFFICETOOLS_EXPORT KOFFICE_EXPORT +#define KOFFICEFILTER_EXPORT KOFFICE_EXPORT +#define KOCHARTINTERFACE_EXPORT KOFFICE_EXPORT +#define KIVIOPLUGINS_EXPORT KOFFICE_EXPORT +#define KIVIO_EXPORT KOFFICE_EXPORT +#define KRITA_EXPORT KOFFICE_EXPORT +#define KRITAUI_EXPORT KOFFICE_EXPORT +#define KRITACORE_EXPORT KOFFICE_EXPORT +#define KRITATOOL_EXPORT KOFFICE_EXPORT +#define KRITAPAINT_EXPORT KOFFICE_EXPORT +#ifndef KOPROPERTY_EXPORT +# define KOPROPERTY_EXPORT KOFFICE_EXPORT +#endif +#define EXAMPLE_EXPORT KOFFICE_EXPORT + +#endif /* not windows */ + +#endif /* _KOFFICE_EXPORT_H */ diff --git a/lib/kofficecore/koffice_shell.rc b/lib/kofficecore/koffice_shell.rc new file mode 100644 index 00000000..a619390b --- /dev/null +++ b/lib/kofficecore/koffice_shell.rc @@ -0,0 +1,58 @@ +<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd"> +<kpartgui name="KOffice" version="17"> +<MenuBar> + <Menu name="file" noMerge="1"><text>&File</text> + <Action name="file_new" /> + <Action name="file_open" /> + <Action name="file_open_recent" /> + <Separator /> + <Action name="file_save" /> + <Action name="file_save_as" /> + <Action name="file_reload_file" /> +<!-- <Action name="file_versions_file"/> --> + <Separator /> + <Action name="file_import_file" /> + <Action name="file_export_file" /> + <Action name="file_send_file" /> + <Separator /> + <Merge /> + <Separator /> + <Action name="file_print" /> + <Action name="file_print_preview" /> + <Separator /> + <Action name="file_documentinfo" /> + <Separator /> + <Action name="file_close" /> + <Action name="file_quit" /> + </Menu> + <Merge /> + <Menu name="settings" noMerge="1"><text>&Settings</text> + <DefineGroup name="settings_top"/> + <Merge/> +<!-- <ActionList name="toolbarlist"/> --> + <Separator/> + <DefineGroup name="settings_show"/> + <Separator/> + <Action name="options_configure_keybinding"/> + <Action name="options_configure_toolbars"/> + <DefineGroup name="settings_configure"/> + </Menu> + <Menu name="help" noMerge="1"><text>&Help</text> + <Merge /> + <Action name="help_contents"/> + <Action name="help_whats_this"/> + <Separator/> + <Action name="help_report_bug"/> + <Separator/> + <Action name="help_about_app"/> + <Action name="help_about_kde"/> + </Menu> +</MenuBar> +<ToolBar name="mainToolBar" fullWidth="false" noMerge="1"><Text>File</Text> + <Action name="file_new" /> + <Action name="file_open" /> + <Action name="file_save" /> + <Action name="file_print" /> + <Action name="file_print_preview" /> +</ToolBar> +</kpartgui> diff --git a/lib/kofficecore/kofficeversion.cc b/lib/kofficecore/kofficeversion.cc new file mode 100644 index 00000000..262664fc --- /dev/null +++ b/lib/kofficecore/kofficeversion.cc @@ -0,0 +1,46 @@ +/* This file is part of the KOffice libraries + Copyright (c) 2003 David Faure <faure@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 "kofficeversion.h" + +unsigned int KOffice::version() +{ + return KOFFICE_VERSION; +} + +unsigned int KOffice::versionMajor() +{ + return KOFFICE_VERSION_MAJOR; +} + +unsigned int KOffice::versionMinor() +{ + return KOFFICE_VERSION_MINOR; +} + +unsigned int KOffice::versionRelease() +{ + return KOFFICE_VERSION_RELEASE; +} + +const char *KOffice::versionString() +{ + return KOFFICE_VERSION_STRING; +} + diff --git a/lib/kofficecore/kofficeversion.h b/lib/kofficecore/kofficeversion.h new file mode 100644 index 00000000..89022330 --- /dev/null +++ b/lib/kofficecore/kofficeversion.h @@ -0,0 +1,86 @@ +/* This file is part of the KOffice libraries + Copyright (c) 2003 David Faure <faure@kde.org> + Copyright (c) 2003 Lukas Tinkl <lukas@kde.org> + Copyright (c) 2004 Nicolas Goutte <goutte@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 _KOFFICE_VERSION_H_ +#define _KOFFICE_VERSION_H_ + +// Remmber to synchronize the version number with the file(s): +// - koffice/configure.in.in +// +// NOT synchronized are: +// - karbon/karbon_aboutdata.h +// + +#define KOFFICE_VERSION_STRING "1.6.3" +#define KOFFICE_VERSION_MAJOR 1 +#define KOFFICE_VERSION_MINOR 6 +#define KOFFICE_VERSION_RELEASE 3 +#define KOFFICE_MAKE_VERSION( a,b,c ) (((a) << 16) | ((b) << 8) | (c)) + +#define KOFFICE_VERSION \ + KOFFICE_MAKE_VERSION(KOFFICE_VERSION_MAJOR,KOFFICE_VERSION_MINOR,KOFFICE_VERSION_RELEASE) + +#define KOFFICE_IS_VERSION(a,b,c) ( KOFFICE_VERSION >= KOFFICE_MAKE_VERSION(a,b,c) ) + +/** + * Namespace for general KOFFICE functions. + */ +namespace KOffice +{ + /** + * Returns the encoded number of KOffice's version, see the KOFFICE_VERSION macro. + * In contrary to that macro this function returns the number of the actually + * installed KOffice version, not the number of the KOffice version that was + * installed when the program was compiled. + * @return the version number, encoded in a single uint + * @since 1.3 + */ + unsigned int version(); + /** + * Returns the major number of KOffice's version, e.g. + * 1 for KOffice 1.2.3. + * @return the major version number + * @since 1.3 + */ + unsigned int versionMajor(); + /** + * Returns the minor number of KOffice's version, e.g. + * 2 for KOffice 1.2.3. + * @return the minor version number + * @since 1.3 + */ + unsigned int versionMinor(); + /** + * Returns the release of KOffice's version, e.g. + * 3 for KOffice 1.2.3. + * @return the release number + * @since 1.3 + */ + unsigned int versionRelease(); + /** + * Returns the KOffice version as string, e.g. "1.2.3". + * @return the KOffice version. You can keep the string forever + * @since 1.3 + */ + const char *versionString(); +} + +#endif // _KOFFICE_VERSION_H_ diff --git a/lib/kofficecore/priorityqueue.h b/lib/kofficecore/priorityqueue.h new file mode 100644 index 00000000..68019143 --- /dev/null +++ b/lib/kofficecore/priorityqueue.h @@ -0,0 +1,216 @@ +/* This file is part of the KOffice libraries + Copyright (C) 2001 Werner Trobin <trobin@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 priority_queue_h +#define priority_queue_h + +#include <vector> +#include <qstring.h> +#include <qasciidict.h> +#include <kdebug.h> + +// Better put all those internal classes in some namespace to avoid clashes +// as the names are quite generic +namespace KOffice { + + /** + * This PriorityQueue class is implemented as "upside down" heap, i.e. the item + * with the \b smallest key is at the root of the heap. + * If you feel like using that class your template parameter has to have a public + * method "unsigned int key() const" which returns - surprise - the key. The + * supplied key must not be "negative." We use UINT_MAX as value to represent + * "infinity" for the shortest path algorithm. + * As this is a very specialized class we also demand a "void setIndex(int i)" + * method in order to tell nodes where they are located inside the heap. This is + * a very ugly approach, but it's the only way I see if you want to avoid a O(n) + * search for the item where you decreased the key :} + * Just to make it even worse we also use a "int index() const" method + * to fetch the index... well, you most likely would need one anyway ;) + * Note: This class is pointer based (like QPtr*) - if you create PriorityQueue<X> + * we actually operate on X* ! We don't care about deleting your pointers at all. + * We don't copy them, we don't create new ones,... - you own them, you have + * to delete them :) + * + * In case you change a key value make sure to call keyDecreased and pass the + * item you touched. This is running in O(log n) according to Cormen... + * + * All the ideas are stol^H^H^H^Hborrowed from "Introduction to Algorithms", + * Cormen et al + * + * @author Werner Trobin <trobin@kde.org> + */ + template<class T> class PriorityQueue { + + public: + PriorityQueue() {} + PriorityQueue( const PriorityQueue<T>& rhs ) : m_vector( rhs.m_vector ) {} + PriorityQueue( const QAsciiDict<T>& items ); + ~PriorityQueue() {} + + PriorityQueue<T> &operator=( const PriorityQueue<T>& rhs ) { m_vector = rhs.m_vector; return *this; } + bool operator==( const PriorityQueue<T>& rhs ) { return m_vector == rhs.m_vector; } + + unsigned int count() const { return m_vector.size(); } + bool isEmpty() const { return m_vector.empty(); } + + void insert( T* item ); + + /** + * Call this method after decreasing the key of the ith item. The heap + * properties will no longer be valid if you either forget to call that + * method, or if you \b increase the key. + */ + void keyDecreased( T* item ); + + T* extractMinimum(); + + /** + * For debugging + */ + void dump() const; + + private: + // Note: We have to use a 1-based index here, and we get/return 0-based ones + int parent( int i ) { return ( ( i + 1 ) >> 1 ) - 1; } + int left( int i ) { return ( ( i + 1 ) << 1 ) - 1; } + int right( int i ) { return ( i + 1 ) << 1; } + + void heapify( int i ); + // item is !=0 for sure + void bubbleUp( T* item, int i ); + // Builds the heap in the vector in O(n) + void buildHeap(); + + std::vector<T*> m_vector; + }; + + template<class T> + PriorityQueue<T>::PriorityQueue( const QAsciiDict<T>& items ) : m_vector( items.count() ) + { + // First put all items into the vector + QAsciiDictIterator<T> it( items ); + for ( int i = 0; it.current(); ++it, ++i ) { + it.current()->setIndex( i ); + m_vector[ i ] = it.current(); + } + // Then build a heap in that vector + buildHeap(); + } + + template<class T> + void PriorityQueue<T>::insert( T* item ) + { + if ( !item ) + return; + int i = static_cast<int>( m_vector.size() ); + m_vector.push_back( 0 ); // extend the vector by one item. i == index to the last item + bubbleUp( item, i ); + } + + template<class T> + void PriorityQueue<T>::keyDecreased( T* item ) + { + if ( !item ) + return; + bubbleUp( item, item->index() ); + } + + template<class T> + T* PriorityQueue<T>::extractMinimum() + { + if ( m_vector.size() < 1 ) + return 0; + T *min = m_vector[ 0 ]; + m_vector[ 0 ] = m_vector[ m_vector.size() - 1 ]; + // update the index + m_vector[ 0 ]->setIndex( 0 ); + m_vector.pop_back(); + heapify( 0 ); + return min; + } + + template<class T> + void PriorityQueue<T>::dump() const + { + kdDebug( 30500 ) << "++++++++++ PriorityQueue::dump ++++++++++" << endl; + QString out; + int size = static_cast<int>( m_vector.size() ); + for ( int i = 0; i < size; ++i ) { + if ( m_vector[ i ]->index() != i ) + out += " ERROR: index out of sync. Should be " + QString::number( i ) + ", is " + + QString::number( m_vector[ i ]->index() ) + ". "; + out += QString::number( m_vector[ i ]->key() ); + out += ", "; + } + if ( out.isEmpty() ) + out = "(empty)"; + kdDebug( 30500 ) << out << endl; + kdDebug( 30500 ) << "++++++++++ PriorityQueue::dump (done) ++++++++++" << endl; + } + + template<class T> + void PriorityQueue<T>::heapify( int i ) + { + int l = left( i ), r = right( i ), size = static_cast<int>( m_vector.size() ); + int smallest; + + if ( l < size && m_vector[ l ]->key() < m_vector[ i ]->key() ) + smallest = l; + else + smallest = i; + if ( r < size && m_vector[ r ]->key() < m_vector[ smallest ]->key() ) + smallest = r; + + if ( smallest != i ) { + T* tmp = m_vector[ i ]; + m_vector[ i ] = m_vector[ smallest ]; + // update indices + m_vector[ i ]->setIndex( i ); + tmp->setIndex( smallest ); + m_vector[ smallest ] = tmp; + heapify( smallest ); + } + } + + template<class T> + void PriorityQueue<T>::bubbleUp( T* item, int i ) + { + int p = parent( i ); + while ( i > 0 && m_vector[ p ]->key() > item->key() ) { + // update the index first + m_vector[ p ]->setIndex( i ); + // then move it there + m_vector[ i ] = m_vector[ p ]; + i = p; + p = parent( i ); + } + item->setIndex( i ); + m_vector[ i ] = item; + } + + template<class T> + void PriorityQueue<T>::buildHeap() + { + // from size() / 2 down to 1 + for ( int i = ( m_vector.size() >> 1 ) - 1; i >= 0; --i ) + heapify( i ); + } + +} // namespace KOffice + +#endif // priority_queue_h diff --git a/lib/kofficecore/tests/Makefile.am b/lib/kofficecore/tests/Makefile.am new file mode 100644 index 00000000..3cc6827a --- /dev/null +++ b/lib/kofficecore/tests/Makefile.am @@ -0,0 +1,40 @@ +####### General stuff + +INCLUDES= -I$(srcdir)/.. $(KSTORE_INCLUDES) $(all_includes) + +####### Files + +check_PROGRAMS = priorityqueue_test filterchain_test filter_graph kogenstylestest \ + kodomtest kooasissettingstest kooasisstoretest kopointtest korecttest + +TESTS = kogenstylestest kodomtest kooasissettingstest kooasisstoretest kopointtest korecttest + +priorityqueue_test_SOURCES = priorityqueue_test.cpp +priorityqueue_test_LDADD = ../libkofficecore.la + +filterchain_test_SOURCES = filterchain_test.cpp +filterchain_test_LDADD = ../libkofficecore.la + +filter_graph_SOURCES = filter_graph.cpp +filter_graph_LDADD = ../libkofficecore.la + +kogenstylestest_SOURCES = kogenstylestest.cpp +kogenstylestest_LDADD = ../libkofficecore.la + +kodomtest_SOURCES = kodomtest.cpp +kodomtest_LDADD = ../libkofficecore.la + +kooasissettingstest_SOURCES = kooasissettingstest.cpp +kooasissettingstest_LDADD = ../libkofficecore.la + +kooasisstoretest_SOURCES = kooasisstoretest.cpp +kooasisstoretest_LDADD = ../libkofficecore.la + +kopointtest_SOURCES = kopointtest.cpp +kopointtest_LDADD = ../libkofficecore.la + +korecttest_SOURCES = korecttest.cpp +korecttest_LDADD = ../libkofficecore.la + +dot: + dot -Tpng -o graph.png graph.dot diff --git a/lib/kofficecore/tests/filter_graph.cpp b/lib/kofficecore/tests/filter_graph.cpp new file mode 100644 index 00000000..22323355 --- /dev/null +++ b/lib/kofficecore/tests/filter_graph.cpp @@ -0,0 +1,115 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Werner Trobin <trobin@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 <qfile.h> +#include <KoQueryTrader.h> +#include <KoFilterManager.h> +#include <kinstance.h> +#include <kdebug.h> + +int main( int /*argc*/, char ** /*argv*/ ) +{ + KInstance instance( "filter_graph" ); // we need an instance when using the trader + + QCString output = "digraph filters {\n"; + + // The following code is shamelessly copied over from KOffice::Graph::buildGraph + // It wasn't feasible to do some serious changes in the lib for that tiny bit + // of duplicated code in a test file. + + QValueList<QString> vertices; // to keep track of already inserted values, not performance critical + + // Make sure that all available parts are added to the graph + QValueList<KoDocumentEntry> parts( KoDocumentEntry::query() ); + QValueList<KoDocumentEntry>::ConstIterator partIt( parts.begin() ); + QValueList<KoDocumentEntry>::ConstIterator partEnd( parts.end() ); + + while ( partIt != partEnd ) { + //kdDebug() << ( *partIt ).service()->desktopEntryName() << endl; + QStringList nativeMimeTypes = ( *partIt ).service()->property( "X-KDE-ExtraNativeMimeTypes" ).toStringList(); + nativeMimeTypes += ( *partIt ).service()->property( "X-KDE-NativeMimeType" ).toString(); + QStringList::ConstIterator it = nativeMimeTypes.begin(); + QStringList::ConstIterator end = nativeMimeTypes.end(); + for ( ; it != end; ++it ) { + QString key = *it; + //kdDebug() << " " << key << endl; + if ( !key.isEmpty() ) { + output += " \""; + output += key.latin1(); + output += "\" [shape=box, style=filled, fillcolor=lightblue];\n"; + if ( vertices.find( key ) == vertices.end() ) + vertices.append( key ); + } + } + ++partIt; + } + + QValueList<KoFilterEntry::Ptr> filters( KoFilterEntry::query() ); // no constraint here - we want *all* :) + QValueList<KoFilterEntry::Ptr>::ConstIterator it = filters.begin(); + QValueList<KoFilterEntry::Ptr>::ConstIterator end = filters.end(); + + for ( ; it != end; ++it ) { + kdDebug() << "import " << ( *it )->import << " export " << ( *it )->export_ << endl; + // First add the "starting points" + QStringList::ConstIterator importIt = ( *it )->import.begin(); + QStringList::ConstIterator importEnd = ( *it )->import.end(); + for ( ; importIt != importEnd; ++importIt ) { + // already there? + if ( vertices.find( *importIt ) == vertices.end() ) { + vertices.append( *importIt ); + output += " \""; + output += ( *importIt ).latin1(); + output += "\";\n"; + } + } + + QStringList::ConstIterator exportIt = ( *it )->export_.begin(); + QStringList::ConstIterator exportEnd = ( *it )->export_.end(); + + for ( ; exportIt != exportEnd; ++exportIt ) { + // First make sure the export vertex is in place + if ( vertices.find( *exportIt ) == vertices.end() ) { + output += " \""; + output += ( *exportIt ).latin1(); + output += "\";\n"; + vertices.append( *exportIt ); + } + // Then create the appropriate edges + importIt = ( *it )->import.begin(); + for ( ; importIt != importEnd; ++importIt ) { + output += " \""; + output += ( *importIt ).latin1(); + output += "\" -> \""; + output += ( *exportIt ).latin1(); + if ( KoFilterManager::filterAvailable( *it ) ) + output += "\";\n"; + else + output += "\" [style=dotted];\n"; + } + } + } + + output += "}\n"; + + QFile f( "graph.dot" ); + if ( f.open( IO_WriteOnly ) ) + f.writeBlock( output.data(), output.size() - 1 ); + f.close(); + return 0; +} diff --git a/lib/kofficecore/tests/filterchain_test.cpp b/lib/kofficecore/tests/filterchain_test.cpp new file mode 100644 index 00000000..23f620a1 --- /dev/null +++ b/lib/kofficecore/tests/filterchain_test.cpp @@ -0,0 +1,107 @@ +/* This file is part of the KDE project + Copyright (C) 2001 Werner Trobin <trobin@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 <KoFilterChain.h> +#include <KoFilterManager.h> +#include <kinstance.h> +#include <kdebug.h> + +int main( int /*argc*/, char ** /*argv*/ ) +{ + KInstance instance( "filterchain_test" ); // we need an instance when using the trader + KOffice::Graph g( "application/x-kspread" ); + g.dump(); + g.setSourceMimeType( "application/x-kword" ); + g.dump(); + + KoFilterManager *manager = new KoFilterManager( 0 ); + kdDebug() << "Trying to build some filter chains..." << endl; + QCString mimeType( "foo/bar" ); + KoFilterChain::Ptr chain = g.chain( manager, mimeType ); + if ( !chain ) + kdDebug() << "Chain for 'foo/bar' is not available, OK" << endl; + else { + kdError() << "Chain for 'foo/bar' is available!" << endl; + chain->dump(); + } + + mimeType = "application/x-krita"; + chain = g.chain( manager, mimeType ); + if ( !chain ) + kdDebug() << "Chain for 'application/x-krita' is not available, OK" << endl; + else { + kdError() << "Chain 'application/x-krita' is available!" << endl; + chain->dump(); + } + + mimeType = "text/x-csv"; + chain = g.chain( manager, mimeType ); + if ( !chain ) + kdError() << "Chain for 'text/x-csv' is not available!" << endl; + else { + kdDebug() << "Chain for 'text/x-csv' is available, OK" << endl; + chain->dump(); + } + + // Try to find the closest KOffice part + mimeType = ""; + chain = g.chain( manager, mimeType ); + if ( !chain ) + kdDebug() << "It was already a KOffice part, OK" << endl; + else + kdError() << "We really got a chain? ugh :}" << endl; + + g.setSourceMimeType( "text/x-csv" ); + mimeType = ""; + chain = g.chain( manager, mimeType ); + if ( !chain ) + kdError() << "Hmm... why didn't we find a chain?" << endl; + else { + kdDebug() << "Chain for 'text/x-csv' -> closest part is available (" + << mimeType << "), OK" << endl; + chain->dump(); + } + + kdDebug() << "Checking mimeFilter() for Import:" << endl; + QStringList list = KoFilterManager::mimeFilter( "application/x-kword", KoFilterManager::Import ); + QStringList::ConstIterator it = list.begin(); + QStringList::ConstIterator end = list.end(); + for ( ; it != end; ++it ) + kdDebug() << " " << *it << endl; + kdDebug() << " " << list.count() << " entries." << endl; + + kdDebug() << "Checking mimeFilter() for Export:" << endl; + list = KoFilterManager::mimeFilter( "application/x-kword", KoFilterManager::Export ); + it = list.begin(); + end = list.end(); + for ( ; it != end; ++it ) + kdDebug() << " " << *it << endl; + kdDebug() << " " << list.count() << " entries." << endl; + + kdDebug() << "Checking KoShell's mimeFilter():" << endl; + list = KoFilterManager::mimeFilter(); + it = list.begin(); + end = list.end(); + for ( ; it != end; ++it ) + kdDebug() << " " << *it << endl; + kdDebug() << " " << list.count() << " entries." << endl; + + delete manager; + return 0; +} diff --git a/lib/kofficecore/tests/kodomtest.cpp b/lib/kofficecore/tests/kodomtest.cpp new file mode 100644 index 00000000..c8e3c539 --- /dev/null +++ b/lib/kofficecore/tests/kodomtest.cpp @@ -0,0 +1,129 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <faure@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 "KoDom.h" +#include "KoXmlNS.h" + +#include <qapplication.h> +#include <assert.h> + +//static void debugElemNS( const QDomElement& elem ) +//{ +// qDebug( "nodeName=%s tagName=%s localName=%s prefix=%s namespaceURI=%s", elem.nodeName().latin1(), elem.tagName().latin1(), elem.localName().latin1(), elem.prefix().latin1(), elem.namespaceURI().latin1() ); +//} + +void testQDom( const QDomDocument& doc ) +{ + QDomElement docElem = doc.documentElement(); + //debugElemNS( docElem ); + assert( docElem.nodeName() == "o:document-content" ); + assert( docElem.tagName() == "document-content" ); + assert( docElem.localName() == "document-content" ); + assert( docElem.prefix() == "o" ); + assert( docElem.namespaceURI() == KoXmlNS::office ); + + // WARNING, elementsByTagNameNS is recursive!!! + QDomNodeList docElemChildren = docElem.elementsByTagNameNS( KoXmlNS::office, "body" ); + assert( docElemChildren.length() == 1 ); + + QDomElement elem = docElemChildren.item( 0 ).toElement(); + //debugElemNS( elem ); + assert( elem.tagName() == "body" ); + assert( elem.localName() == "body" ); + assert( elem.prefix() == "o" ); + assert( elem.namespaceURI() == KoXmlNS::office ); + + QDomNode n = elem.firstChild(); + for ( ; !n.isNull() ; n = n.nextSibling() ) { + if ( !n.isElement() ) + continue; + QDomElement e = n.toElement(); + //debugElemNS( e ); + assert( e.tagName() == "p" ); + assert( e.localName() == "p" ); + assert( e.prefix().isEmpty() ); + assert( e.namespaceURI() == KoXmlNS::text ); + } + + qDebug("testQDom... ok"); +} + +void testKoDom( const QDomDocument& doc ) +{ + QDomElement docElem = KoDom::namedItemNS( doc, KoXmlNS::office, "document-content" ); + assert( !docElem.isNull() ); + assert( docElem.localName() == "document-content" ); + assert( docElem.namespaceURI() == KoXmlNS::office ); + + QDomElement body = KoDom::namedItemNS( docElem, KoXmlNS::office, "body" ); + assert( !body.isNull() ); + assert( body.localName() == "body" ); + assert( body.namespaceURI() == KoXmlNS::office ); + + QDomElement p = KoDom::namedItemNS( body, KoXmlNS::text, "p" ); + assert( !p.isNull() ); + assert( p.localName() == "p" ); + assert( p.namespaceURI() == KoXmlNS::text ); + + const QDomElement officeStyle = KoDom::namedItemNS( docElem, KoXmlNS::office, "styles" ); + assert( !officeStyle.isNull() ); + + // Look for a non-existing element + QDomElement notexist = KoDom::namedItemNS( body, KoXmlNS::text, "notexist" ); + assert( notexist.isNull() ); + + int count = 0; + QDomElement elem; + forEachElement( elem, body ) { + assert( elem.localName() == "p" ); + assert( elem.namespaceURI() == KoXmlNS::text ); + ++count; + } + assert( count == 2 ); + + // Attributes + // ### Qt bug: it doesn't work if using style-name instead of text:style-name in the XML + const QString styleName = p.attributeNS( KoXmlNS::text, "style-name", QString::null ); + qDebug( "%s", styleName.latin1() ); + assert( styleName == "L1" ); + + qDebug("testKoDom... ok"); +} + +int main( int argc, char** argv ) { + QApplication app( argc, argv, QApplication::Tty ); + + const QCString xml = QCString( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<o:document-content xmlns:o=\"" ) + + KoXmlNS::office + + "\" xmlns=\"" + KoXmlNS::text + + "\" xmlns:text=\"" + KoXmlNS::text + + "\">\n" + "<o:body><p text:style-name=\"L1\">foobar</p><p>2nd</p></o:body><o:styles></o:styles>\n" + "</o:document-content>\n"; + QDomDocument doc; + //QXmlSimpleReader reader; + //reader.setFeature( "http://xml.org/sax/features/namespaces", TRUE ); + //reader.setFeature( "http://xml.org/sax/features/namespace-prefixes", FALSE ); + bool ok = doc.setContent( xml, true /* namespace processing */ ); + assert( ok ); + + testQDom(doc); + testKoDom(doc); +} diff --git a/lib/kofficecore/tests/kogenstylestest.cpp b/lib/kofficecore/tests/kogenstylestest.cpp new file mode 100644 index 00000000..ddfac1f6 --- /dev/null +++ b/lib/kofficecore/tests/kogenstylestest.cpp @@ -0,0 +1,270 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2006 David Faure <faure@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 <KoGenStyles.h> +#include <KoXmlWriter.h> +#include "../../store/tests/xmlwritertest.h" +#include <kdebug.h> +#include <assert.h> + +int testLookup() +{ + kdDebug() << k_funcinfo << endl; + KoGenStyles coll; + + QMap<QString, QString> map1; + map1.insert( "map1key", "map1value" ); + QMap<QString, QString> map2; + map2.insert( "map2key1", "map2value1" ); + map2.insert( "map2key2", "map2value2" ); + + KoGenStyle first( KoGenStyle::STYLE_AUTO, "paragraph" ); + first.addAttribute( "style:master-page-name", "Standard" ); + first.addProperty( "style:page-number", "0" ); + first.addProperty( "style:foobar", "2", KoGenStyle::TextType ); + first.addStyleMap( map1 ); + first.addStyleMap( map2 ); + + QString firstName = coll.lookup( first ); + kdDebug() << "The first style got assigned the name " << firstName << endl; + assert( firstName == "A1" ); // it's fine if it's something else, but the koxmlwriter tests require a known name + assert( first.type() == KoGenStyle::STYLE_AUTO ); + + KoGenStyle second( KoGenStyle::STYLE_AUTO, "paragraph" ); + second.addAttribute( "style:master-page-name", "Standard" ); + second.addProperty( "style:page-number", "0" ); + second.addProperty( "style:foobar", "2", KoGenStyle::TextType ); + second.addStyleMap( map1 ); + second.addStyleMap( map2 ); + + QString secondName = coll.lookup( second ); + kdDebug() << "The second style got assigned the name " << secondName << endl; + + assert( firstName == secondName ); // check that sharing works + assert( first == second ); // check that operator== works :) + + const KoGenStyle* s = coll.style( firstName ); // check lookup of existing style + assert( s ); + assert( *s == first ); + s = coll.style( "foobarblah" ); // check lookup of non-existing style + assert( !s ); + + KoGenStyle third( KoGenStyle::STYLE_AUTO, "paragraph", secondName ); // inherited style + third.addProperty( "style:margin-left", "1.249cm" ); + third.addProperty( "style:page-number", "0" ); // same as parent + third.addProperty( "style:foobar", "3", KoGenStyle::TextType ); // different from parent + assert( third.parentName() == secondName ); + + QString thirdName = coll.lookup( third, "P" ); + kdDebug() << "The third style got assigned the name " << thirdName << endl; + assert( thirdName == "P1" ); + + KoGenStyle user( KoGenStyle::STYLE_USER, "paragraph" ); // differs from third since it doesn't inherit second, and has a different type + user.addProperty( "style:margin-left", "1.249cm" ); + + QString userStyleName = coll.lookup( user, "User", KoGenStyles::DontForceNumbering ); + kdDebug() << "The user style got assigned the name " << userStyleName << endl; + assert( userStyleName == "User" ); + + KoGenStyle sameAsParent( KoGenStyle::STYLE_AUTO, "paragraph", secondName ); // inherited style + sameAsParent.addAttribute( "style:master-page-name", "Standard" ); + sameAsParent.addProperty( "style:page-number", "0" ); + sameAsParent.addProperty( "style:foobar", "2", KoGenStyle::TextType ); + sameAsParent.addStyleMap( map1 ); + sameAsParent.addStyleMap( map2 ); + QString sapName = coll.lookup( sameAsParent, "foobar" ); + kdDebug() << "The 'same as parent' style got assigned the name " << sapName << endl; + + assert( sapName == secondName ); + assert( coll.styles().count() == 3 ); + + // OK, now add a style marked as for styles.xml; it looks like the above style, but + // since it's marked for styles.xml it shouldn't be shared with it. + KoGenStyle headerStyle( KoGenStyle::STYLE_AUTO, "paragraph" ); + headerStyle.addAttribute( "style:master-page-name", "Standard" ); + headerStyle.addProperty( "style:page-number", "0" ); + headerStyle.addProperty( "style:foobar", "2", KoGenStyle::TextType ); + headerStyle.addStyleMap( map1 ); + headerStyle.addStyleMap( map2 ); + headerStyle.setAutoStyleInStylesDotXml( true ); + QString headerStyleName = coll.lookup( headerStyle, "foobar" ); + + assert( coll.styles().count() == 4 ); + assert( coll.styles( KoGenStyle::STYLE_AUTO ).count() == 2 ); + assert( coll.styles( KoGenStyle::STYLE_USER ).count() == 1 ); + + QValueList<KoGenStyles::NamedStyle> stylesXmlStyles = coll.styles( KoGenStyle::STYLE_AUTO, true ); + assert( stylesXmlStyles.count() == 1 ); + KoGenStyles::NamedStyle firstStyle = stylesXmlStyles.first(); + assert( firstStyle.name == headerStyleName ); + + TEST_BEGIN( 0, 0 ); + first.writeStyle( &writer, coll, "style:style", firstName, "style:paragraph-properties" ); + TEST_END( "XML for first/second style", "<r>\n <style:style style:name=\"A1\" style:family=\"paragraph\" style:master-page-name=\"Standard\">\n <style:paragraph-properties style:page-number=\"0\"/>\n <style:text-properties style:foobar=\"2\"/>\n <style:map map1key=\"map1value\"/>\n <style:map map2key1=\"map2value1\" map2key2=\"map2value2\"/>\n </style:style>\n</r>\n" ); + + TEST_BEGIN( 0, 0 ); + third.writeStyle( &writer, coll, "style:style", thirdName, "style:paragraph-properties" ); + TEST_END( "XML for third style", "<r>\n <style:style style:name=\"P1\" style:parent-style-name=\"A1\" style:family=\"paragraph\">\n <style:paragraph-properties style:margin-left=\"1.249cm\"/>\n <style:text-properties style:foobar=\"3\"/>\n </style:style>\n</r>\n" ); + + coll.markStyleForStylesXml( firstName ); + { + QValueList<KoGenStyles::NamedStyle> stylesXmlStyles = coll.styles( KoGenStyle::STYLE_AUTO, true ); + assert( stylesXmlStyles.count() == 2 ); + QValueList<KoGenStyles::NamedStyle> contentXmlStyles = coll.styles( KoGenStyle::STYLE_AUTO, false ); + assert( contentXmlStyles.count() == 1 ); + } + + return 0; +} + +int testDefaultStyle() +{ + kdDebug() << k_funcinfo << endl; + /* Create a default style, + * and then an auto style with exactly the same attributes + * -> the lookup gives the default style. + * + * Also checks how the default style gets written out to XML. + */ + KoGenStyles coll; + + KoGenStyle defaultStyle( KoGenStyle::STYLE_AUTO, "paragraph" ); + defaultStyle.addAttribute( "style:master-page-name", "Standard" ); + defaultStyle.addProperty( "myfont", "isBold" ); + defaultStyle.setDefaultStyle( true ); + QString defaultStyleName = coll.lookup( defaultStyle ); + assert( !defaultStyleName.isEmpty() ); // whatever, but not empty + assert( defaultStyle.type() == KoGenStyle::STYLE_AUTO ); + assert( defaultStyle.isDefaultStyle() ); + + KoGenStyle anotherStyle( KoGenStyle::STYLE_AUTO, "paragraph" ); + anotherStyle.addAttribute( "style:master-page-name", "Standard" ); + anotherStyle.addProperty( "myfont", "isBold" ); + QString anotherStyleName = coll.lookup( anotherStyle ); + assert( anotherStyleName == defaultStyleName ); + + assert( coll.styles().count() == 1 ); + + TEST_BEGIN( 0, 0 ); + defaultStyle.writeStyle( &writer, coll, "style:default-style", defaultStyleName, "style:paragraph-properties" ); + TEST_END( "XML for default style", "<r>\n <style:default-style style:family=\"paragraph\" style:master-page-name=\"Standard\">\n <style:paragraph-properties myfont=\"isBold\"/>\n </style:default-style>\n</r>\n" ); + + // The kspread case: not writing out all properties, only if they differ + // from the default style. + // KoGenStyles doesn't fetch info from the parent style when testing + // for equality, so KSpread uses isEmpty() to check for equality-to-parent. + KoGenStyle dataStyle( KoGenStyle::STYLE_AUTO, "paragraph", defaultStyleName ); + assert( dataStyle.isEmpty() ); + // and then it doesn't look up the auto style, but rather uses the parent style directly. + + return 0; +} + +int testUserStyles() +{ + kdDebug() << k_funcinfo << endl; + /* Two user styles with exactly the same attributes+properties will not get merged, since + * they don't have exactly the same attributes after all: the display-name obviously differs :) + */ + KoGenStyles coll; + + KoGenStyle user1( KoGenStyle::STYLE_USER, "paragraph" ); + user1.addAttribute( "style:display-name", "User 1" ); + user1.addProperty( "myfont", "isBold" ); + + QString user1StyleName = coll.lookup( user1, "User1", KoGenStyles::DontForceNumbering ); + kdDebug() << "The user style got assigned the name " << user1StyleName << endl; + assert( user1StyleName == "User1" ); + + KoGenStyle user2( KoGenStyle::STYLE_USER, "paragraph" ); + user2.addAttribute( "style:display-name", "User 2" ); + user2.addProperty( "myfont", "isBold" ); + + QString user2StyleName = coll.lookup( user2, "User2", KoGenStyles::DontForceNumbering ); + kdDebug() << "The user style got assigned the name " << user2StyleName << endl; + assert( user2StyleName == "User2" ); + + // And now, what if the data uses that style? + // This is like sameAsParent in the other test, but this time the + // parent is a STYLE_USER... + KoGenStyle dataStyle( KoGenStyle::STYLE_AUTO, "paragraph", user2StyleName ); + dataStyle.addProperty( "myfont", "isBold" ); + + QString dataStyleName = coll.lookup( dataStyle, "DataStyle" ); + kdDebug() << "The auto style got assigned the name " << dataStyleName << endl; + assert( dataStyleName == "User2" ); // it found the parent as equal + + // Let's do the opposite test, just to make sure + KoGenStyle dataStyle2( KoGenStyle::STYLE_AUTO, "paragraph", user2StyleName ); + dataStyle2.addProperty( "myfont", "isNotBold" ); + + QString dataStyle2Name = coll.lookup( dataStyle2, "DataStyle" ); + kdDebug() << "The different auto style got assigned the name " << dataStyle2Name << endl; + assert( dataStyle2Name == "DataStyle1" ); + + assert( coll.styles().count() == 3 ); + + TEST_BEGIN( 0, 0 ); + user1.writeStyle( &writer, coll, "style:style", user1StyleName, "style:paragraph-properties" ); + TEST_END( "XML for user style 1", "<r>\n <style:style style:name=\"User1\" style:display-name=\"User 1\" style:family=\"paragraph\">\n <style:paragraph-properties myfont=\"isBold\"/>\n </style:style>\n</r>\n" ); + + TEST_BEGIN( 0, 0 ); + user2.writeStyle( &writer, coll, "style:style", user2StyleName, "style:paragraph-properties" ); + TEST_END( "XML for user style 2", "<r>\n <style:style style:name=\"User2\" style:display-name=\"User 2\" style:family=\"paragraph\">\n <style:paragraph-properties myfont=\"isBold\"/>\n </style:style>\n</r>\n" ); + + return 0; +} + +int testStylesDotXml() +{ + kdDebug() << k_funcinfo << endl; + KoGenStyles coll; + + // Check that an autostyle-in-style.xml and an autostyle-in-content.xml + // don't get the same name. It confuses KoGenStyle's named-based maps. + KoGenStyle headerStyle( KoGenStyle::STYLE_AUTO, "paragraph" ); + headerStyle.addAttribute( "style:master-page-name", "Standard" ); + headerStyle.addProperty( "style:page-number", "0" ); + headerStyle.setAutoStyleInStylesDotXml( true ); + QString headerStyleName = coll.lookup( headerStyle, "P" ); + assert( headerStyleName == "P1" ); + + //coll.dump(); + + KoGenStyle first( KoGenStyle::STYLE_AUTO, "paragraph" ); + first.addAttribute( "style:master-page-name", "Standard" ); + QString firstName = coll.lookup( first, "P" ); + kdDebug() << "The auto style got assigned the name " << firstName << endl; + assert( firstName == "P2" ); // anything but not P1. + return 0; +} + +int main( int, char** ) { + fprintf( stderr, "OK\n" ); + + if ( testLookup() ) + return 1; + if ( testDefaultStyle() ) + return 1; + if ( testUserStyles() ) + return 1; + if ( testStylesDotXml() ) + return 1; + + return 0; +} diff --git a/lib/kofficecore/tests/kooasissettingstest.cpp b/lib/kofficecore/tests/kooasissettingstest.cpp new file mode 100644 index 00000000..ef29536d --- /dev/null +++ b/lib/kofficecore/tests/kooasissettingstest.cpp @@ -0,0 +1,106 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <faure@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 <KoOasisSettings.h> +#include <KoDom.h> +#include <kdebug.h> +#include <assert.h> + +void testSelectItemSet( KoOasisSettings& settings ) +{ + KoOasisSettings::Items items = settings.itemSet( "notexist" ); + assert( items.isNull() ); + items = settings.itemSet( "view-settings" ); + assert( !items.isNull() ); + kdDebug() << "testSelectItemSet OK" << endl; +} + +void testParseConfigItemString( KoOasisSettings& settings ) +{ + KoOasisSettings::Items viewSettings = settings.itemSet( "view-settings" ); + const QString unit = viewSettings.parseConfigItemString( "unit" ); + qDebug( "%s", unit.latin1() ); + assert( unit == "mm" ); + kdDebug() << "testParseConfigItemString OK" << endl; +} + +void testIndexedMap( KoOasisSettings& settings ) +{ + KoOasisSettings::Items viewSettings = settings.itemSet( "view-settings" ); + assert( !viewSettings.isNull() ); + KoOasisSettings::IndexedMap viewMap = viewSettings.indexedMap( "Views" ); + assert( !viewMap.isNull() ); + KoOasisSettings::Items firstView = viewMap.entry( 0 ); + assert( !firstView.isNull() ); + const short zoomFactor = firstView.parseConfigItemShort( "ZoomFactor" ); + assert( zoomFactor == 100 ); + KoOasisSettings::Items secondView = viewMap.entry( 1 ); + assert( secondView.isNull() ); + kdDebug() << "testIndexedMap OK" << endl; +} + +void testNamedMap( KoOasisSettings& settings ) +{ + KoOasisSettings::Items viewSettings = settings.itemSet( "view-settings" ); + assert( !viewSettings.isNull() ); + KoOasisSettings::NamedMap viewMap = viewSettings.namedMap( "NamedMap" ); + assert( !viewMap.isNull() ); + KoOasisSettings::Items foo = viewMap.entry( "foo" ); + assert( !foo.isNull() ); + const int zoomFactor = foo.parseConfigItemShort( "ZoomFactor" ); + assert( zoomFactor == 100 ); + KoOasisSettings::Items secondView = viewMap.entry( "foobar" ); + assert( secondView.isNull() ); + kdDebug() << "testNamedMap OK" << endl; +} + +int main( int, char** ) { + + const QCString xml = "\ +<?xml version=\"1.0\" encoding=\"UTF-8\"?> \ +<office:document-settings xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" xmlns:config=\"urn:oasis:names:tc:opendocument:xmlns:config:1.0\"> \ + <office:settings> \ + <config:config-item-set config:name=\"view-settings\"> \ + <config:config-item config:name=\"unit\" config:type=\"string\">mm</config:config-item> \ + <config:config-item-map-indexed config:name=\"Views\"> \ + <config:config-item-map-entry> \ + <config:config-item config:name=\"ZoomFactor\" config:type=\"short\">100</config:config-item> \ + </config:config-item-map-entry> \ + </config:config-item-map-indexed> \ + <config:config-item-map-named config:name=\"NamedMap\"> \ + <config:config-item-map-entry config:name=\"foo\"> \ + <config:config-item config:name=\"ZoomFactor\" config:type=\"int\">100</config:config-item> \ + </config:config-item-map-entry> \ + </config:config-item-map-named> \ + </config:config-item-set> \ + </office:settings> \ +</office:document-settings> \ +"; + + QDomDocument doc; + bool ok = doc.setContent( xml, true /* namespace processing */ ); + assert( ok ); + + KoOasisSettings settings( doc ); + + testSelectItemSet( settings ); + testParseConfigItemString( settings ); + testIndexedMap( settings ); + testNamedMap( settings ); + return 0; +} diff --git a/lib/kofficecore/tests/kooasisstoretest.cpp b/lib/kofficecore/tests/kooasisstoretest.cpp new file mode 100644 index 00000000..6750904b --- /dev/null +++ b/lib/kofficecore/tests/kooasisstoretest.cpp @@ -0,0 +1,58 @@ +/* This file is part of the KDE project + Copyright (C) 2005 David Faure <faure@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 <KoOasisStore.h> +#include <KoDom.h> +#include <kdebug.h> +#include <assert.h> + +void testMimeForPath( QDomDocument& doc ) +{ + QString mime = KoOasisStore::mimeForPath( doc, "Object 1" ); + kdDebug() << k_funcinfo << mime << endl; + assert( !mime.isNull() ); + assert( !mime.isEmpty() ); + assert( mime == "application/vnd.oasis.opendocument.text" ); + kdDebug() << "testMimeForPath OK" << endl; +} + +int main( int, char** ) { + + const QCString xml = "\ +<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\ +<manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\">\n\ + <manifest:file-entry manifest:media-type=\"application/vnd.oasis.opendocument.text\" manifest:full-path=\"/\"/>\n\ + <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"content.xml\"/>\n\ + <manifest:file-entry manifest:media-type=\"application/vnd.oasis.opendocument.text\" manifest:full-path=\"Object 1\"/>\n\ +</manifest:manifest> \ +"; + + QDomDocument doc; + QString errorMsg; + int errorLine, errorColumn; + bool ok = doc.setContent( xml, true /* namespace processing */, &errorMsg, &errorLine, &errorColumn ); + if ( !ok ) { + kdError() << "Parsing error! Aborting!" << endl + << " In line: " << errorLine << ", column: " << errorColumn << endl + << " Error message: " << errorMsg << endl; + return 1; + } + + testMimeForPath( doc ); + return 0; +} diff --git a/lib/kofficecore/tests/kopointtest.cpp b/lib/kofficecore/tests/kopointtest.cpp new file mode 100644 index 00000000..25286a5d --- /dev/null +++ b/lib/kofficecore/tests/kopointtest.cpp @@ -0,0 +1,89 @@ +#include <KoPoint.h> +#include <kdebug.h> +#include <kglobal.h> +#include <stdlib.h> +#include <math.h> + +bool check( QString txt, bool res, bool expected ) +{ + if ( res == expected ) { + kdDebug() << txt << " : checking '" << res << "' against expected value '" << expected << "'... " << "ok" << endl; + } else { + kdDebug() << txt << " : checking '" << res << "' against expected value '" << expected << "'... " << "KO !" << endl; + exit( 1 ); + } + return true; +} + +bool check( QString txt, double res, double expected ) +{ + if ( kAbs(res - expected) < 0.000001 ) { + kdDebug() << txt << " : checking '" << res << "' against expected value '" << expected << "'... " << "ok" << endl; + } else { + kdDebug() << txt << " : checking '" << res << "' against expected value '" << expected << "'... " << "KO !" << endl; + exit( 1 ); + } + return true; +} + +int main() +{ + KoPoint p0; + check( "KoPoint() is null", p0.isNull(), true ); + KoPoint p1( 10.1, 20.2 ); + check( "KoPoint(...) is not null", p1.isNull(), false ); + check( "KoPoint::x()", p1.x(), 10.1 ); + check( "KoPoint::y()", p1.y(), 20.2 ); + p1.setX( 2.1 ); + p1.setY( 3.2 ); + check( "KoPoint::setX()", p1.x(), 2.1 ); + check( "KoPoint::setY()", p1.y(), 3.2 ); + + KoPoint p2( QPoint( -30, -40 ) ); + check( "KoPoint(const QPoint&)", p2.x(), -30.0 ); + check( "KoPoint(const QPoint&)", p2.y(), -40.0 ); + p2.rx() = 1.5; + p2.ry() = 2.5; + check( "KoPoint::rx()", p2.x(), 1.5 ); + check( "KoPoint::ry()", p2.y(), 2.5 ); + p2.setCoords( -1.6, -1.7 ); + check( "KoPoint::setCoords(double, double)", p2.x(), -1.6 ); + check( "KoPoint::setCoords(double, double)", p2.y(), -1.7 ); + + check( "KoPoint::operator==(const KoPoint&)", p1 == p1, true ); + check( "KoPoint::operator!=(const KoPoint&)", p1 != p2, true ); + + KoPoint p4( 10.2, 20.2 ); + KoPoint p5( 30.4, 40.4 ); + p4 += p5; + check( "KoPoint::operator+=(const KoPoint&)", p4.x(), 40.6 ); + check( "KoPoint::operator+=(const KoPoint&)", p4.y(), 60.6 ); + p4 -= p5; + check( "KoPoint::operator-=(const KoPoint&)", p4.x(), 10.2 ); + check( "KoPoint::operator-=(const KoPoint&)", p4.y(), 20.2 ); + p4 *= 2.0; + check( "KoPoint::operator*=(double)", p4.x(), 20.4 ); + check( "KoPoint::operator*=(double)", p4.y(), 40.4 ); + p4 = p5; + check( "KoPoint::operator=(const KoPoint&)", p4.x(), 30.4 ); + check( "KoPoint::operator=(const KoPoint&)", p4.y(), 40.4 ); + + // ### global operator+, operator- and operator* + // ### transform + + KoPoint p6( 1.0, 2.0 ); + check( "KoPoint::isNear()", p6.isNear( p6, 0 ), true ); + check( "KoPoint::isNear()", p6.isNear( KoPoint( 2, 10 ), 1 ), false ); + check( "KoPoint::isNear()", p6.isNear( KoPoint( 2, 1 ), 1 ), true ); + + KoPoint p7( 10, 10 ); + check( "KoPoint::getAngle()", + fmod( KoPoint::getAngle( p7, p7 * -1 ) + 10*360.0, 360.0 ), 45.0 ); + // ### ??? check( "KoPoint::getAngle()", + // fmod( KoPoint::getAngle( -1 * p7, p7 ) + 10*360.0, 360.0 ), 45.0 ); + // can we define a behaviour ? + // ### check( "KoPoint::getAngle()", KoPoint::getAngle( p7, p7 ), ??? ); + + kdDebug() << endl << "Test OK !" << endl; +} + diff --git a/lib/kofficecore/tests/korecttest.cpp b/lib/kofficecore/tests/korecttest.cpp new file mode 100644 index 00000000..9c2cb8b0 --- /dev/null +++ b/lib/kofficecore/tests/korecttest.cpp @@ -0,0 +1,53 @@ +#include <KoRect.h> +#include <stdio.h> +#include <kapplication.h> +#include <stdlib.h> +#include <kdebug.h> +#include <kglobal.h> + +bool check(QString txt, bool res, bool expected) +{ + if (res == expected) { + kdDebug() << txt << " : checking '" << res << "' against expected value '" << expected << "'... " << "ok" << endl; + } + else { + kdDebug() << txt << " : checking '" << res << "' against expected value '" << expected << "'... " << "KO !" << endl; + exit(1); + } + return true; +} + +bool check(QString txt, QString a, QString b) +{ + 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; +} + +int main(int argc, char *argv[]) +{ + KApplication app(argc,argv,"korecttest",false,false); + + KoRect emptyRect; + check( "KoRect() is null", emptyRect.isNull(), true ); + check( "KoRect() is empty", emptyRect.isEmpty(), true ); + KoRect rect( 1, 15, 250, 156.14 ); + check( "KoRect(...) is not null", rect.isNull(), false ); + check( "KoRect(...) is not empty", rect.isEmpty(), false ); + KoRect unionRect = rect | emptyRect; + check( "Union is not null", unionRect.isNull(), false ); + check( "Union is not empty", unionRect.isEmpty(), false ); + kdDebug() << unionRect << endl; + + printf("\nTest OK !\n"); +} + diff --git a/lib/kofficecore/tests/koxmlreadertest.cpp b/lib/kofficecore/tests/koxmlreadertest.cpp new file mode 100644 index 00000000..213fe0db --- /dev/null +++ b/lib/kofficecore/tests/koxmlreadertest.cpp @@ -0,0 +1,2074 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Ariya Hidayat <ariya@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, +*/ + +// xmlreadertest.cpp - test KoXml classes +// Ariya Hidayat, November 2005 + +#include <qstring.h> +#include <qcstring.h> +#include <qbuffer.h> +#include <qtextstream.h> +#include <qdatetime.h> +#include <qfile.h> + +#include "KoXmlReader.h" +#include <qxml.h> + +#define CHECK(x,y) check(__FILE__,__LINE__,#x,x,y) + +static int testCount = 0; +static int testFailed = 0; + +template<typename T> +void check( const char *file, int line, const char* msg, +const T& result, const T& expected ) +{ + testCount++; + if( result != expected ) + { + testFailed++; + QString message; + QTextStream ts( &message, IO_WriteOnly ); + ts << msg; + ts << " Result:"; + ts << result; + ts << ", "; + ts << "Expected:"; + ts << expected; + printf( "%s [%d]: %s\n", file, line, message.latin1() ); + } +} + +void check( const char *file, int line, const char* msg, bool result, +bool expected ) +{ + testCount++; + if( result != expected ) + { + testFailed++; + QString message; + QTextStream ts( &message, IO_WriteOnly ); + ts << msg; + ts << " Result: "; + if( result ) ts << "True"; else ts << "False"; + ts << ", "; + ts << "Expected: "; + if( expected ) ts << "True"; else ts << "False"; + printf( "%s [%d]: %s\n", file, line, message.latin1() ); + } +} + +void testNode() +{ + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + + QByteArray xmlbuf; + QBuffer xmldevice( xmlbuf ); + QTextStream xmlstream( xmlbuf, IO_WriteOnly ); + xmlstream << "<earth>"; + xmlstream << "<continents>"; + xmlstream << "<asia/>"; + xmlstream << "<africa/>"; + xmlstream << "<europe/>"; + xmlstream << "<america/>"; + xmlstream << "<australia/>"; + xmlstream << "<antartic/>"; + xmlstream << "</continents>"; + xmlstream << "<oceans>"; + xmlstream << "<pacific/>"; + xmlstream << "<atlantic/>"; + xmlstream << "</oceans>"; + xmlstream << "</earth>"; + + KoXmlDocument doc; + CHECK( doc.setContent( &xmldevice, &errorMsg, &errorLine, &errorColumn ), true ); + CHECK( errorMsg.isEmpty(), true ); + CHECK( errorLine, 0 ); + CHECK( errorColumn, 0 ); + + // null node + KoXmlNode node1; + CHECK( node1.nodeName(), QString::null ); + CHECK( node1.isNull(), true ); + CHECK( node1.isElement(), false ); + CHECK( node1.isElement(), false ); + CHECK( node1.isDocument(), false ); + CHECK( node1.ownerDocument().isNull(), false ); + CHECK( node1.parentNode().isNull(), true ); + CHECK( node1.hasChildNodes(), false ); + CHECK( node1.firstChild().isNull(), true ); + CHECK( node1.lastChild().isNull(), true ); + CHECK( node1.previousSibling().isNull(), true ); + CHECK( node1.nextSibling().isNull(), true ); + + // compare with another null node + KoXmlNode node2; + CHECK( node2.isNull(), true ); + CHECK( node1==node2, true ); + CHECK( node1!=node2, false ); + + // a node which is a document + KoXmlNode node3 = doc; + CHECK( node3.nodeName(), QString("#document") ); + CHECK( node3.isNull(), false ); + CHECK( node3.isElement(), false ); + CHECK( node3.isText(), false ); + CHECK( node3.isDocument(), true ); + CHECK( node3.ownerDocument().isNull(), false ); + CHECK( node3.ownerDocument()==doc, true ); + CHECK( node3.toDocument()==doc, true ); + + // convert to document and the compare + KoXmlDocument doc2 = node3.toDocument(); + CHECK( doc2.nodeName(), QString("#document") ); + CHECK( doc2.isNull(), false ); + CHECK( doc2.isDocument(), true ); + CHECK( node3==doc2, true ); + + // a document is of course can't be converted to element + KoXmlElement invalidElement = node3.toElement(); + CHECK( invalidElement.nodeName(), QString::null ); + CHECK( invalidElement.isNull(), true ); + CHECK( invalidElement.isElement(), true ); + CHECK( invalidElement.isText(), false ); + CHECK( invalidElement.isDocument(), false ); + + // clear() makes it a null node again + node3.clear(); + CHECK( node3.isNull(), true ); + CHECK( node3.nodeName(), QString::null ); + CHECK( node3.isElement(), false ); + CHECK( node3.isText(), false ); + CHECK( node3.isDocument(), false ); + CHECK( node3.ownerDocument().isNull(), false ); + CHECK( node1==node3, true ); + CHECK( node1!=node3, false ); + + // a node which is an element for <earth> + KoXmlNode node4 = doc.firstChild(); + CHECK( node4.isNull(), false ); + CHECK( node4.isElement(), true ); + CHECK( node4.isText(), false ); + CHECK( node4.isDocument(), false ); + CHECK( node4.hasChildNodes(), true ); + CHECK( node4.ownerDocument()==doc, true ); + CHECK( node4.toElement()==doc.firstChild().toElement(), true ); + + // clear() makes it a null node again + node4.clear(); + CHECK( node4.isNull(), true ); + CHECK( node4.isElement(), false ); + CHECK( node4.isText(), false ); + CHECK( node4.isDocument(), false ); + CHECK( node4==node1, true ); + CHECK( node4!=node1, false ); + + // a node which is an element for <continents> + KoXmlNode node5 = doc.firstChild().firstChild(); + CHECK( node5.nodeName(), QString("continents") ); + CHECK( node5.isNull(), false ); + CHECK( node5.isElement(), true ); + CHECK( node5.isText(), false ); + CHECK( node5.isDocument(), false ); + CHECK( node5.hasChildNodes(), true ); + CHECK( node5.ownerDocument()==doc, true ); + + // convert to element and the compare + KoXmlElement continentsElement = node5.toElement(); + CHECK( node5==continentsElement, true ); + CHECK( continentsElement.isNull(), false ); + CHECK( continentsElement.isElement(), true ); + CHECK( continentsElement.isText(), false ); + CHECK( continentsElement.hasChildNodes(), true ); + CHECK( continentsElement.ownerDocument()==doc, true ); + + // and it doesn't make sense to convert that node to document + // (instead a brand new document is created, i.e. not a null node) + KoXmlDocument invalidDoc = node5.toDocument(); + CHECK( invalidDoc.isNull(), false ); + CHECK( invalidDoc.isElement(), false ); + CHECK( invalidDoc.isText(), false ); + CHECK( invalidDoc.isDocument(), true ); + + // node for <europe> using namedItem() function + KoXmlNode europeNode = continentsElement.namedItem( QString("europe") ); + CHECK( europeNode.nodeName(), QString("europe") ); + CHECK( europeNode.isNull(), false ); + CHECK( europeNode.isElement(), true ); + CHECK( europeNode.isText(), false ); + CHECK( europeNode.hasChildNodes(), false ); + CHECK( europeNode.ownerDocument()==doc, true ); + + // search non-existing node + KoXmlNode fooNode = continentsElement.namedItem( QString("foobar") ); + CHECK( fooNode.isNull(), true ); + CHECK( fooNode.isElement(), false ); + CHECK( fooNode.isText(), false ); + CHECK( fooNode.isCDATASection(), false ); +} + +void testElement() +{ + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + + QByteArray xmlbuf; + QBuffer xmldevice( xmlbuf ); + + QTextStream xmlstream( xmlbuf, IO_WriteOnly ); + xmlstream << "<html>"; + xmlstream << "<body bgcolor=\"#000\">"; + xmlstream << "<p>"; + xmlstream << "Hello, world!"; + xmlstream << "</p>"; + xmlstream << "</body>"; + xmlstream << "</html>"; + + KoXmlDocument doc; + CHECK( doc.setContent( &xmldevice, &errorMsg, &errorLine, &errorColumn ), true ); + CHECK( errorMsg.isEmpty(), true ); + CHECK( errorLine, 0 ); + CHECK( errorColumn, 0 ); + + // element for <html> + KoXmlElement rootElement; + rootElement = doc.documentElement(); + CHECK( rootElement.nodeName(), QString("html") ); + CHECK( rootElement.isNull(), false ); + CHECK( rootElement.isElement(), true ); + CHECK( rootElement.isDocument(), false ); + CHECK( rootElement.ownerDocument().isNull(), false ); + CHECK( rootElement.ownerDocument()==doc, true ); + CHECK( rootElement.parentNode().isNull(), false ); + CHECK( rootElement.parentNode().toDocument()==doc, true ); + CHECK( rootElement.hasChildNodes(), true ); + CHECK( rootElement.tagName(), QString("html") ); + CHECK( rootElement.prefix().isNull(), true ); + + // element for <body> + KoXmlElement bodyElement; + bodyElement = rootElement.firstChild().toElement(); + CHECK( bodyElement.nodeName(), QString("body") ); + CHECK( bodyElement.isNull(), false ); + CHECK( bodyElement.isElement(), true ); + CHECK( bodyElement.isDocument(), false ); + CHECK( bodyElement.ownerDocument().isNull(), false ); + CHECK( bodyElement.ownerDocument()==doc, true ); + CHECK( bodyElement.parentNode().isNull(), false ); + CHECK( bodyElement.parentNode()==rootElement, true ); + CHECK( bodyElement.hasChildNodes(), true ); + CHECK( bodyElement.tagName(), QString("body") ); + CHECK( bodyElement.prefix().isNull(), true ); + CHECK( bodyElement.hasAttribute("bgcolor"), true ); + CHECK( bodyElement.attribute("bgcolor"), QString("#000") ); + + // a shared copy of <body>, will still have access to attribute bgcolor + KoXmlElement body2Element; + body2Element = bodyElement; + CHECK( body2Element.nodeName(), QString("body") ); + CHECK( body2Element.isNull(), false ); + CHECK( body2Element.isElement(), true ); + CHECK( body2Element.isDocument(), false ); + CHECK( body2Element.ownerDocument().isNull(), false ); + CHECK( body2Element.ownerDocument()==doc, true ); + CHECK( body2Element==bodyElement, true ); + CHECK( body2Element!=bodyElement, false ); + CHECK( body2Element.hasChildNodes(), true ); + CHECK( body2Element.tagName(), QString("body") ); + CHECK( body2Element.prefix().isNull(), true ); + CHECK( body2Element.hasAttribute("bgcolor"), true ); + CHECK( body2Element.attribute("bgcolor"), QString("#000") ); + + // empty element, by default constructor + KoXmlElement testElement; + CHECK( testElement.nodeName(), QString::null ); + CHECK( testElement.isNull(), true ); + CHECK( testElement.isElement(), true ); + CHECK( testElement.isDocument(), false ); + CHECK( testElement.ownerDocument().isNull(), false ); + CHECK( testElement.ownerDocument()!=doc, true ); + CHECK( testElement==rootElement, false ); + CHECK( testElement!=rootElement, true ); + CHECK( testElement.parentNode().isNull(), true ); + CHECK( testElement.hasChildNodes(), false ); + + // check assignment operator + testElement = rootElement; + CHECK( testElement.nodeName(), QString("html") ); + CHECK( testElement.isNull(), false ); + CHECK( testElement.isElement(), true ); + CHECK( testElement.isDocument(), false ); + CHECK( testElement==rootElement, true ); + CHECK( testElement!=rootElement, false ); + CHECK( testElement.parentNode().isNull(), false ); + CHECK( testElement.parentNode().toDocument()==doc, true ); + CHECK( testElement.tagName(), QString("html") ); + CHECK( testElement.prefix().isNull(), true ); + + // assigned from another empty element + testElement = KoXmlElement(); + CHECK( testElement.isNull(), true ); + CHECK( testElement!=rootElement, true ); + + // assigned from <body> + testElement = bodyElement; + CHECK( testElement.isNull(), false ); + CHECK( testElement.isElement(), true ); + CHECK( testElement.isDocument(), false ); + CHECK( testElement.ownerDocument().isNull(), false ); + CHECK( testElement.ownerDocument()==doc, true ); + CHECK( testElement==bodyElement, true ); + CHECK( testElement.parentNode().isNull(), false ); + CHECK( testElement.tagName(), QString("body") ); + CHECK( testElement.prefix().isNull(), true ); + CHECK( testElement.hasChildNodes(), true ); + + // copy constructor + KoXmlElement dummyElement( rootElement ); + CHECK( dummyElement.isNull(), false ); + CHECK( dummyElement.isElement(), true ); + CHECK( dummyElement.isDocument(), false ); + CHECK( dummyElement.ownerDocument().isNull(), false ); + CHECK( dummyElement.ownerDocument()==doc, true ); + CHECK( dummyElement==rootElement, true ); + CHECK( dummyElement.parentNode().isNull(), false ); + CHECK( dummyElement.hasChildNodes(), true ); + CHECK( dummyElement.tagName(), QString("html") ); + CHECK( dummyElement.prefix().isNull(), true ); + + // clear() turns element to null node + dummyElement.clear(); + CHECK( dummyElement.isNull(), true ); + CHECK( dummyElement.isElement(), true ); + CHECK( dummyElement.isDocument(), false ); + CHECK( dummyElement.ownerDocument().isNull(), false ); + CHECK( dummyElement.ownerDocument()==doc, false ); + CHECK( dummyElement.hasChildNodes(), false ); + CHECK( dummyElement==rootElement, false ); + CHECK( dummyElement!=rootElement, true ); + + // check for plain null node converted to element + KoXmlNode dummyNode; + dummyElement = dummyNode.toElement(); + CHECK( dummyElement.isNull(), true ); + CHECK( dummyElement.isElement(), true ); + CHECK( dummyElement.isDocument(), false ); + CHECK( dummyElement.ownerDocument().isNull(), false ); + CHECK( dummyElement.hasChildNodes(), false ); + CHECK( dummyElement.ownerDocument()==doc, false ); +} + +void testAttributes() +{ + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + + QByteArray xmlbuf; + QBuffer xmldevice( xmlbuf ); + + QTextStream xmlstream( xmlbuf, IO_WriteOnly ); + xmlstream << "<p>"; + xmlstream << "<img src=\"foo.png\" width=\"300\" height=\"150\"/>"; + xmlstream << "</p>"; + + KoXmlDocument doc; + CHECK( doc.setContent( &xmldevice, &errorMsg, &errorLine, &errorColumn ), true ); + CHECK( errorMsg.isEmpty(), true ); + CHECK( errorLine, 0 ); + CHECK( errorColumn, 0 ); + + KoXmlElement rootElement; + rootElement = doc.documentElement(); + CHECK( rootElement.isNull(), false ); + CHECK( rootElement.isElement(), true ); + CHECK( rootElement.parentNode().isNull(), false ); + CHECK( rootElement.parentNode().toDocument()==doc, true ); + CHECK( rootElement.tagName(), QString("p") ); + CHECK( rootElement.prefix().isNull(), true ); + + KoXmlElement imgElement; + imgElement = rootElement.firstChild().toElement(); + CHECK( imgElement.isNull(), false ); + CHECK( imgElement.isElement(), true ); + CHECK( imgElement.tagName(), QString("img") ); + CHECK( imgElement.prefix().isNull(), true ); + CHECK( imgElement.hasAttribute("src"), true ); + CHECK( imgElement.hasAttribute("width"), true ); + CHECK( imgElement.hasAttribute("height"), true ); + CHECK( imgElement.hasAttribute("non-exist"), false ); + CHECK( imgElement.hasAttribute("SRC"), false ); + CHECK( imgElement.attribute("src"), QString("foo.png") ); + CHECK( imgElement.attribute("width"), QString("300") ); + CHECK( imgElement.attribute("width").toInt(), 300 ); + CHECK( imgElement.attribute("height"), QString("150") ); + CHECK( imgElement.attribute("height").toInt(), 150 ); + CHECK( imgElement.attribute("border").isEmpty(), true ); + CHECK( imgElement.attribute("border","0").toInt(), 0 ); + CHECK( imgElement.attribute("border","-1").toInt(), -1 ); +} + +void testText() +{ + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + + QByteArray xmlbuf; + QBuffer xmldevice( xmlbuf ); + + QTextStream xmlstream( xmlbuf, IO_WriteOnly ); + xmlstream << "<p>"; + xmlstream << "Hello "; + xmlstream << "<b>world</b>"; + xmlstream << "</p>"; + + KoXmlDocument doc; + CHECK( doc.setContent( &xmldevice, &errorMsg, &errorLine, &errorColumn ), true ); + CHECK( errorMsg.isEmpty(), true ); + CHECK( errorLine, 0 ); + CHECK( errorColumn, 0 ); + + // element for <p> + KoXmlElement parElement; + parElement = doc.documentElement(); + CHECK( parElement.isNull(), false ); + CHECK( parElement.isElement(), true ); + CHECK( parElement.isText(), false ); + CHECK( parElement.isDocument(), false ); + CHECK( parElement.ownerDocument().isNull(), false ); + CHECK( parElement.ownerDocument()==doc, true ); + CHECK( parElement.parentNode().isNull(), false ); + CHECK( parElement.parentNode().toDocument()==doc, true ); + CHECK( parElement.hasChildNodes(), true ); + CHECK( parElement.tagName(), QString("p") ); + CHECK( parElement.prefix().isNull(), true ); + CHECK( parElement.text(), QString("Hello world") ); + + // node for "Hello" + KoXmlNode helloNode; + helloNode = parElement.firstChild(); + CHECK( helloNode.nodeName(), QString("#text") ); + CHECK( helloNode.isNull(), false ); + CHECK( helloNode.isElement(), false ); + CHECK( helloNode.isText(), true ); + CHECK( helloNode.isDocument(), false ); + + // "Hello" text + KoXmlText helloText; + helloText = helloNode.toText(); + CHECK( helloText.nodeName(), QString("#text") ); + CHECK( helloText.isNull(), false ); + CHECK( helloText.isElement(), false ); + CHECK( helloText.isText(), true ); + CHECK( helloText.isDocument(), false ); + CHECK( helloText.data(), QString("Hello ") ); + + // shared copy of the text + KoXmlText hello2Text; + hello2Text = helloText; + CHECK( hello2Text.isNull(), false ); + CHECK( hello2Text.isElement(), false ); + CHECK( hello2Text.isText(), true ); + CHECK( hello2Text.isDocument(), false ); + CHECK( hello2Text.data(), QString("Hello ") ); + + // element for <b> + KoXmlElement boldElement; + boldElement = helloNode.nextSibling().toElement(); + CHECK( boldElement.isNull(), false ); + CHECK( boldElement.isElement(), true ); + CHECK( boldElement.isText(), false ); + CHECK( boldElement.isDocument(), false ); + CHECK( boldElement.ownerDocument().isNull(), false ); + CHECK( boldElement.ownerDocument()==doc, true ); + CHECK( boldElement.parentNode().isNull(), false ); + CHECK( boldElement.hasChildNodes(), true ); + CHECK( boldElement.tagName(), QString("b") ); + CHECK( boldElement.prefix().isNull(), true ); + + // "world" text + KoXmlText worldText; + worldText = boldElement.firstChild().toText(); + CHECK( worldText.isNull(), false ); + CHECK( worldText.isElement(), false ); + CHECK( worldText.isText(), true ); + CHECK( worldText.isDocument(), false ); + CHECK( worldText.data(), QString("world") ); +} + +void testCDATA() +{ + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + + QByteArray xmlbuf; + QBuffer xmldevice( xmlbuf ); + + QTextStream xmlstream( xmlbuf, IO_WriteOnly ); + xmlstream << "<p>"; + xmlstream << "Hello "; + xmlstream << "<![CDATA[world]]>"; + xmlstream << "</p>"; + + KoXmlDocument doc; + CHECK( doc.setContent( &xmldevice, &errorMsg, &errorLine, &errorColumn ), true ); + CHECK( errorMsg.isEmpty(), true ); + CHECK( errorLine, 0 ); + CHECK( errorColumn, 0 ); + + // element for <p> + KoXmlElement parElement; + parElement = doc.documentElement(); + CHECK( parElement.isNull(), false ); + CHECK( parElement.isElement(), true ); + CHECK( parElement.isText(), false ); + CHECK( parElement.isDocument(), false ); + CHECK( parElement.ownerDocument().isNull(), false ); + CHECK( parElement.ownerDocument()==doc, true ); + CHECK( parElement.parentNode().isNull(), false ); + CHECK( parElement.parentNode().toDocument()==doc, true ); + CHECK( parElement.hasChildNodes(), true ); + CHECK( parElement.tagName(), QString("p") ); + CHECK( parElement.prefix().isNull(), true ); + CHECK( parElement.text(), QString("Hello world") ); + + // node for "Hello" + KoXmlNode helloNode; + helloNode = parElement.firstChild(); + CHECK( helloNode.isNull(), false ); + CHECK( helloNode.isElement(), false ); + CHECK( helloNode.isText(), true ); + CHECK( helloNode.isDocument(), false ); + + // "Hello" text + KoXmlText helloText; + helloText = helloNode.toText(); + CHECK( helloText.isNull(), false ); + CHECK( helloText.isElement(), false ); + CHECK( helloText.isText(), true ); + CHECK( helloText.isDocument(), false ); + CHECK( helloText.data(), QString("Hello ") ); + + // node for CDATA "world!" + // Note: isText() is also true for CDATA + KoXmlNode worldNode; + worldNode = helloNode.nextSibling(); + CHECK( worldNode.nodeName(), QString("#cdata-section") ); + CHECK( worldNode.isNull(), false ); + CHECK( worldNode.isElement(), false ); + CHECK( worldNode.isText(), true ); + CHECK( worldNode.isCDATASection(), true ); + CHECK( worldNode.isDocument(), false ); + + // CDATA section for "world!" + // Note: isText() is also true for CDATA + KoXmlCDATASection worldCDATA; + worldCDATA = worldNode.toCDATASection(); + CHECK( worldCDATA.nodeName(), QString("#cdata-section") ); + CHECK( worldCDATA.isNull(), false ); + CHECK( worldCDATA.isElement(), false ); + CHECK( worldCDATA.isText(), true ); + CHECK( worldCDATA.isCDATASection(), true ); + CHECK( worldCDATA.isDocument(), false ); + CHECK( worldCDATA.data(), QString("world") ); +} + +void testDocument() +{ + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + + QByteArray xmlbuf; + QBuffer xmldevice( xmlbuf ); + + QTextStream xmlstream( xmlbuf, IO_WriteOnly ); + xmlstream << "<koffice>"; + xmlstream << " <kword/>\n"; + xmlstream << " <kpresenter/>\n"; + xmlstream << " <krita/>\n"; + xmlstream << "</koffice>"; + + KoXmlDocument doc; + + // empty document + CHECK( doc.nodeName(), QString("#document") ); + CHECK( doc.isNull(), false ); + CHECK( doc.isElement(), false ); + CHECK( doc.isDocument(), true ); + CHECK( doc.parentNode().isNull(), true ); + CHECK( doc.firstChild().isNull(), true ); + CHECK( doc.lastChild().isNull(), true ); + CHECK( doc.previousSibling().isNull(), true ); + CHECK( doc.nextSibling().isNull(), true ); + + // now give something as the content + CHECK( doc.setContent(&xmldevice,&errorMsg,&errorLine,&errorColumn ), true ); + CHECK( errorMsg.isEmpty(), true ); + CHECK( errorLine, 0 ); + CHECK( errorColumn, 0 ); + + // this document has something already + CHECK( doc.nodeName(), QString("#document") ); + CHECK( doc.isNull(), false ); + CHECK( doc.isElement(), false ); + CHECK( doc.isDocument(), true ); + CHECK( doc.parentNode().isNull(), true ); + CHECK( doc.firstChild().isNull(), false ); + CHECK( doc.lastChild().isNull(), false ); + CHECK( doc.previousSibling().isNull(), true ); + CHECK( doc.nextSibling().isNull(), true ); + + // make sure its children are fine + KoXmlElement rootElement; + rootElement = doc.firstChild().toElement(); + CHECK( rootElement.isNull(), false ); + CHECK( rootElement.isElement(), true ); + CHECK( rootElement.isDocument(), false ); + CHECK( rootElement.parentNode().isNull(), false ); + CHECK( rootElement.parentNode().toDocument()==doc, true ); + rootElement = doc.lastChild().toElement(); + CHECK( rootElement.isNull(), false ); + CHECK( rootElement.isElement(), true ); + CHECK( rootElement.isDocument(), false ); + CHECK( rootElement.parentNode().isNull(), false ); + CHECK( rootElement.parentNode().toDocument()==doc, true ); + + // clear() converts it into null node + doc.clear(); + CHECK( doc.nodeName(), QString::null ); + CHECK( doc.isNull(), true ); + CHECK( doc.isElement(), false ); + CHECK( doc.isDocument(), true ); + CHECK( doc.parentNode().isNull(), true ); + CHECK( doc.firstChild().isNull(), true ); + CHECK( doc.lastChild().isNull(), true ); + CHECK( doc.previousSibling().isNull(), true ); + CHECK( doc.nextSibling().isNull(), true ); + + // assigned from another empty document + doc = KoXmlDocument(); + CHECK( doc.nodeName(), QString("#document") ); + CHECK( doc.isNull(), false ); + CHECK( doc.isElement(), false ); + CHECK( doc.isDocument(), true ); + CHECK( doc.parentNode().isNull(), true ); +} + +void testNamespace() +{ + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + + QByteArray xmlbuf; + QBuffer xmldevice( xmlbuf ); + QTextStream xmlstream( xmlbuf, IO_WriteOnly ); + + // taken from example in Qt documentation (xml.html) + xmlstream << "<document xmlns:book = \"http://trolltech.com/fnord/book/\""; + xmlstream << " xmlns = \"http://trolltech.com/fnord/\" >"; + xmlstream << "<book>"; + xmlstream << " <book:title>Practical XML</book:title>"; + xmlstream << " <book:author xmlns:fnord = \"http://trolltech.com/fnord/\""; + xmlstream << " title=\"Ms\""; + xmlstream << " fnord:title=\"Goddess\""; + xmlstream << " name=\"Eris Kallisti\"/>"; + xmlstream << " <chapter>"; + xmlstream << " <title>A Namespace Called fnord</title>"; + xmlstream << " </chapter>"; + xmlstream << "</book>"; + xmlstream << "</document>"; + + KoXmlDocument doc; + KoXmlElement rootElement; + KoXmlElement bookElement; + KoXmlElement bookTitleElement; + KoXmlElement bookAuthorElement; + + // ------------- first without any namespace processing ----------- + CHECK( doc.setContent( &xmldevice, &errorMsg, &errorLine, &errorColumn ), true ); + CHECK( errorMsg.isEmpty(), true ); + CHECK( errorLine, 0 ); + CHECK( errorColumn, 0 ); + + rootElement = doc.documentElement(); + CHECK( rootElement.isNull(), false ); + CHECK( rootElement.isElement(), true ); + CHECK( rootElement.tagName(), QString("document") ); + CHECK( rootElement.prefix().isNull(), true ); + + bookElement = rootElement.firstChild().toElement(); + CHECK( bookElement.isNull(), false ); + CHECK( bookElement.isElement(), true ); + CHECK( bookElement.tagName(), QString("book") ); + CHECK( bookElement.prefix().isNull(), true ); + CHECK( bookElement.localName(), QString::null ); + + bookTitleElement = bookElement.firstChild().toElement(); + CHECK( bookTitleElement.isNull(), false ); + CHECK( bookTitleElement.isElement(), true ); + CHECK( bookTitleElement.tagName(), QString("book:title") ); + CHECK( bookTitleElement.prefix().isNull(), true ); + CHECK( bookTitleElement.localName(), QString::null ); + + bookAuthorElement = bookTitleElement.nextSibling().toElement(); + CHECK( bookAuthorElement.isNull(), false ); + CHECK( bookAuthorElement.isElement(), true ); + CHECK( bookAuthorElement.tagName(), QString("book:author") ); + CHECK( bookAuthorElement.prefix().isNull(), true ); + CHECK( bookAuthorElement.attribute("title"), QString("Ms") ); + CHECK( bookAuthorElement.attribute("fnord:title"), QString("Goddess") ); + CHECK( bookAuthorElement.attribute("name"), QString("Eris Kallisti") ); + + // ------------- now with namespace processing ----------- + xmldevice.at(0); // just to rewind + + CHECK( doc.setContent( &xmldevice, true, &errorMsg, &errorLine, &errorColumn ), true ); + CHECK( errorMsg.isEmpty(), true ); + CHECK( errorLine, 0 ); + CHECK( errorColumn, 0 ); + + char* defaultNS = "http://trolltech.com/fnord/"; + char* bookNS = "http://trolltech.com/fnord/book/"; + char* fnordNS = "http://trolltech.com/fnord/"; + + // <document> + rootElement = doc.documentElement(); + CHECK( rootElement.isNull(), false ); + CHECK( rootElement.isElement(), true ); + CHECK( rootElement.tagName(), QString("document") ); + CHECK( rootElement.prefix().isEmpty(), true ); + CHECK( rootElement.namespaceURI(), QString( defaultNS ) ); + CHECK( rootElement.localName(), QString("document") ); + + // <book> + bookElement = rootElement.firstChild().toElement(); + CHECK( bookElement.isNull(), false ); + CHECK( bookElement.isElement(), true ); + CHECK( bookElement.tagName(), QString("book") ); + CHECK( bookElement.prefix().isEmpty(), true ); + CHECK( bookElement.namespaceURI(), QString( defaultNS ) ); + CHECK( bookElement.localName(), QString("book") ); + + // <book:title> + bookTitleElement = bookElement.firstChild().toElement(); + CHECK( bookTitleElement.isNull(), false ); + CHECK( bookTitleElement.isElement(), true ); + CHECK( bookTitleElement.tagName(), QString("title") ); + CHECK( bookTitleElement.prefix(), QString("book") ); + CHECK( bookTitleElement.namespaceURI(), QString(bookNS) ); + CHECK( bookTitleElement.localName(), QString("title") ); + + // another way, find it using namedItemNS() + KoXmlElement book2TitleElement; + book2TitleElement = KoXml::namedItemNS( rootElement.firstChild(), bookNS, "title" ); + //book2TitleElement = bookElement.namedItemNS( bookNS, "title" ).toElement(); + CHECK( book2TitleElement==bookTitleElement, true ); + CHECK( book2TitleElement.isNull(), false ); + CHECK( book2TitleElement.isElement(), true ); + CHECK( book2TitleElement.tagName(), QString("title") ); + + // <book:author> + bookAuthorElement = bookTitleElement.nextSibling().toElement(); + CHECK( bookAuthorElement.isNull(), false ); + CHECK( bookAuthorElement.isElement(), true ); + CHECK( bookAuthorElement.tagName(), QString("author") ); + CHECK( bookAuthorElement.prefix(), QString("book") ); + CHECK( bookAuthorElement.namespaceURI(), QString(bookNS) ); + CHECK( bookAuthorElement.localName(), QString("author") ); + + // another way, find it using namedItemNS() + KoXmlElement book2AuthorElement; + book2AuthorElement = KoXml::namedItemNS( bookElement, bookNS, "author" ); + //book2AuthorElement = bookElement.namedItemNS( bookNS, "author" ).toElement(); + CHECK( book2AuthorElement==bookAuthorElement, true ); + CHECK( book2AuthorElement.isNull(), false ); + CHECK( book2AuthorElement.isElement(), true ); + CHECK( book2AuthorElement.tagName(), QString("author") ); + + // attributes in <book:author> + // Note: with namespace processing, attribute's prefix is taken out and + // hence "fnord:title" will simply override "title" + // and searching attribute with prefix will give no result + CHECK( bookAuthorElement.hasAttribute("title"), true ); + CHECK( bookAuthorElement.hasAttribute("fnord:title"), false ); + CHECK( bookAuthorElement.hasAttribute("name"), true ); + CHECK( bookAuthorElement.attribute("title"), QString("Goddess") ); + CHECK( bookAuthorElement.attribute("fnord:title").isEmpty(), true ); + CHECK( bookAuthorElement.attribute("name"), QString("Eris Kallisti") ); + + // attributes in <book:author>, with NS family of functions + // those without prefix are not accessible at all, because they do not belong + // to any namespace at all. + // Note: default namespace does not apply to attribute names! + CHECK( bookAuthorElement.hasAttributeNS(defaultNS,"title"), true ); + CHECK( bookAuthorElement.hasAttributeNS(bookNS,"title"), false ); + CHECK( bookAuthorElement.hasAttributeNS(fnordNS,"title"), true ); + + CHECK( bookAuthorElement.attributeNS(defaultNS,"title",""), QString("Goddess") ); + CHECK( bookAuthorElement.attributeNS(bookNS,"title",""), QString("") ); + CHECK( bookAuthorElement.attributeNS(fnordNS,"title",""), QString("Goddess") ); + + CHECK( bookAuthorElement.hasAttributeNS(defaultNS,"fnord:title"), false ); + CHECK( bookAuthorElement.hasAttributeNS(bookNS,"fnord:title"), false ); + CHECK( bookAuthorElement.hasAttributeNS(fnordNS,"fnord:title"), false ); + + CHECK( bookAuthorElement.hasAttributeNS(defaultNS,"name"), false ); + CHECK( bookAuthorElement.hasAttributeNS(bookNS,"name"), false ); + CHECK( bookAuthorElement.hasAttributeNS(fnordNS,"name"), false ); + + CHECK( bookAuthorElement.attributeNS(defaultNS,"name",QString::null).isEmpty(), true ); + CHECK( bookAuthorElement.attributeNS(bookNS,"name",QString::null).isEmpty(), true ); + CHECK( bookAuthorElement.attributeNS(fnordNS,"name",QString::null).isEmpty(), true ); +} + +void testUnload() +{ + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + + QByteArray xmlbuf; + QBuffer xmldevice( xmlbuf ); + QTextStream xmlstream( xmlbuf, IO_WriteOnly ); + xmlstream << "<earth>"; + xmlstream << "<continents>"; + xmlstream << "<asia/>"; + xmlstream << "<africa/>"; + xmlstream << "<europe/>"; + xmlstream << "<america/>"; + xmlstream << "<australia/>"; + xmlstream << "<antartic/>"; + xmlstream << "</continents>"; + xmlstream << "<oceans>"; + xmlstream << "<pacific/>"; + xmlstream << "<atlantic/>"; + xmlstream << "</oceans>"; + xmlstream << "</earth>"; + + KoXmlDocument doc; + CHECK( doc.setContent( &xmldevice, &errorMsg, &errorLine, &errorColumn ), true ); + CHECK( errorMsg.isEmpty(), true ); + CHECK( errorLine, 0 ); + CHECK( errorColumn, 0 ); + + KoXmlElement earthElement; + earthElement = doc.documentElement().toElement(); + CHECK( earthElement.isNull(), false ); + CHECK( earthElement.isElement(), true ); + CHECK( earthElement.parentNode().isNull(), false ); + CHECK( earthElement.hasChildNodes(), true ); + CHECK( earthElement.tagName(), QString("earth") ); + CHECK( earthElement.prefix().isNull(), true ); + + // this ensures that all child nodes of <earth> are loaded + earthElement.firstChild(); + + // explicitly unload all child nodes of <earth> + KoXml::unload( earthElement ); + + // we should get the correct first child + KoXmlElement continentsElement = earthElement.firstChild().toElement(); + CHECK( continentsElement.nodeName(), QString("continents") ); + CHECK( continentsElement.isNull(), false ); + CHECK( continentsElement.isElement(), true ); + CHECK( continentsElement.isText(), false ); + CHECK( continentsElement.isDocument(), false ); + CHECK( continentsElement.hasChildNodes(), true ); + + // let us unload everything again + KoXml::unload( earthElement ); + + // we should get the correct last child + KoXmlElement oceansElement = earthElement.lastChild().toElement(); + CHECK( oceansElement.nodeName(), QString("oceans") ); + CHECK( oceansElement.isNull(), false ); + CHECK( oceansElement.isElement(), true ); + CHECK( oceansElement.isText(), false ); + CHECK( oceansElement.isDocument(), false ); + CHECK( oceansElement.hasChildNodes(), true ); +} + +void testSimpleXML() +{ + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + + QByteArray xmlbuf; + QBuffer xmldevice( xmlbuf ); + + QTextStream xmlstream( xmlbuf, IO_WriteOnly ); + xmlstream << "<solarsystem>"; + xmlstream << " <mercurius/>\n"; + xmlstream << " <venus/>\n"; + xmlstream << " <earth>\n"; + xmlstream << " <moon/>\n"; + xmlstream << " </earth>\n"; + xmlstream << " <mars/>\n"; + xmlstream << " <jupiter/>\n"; + xmlstream << "</solarsystem>"; + + KoXmlDocument doc; + CHECK( doc.setContent(&xmldevice,&errorMsg,&errorLine,&errorColumn ), true ); + CHECK( errorMsg.isEmpty(), true ); + CHECK( errorLine, 0 ); + CHECK( errorColumn, 0 ); + + // <solarsystem> + KoXmlElement rootElement; + rootElement = doc.documentElement(); + CHECK( rootElement.isNull(), false ); + CHECK( rootElement.isElement(), true ); + CHECK( rootElement.parentNode().isNull(), false ); + CHECK( rootElement.hasChildNodes(), true ); + CHECK( rootElement.tagName(), QString("solarsystem") ); + CHECK( rootElement.prefix().isNull(), true ); + + // node <mercurius> + KoXmlNode firstPlanetNode; + firstPlanetNode = rootElement.firstChild(); + CHECK( firstPlanetNode.isNull(), false ); + CHECK( firstPlanetNode.isElement(), true ); + CHECK( firstPlanetNode.nextSibling().isNull(), false ); + CHECK( firstPlanetNode.previousSibling().isNull(), true ); + CHECK( firstPlanetNode.parentNode().isNull(), false ); + CHECK( firstPlanetNode.parentNode()==rootElement, true ); + CHECK( firstPlanetNode.parentNode()!=rootElement, false ); + CHECK( firstPlanetNode.hasChildNodes(), false ); + CHECK( firstPlanetNode.firstChild().isNull(), true ); + CHECK( firstPlanetNode.lastChild().isNull(), true ); + + // element <mercurius> + KoXmlElement firstPlanetElement; + firstPlanetElement = firstPlanetNode.toElement(); + CHECK( firstPlanetElement.isNull(), false ); + CHECK( firstPlanetElement.isElement(), true ); + CHECK( firstPlanetElement.parentNode().isNull(), false ); + CHECK( firstPlanetElement.parentNode()==rootElement, true ); + CHECK( firstPlanetElement.hasChildNodes(), false ); + CHECK( firstPlanetElement.firstChild().isNull(), true ); + CHECK( firstPlanetElement.lastChild().isNull(), true ); + CHECK( firstPlanetElement.tagName(), QString("mercurius") ); + CHECK( firstPlanetElement.prefix().isNull(), true ); + + // node <venus> + KoXmlNode secondPlanetNode; + secondPlanetNode = firstPlanetNode.nextSibling(); + CHECK( secondPlanetNode.isNull(), false ); + CHECK( secondPlanetNode.isElement(), true ); + CHECK( secondPlanetNode.nextSibling().isNull(), false ); + CHECK( secondPlanetNode.previousSibling().isNull(), false ); + CHECK( secondPlanetNode.previousSibling()==firstPlanetNode, true ); + CHECK( secondPlanetNode.previousSibling()==firstPlanetElement, true ); + CHECK( secondPlanetNode.parentNode().isNull(), false ); + CHECK( secondPlanetNode.parentNode()==rootElement, true ); + CHECK( secondPlanetNode.parentNode()!=rootElement, false ); + CHECK( secondPlanetNode.hasChildNodes(), false ); + CHECK( secondPlanetNode.firstChild().isNull(), true ); + CHECK( secondPlanetNode.lastChild().isNull(), true ); + + // element <venus> + KoXmlElement secondPlanetElement; + secondPlanetElement = secondPlanetNode.toElement(); + CHECK( secondPlanetElement.isNull(), false ); + CHECK( secondPlanetElement.isElement(), true ); + CHECK( secondPlanetElement.nextSibling().isNull(), false ); + CHECK( secondPlanetElement.previousSibling().isNull(), false ); + CHECK( secondPlanetElement.previousSibling()==firstPlanetNode, true ); + CHECK( secondPlanetElement.previousSibling()==firstPlanetElement, true ); + CHECK( secondPlanetElement.parentNode().isNull(), false ); + CHECK( secondPlanetElement.parentNode()==rootElement, true ); + CHECK( secondPlanetElement.parentNode()!=rootElement, false ); + CHECK( secondPlanetElement.hasChildNodes(), false ); + CHECK( secondPlanetElement.firstChild().isNull(), true ); + CHECK( secondPlanetElement.lastChild().isNull(), true ); + CHECK( secondPlanetElement.tagName(), QString("venus") ); + CHECK( secondPlanetElement.prefix().isNull(), true ); +} + +void testRootError() +{ + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + + // multiple root nodes are not valid ! + QByteArray xmlbuf; + QBuffer xmldevice( xmlbuf ); + QTextStream xmlstream( xmlbuf, IO_WriteOnly ); + xmlstream << "<earth></earth><moon></moon>"; + + KoXmlDocument doc; + CHECK( doc.setContent( &xmldevice, &errorMsg, &errorLine, &errorColumn ), false ); + CHECK( errorMsg.isEmpty(), false ); + CHECK( errorMsg, QString("unexpected character") ); + CHECK( errorLine, 1 ); + CHECK( errorColumn, 17 ); +} + +void testMismatchedTag() +{ + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + + QByteArray xmlbuf; + QBuffer xmldevice( xmlbuf ); + QTextStream xmlstream( xmlbuf, IO_WriteOnly ); + xmlstream << "<earth></e>"; + + KoXmlDocument doc; + CHECK( doc.setContent( &xmldevice, &errorMsg, &errorLine, &errorColumn ), false ); + CHECK( errorMsg.isEmpty(), false ); + CHECK( errorMsg, QString("tag mismatch") ); + CHECK( errorLine, 1 ); + CHECK( errorColumn, 11 ); +} + +void testSimpleOpenDocumentText() +{ + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + + QByteArray xmlbuf; + QBuffer xmldevice( xmlbuf ); + QTextStream xmlstream( xmlbuf, IO_WriteOnly ); + + // content.xml from a simple OpenDocument text + // it has only paragraph "Hello, world!" + // automatic styles, declarations and unnecessary namespaces are omitted. + xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; + xmlstream << "<office:document-content "; + xmlstream << " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\""; + xmlstream << " xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\""; + xmlstream << " xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" "; + xmlstream << " office:version=\"1.0\">"; + xmlstream << " <office:automatic-styles/>"; + xmlstream << " <office:body>"; + xmlstream << " <office:text>"; + xmlstream << " <text:p text:style-name=\"Standard\">Hello, world!</text:p>"; + xmlstream << " </office:text>"; + xmlstream << " </office:body>"; + xmlstream << "</office:document-content>"; + + KoXmlDocument doc; + CHECK( doc.setContent( &xmldevice, true, &errorMsg, &errorLine, &errorColumn ), true ); + CHECK( errorMsg.isEmpty(), true ); + CHECK( errorLine, 0 ); + CHECK( errorColumn, 0 ); + + char* officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; + char* textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; + + // <office:document-content> + KoXmlElement contentElement; + contentElement = doc.documentElement(); + CHECK( contentElement.isNull(), false ); + CHECK( contentElement.isElement(), true ); + CHECK( contentElement.parentNode().isNull(), false ); + CHECK( contentElement.parentNode().toDocument()==doc, true ); + CHECK( contentElement.firstChild().isNull(), false ); + CHECK( contentElement.lastChild().isNull(), false ); + CHECK( contentElement.previousSibling().isNull(), false ); + CHECK( contentElement.nextSibling().isNull(), true ); + CHECK( contentElement.localName(), QString("document-content") ); + CHECK( contentElement.hasAttributeNS(officeNS,"version"), true ); + CHECK( contentElement.attributeNS(officeNS,"version",""), QString("1.0") ); + + // <office:automatic-styles> + KoXmlElement stylesElement; + stylesElement = KoXml::namedItemNS( contentElement, officeNS, "automatic-styles" ); + CHECK( stylesElement.isNull(), false ); + CHECK( stylesElement.isElement(), true ); + CHECK( stylesElement.parentNode().isNull(), false ); + CHECK( stylesElement.parentNode()==contentElement, true ); + CHECK( stylesElement.firstChild().isNull(), true ); + CHECK( stylesElement.lastChild().isNull(), true ); + CHECK( stylesElement.previousSibling().isNull(), true ); + CHECK( stylesElement.nextSibling().isNull(), false ); + CHECK( stylesElement.localName(), QString("automatic-styles") ); + + // also same <office:automatic-styles>, but without namedItemNS + KoXmlNode styles2Element; + styles2Element = contentElement.firstChild().toElement(); + CHECK( styles2Element.isNull(), false ); + CHECK( styles2Element.isElement(), true ); + CHECK( styles2Element.parentNode().isNull(), false ); + CHECK( styles2Element.parentNode()==contentElement, true ); + CHECK( styles2Element.firstChild().isNull(), true ); + CHECK( styles2Element.lastChild().isNull(), true ); + CHECK( styles2Element.previousSibling().isNull(), true ); + CHECK( styles2Element.nextSibling().isNull(), false ); + CHECK( styles2Element.localName(), QString("automatic-styles") ); + + // <office:body> + KoXmlElement bodyElement; + bodyElement = KoXml::namedItemNS( contentElement, officeNS, "body" ); + CHECK( bodyElement.isNull(), false ); + CHECK( bodyElement.isElement(), true ); + CHECK( bodyElement.parentNode().isNull(), false ); + CHECK( bodyElement.parentNode()==contentElement, true ); + CHECK( bodyElement.firstChild().isNull(), false ); + CHECK( bodyElement.lastChild().isNull(), false ); + CHECK( bodyElement.previousSibling().isNull(), false ); + CHECK( bodyElement.nextSibling().isNull(), true ); + CHECK( bodyElement.localName(), QString("body") ); + + // also same <office:body>, but without namedItemNS + KoXmlElement body2Element; + body2Element = stylesElement.nextSibling().toElement(); + CHECK( body2Element.isNull(), false ); + CHECK( body2Element.isElement(), true ); + CHECK( body2Element.parentNode().isNull(), false ); + CHECK( body2Element.parentNode()==contentElement, true ); + CHECK( body2Element.firstChild().isNull(), false ); + CHECK( body2Element.lastChild().isNull(), false ); + CHECK( body2Element.previousSibling().isNull(), false ); + CHECK( body2Element.nextSibling().isNull(), true ); + CHECK( body2Element.localName(), QString("body") ); + + // <office:text> + KoXmlElement textElement; + textElement = KoXml::namedItemNS( bodyElement, officeNS, "text" ); + CHECK( textElement.isNull(), false ); + CHECK( textElement.isElement(), true ); + CHECK( textElement.parentNode().isNull(), false ); + CHECK( textElement.parentNode()==bodyElement, true ); + CHECK( textElement.firstChild().isNull(), false ); + CHECK( textElement.lastChild().isNull(), false ); + CHECK( textElement.previousSibling().isNull(), true ); + CHECK( textElement.nextSibling().isNull(), true ); + CHECK( textElement.localName(), QString("text") ); + + // the same <office:text>, but without namedItemNS + KoXmlElement text2Element; + text2Element = bodyElement.firstChild().toElement(); + CHECK( text2Element.isNull(), false ); + CHECK( text2Element.isElement(), true ); + CHECK( text2Element.parentNode().isNull(), false ); + CHECK( text2Element.parentNode()==bodyElement, true ); + CHECK( text2Element.firstChild().isNull(), false ); + CHECK( text2Element.lastChild().isNull(), false ); + CHECK( text2Element.previousSibling().isNull(), true ); + CHECK( text2Element.nextSibling().isNull(), true ); + CHECK( text2Element.localName(), QString("text") ); + + // <text:p> + KoXmlElement parElement; + parElement = textElement.firstChild().toElement(); + CHECK( parElement.isNull(), false ); + CHECK( parElement.isElement(), true ); + CHECK( parElement.parentNode().isNull(), false ); + CHECK( parElement.parentNode()==textElement, true ); + CHECK( parElement.firstChild().isNull(), false ); + CHECK( parElement.lastChild().isNull(), false ); + CHECK( parElement.previousSibling().isNull(), true ); + CHECK( parElement.nextSibling().isNull(), true ); + CHECK( parElement.tagName(), QString("p") ); + CHECK( parElement.text(), QString("Hello, world!") ); + CHECK( parElement.attributeNS( QString(textNS),"style-name",""), QString("Standard") ); +} + +void testSimpleOpenDocumentSpreadsheet() +{ + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + + QByteArray xmlbuf; + QBuffer xmldevice( xmlbuf ); + QTextStream xmlstream( xmlbuf, IO_WriteOnly ); + + // content.xml from a simple OpenDocument spreadsheet + // the document has three worksheets, the last two are empty. + // on the first sheet, cell A1 contains the text "Hello, world". + // automatic styles, font declarations and unnecessary namespaces are omitted. + + xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; + xmlstream << "<office:document-content "; + xmlstream << "xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\""; + xmlstream << "xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" "; + xmlstream << "xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\">"; + xmlstream << "<office:body>"; + xmlstream << "<office:spreadsheet>"; + xmlstream << "<table:table table:name=\"Sheet1\" table:style-name=\"ta1\" table:print=\"false\">"; + xmlstream << "<table:table-column table:style-name=\"co1\" table:default-cell-style-name=\"Default\"/>"; + xmlstream << "<table:table-row table:style-name=\"ro1\">"; + xmlstream << "<table:table-cell office:value-type=\"string\">"; + xmlstream << "<text:p>Hello, world</text:p>"; + xmlstream << "</table:table-cell>"; + xmlstream << "</table:table-row>"; + xmlstream << "</table:table>"; + xmlstream << "<table:table table:name=\"Sheet2\" table:style-name=\"ta1\" table:print=\"false\">"; + xmlstream << "<table:table-column table:style-name=\"co1\" table:default-cell-style-name=\"Default\"/>"; + xmlstream << "<table:table-row table:style-name=\"ro1\">"; + xmlstream << "<table:table-cell/>"; + xmlstream << "</table:table-row>"; + xmlstream << "</table:table>"; + xmlstream << "<table:table table:name=\"Sheet3\" table:style-name=\"ta1\" table:print=\"false\">"; + xmlstream << "<table:table-column table:style-name=\"co1\" table:default-cell-style-name=\"Default\"/>"; + xmlstream << "<table:table-row table:style-name=\"ro1\">"; + xmlstream << "<table:table-cell/>"; + xmlstream << "</table:table-row>"; + xmlstream << "</table:table>"; + xmlstream << "</office:spreadsheet>"; + xmlstream << "</office:body>"; + xmlstream << "</office:document-content>"; + + KoXmlDocument doc; + CHECK( doc.setContent( &xmldevice, true, &errorMsg, &errorLine, &errorColumn ), true ); + CHECK( errorMsg.isEmpty(), true ); + CHECK( errorLine, 0 ); + CHECK( errorColumn, 0 ); + + QString officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; + QString tableNS = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"; + QString textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; + + // <office:document-content> + KoXmlElement contentElement; + contentElement = doc.documentElement(); + CHECK( contentElement.isNull(), false ); + CHECK( contentElement.isElement(), true ); + CHECK( contentElement.parentNode().isNull(), false ); + CHECK( contentElement.parentNode().toDocument()==doc, true ); + CHECK( contentElement.firstChild().isNull(), false ); + CHECK( contentElement.lastChild().isNull(), false ); + CHECK( contentElement.previousSibling().isNull(), false ); + CHECK( contentElement.nextSibling().isNull(), true ); + CHECK( contentElement.localName(), QString("document-content") ); + + // <office:body> + KoXmlElement bodyElement; + bodyElement = contentElement.firstChild().toElement(); + CHECK( bodyElement.isNull(), false ); + CHECK( bodyElement.isElement(), true ); + CHECK( bodyElement.parentNode().isNull(), false ); + CHECK( bodyElement.parentNode()==contentElement, true ); + CHECK( bodyElement.firstChild().isNull(), false ); + CHECK( bodyElement.lastChild().isNull(), false ); + CHECK( bodyElement.previousSibling().isNull(), true ); + CHECK( bodyElement.nextSibling().isNull(), true ); + CHECK( bodyElement.localName(), QString("body") ); + + // <office:spreadsheet> + KoXmlElement spreadsheetElement; + spreadsheetElement = bodyElement.firstChild().toElement(); + CHECK( spreadsheetElement.isNull(), false ); + CHECK( spreadsheetElement.isElement(), true ); + CHECK( spreadsheetElement.parentNode().isNull(), false ); + CHECK( spreadsheetElement.parentNode()==bodyElement, true ); + CHECK( spreadsheetElement.firstChild().isNull(), false ); + CHECK( spreadsheetElement.lastChild().isNull(), false ); + CHECK( spreadsheetElement.previousSibling().isNull(), true ); + CHECK( spreadsheetElement.nextSibling().isNull(), true ); + CHECK( spreadsheetElement.localName(), QString("spreadsheet") ); + + // <table:table> for Sheet1 + KoXmlElement sheet1Element; + sheet1Element = spreadsheetElement.firstChild().toElement(); + CHECK( sheet1Element.isNull(), false ); + CHECK( sheet1Element.isElement(), true ); + CHECK( sheet1Element.parentNode().isNull(), false ); + CHECK( sheet1Element.parentNode()==spreadsheetElement, true ); + CHECK( sheet1Element.firstChild().isNull(), false ); + CHECK( sheet1Element.lastChild().isNull(), false ); + CHECK( sheet1Element.previousSibling().isNull(), true ); + CHECK( sheet1Element.nextSibling().isNull(), false ); + CHECK( sheet1Element.tagName(), QString("table") ); + CHECK( sheet1Element.hasAttributeNS(tableNS,"name"), true ); + CHECK( sheet1Element.attributeNS(tableNS,"name",""), QString("Sheet1") ); + CHECK( sheet1Element.attributeNS(tableNS,"style-name",""), QString("ta1") ); + CHECK( sheet1Element.attributeNS(tableNS,"print",""), QString("false") ); + + KoXml::load( sheet1Element, 100 ); + + // <table:table-column> + KoXmlElement columnElement; + columnElement = sheet1Element.firstChild().toElement(); + CHECK( columnElement.isNull(), false ); + CHECK( columnElement.isElement(), true ); + CHECK( columnElement.parentNode().isNull(), false ); + CHECK( columnElement.parentNode()==sheet1Element, true ); + CHECK( columnElement.firstChild().isNull(), true ); + CHECK( columnElement.lastChild().isNull(), true ); + CHECK( columnElement.previousSibling().isNull(), true ); + CHECK( columnElement.nextSibling().isNull(), false ); + CHECK( columnElement.tagName(), QString("table-column") ); + CHECK( columnElement.attributeNS(tableNS,"style-name",""), QString("co1") ); + CHECK( columnElement.attributeNS(tableNS,"default-cell-style-name",""), QString("Default") ); + + // <table:table-row> + KoXmlElement rowElement; + rowElement = columnElement.nextSibling().toElement(); + CHECK( rowElement.isNull(), false ); + CHECK( rowElement.isElement(), true ); + CHECK( rowElement.parentNode().isNull(), false ); + CHECK( rowElement.parentNode()==sheet1Element, true ); + CHECK( rowElement.firstChild().isNull(), false ); + CHECK( rowElement.lastChild().isNull(), false ); + CHECK( rowElement.previousSibling().isNull(), false ); + CHECK( rowElement.nextSibling().isNull(), true ); + CHECK( rowElement.tagName(), QString("table-row") ); + CHECK( rowElement.attributeNS(tableNS,"style-name",""), QString("ro1") ); + + // <table:table-cell> + KoXmlElement cellElement; + cellElement = rowElement.firstChild().toElement(); + CHECK( cellElement.isNull(), false ); + CHECK( cellElement.isElement(), true ); + CHECK( cellElement.parentNode().isNull(), false ); + CHECK( cellElement.parentNode()==rowElement, true ); + CHECK( cellElement.firstChild().isNull(), false ); + CHECK( cellElement.lastChild().isNull(), false ); + CHECK( cellElement.previousSibling().isNull(), true ); + CHECK( cellElement.nextSibling().isNull(), true ); + CHECK( cellElement.tagName(), QString("table-cell") ); + CHECK( cellElement.attributeNS(officeNS,"value-type",""), QString("string") ); + + // <text:p> + KoXmlElement parElement; + parElement = cellElement.firstChild().toElement(); + CHECK( parElement.isNull(), false ); + CHECK( parElement.isElement(), true ); + CHECK( parElement.parentNode().isNull(), false ); + CHECK( parElement.parentNode()==cellElement, true ); + CHECK( parElement.firstChild().isNull(), false ); + CHECK( parElement.lastChild().isNull(), false ); + CHECK( parElement.previousSibling().isNull(), true ); + CHECK( parElement.nextSibling().isNull(), true ); + CHECK( parElement.tagName(), QString("p") ); + CHECK( parElement.text(), QString("Hello, world") ); + + // <table:table> for Sheet2 + KoXmlElement sheet2Element; + sheet2Element = sheet1Element.nextSibling().toElement(); + CHECK( sheet2Element.isNull(), false ); + CHECK( sheet2Element.isElement(), true ); + CHECK( sheet2Element.parentNode().isNull(), false ); + CHECK( sheet2Element.parentNode()==spreadsheetElement, true ); + CHECK( sheet2Element.firstChild().isNull(), false ); + CHECK( sheet2Element.lastChild().isNull(), false ); + CHECK( sheet2Element.previousSibling().isNull(), false ); + CHECK( sheet2Element.nextSibling().isNull(), false ); + CHECK( sheet2Element.tagName(), QString("table") ); + + // </table:table> for Sheet3 + KoXmlElement sheet3Element; + sheet3Element = sheet2Element.nextSibling().toElement(); + CHECK( sheet3Element.isNull(), false ); + CHECK( sheet3Element.isElement(), true ); + CHECK( sheet3Element.parentNode().isNull(), false ); + CHECK( sheet3Element.parentNode()==spreadsheetElement, true ); + CHECK( sheet3Element.firstChild().isNull(), false ); + CHECK( sheet3Element.lastChild().isNull(), false ); + CHECK( sheet3Element.previousSibling().isNull(), false ); + CHECK( sheet3Element.nextSibling().isNull(), true ); + CHECK( sheet3Element.tagName(), QString("table") ); +} + +void testSimpleOpenDocumentPresentation() +{ + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + + QByteArray xmlbuf; + QBuffer xmldevice( xmlbuf ); + QTextStream xmlstream( xmlbuf, IO_WriteOnly ); + + // content.xml from a simple OpenDocument presentation + // styles, declarations and unnecessary namespaces are omitted + // the first page is "Title" and has two text boxes + // the second page is + + xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; + xmlstream << "<office:document-content "; + xmlstream << " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" "; + xmlstream << " xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" "; + xmlstream << " xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\" "; + xmlstream << " xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\" "; + xmlstream << " xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\" "; + xmlstream << " office:version=\"1.0\">"; + xmlstream << " <office:scripts/>"; + xmlstream << " <office:automatic-styles/>"; + xmlstream << " <office:body>"; + xmlstream << " <office:presentation>"; + xmlstream << " <draw:page draw:name=\"Title\" draw:style-name=\"dp1\" "; + xmlstream << " draw:master-page-name=\"lyt-cool\" "; + xmlstream << " presentation:presentation-page-layout-name=\"AL1T0\">"; + xmlstream << " <draw:frame presentation:style-name=\"pr1\" "; + xmlstream << " draw:text-style-name=\"P2\" draw:layer=\"layout\" "; + xmlstream << " svg:width=\"23.912cm\" svg:height=\"3.508cm\" "; + xmlstream << " svg:x=\"2.058cm\" svg:y=\"1.543cm\" "; + xmlstream << " presentation:class=\"title\" "; + xmlstream << " presentation:user-transformed=\"true\">"; + xmlstream << " <draw:text-box>"; + xmlstream << " <text:p text:style-name=\"P1\">Foobar</text:p>"; + xmlstream << " </draw:text-box>"; + xmlstream << " </draw:frame>"; + xmlstream << " <draw:frame presentation:style-name=\"pr2\" "; + xmlstream << " draw:text-style-name=\"P3\" draw:layer=\"layout\""; + xmlstream << " svg:width=\"23.912cm\" svg:height=\"13.231cm\""; + xmlstream << " svg:x=\"2.058cm\" svg:y=\"5.838cm\" "; + xmlstream << " presentation:class=\"subtitle\">"; + xmlstream << " <draw:text-box>"; + xmlstream << " <text:p text:style-name=\"P3\">Foo</text:p>"; + xmlstream << " </draw:text-box>"; + xmlstream << " </draw:frame>"; + xmlstream << " <presentation:notes draw:style-name=\"dp2\">"; + xmlstream << " <draw:page-thumbnail draw:style-name=\"gr1\" draw:layer=\"layout\" svg:width=\"13.706cm\" svg:height=\"10.28cm\" svg:x=\"3.647cm\" svg:y=\"2.853cm\" draw:page-number=\"1\" presentation:class=\"page\"/>"; + xmlstream << " <draw:frame presentation:style-name=\"pr3\" draw:text-style-name=\"P1\" draw:layer=\"layout\" svg:width=\"14.518cm\" svg:height=\"11.411cm\" svg:x=\"3.249cm\" svg:y=\"14.13cm\" presentation:class=\"notes\" presentation:placeholder=\"true\">"; + xmlstream << " <draw:text-box/>"; + xmlstream << " </draw:frame>"; + xmlstream << " </presentation:notes>"; + xmlstream << " </draw:page>"; + xmlstream << " <presentation:settings presentation:stay-on-top=\"true\"/>"; + xmlstream << " </office:presentation>"; + xmlstream << " </office:body>"; + xmlstream << "</office:document-content>"; + + KoXmlDocument doc; + CHECK( doc.setContent( &xmldevice, true, &errorMsg, &errorLine, &errorColumn ), true ); + CHECK( errorMsg.isEmpty(), true ); + CHECK( errorLine, 0 ); + CHECK( errorColumn, 0 ); + + char* officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; + char* drawNS = "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"; + char* textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; + char* presentationNS = "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0"; + char* svgNS = "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"; + + // <office:document-content> + KoXmlElement contentElement; + contentElement = doc.documentElement(); + CHECK( contentElement.isNull(), false ); + + CHECK( contentElement.isElement(), true ); + CHECK( contentElement.parentNode().isNull(), false ); + CHECK( contentElement.parentNode().toDocument()==doc, true ); + CHECK( contentElement.firstChild().isNull(), false ); + CHECK( contentElement.lastChild().isNull(), false ); + CHECK( contentElement.previousSibling().isNull(), false ); + + CHECK( contentElement.nextSibling().isNull(), true ); + CHECK( contentElement.localName(), QString("document-content") ); + CHECK( contentElement.hasAttributeNS(officeNS,"version"), true ); + CHECK( contentElement.attributeNS(officeNS,"version",""), QString("1.0") ); + + // <office:scripts> + KoXmlElement scriptsElement; + scriptsElement = KoXml::namedItemNS( contentElement, officeNS, "scripts" ); + CHECK( scriptsElement.isNull(), false ); + CHECK( scriptsElement.isElement(), true ); + CHECK( scriptsElement.parentNode().isNull(), false ); + CHECK( scriptsElement.parentNode()==contentElement, true ); + CHECK( scriptsElement.firstChild().isNull(), true ); + CHECK( scriptsElement.lastChild().isNull(), true ); + CHECK( scriptsElement.previousSibling().isNull(), true ); + CHECK( scriptsElement.nextSibling().isNull(), false ); + CHECK( scriptsElement.localName(), QString("scripts") ); + + // <office:automatic-styles> + KoXmlElement stylesElement; + stylesElement = KoXml::namedItemNS( contentElement, officeNS, "automatic-styles" ); + CHECK( stylesElement.isNull(), false ); + CHECK( stylesElement.isElement(), true ); + CHECK( stylesElement.parentNode().isNull(), false ); + CHECK( stylesElement.parentNode()==contentElement, true ); + CHECK( stylesElement.firstChild().isNull(), true ); + CHECK( stylesElement.lastChild().isNull(), true ); + CHECK( stylesElement.previousSibling().isNull(), false ); + CHECK( stylesElement.nextSibling().isNull(), false ); + CHECK( stylesElement.localName(), QString("automatic-styles") ); + + // also same <office:automatic-styles>, but without namedItemNS + KoXmlNode styles2Element; + styles2Element = scriptsElement.nextSibling().toElement(); + CHECK( styles2Element.isNull(), false ); + CHECK( styles2Element.isElement(), true ); + CHECK( styles2Element.parentNode().isNull(), false ); + CHECK( styles2Element.parentNode()==contentElement, true ); + CHECK( styles2Element.firstChild().isNull(), true ); + CHECK( styles2Element.lastChild().isNull(), true ); + CHECK( styles2Element.previousSibling().isNull(), false ); + CHECK( styles2Element.nextSibling().isNull(), false ); + CHECK( styles2Element.localName(), QString("automatic-styles") ); + + // <office:body> + KoXmlElement bodyElement; + bodyElement = KoXml::namedItemNS( contentElement, officeNS, "body" ); + CHECK( bodyElement.isNull(), false ); + CHECK( bodyElement.isElement(), true ); + CHECK( bodyElement.parentNode().isNull(), false ); + CHECK( bodyElement.parentNode()==contentElement, true ); + CHECK( bodyElement.firstChild().isNull(), false ); + CHECK( bodyElement.lastChild().isNull(), false ); + CHECK( bodyElement.previousSibling().isNull(), false ); + CHECK( bodyElement.nextSibling().isNull(), true ); + CHECK( bodyElement.localName(), QString("body") ); + + // also same <office:body>, but without namedItemNS + KoXmlElement body2Element; + body2Element = stylesElement.nextSibling().toElement(); + CHECK( body2Element.isNull(), false ); + CHECK( body2Element.isElement(), true ); + CHECK( body2Element.parentNode().isNull(), false ); + CHECK( body2Element.parentNode()==contentElement, true ); + CHECK( body2Element.firstChild().isNull(), false ); + CHECK( body2Element.lastChild().isNull(), false ); + CHECK( body2Element.previousSibling().isNull(), false ); + CHECK( body2Element.nextSibling().isNull(), true ); + CHECK( body2Element.localName(), QString("body") ); + + // <office:presentation> + KoXmlElement presentationElement; + presentationElement = KoXml::namedItemNS( bodyElement, officeNS, "presentation" ); + CHECK( presentationElement.isNull(), false ); + CHECK( presentationElement.isElement(), true ); + CHECK( presentationElement.parentNode().isNull(), false ); + CHECK( presentationElement.parentNode()==bodyElement, true ); + CHECK( presentationElement.firstChild().isNull(), false ); + CHECK( presentationElement.lastChild().isNull(), false ); + CHECK( presentationElement.previousSibling().isNull(), true ); + CHECK( presentationElement.nextSibling().isNull(), true ); + CHECK( presentationElement.localName(), QString("presentation") ); + + // the same <office:presentation>, but without namedItemNS + KoXmlElement presentation2Element; + presentation2Element = bodyElement.firstChild().toElement(); + CHECK( presentation2Element.isNull(), false ); + CHECK( presentation2Element.isElement(), true ); + CHECK( presentation2Element.parentNode().isNull(), false ); + CHECK( presentation2Element.parentNode()==bodyElement, true ); + CHECK( presentation2Element.firstChild().isNull(), false ); + CHECK( presentation2Element.lastChild().isNull(), false ); + CHECK( presentation2Element.previousSibling().isNull(), true ); + CHECK( presentation2Element.nextSibling().isNull(), true ); + CHECK( presentation2Element.localName(), QString("presentation") ); + + // <draw:page> for "Title" + KoXmlElement titlePageElement; + titlePageElement = presentationElement.firstChild().toElement(); + CHECK( titlePageElement.isNull(), false ); + CHECK( titlePageElement.isElement(), true ); + CHECK( titlePageElement.parentNode().isNull(), false ); + CHECK( titlePageElement.parentNode()==presentationElement, true ); + CHECK( titlePageElement.firstChild().isNull(), false ); + CHECK( titlePageElement.lastChild().isNull(), false ); + CHECK( titlePageElement.previousSibling().isNull(), true ); + CHECK( titlePageElement.nextSibling().isNull(), false ); + CHECK( titlePageElement.localName(), QString("page") ); + CHECK( titlePageElement.attributeNS(drawNS,"name",""), QString("Title") ); + CHECK( titlePageElement.attributeNS(drawNS,"style-name",""), QString("dp1") ); + CHECK( titlePageElement.attributeNS(drawNS,"master-page-name",""), QString("lyt-cool") ); + CHECK( titlePageElement.attributeNS(presentationNS, + "presentation-page-layout-name",""), QString("AL1T0") ); + + // <draw:frame> for the title frame + KoXmlElement titleFrameElement; + titleFrameElement = titlePageElement.firstChild().toElement(); + CHECK( titleFrameElement.isNull(), false ); + CHECK( titleFrameElement.isElement(), true ); + CHECK( titleFrameElement.parentNode().isNull(), false ); + CHECK( titleFrameElement.parentNode()==titlePageElement, true ); + CHECK( titleFrameElement.firstChild().isNull(), false ); + CHECK( titleFrameElement.lastChild().isNull(), false ); + CHECK( titleFrameElement.previousSibling().isNull(), true ); + CHECK( titleFrameElement.nextSibling().isNull(), false ); + CHECK( titleFrameElement.localName(), QString("frame") ); + CHECK( titleFrameElement.attributeNS(presentationNS,"style-name",""), QString("pr1") ); + CHECK( titleFrameElement.attributeNS(presentationNS,"class",""), QString("title") ); + CHECK( titleFrameElement.attributeNS(presentationNS,"user-transformed",""), QString("true") ); + CHECK( titleFrameElement.attributeNS(drawNS,"text-style-name",""), QString("P2") ); + CHECK( titleFrameElement.attributeNS(drawNS,"layer",""), QString("layout") ); + CHECK( titleFrameElement.attributeNS(svgNS,"width",""), QString("23.912cm") ); + CHECK( titleFrameElement.attributeNS(svgNS,"height",""), QString("3.508cm") ); + CHECK( titleFrameElement.attributeNS(svgNS,"x",""), QString("2.058cm") ); + CHECK( titleFrameElement.attributeNS(svgNS,"y",""), QString("1.543cm") ); + + // <draw:text-box> of the title frame + KoXmlElement titleBoxElement; + titleBoxElement = titleFrameElement.firstChild().toElement(); + CHECK( titleBoxElement.isNull(), false ); + CHECK( titleBoxElement.isElement(), true ); + CHECK( titleBoxElement.parentNode().isNull(), false ); + CHECK( titleBoxElement.parentNode()==titleFrameElement, true ); + CHECK( titleBoxElement.firstChild().isNull(), false ); + CHECK( titleBoxElement.lastChild().isNull(), false ); + CHECK( titleBoxElement.previousSibling().isNull(), true ); + CHECK( titleBoxElement.nextSibling().isNull(), true ); + CHECK( titleBoxElement.localName(), QString("text-box") ); + + // <text:p> for the title text-box + KoXmlElement titleParElement; + titleParElement = titleBoxElement.firstChild().toElement(); + CHECK( titleParElement.isNull(), false ); + CHECK( titleParElement.isElement(), true ); + CHECK( titleParElement.parentNode().isNull(), false ); + CHECK( titleParElement.parentNode()==titleBoxElement, true ); + CHECK( titleParElement.firstChild().isNull(), false ); + CHECK( titleParElement.lastChild().isNull(), false ); + CHECK( titleParElement.previousSibling().isNull(), true ); + CHECK( titleParElement.nextSibling().isNull(), true ); + CHECK( titleParElement.localName(), QString("p") ); + CHECK( titleParElement.attributeNS(textNS,"style-name",""), QString("P1") ); + CHECK( titleParElement.text(), QString("Foobar") ); + + // <draw:frame> for the subtitle frame + KoXmlElement subtitleFrameElement; + subtitleFrameElement = titleFrameElement.nextSibling().toElement(); + CHECK( subtitleFrameElement.isNull(), false ); + CHECK( subtitleFrameElement.isElement(), true ); + CHECK( subtitleFrameElement.parentNode().isNull(), false ); + CHECK( subtitleFrameElement.parentNode()==titlePageElement, true ); + CHECK( subtitleFrameElement.firstChild().isNull(), false ); + CHECK( subtitleFrameElement.lastChild().isNull(), false ); + CHECK( subtitleFrameElement.previousSibling().isNull(), false ); + CHECK( subtitleFrameElement.nextSibling().isNull(), false ); + CHECK( subtitleFrameElement.localName(), QString("frame") ); + CHECK( subtitleFrameElement.attributeNS(presentationNS,"style-name",""), QString("pr2") ); + CHECK( subtitleFrameElement.attributeNS(presentationNS,"class",""), QString("subtitle") ); + CHECK( subtitleFrameElement.hasAttributeNS(presentationNS,"user-transformed"), false ); + CHECK( subtitleFrameElement.attributeNS(drawNS,"text-style-name",""), QString("P3") ); + CHECK( subtitleFrameElement.attributeNS(drawNS,"layer",""), QString("layout") ); + CHECK( subtitleFrameElement.attributeNS(svgNS,"width",""), QString("23.912cm") ); + CHECK( subtitleFrameElement.attributeNS(svgNS,"height",""), QString("13.231cm") ); + CHECK( subtitleFrameElement.attributeNS(svgNS,"x",""), QString("2.058cm") ); + CHECK( subtitleFrameElement.attributeNS(svgNS,"y",""), QString("5.838cm") ); + + // <draw:text-box> of the subtitle frame + KoXmlElement subtitleBoxElement; + subtitleBoxElement = subtitleFrameElement.firstChild().toElement(); + CHECK( subtitleBoxElement.isNull(), false ); + CHECK( subtitleBoxElement.isElement(), true ); + CHECK( subtitleBoxElement.parentNode().isNull(), false ); + CHECK( subtitleBoxElement.parentNode()==subtitleFrameElement, true ); + CHECK( subtitleBoxElement.firstChild().isNull(), false ); + CHECK( subtitleBoxElement.lastChild().isNull(), false ); + CHECK( subtitleBoxElement.previousSibling().isNull(), true ); + CHECK( subtitleBoxElement.nextSibling().isNull(), true ); + CHECK( subtitleBoxElement.localName(), QString("text-box") ); + + // <text:p> for the subtitle text-box + KoXmlElement subtitleParElement; + subtitleParElement = subtitleBoxElement.firstChild().toElement(); + CHECK( subtitleParElement.isNull(), false ); + CHECK( subtitleParElement.isElement(), true ); + CHECK( subtitleParElement.parentNode().isNull(), false ); + CHECK( subtitleParElement.parentNode()==subtitleBoxElement, true ); + CHECK( subtitleParElement.firstChild().isNull(), false ); + CHECK( subtitleParElement.lastChild().isNull(), false ); + CHECK( subtitleParElement.previousSibling().isNull(), true ); + CHECK( subtitleParElement.nextSibling().isNull(), true ); + CHECK( subtitleParElement.localName(), QString("p") ); + CHECK( subtitleParElement.attributeNS(textNS,"style-name",""), QString("P3") ); + CHECK( subtitleParElement.text(), QString("Foo") ); +} + +void testSimpleOpenDocumentFormula() +{ + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + + QByteArray xmlbuf; + QBuffer xmldevice( xmlbuf ); + QTextStream xmlstream( xmlbuf, IO_WriteOnly ); + + // content.xml from a simple OpenDocument formula + // this is essentially MathML + xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; + xmlstream << "<!DOCTYPE math:math PUBLIC \"-//OpenOffice.org//DTD Modified W3C MathML 1.01//EN\" \"math.dtd\">"; + xmlstream << "<math:math xmlns:math=\"http://www.w3.org/1998/Math/MathML\">"; + xmlstream << " <math:semantics>"; + xmlstream << " <math:mrow>"; + xmlstream << " <math:mi>E</math:mi>"; + xmlstream << " <math:mo math:stretchy=\"false\">=</math:mo>"; + xmlstream << " <math:msup>"; + xmlstream << " <math:mi math:fontstyle=\"italic\">mc</math:mi>"; + xmlstream << " <math:mn>2</math:mn>"; + xmlstream << " </math:msup>"; + xmlstream << " </math:mrow>"; + xmlstream << " <math:annotation math:encoding=\"StarMath 5.0\">E = mc^2 </math:annotation>"; + xmlstream << " </math:semantics>"; + xmlstream << "</math:math>"; + + KoXmlDocument doc; + CHECK( doc.setContent( &xmldevice, true, &errorMsg, &errorLine, &errorColumn ), true ); + CHECK( errorMsg.isEmpty(), true ); + CHECK( errorLine, 0 ); + CHECK( errorColumn, 0 ); + + char* mathNS = "http://www.w3.org/1998/Math/MathML"; + + // <math:math> + KoXmlElement mathElement; + mathElement = doc.documentElement(); + CHECK( mathElement.isNull(), false ); + CHECK( mathElement.isElement(), true ); + CHECK( mathElement.parentNode().isNull(), false ); + CHECK( mathElement.parentNode().toDocument()==doc, true ); + CHECK( mathElement.firstChild().isNull(), false ); + CHECK( mathElement.lastChild().isNull(), false ); + CHECK( mathElement.previousSibling().isNull(), false ); + CHECK( mathElement.nextSibling().isNull(), true ); + CHECK( mathElement.localName(), QString("math") ); + + // <math:semantics> + KoXmlElement semanticsElement; + semanticsElement = KoXml::namedItemNS( mathElement, mathNS, "semantics" ); + CHECK( semanticsElement.isNull(), false ); + CHECK( semanticsElement.isElement(), true ); + CHECK( semanticsElement.parentNode().isNull(), false ); + CHECK( semanticsElement.parentNode().toElement()==mathElement, true ); + CHECK( semanticsElement.firstChild().isNull(), false ); + CHECK( semanticsElement.lastChild().isNull(), false ); + CHECK( semanticsElement.previousSibling().isNull(), true ); + CHECK( semanticsElement.nextSibling().isNull(), true ); + CHECK( semanticsElement.localName(), QString("semantics") ); + + // the same <math:semantics> but without namedItemNS + KoXmlElement semantics2Element; + semantics2Element = mathElement.firstChild().toElement(); + CHECK( semantics2Element.isNull(), false ); + CHECK( semantics2Element.isElement(), true ); + CHECK( semantics2Element.parentNode().isNull(), false ); + CHECK( semantics2Element.parentNode().toElement()==mathElement, true ); + CHECK( semantics2Element.firstChild().isNull(), false ); + CHECK( semantics2Element.lastChild().isNull(), false ); + CHECK( semantics2Element.previousSibling().isNull(), true ); + CHECK( semantics2Element.nextSibling().isNull(), true ); + CHECK( semantics2Element.localName(), QString("semantics") ); + + // <math:mrow> + KoXmlElement mrowElement; + mrowElement = semanticsElement.firstChild().toElement(); + CHECK( mrowElement.isNull(), false ); + CHECK( mrowElement.isElement(), true ); + CHECK( mrowElement.parentNode().isNull(), false ); + CHECK( mrowElement.parentNode().toElement()==semanticsElement, true ); + CHECK( mrowElement.firstChild().isNull(), false ); + CHECK( mrowElement.lastChild().isNull(), false ); + CHECK( mrowElement.previousSibling().isNull(), true ); + CHECK( mrowElement.nextSibling().isNull(), false ); + CHECK( mrowElement.localName(), QString("mrow") ); + + // <math:mi> for "E" + KoXmlElement miElement; + miElement = mrowElement.firstChild().toElement(); + CHECK( miElement.isNull(), false ); + CHECK( miElement.isElement(), true ); + CHECK( miElement.parentNode().isNull(), false ); + CHECK( miElement.parentNode().toElement()==mrowElement, true ); + CHECK( miElement.firstChild().isNull(), false ); + CHECK( miElement.lastChild().isNull(), false ); + CHECK( miElement.previousSibling().isNull(), true ); + CHECK( miElement.nextSibling().isNull(), false ); + CHECK( miElement.localName(), QString("mi") ); + + // <math:mo> for "=" + KoXmlElement moElement; + moElement = miElement.nextSibling().toElement(); + CHECK( moElement.isNull(), false ); + CHECK( moElement.isElement(), true ); + CHECK( moElement.parentNode().isNull(), false ); + CHECK( moElement.parentNode().toElement()==mrowElement, true ); + CHECK( moElement.firstChild().isNull(), false ); + CHECK( moElement.lastChild().isNull(), false ); + CHECK( moElement.previousSibling().isNull(), false ); + CHECK( moElement.nextSibling().isNull(), false ); + CHECK( moElement.localName(), QString("mo") ); + CHECK( moElement.attributeNS(mathNS,"stretchy",""), QString("false") ); + + // <math:msup> for "mc" and superscripted "2" + KoXmlElement msupElement; + msupElement = moElement.nextSibling().toElement(); + CHECK( msupElement.isNull(), false ); + CHECK( msupElement.isElement(), true ); + CHECK( msupElement.parentNode().isNull(), false ); + CHECK( msupElement.parentNode().toElement()==mrowElement, true ); + CHECK( msupElement.firstChild().isNull(), false ); + CHECK( msupElement.lastChild().isNull(), false ); + CHECK( msupElement.previousSibling().isNull(), false ); + CHECK( msupElement.nextSibling().isNull(), true ); + CHECK( msupElement.localName(), QString("msup") ); + + // <math:mi> inside the <math:msup> for "mc" + KoXmlElement mcElement; + mcElement = msupElement.firstChild().toElement(); + CHECK( mcElement.isNull(), false ); + CHECK( mcElement.isElement(), true ); + CHECK( mcElement.parentNode().isNull(), false ); + CHECK( mcElement.parentNode().toElement()==msupElement, true ); + CHECK( mcElement.firstChild().isNull(), false ); + CHECK( mcElement.lastChild().isNull(), false ); + CHECK( mcElement.previousSibling().isNull(), true ); + CHECK( mcElement.nextSibling().isNull(), false ); + CHECK( mcElement.localName(), QString("mi") ); + CHECK( mcElement.text(), QString("mc") ); + CHECK( mcElement.attributeNS(mathNS,"fontstyle",""), QString("italic") ); + + // <math:mn> inside the <math:msup> for "2" (superscript) + KoXmlElement mnElement; + mnElement = mcElement.nextSibling().toElement(); + CHECK( mnElement.isNull(), false ); + CHECK( mnElement.isElement(), true ); + CHECK( mnElement.parentNode().isNull(), false ); + CHECK( mnElement.parentNode().toElement()==msupElement, true ); + CHECK( mnElement.firstChild().isNull(), false ); + CHECK( mnElement.lastChild().isNull(), false ); + CHECK( mnElement.previousSibling().isNull(), false ); + CHECK( mnElement.nextSibling().isNull(), true ); + CHECK( mnElement.localName(), QString("mn") ); + CHECK( mnElement.text(), QString("2") ); + + // <math:annotation> + KoXmlElement annotationElement; + annotationElement = semanticsElement.lastChild().toElement(); + CHECK( annotationElement.isNull(), false ); + CHECK( annotationElement.isElement(), true ); + CHECK( annotationElement.parentNode().isNull(), false ); + CHECK( annotationElement.parentNode().toElement()==semanticsElement, true ); + CHECK( annotationElement.firstChild().isNull(), false ); + CHECK( annotationElement.lastChild().isNull(), false ); + CHECK( annotationElement.previousSibling().isNull(), false ); + CHECK( annotationElement.nextSibling().isNull(), true ); + CHECK( annotationElement.localName(), QString("annotation") ); + CHECK( annotationElement.text(), QString("E = mc^2 ") ); + CHECK( annotationElement.attributeNS(mathNS,"encoding",""), QString("StarMath 5.0") ); +} + +void testLargeOpenDocumentSpreadsheet() +{ + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + + int sheetCount = 4; + int rowCount = 200; + int colCount = 200*2; + + QByteArray xmlbuf; + QBuffer xmldevice( xmlbuf ); + QTextStream xmlstream( xmlbuf, IO_WriteOnly ); + + // content.xml + xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; + xmlstream << "<office:document-content "; + xmlstream << "xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" "; + xmlstream << "xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" "; + xmlstream << "xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\" >"; + xmlstream << "<office:body>"; + xmlstream << "<office:spreadsheet>"; + for( int i = 0; i < sheetCount; i++ ) + { + QString sheetName = QString("Sheet%1").arg(i+1); + xmlstream << "<table:table table:name=\"" << sheetName; + xmlstream << "\" table:print=\"false\">"; + for( int j = 0; j < rowCount; j++ ) + { + xmlstream << "<table:table-row>"; + for( int k = 0; k < colCount; k++ ) + { + xmlstream << "<table:table-cell office:value-type=\"string\">"; + xmlstream << "<text:p>Hello, world</text:p>"; + xmlstream << "</table:table-cell>"; + } + xmlstream << "</table:table-row>"; + } + xmlstream << "</table:table>"; + } + xmlstream << "</office:spreadsheet>"; + xmlstream << "</office:body>"; + xmlstream << "</office:document-content>"; + + printf("Raw XML size: %d KB\n", xmlbuf.size()/1024 ); + + QTime timer; + timer.start(); + +#if 1 + // just to test parsing speed with plain dumb handler + QXmlSimpleReader* reader = new QXmlSimpleReader; + reader->setFeature( "http://xml.org/sax/features/namespaces", true ); + QXmlDefaultHandler handler; + reader->setContentHandler( &handler ); + reader->setErrorHandler( &handler ); + reader->setLexicalHandler( &handler ); + reader->setDeclHandler( &handler ); + reader->setDTDHandler( &handler ); + QXmlInputSource xmlSource; + xmlSource.setData( QString::fromUtf8( xmlbuf.data(), xmlbuf.size() ) ); + timer.start(); + reader->parse( &xmlSource ); + printf("Large spreadsheet: QXmlDefaultHandler parsing time is %d ms\n", timer.elapsed() ); + delete reader; +#endif + + timer.start(); + KoXmlDocument doc; + + // uncomment to see the performance if on-demand/lazy loading is not used + // will definitely eat more memory +#ifndef KOXML_USE_QDOM + doc.setFastLoading( true ); +#endif + + CHECK( doc.setContent( &xmldevice, true, &errorMsg, &errorLine, &errorColumn ), true ); + CHECK( errorMsg.isEmpty(), true ); + CHECK( errorLine, 0 ); + CHECK( errorColumn, 0 ); + + printf("Large spreadsheet: parsing time is %d ms\n", timer.elapsed() ); + + // release memory taken by the XML document content + xmlbuf.resize( 0 ); + + // namespaces that will be used + QString officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; + QString tableNS = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"; + QString textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; + + // <office:document-content> + KoXmlElement contentElement; + contentElement = doc.documentElement(); + CHECK( contentElement.isNull(), false ); + CHECK( contentElement.isElement(), true ); + CHECK( contentElement.localName(), QString("document-content") ); + + // <office:body> + KoXmlElement bodyElement; + bodyElement = contentElement.firstChild().toElement(); + CHECK( bodyElement.isNull(), false ); + CHECK( bodyElement.isElement(), true ); + CHECK( bodyElement.localName(), QString("body") ); + + // <office:spreadsheet> + KoXmlElement spreadsheetElement; + spreadsheetElement = bodyElement.firstChild().toElement(); + CHECK( spreadsheetElement.isNull(), false ); + CHECK( spreadsheetElement.isElement(), true ); + CHECK( spreadsheetElement.localName(), QString("spreadsheet") ); + + // now we visit every sheet, every row, every cell + timer.start(); + KoXmlElement tableElement; + tableElement = spreadsheetElement.firstChild().toElement(); + for( int table=0; table < sheetCount; table++ ) + { + QString tableName = QString("Sheet%1").arg(table+1); + CHECK( tableElement.isNull(), false ); + CHECK( tableElement.isElement(), true ); + CHECK( tableElement.localName(), QString("table") ); + CHECK( tableElement.hasAttributeNS(tableNS,"name"), true ); + CHECK( tableElement.attributeNS(tableNS,"name",""), tableName ); + CHECK( tableElement.attributeNS(tableNS,"print",""), QString("false") ); + + // load everything for this table + KoXml::load( tableElement, 99 ); + + CHECK( tableElement.parentNode().isNull(), false ); + CHECK( tableElement.parentNode()==spreadsheetElement, true ); + CHECK( tableElement.firstChild().isNull(), false ); + CHECK( tableElement.lastChild().isNull(), false ); + + KoXmlElement rowElement; + rowElement = tableElement.firstChild().toElement(); + for( int row=0; row < rowCount; row++ ) + { + CHECK( rowElement.isNull(), false ); + CHECK( rowElement.isElement(), true ); + CHECK( rowElement.localName(), QString("table-row") ); + CHECK( rowElement.parentNode().isNull(), false ); + CHECK( rowElement.parentNode()==tableElement, true ); + CHECK( rowElement.firstChild().isNull(), false ); + CHECK( rowElement.lastChild().isNull(), false ); + + KoXmlElement cellElement; + cellElement = rowElement.firstChild().toElement(); + for( int col=0; col < colCount; col++ ) + { + CHECK( cellElement.isNull(), false ); + CHECK( cellElement.isElement(), true ); + CHECK( cellElement.localName(), QString("table-cell") ); + CHECK( cellElement.text(), QString("Hello, world") ); + CHECK( cellElement.hasAttributeNS(officeNS,"value-type"), true ); + CHECK( cellElement.attributeNS(officeNS,"value-type",""), QString("string" ) ); + CHECK( cellElement.parentNode().isNull(), false ); + CHECK( cellElement.parentNode()==rowElement, true ); + CHECK( cellElement.firstChild().isNull(), false ); + CHECK( cellElement.lastChild().isNull(), false ); + cellElement = cellElement.nextSibling().toElement(); + } + + //KoXml::unload( rowElement ); + rowElement = rowElement.nextSibling().toElement(); + } + + KoXml::unload( tableElement ); + tableElement = tableElement.nextSibling().toElement(); + } + + printf("Large spreadsheet: iterating time is %d ms\n", timer.elapsed() ); +} + +int main( int argc, char** argv ) +{ + Q_UNUSED( argc ); + Q_UNUSED( argv ); + + testNode(); + testElement(); + testAttributes(); + testText(); + testCDATA(); + testDocument(); + testNamespace(); + + testUnload(); + + testSimpleXML(); + testRootError(); + testMismatchedTag(); + + testSimpleOpenDocumentText(); + testSimpleOpenDocumentSpreadsheet(); + testSimpleOpenDocumentPresentation(); + testSimpleOpenDocumentFormula(); + +// testLargeOpenDocumentSpreadsheet(); + +#ifdef KOXML_USE_QDOM + printf("Using QDom: "); +#else + printf("Using KoXml: "); +#endif + printf("%d tests, %d failed\n", testCount, testFailed ); + + return testFailed; +} diff --git a/lib/kofficecore/tests/priorityqueue_test.cpp b/lib/kofficecore/tests/priorityqueue_test.cpp new file mode 100644 index 00000000..d2c7eafd --- /dev/null +++ b/lib/kofficecore/tests/priorityqueue_test.cpp @@ -0,0 +1,87 @@ +/* This file is part of the KDE project + Copyright (C) 2001 Werner Trobin <trobin@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 <priorityqueue.h> +#include <kdebug.h> +#include <qptrlist.h> +#include <qasciidict.h> +#include <stdlib.h> +#include <time.h> + +struct Node { + Node( unsigned int key ) : m_key( key ), m_index( 0 ) {} + + unsigned int key() const { return m_key; } + void setKey( unsigned int key ) { m_key = key; } + + int index() const { return m_index; } + void setIndex( int i ) { m_index = i; } +private: + unsigned int m_key; + int m_index; +}; + +static const char* const keys[] = { "one", "two", "three", "four", "five", + "six", "seven", "eight", "nine", "ten", + "eleven", "twelve", 0 }; + +int main( int /*argc*/, char **/*argv*/ ) +{ + QPtrList<Node> list; + list.setAutoDelete( true ); + QAsciiDict<Node> dict; + + KOffice::PriorityQueue<Node> queue; + + srand( time( 0 ) ); + for ( int i = 0; i < 12; ++i ) { + Node *n = new Node( rand() % 20 ); + list.append( n ); + queue.insert( n ); + // Check whether the AsciiDict CTOR is okay + Node *n2 = new Node( *n ); + dict.insert( keys[ i ], n2 ); + } + + kdDebug() << "##### Queue 1: " << endl; + queue.dump(); + + kdDebug() << "##### Queue 2: " << endl; + KOffice::PriorityQueue<Node> queue2( dict ); + queue2.dump(); + + Node *n = list.at( 6 ); + kdDebug() << "##### Decreasing node: " << n->key() << " at " << n->index() << endl; + n->setKey( 2 ); + queue.keyDecreased( n ); + queue.dump(); + + n = list.at( 2 ); + kdDebug() << "##### Decreasing node: " << n->key() << " at " << n->index() << endl; + n->setKey( 0 ); + queue.keyDecreased( n ); + queue.dump(); + + n = queue.extractMinimum(); + while ( n ) { + queue.dump(); + n = queue.extractMinimum(); + } + return 0; +} |