Warning, file /office/calligra/libs/flake/KoShapeRegistry.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /* This file is part of the KDE project
0002  * Copyright (c) 2006 Boudewijn Rempt (boud@valdyas.org)
0003  * Copyright (C) 2006-2007, 2010 Thomas Zander <zander@kde.org>
0004  * Copyright (C) 2006,2008-2010 Thorsten Zachmann <zachmann@kde.org>
0005  * Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
0006  * Copyright (C) 2010 Inge Wallin <inge@lysator.liu.se>
0007  *
0008  * This library is free software; you can redistribute it and/or
0009  * modify it under the terms of the GNU Library General Public
0010  * License as published by the Free Software Foundation; either
0011  * version 2 of the License, or (at your option) any later version.
0012  *
0013  * This library is distributed in the hope that it will be useful,
0014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0016  * Library General Public License for more details.
0017  *
0018  * You should have received a copy of the GNU Library General Public License
0019  * along with this library; see the file COPYING.LIB.  If not, write to
0020  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0021  * Boston, MA 02110-1301, USA.
0022  */
0023 
0024 // Own
0025 #include "KoShapeRegistry.h"
0026 
0027 #include "KoPathShapeFactory.h"
0028 #include "KoConnectionShapeFactory.h"
0029 #include "KoShapeLoadingContext.h"
0030 #include "KoShapeSavingContext.h"
0031 #include "KoShapeGroup.h"
0032 #include "KoShapeLayer.h"
0033 #include "KoUnavailShape.h"
0034 #include "SvgShapeFactory.h"
0035 
0036 #include <KoPluginLoader.h>
0037 #include <KoXmlReader.h>
0038 #include <KoXmlNS.h>
0039 #include <KoOdfLoadingContext.h>
0040 #include <KoStyleStack.h>
0041 
0042 #include <QString>
0043 #include <QHash>
0044 #include <QMultiMap>
0045 #include <QPainter>
0046 #include <QGlobalStatic>
0047 
0048 #include <FlakeDebug.h>
0049 
0050 Q_GLOBAL_STATIC(KoShapeRegistry, s_instance)
0051 
0052 class Q_DECL_HIDDEN KoShapeRegistry::Private
0053 {
0054 public:
0055     void insertFactory(KoShapeFactoryBase *factory);
0056     void init(KoShapeRegistry *q);
0057 
0058     KoShape *createShapeInternal(const KoXmlElement &fullElement, KoShapeLoadingContext &context, const KoXmlElement &element) const;
0059 
0060     // Map namespace,tagname to priority:factory
0061     QHash<QPair<QString, QString>, QMultiMap<int, KoShapeFactoryBase*> > factoryMap;
0062 };
0063 
0064 KoShapeRegistry::KoShapeRegistry()
0065         : d(new Private())
0066 {
0067 }
0068 
0069 KoShapeRegistry::~KoShapeRegistry()
0070 {
0071     qDeleteAll(doubleEntries());
0072     qDeleteAll(values());
0073     delete d;
0074 }
0075 
0076 void KoShapeRegistry::Private::init(KoShapeRegistry *q)
0077 {
0078     KoPluginLoader::PluginsConfig config;
0079     config.whiteList = "FlakePlugins";
0080     config.blacklist = "FlakePluginsDisabled";
0081     config.group = "calligra";
0082     KoPluginLoader::load(QStringLiteral("calligra/flakes"), config);
0083     config.whiteList = "ShapePlugins";
0084     config.blacklist = "ShapePluginsDisabled";
0085     KoPluginLoader::load(QStringLiteral("calligra/shapes"), config);
0086 
0087     // Also add our hard-coded basic shapes
0088     q->add(new KoPathShapeFactory(QStringList()));
0089     q->add(new KoConnectionShapeFactory());
0090     // As long as there is no shape dealing with embedded svg images
0091     // we add the svg shape factory here by default
0092     q->add(new SvgShapeFactory);
0093 
0094     // Now all shape factories are registered with us, determine their
0095     // associated odf tagname & priority and prepare ourselves for
0096     // loading ODF.
0097 
0098     QList<KoShapeFactoryBase*> factories = q->values();
0099     for (int i = 0; i < factories.size(); ++i) {
0100         insertFactory(factories[i]);
0101     }
0102 }
0103 
0104 KoShapeRegistry* KoShapeRegistry::instance()
0105 {
0106     if (!s_instance.exists()) {
0107         s_instance->d->init(s_instance);
0108     }
0109     return s_instance;
0110 }
0111 
0112 void KoShapeRegistry::addFactory(KoShapeFactoryBase * factory)
0113 {
0114     add(factory);
0115     d->insertFactory(factory);
0116 }
0117 
0118 void KoShapeRegistry::Private::insertFactory(KoShapeFactoryBase *factory)
0119 {
0120     const QList<QPair<QString, QStringList> > odfElements(factory->odfElements());
0121 
0122     if (odfElements.isEmpty()) {
0123         debugFlake << "Shape factory" << factory->id() << " does not have OdfNamespace defined, ignoring";
0124     }
0125     else {
0126         int priority = factory->loadingPriority();
0127         for (QList<QPair<QString, QStringList> >::const_iterator it(odfElements.begin()); it != odfElements.end(); ++it) {
0128             foreach (const QString &elementName, (*it).second) {
0129                 QPair<QString, QString> p((*it).first, elementName);
0130 
0131                 QMultiMap<int, KoShapeFactoryBase*> & priorityMap = factoryMap[p];
0132 
0133                 priorityMap.insert(priority, factory);
0134 
0135                 debugFlake << "Inserting factory" << factory->id() << " for"
0136                     << p << " with priority "
0137                     << priority << " into factoryMap making "
0138                     << priorityMap.size() << " entries. ";
0139             }
0140         }
0141     }
0142 }
0143 
0144 KoShape * KoShapeRegistry::createShapeFromOdf(const KoXmlElement & e, KoShapeLoadingContext & context) const
0145 {
0146     debugFlake << "Going to check for" << e.namespaceURI() << ":" << e.tagName();
0147 
0148     KoShape * shape = 0;
0149 
0150     // Handle the case where the element is a draw:frame differently from other cases.
0151     if (e.tagName() == "frame" && e.namespaceURI() == KoXmlNS::draw) {
0152         // If the element is in a frame, the frame is already added by the
0153         // application and we only want to create a shape from the
0154         // embedded element. The very first shape we create is accepted.
0155         //
0156         // FIXME: we might want to have some code to determine which is
0157         //        the "best" of the creatable shapes.
0158 
0159         // The logic is thus:
0160         // First attempt to check whether we can in fact load the first child,
0161         // and only use a Shape if the first child is accepted. If this is not the case, then
0162         // use the UnavailShape which ensures data integrity and that the fallback views are not
0163         // edited and subsequently saved back (as they would then no longer be a true
0164         // representation of the data they are supposed to be views of).
0165         // The reason is that all subsequent children will be fallbacks, in order of preference.
0166 
0167         if (e.hasChildNodes()) {
0168             // if we don't ignore white spaces it can be that the first child is not a element so look for the first element
0169             KoXmlNode node = e.firstChild();
0170             KoXmlElement element;
0171             while (!node.isNull() && element.isNull()) {
0172                 element = node.toElement();
0173                 node = node.nextSibling();
0174             }
0175 
0176             if (!element.isNull()) {
0177                 // Check for draw:object
0178                 if (element.tagName() == "object" && element.namespaceURI() == KoXmlNS::draw && element.hasChildNodes()) {
0179                     // Loop through the elements and find the first one
0180                     // that is handled by any shape.
0181                     KoXmlNode n = element.firstChild();
0182                     for (; !n.isNull(); n = n.nextSibling()) {
0183                         if (n.isElement()) {
0184                             debugFlake << "trying for element " << n.toElement().tagName();
0185                             shape = d->createShapeInternal(e, context, n.toElement());
0186                             if (shape)
0187                                 break;
0188                         }
0189                     }
0190                     if (shape)
0191                         debugFlake << "Found a shape for draw:object";
0192                     else
0193                         debugFlake << "Found NO shape shape for draw:object";
0194                 }
0195 
0196                 if (!shape) {
0197                     // If not draw:object, e.g draw:image or draw:plugin
0198                     shape = d->createShapeInternal(e, context, element);
0199                 }
0200             }
0201 
0202             if (shape) {
0203                 debugFlake << "A shape supporting the requested type was found.";
0204             }
0205             else {
0206                 // If none of the registered shapes could handle the frame
0207                 // contents, create an UnavailShape.  This should never fail.
0208                 debugFlake << "No shape found; Creating an unavail shape";
0209 
0210                 KoUnavailShape *uShape = new KoUnavailShape();
0211                 uShape->setShapeId(KoUnavailShape_SHAPEID);
0212                 //FIXME: Add creating/setting the collection here(?)
0213                 uShape->loadOdf(e, context);
0214 
0215                 // Check whether we can load a shape to fit the current object.
0216                 KoXmlElement child;
0217                 KoShape *childShape = 0;
0218                 bool first = true;
0219                 forEachElement(child, e) {
0220                     // no need to try to load the first element again as it was already tried before and we could not load it
0221                     if (first) {
0222                         first = false;
0223                         continue;
0224                     }
0225                     debugFlake << "--------------------------------------------------------";
0226                     debugFlake << "Attempting to check if we can fall back ability to the item"
0227                                   << child.nodeName();
0228                     childShape = d->createShapeInternal(e, context, child);
0229                     if (childShape) {
0230                         debugFlake << "Shape was found! Adding as child of unavail shape and stopping search";
0231                         uShape->addShape(childShape);
0232                         childShape->setPosition(QPointF(qreal(0.0), qreal(0.0)));
0233 
0234                         // The embedded shape is just there to show the preview image.
0235                         // We don't want the user to be able to manipulate the picture
0236                         // in any way, so we disable the tools of the shape. This can
0237                         // be done in a hacky way (courtesy of Jaham) by setting its
0238                         // shapeID to "".
0239                         childShape->setShapeId("");
0240                         break;
0241                     }
0242                 }
0243                 if (!childShape)
0244                     debugFlake << "Failed to find fallback for the unavail shape named "
0245                                   << e.tagName();
0246                 shape = uShape;
0247             }
0248         }
0249     }
0250 
0251     // Hardwire the group shape into the loading as it should not appear
0252     // in the shape selector
0253     else if (e.localName() == "g" && e.namespaceURI() == KoXmlNS::draw) {
0254         KoShapeGroup * group = new KoShapeGroup();
0255 
0256         context.odfLoadingContext().styleStack().save();
0257         bool loaded = group->loadOdf(e, context);
0258         context.odfLoadingContext().styleStack().restore();
0259 
0260         if (loaded) {
0261             shape = group;
0262         }
0263         else {
0264             delete group;
0265         }
0266     } else {
0267         shape = d->createShapeInternal(e, context, e);
0268     }
0269 
0270     if (shape) {
0271         context.shapeLoaded(shape);
0272     }
0273 
0274     return shape;
0275 }
0276 
0277 KoShape *KoShapeRegistry::Private::createShapeInternal(const KoXmlElement &fullElement,
0278                                                        KoShapeLoadingContext &context,
0279                                                        const KoXmlElement &element) const
0280 {
0281     // Pair of namespace, tagname
0282     QPair<QString, QString> p = QPair<QString, QString>(element.namespaceURI(), element.tagName());
0283 
0284     // Remove duplicate lookup.
0285     if (!factoryMap.contains(p))
0286         return 0;
0287 
0288     QMultiMap<int, KoShapeFactoryBase*> priorityMap = factoryMap.value(p);
0289     QList<KoShapeFactoryBase*> factories = priorityMap.values();
0290 
0291 #ifndef NDEBUG
0292     debugFlake << "Supported factories for=" << p;
0293     foreach (KoShapeFactoryBase *f, factories)
0294         debugFlake << f->id() << f->name();
0295 #endif
0296 
0297     // Loop through all shape factories. If any of them supports this
0298     // element, then we let the factory create a shape from it. This
0299     // may fail because the element itself is too generic to draw any
0300     // real conclusions from it - we actually have to try to load it.
0301     // An example of this is the draw:image element which have
0302     // potentially hundreds of different image formats to support,
0303     // including vector formats.
0304     //
0305     // If it succeeds, then we use this shape, if it fails, then just
0306     // try the next.
0307     //
0308     // Higher numbers are more specific, map is sorted by keys.
0309     for (int i = factories.size() - 1; i >= 0; --i) {
0310         KoShapeFactoryBase * factory = factories[i];
0311         if (factory->supports(element, context)) {
0312             KoShape *shape = factory->createShapeFromOdf(fullElement, context);
0313             if (shape) {
0314                 debugFlake << "Shape found for factory " << factory->id() << factory->name();
0315                 // we return the top-level most shape as thats the one that we'll have to
0316                 // add to the KoShapeManager for painting later (and also to avoid memory leaks)
0317                 // but don't go past a KoShapeLayer as KoShape adds those from the context
0318                 // during loading and those are already added.
0319                 while (shape->parent() && dynamic_cast<KoShapeLayer*>(shape->parent()) == 0)
0320                     shape = shape->parent();
0321 
0322                 return shape;
0323             }
0324             // Maybe a shape with a lower priority can load our
0325             // element, but this attempt has failed.
0326         }
0327         else {
0328             debugFlake << "No support for" << p << "by" << factory->id();
0329         }
0330     }
0331 
0332     return 0;
0333 }
0334 
0335 QList<KoShapeFactoryBase*> KoShapeRegistry::factoriesForElement(const QString &nameSpace, const QString &elementName)
0336 {
0337     // Pair of namespace, tagname
0338     QPair<QString, QString> p = QPair<QString, QString>(nameSpace, elementName);
0339 
0340     QMultiMap<int, KoShapeFactoryBase*> priorityMap = d->factoryMap.value(p);
0341     QList<KoShapeFactoryBase*> shapeFactories;
0342     // sort list by priority
0343     foreach(KoShapeFactoryBase *f, priorityMap) {
0344         shapeFactories.prepend(f);
0345     }
0346 
0347     return shapeFactories;
0348 }