diff --git a/qelectrotech.pro b/qelectrotech.pro index 908d39753..191356000 100644 --- a/qelectrotech.pro +++ b/qelectrotech.pro @@ -71,7 +71,7 @@ TRANSLATIONS += lang/qet_en.ts lang/qet_es.ts lang/qet_fr.ts lang/qet_ru.ts lang TRANSLATIONS += lang/qt_es.ts lang/qt_fr.ts lang/qt_ru.ts lang/qt_pt.ts lang/qt_cs.ts lang/qt_pl.ts lang/qt_de.ts lang/qt_it.ts # Modules Qt utilises par l'application -QT += xml svg network +QT += xml svg network sql # Configuration de la compilation CONFIG += debug_and_release warn_on diff --git a/sources/elementdefinition.h b/sources/elementdefinition.h index 6d542439f..8fbce76ff 100644 --- a/sources/elementdefinition.h +++ b/sources/elementdefinition.h @@ -117,6 +117,7 @@ class ElementDefinition : public ElementsCollectionItem { virtual ElementDefinition *toElement(); virtual bool equals(ElementDefinition &); virtual bool removeContent(); + virtual QDateTime modificationTime() const = 0; void copy(MoveElementsDescription *); void move(MoveElementsDescription *); diff --git a/sources/elementscollection.h b/sources/elementscollection.h index 98b9ae851..08ba89bee 100644 --- a/sources/elementscollection.h +++ b/sources/elementscollection.h @@ -74,6 +74,7 @@ class ElementsCollection : public ElementsCollectionItem { virtual ElementDefinition *createElement(const QString &); virtual bool isEmpty(); virtual int count(); + virtual bool isCacheable() const = 0; // Methodes propres a la classe ElementsCollection public: diff --git a/sources/elementscollectioncache.cpp b/sources/elementscollectioncache.cpp new file mode 100644 index 000000000..93c9ab7ce --- /dev/null +++ b/sources/elementscollectioncache.cpp @@ -0,0 +1,267 @@ +#include "elementscollectioncache.h" +#include "elementscollection.h" +#include "elementscategory.h" +#include "elementdefinition.h" +#include "customelement.h" + +/** + Construct a cache for elements collections. + @param database_path Path of the SQLite database to open. + @param parent Parent QObject +*/ +ElementsCollectionCache::ElementsCollectionCache(const QString &database_path, QObject *parent) : + QObject(parent), + locale_("en"), + pixmap_storage_format_("PNG") +{ + // initialize the cache SQLite database + static int cache_instances = 0; + QString connection_name = QString("ElementsCollectionCache-%1").arg(cache_instances++); + cache_db_ = QSqlDatabase::addDatabase("QSQLITE", connection_name ); + cache_db_.setDatabaseName(database_path); + if (!cache_db_.open()) { + qDebug() << "Unable to open the SQLite database " << database_path << " as " << connection_name << ": " << cache_db_.lastError(); + } else { + cache_db_.exec("PRAGMA temp_store=MEMORY"); + cache_db_.exec("PRAGMA journal_mode = MEMORY"); + cache_db_.exec("PRAGMA synchronous=OFF"); + cache_db_.exec("PRAGMA cache_size=10000"); + /// @todo the tables could already exist, handle that case. + cache_db_.exec("CREATE TABLE names (path VARCHAR(512) NOT NULL, locale VARCHAR(2) NOT NULL, mtime DATETIME NOT NULL, name VARCHAR(128), PRIMARY KEY(path, locale));"); + cache_db_.exec("CREATE TABLE pixmaps (path VARCHAR(512) NOT NULL UNIQUE, mtime DATETIME NOT NULL, pixmap BLOB, PRIMARY KEY(path), FOREIGN KEY(path) REFERENCES names (path) ON DELETE CASCADE);"); + + // prepare queries + select_name_ = new QSqlQuery(cache_db_); + select_pixmap_ = new QSqlQuery(cache_db_); + insert_name_ = new QSqlQuery(cache_db_); + insert_pixmap_ = new QSqlQuery(cache_db_); + select_name_ -> prepare("SELECT name FROM names WHERE path = :path AND locale = :locale AND mtime > :file_mtime"); + select_pixmap_ -> prepare("SELECT pixmap FROM pixmaps WHERE path = :path AND mtime > :file_mtime"); + insert_name_ -> prepare("REPLACE INTO names (path, locale, mtime, name) VALUES (:path, :locale, :mtime, :name)"); + insert_pixmap_ -> prepare("REPLACE INTO pixmaps (path, mtime, pixmap) VALUES (:path, :mtime, :pixmap)"); + } +} + +/** + Destructor +*/ +ElementsCollectionCache::~ElementsCollectionCache() { + cache_db_.close(); +} + +/** + Define the locale to be used when dealing with names. + @param locale New locale to be used. +*/ +void ElementsCollectionCache::setLocale(const QString &locale) { + locale_ = locale; +} + +/** + @return The locale to be used when dealing with names. +*/ +QString ElementsCollectionCache::locale() const { + return(locale_); +} + +/** + Define the storage format for the pixmaps within the SQLite database. See + Qt's QPixmap documentation for more information. + @param format The new pixmap storage format. + @return True if the format change was accepted, false otherwise. +*/ +bool ElementsCollectionCache::setPixmapStorageFormat(const QString &format) { + if (QImageWriter::supportedImageFormats().contains(format.toAscii())) { + pixmap_storage_format_= format; + return(true); + } + return(false); +} + +/** + @return the pixmap storage format. Default is "PNG" + @see setPixmapStorageFormat() +*/ +QString ElementsCollectionCache::pixmapStorageFormat() const { + return(pixmap_storage_format_); +} + +/** + Indicate the cache a new collection is about to be browsed. This is mainly + used to delimit database transactions. + @param collection The elements collection about to be browsed. +*/ +void ElementsCollectionCache::beginCollection(ElementsCollection *collection) { + bool use_cache = cache_db_.isOpen() && collection -> isCacheable(); + if (use_cache) { + bool transaction_started = cache_db_.transaction(); + qDebug() << (transaction_started ? "transaction began for " : "transaction not started for ") << collection -> protocol(); + } +} + +/** + Indicate the cache the currently browsed collection end has been reached. This + is mainly used to delimit database transactions. + @param collection The elements collection being browsed. +*/ +void ElementsCollectionCache::endCollection(ElementsCollection *collection) { + bool use_cache = cache_db_.isOpen() && collection -> isCacheable(); + if (use_cache) { + bool transaction_commited = cache_db_.commit(); + qDebug() << (transaction_commited ? "transaction commited for " : "transaction not commited for") << collection -> protocol(); + } +} + +/** + Retrieve the data for a given element, using the cache if available, + filling it otherwise. Data are then available through pixmap() and name() + methods. + @param element The definition of an element. + @see pixmap() + @see name() + @return True if the retrieval succeeded, false otherwise. +*/ +bool ElementsCollectionCache::fetchElement(ElementDefinition *element) { + // can we use the cache with this element? + bool use_cache = cache_db_.isOpen() && element -> parentCollection() -> isCacheable(); + + // attempt to fetch the element name from the cache database + if (!use_cache) { + return(fetchData(element -> location())); + } else { + QString element_path = element -> location().toString(); + bool got_name = fetchNameFromCache(element_path, element -> modificationTime()); + bool got_pixmap = fetchPixmapFromCache(element_path, element -> modificationTime()); + if (got_name && got_pixmap) { + return(true); + } + if (fetchData(element -> location())) { + cacheName(element_path); + cachePixmap(element_path); + } + return(true); + } +} + +/** + @return The last name fetched through fetchElement(). +*/ +QString ElementsCollectionCache::name() const { + return(current_name_); +} + +/** + @return The last pixmap fetched through fetchElement(). +*/ +QPixmap ElementsCollectionCache::pixmap() const { + return(current_pixmap_); +} + +/** + Retrieve the data by building the full CustomElement object matching the + given location, without using the cache. Data are then available through + pixmap() and name() methods. + @param Location Location of a given Element. + @return True if the retrieval succeeded, false otherwise. +*/ +bool ElementsCollectionCache::fetchData(const ElementsLocation &location) { + int state; + CustomElement *custom_elmt = new CustomElement(location, 0, 0, &state); + if (state) { + qDebug() << "ElementsCollectionCache::fetchData() : Le chargement du composant" << qPrintable(location.toString()) << "a echoue avec le code d'erreur" << state; + } else { + current_name_ = custom_elmt -> name(); + current_pixmap_ = custom_elmt -> pixmap(); + } + delete custom_elmt; + return(!state); +} + +/** + Retrieve the name for an element, given its path and last modification + time. The value is then available through the name() method. + @param path Element path (as obtained using ElementsLocation::toString()) + @param file_mtime Date and time of last modification of this element. Any + older cached value will be ignored. + @return True if the retrieval succeeded, false otherwise. +*/ +bool ElementsCollectionCache::fetchNameFromCache(const QString &path, const QDateTime &file_mtime) { + select_name_ -> bindValue(":path", path); + select_name_ -> bindValue(":locale", locale_); + select_name_ -> bindValue(":file_mtime", file_mtime); + if (select_name_ -> exec()) { + if (select_name_ -> first()) { + current_name_ = select_name_ -> value(0).toString(); + return(true); + } + } else { + qDebug() << "select_name_->exec() failed"; + } + return(false); +} + +/** + Retrieve the pixmap for an element, given its path and last modification + time. It is then available through the pixmap() method. + @param path Element path (as obtained using ElementsLocation::toString()) + @param file_mtime Date and time of last modification of this element. Any + older cached pixmap will be ignored. + @return True if the retrieval succeeded, false otherwise. +*/ +bool ElementsCollectionCache::fetchPixmapFromCache(const QString &path, const QDateTime &file_mtime) { + select_pixmap_ -> bindValue(":path", path); + select_pixmap_ -> bindValue(":file_mtime", file_mtime); + if (select_pixmap_ -> exec()) { + if (select_pixmap_ -> first()) { + QByteArray ba = select_pixmap_ -> value(0).toByteArray(); + // avoid returning always the same pixmap (i.e. same cacheKey()) + current_pixmap_.detach(); + current_pixmap_.loadFromData(ba, qPrintable(pixmap_storage_format_)); + } + return(true); + } else { + qDebug() << "select_pixmap_->exec() failed"; + } + return(false); +} + +/** + Cache the current (i.e. last retrieved) name. The cache entry will use + the current date and time and the locale set via setLocale(). + @param path Element path (as obtained using ElementsLocation::toString()) + @return True if the caching succeeded, false otherwise. + @see name() +*/ +bool ElementsCollectionCache::cacheName(const QString &path) { + insert_name_ -> bindValue(":path", path); + insert_name_ -> bindValue(":locale", locale_); + insert_name_ -> bindValue(":mtime", QVariant(QDateTime::currentDateTime())); + insert_name_ -> bindValue(":name", current_name_); + if (!insert_name_ -> exec()) { + qDebug() << cache_db_.lastError(); + return(false); + } + return(true); +} + +/** + Cache the current (i.e. last retrieved) pixmap. The cache entry will use + the current date and time. + @param path Element path (as obtained using ElementsLocation::toString()) + @return True if the caching succeeded, false otherwise. + @see pixmap() +*/ +bool ElementsCollectionCache::cachePixmap(const QString &path) { + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + current_pixmap_.save(&buffer, qPrintable(pixmap_storage_format_)); + insert_pixmap_ -> bindValue(":path", path); + insert_pixmap_ -> bindValue(":mtime", QVariant(QDateTime::currentDateTime())); + insert_pixmap_ -> bindValue(":pixmap", QVariant(ba)); + if (!insert_pixmap_->exec()) { + qDebug() << cache_db_.lastError(); + return(false); + } + return(true); +} diff --git a/sources/elementscollectioncache.h b/sources/elementscollectioncache.h new file mode 100644 index 000000000..f75378420 --- /dev/null +++ b/sources/elementscollectioncache.h @@ -0,0 +1,69 @@ +/* + Copyright 2006-2011 Xavier Guerrin + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#ifndef ELEMENTS_COLLECTION_CACHE_H +#define ELEMENTS_COLLECTION_CACHE_H +#include +#include +#include "elementslocation.h" +class ElementsCollection; +class ElementsCategory; +class ElementDefinition; +/** + This class implements a SQLite cache for data related to elements + collections, mainly names and pixmaps. This avoids the cost of parsing XML + definitions of elements and building full CustomElement objects when + (re)loading the elements panel. +*/ +class ElementsCollectionCache : public QObject { + public: + // constructor, destructor + ElementsCollectionCache(const QString &database_path, QObject * = 0); + virtual ~ElementsCollectionCache(); + + // methods + public: + void setLocale(const QString &); + QString locale() const; + bool setPixmapStorageFormat(const QString &); + QString pixmapStorageFormat() const; + void beginCollection(ElementsCollection *); + void endCollection(ElementsCollection *); + bool fetchElement(ElementDefinition *); + QString name() const; + QPixmap pixmap() const; + + private: + bool fetchData(const ElementsLocation &); + bool fetchNameFromCache(const QString &, const QDateTime &); + bool fetchPixmapFromCache(const QString &, const QDateTime &); + bool cacheName(const QString &); + bool cachePixmap(const QString &); + + // attributes + private: + QSqlDatabase cache_db_; ///< Object providing access to the SQLite database this cache relies on + QSqlQuery *select_name_; ///< Prepared statement to fetch names from the cache + QSqlQuery *select_pixmap_; ///< Prepared statement to fetch pixmaps from the cache + QSqlQuery *insert_name_; ///< Prepared statement to insert names into the cache + QSqlQuery *insert_pixmap_; ///< Prepared statement to insert pixmaps into the cache + QString locale_; ///< Locale to be used when dealing with names + QString pixmap_storage_format_; ///< Storage format for cached pixmaps + QString current_name_; ///< Last name fetched + QPixmap current_pixmap_; ///< Last pixmap fetched +}; +#endif diff --git a/sources/elementspanel.cpp b/sources/elementspanel.cpp index 3fdf017e2..495ab0aac 100644 --- a/sources/elementspanel.cpp +++ b/sources/elementspanel.cpp @@ -20,6 +20,7 @@ #include "qetproject.h" #include "diagram.h" #include "elementscategory.h" +#include "elementscollectioncache.h" #include "customelement.h" #include "fileelementscollection.h" #include "fileelementdefinition.h" @@ -89,7 +90,6 @@ ElementsPanel::ElementsPanel(QWidget *parent) : first_activation_(true), first_reload_(true) { - // selection unique setSelectionMode(QAbstractItemView::SingleSelection); setColumnCount(1); @@ -120,6 +120,10 @@ ElementsPanel::ElementsPanel(QWidget *parent) : // emet un signal au lieu de gerer son menu contextuel setContextMenuPolicy(Qt::CustomContextMenu); + + QString cache_path = QETApp::configDir() + "/elements_cache.sqlite"; + cache_ = new ElementsCollectionCache(cache_path, this); + cache_ -> setLocale(QLocale::system().name().left(2)); // @todo we need a unique function to get the good language } /** @@ -644,7 +648,9 @@ QTreeWidgetItem *ElementsPanel::addDiagram(QTreeWidgetItem *qtwi_parent, Diagram QTreeWidgetItem *ElementsPanel::addCollection(QTreeWidgetItem *qtwi_parent, ElementsCollection *collection, const QString &coll_name, const QIcon &icon) { if (!collection) return(0); + cache_ -> beginCollection(collection); QTreeWidgetItem *qtwi_coll = addCategory(qtwi_parent, collection -> rootCategory(), coll_name, icon); + cache_ -> endCollection(collection); return(qtwi_coll); } @@ -704,21 +710,21 @@ QTreeWidgetItem *ElementsPanel::addCategory(QTreeWidgetItem *qtwi_parent, Elemen QTreeWidgetItem *ElementsPanel::addElement(QTreeWidgetItem *qtwi_parent, ElementDefinition *element, const QString &elmt_name) { if (!element) return(0); - QString whats_this = tr("Ceci est un \351l\351ment que vous pouvez ins\351rer dans votre sch\351ma par cliquer-d\351placer"); - QString tool_tip = tr("Cliquer-d\351posez cet \351l\351ment sur le sch\351ma pour ins\351rer un \351l\351ment "); - int state; - CustomElement custom_elmt(element -> location(), 0, 0, &state); - if (state) { - qDebug() << "ElementsCategoriesList::addElement() : Le chargement du composant" << qPrintable(element -> location().toString()) << "a echoue avec le code d'erreur" << state; + if (!cache_ -> fetchElement(element)) { return(0); } - QString final_name(elmt_name.isEmpty() ? custom_elmt.name() : elmt_name); + QString custom_element_name = cache_ -> name(); + QPixmap custom_element_pixmap = cache_ -> pixmap(); + + QString whats_this = tr("Ceci est un \351l\351ment que vous pouvez ins\351rer dans votre sch\351ma par cliquer-d\351placer"); + QString tool_tip = tr("Cliquer-d\351posez cet \351l\351ment sur le sch\351ma pour ins\351rer un \351l\351ment "); + QString final_name(elmt_name.isEmpty() ? custom_element_name : elmt_name); QTreeWidgetItem *qtwi = new QTreeWidgetItem(qtwi_parent, QStringList(final_name)); - qtwi -> setStatusTip(0, tool_tip + "\253 " + custom_elmt.name() + " \273"); + qtwi -> setStatusTip(0, tool_tip + "\253 " + custom_element_name + " \273"); qtwi -> setToolTip(0, element -> location().toString()); qtwi -> setWhatsThis(0, whats_this); qtwi -> setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled); - qtwi -> setIcon(0, QIcon(custom_elmt.pixmap())); + qtwi -> setIcon(0, QIcon(custom_element_pixmap)); // actions speciales pour les elements appartenant a un projet if (QETProject *element_project = element -> location().project()) { diff --git a/sources/elementspanel.h b/sources/elementspanel.h index 1a6b2710f..4e0208d52 100644 --- a/sources/elementspanel.h +++ b/sources/elementspanel.h @@ -25,6 +25,7 @@ class ElementsCollection; class ElementsCollectionItem; class ElementsCategory; class ElementDefinition; +class ElementsCollectionCache; /** Cette classe represente le panel d'appareils (en tant qu'element graphique) dans lequel l'utilisateur choisit les composants de @@ -144,5 +145,6 @@ class ElementsPanel : public QTreeWidget { int loading_progress_; bool first_activation_; bool first_reload_; + ElementsCollectionCache *cache_; }; #endif diff --git a/sources/fileelementdefinition.cpp b/sources/fileelementdefinition.cpp index 9f313ae58..2a5512625 100644 --- a/sources/fileelementdefinition.cpp +++ b/sources/fileelementdefinition.cpp @@ -198,3 +198,14 @@ void FileElementDefinition::setFilePath(const QString &path) { } file_path = file_info.canonicalFilePath(); } + +/** + @return the time of the last modification (mtime) for this element file +*/ +QDateTime FileElementDefinition::modificationTime() const { + QFileInfo file_info(file_path); + if (!file_info.exists() || !file_info.isReadable()) { + return QDateTime(); + } + return(file_info.lastModified()); +} diff --git a/sources/fileelementdefinition.h b/sources/fileelementdefinition.h index c9eae9c25..22f3f0933 100644 --- a/sources/fileelementdefinition.h +++ b/sources/fileelementdefinition.h @@ -49,6 +49,7 @@ class FileElementDefinition : public ElementDefinition { virtual bool hasFilePath(); virtual QString filePath(); virtual void setFilePath(const QString &); + virtual QDateTime modificationTime() const; // attributs private: diff --git a/sources/fileelementscollection.cpp b/sources/fileelementscollection.cpp index cca593de5..b76bab07e 100644 --- a/sources/fileelementscollection.cpp +++ b/sources/fileelementscollection.cpp @@ -123,3 +123,11 @@ bool FileElementsCollection::isWritable() { bool FileElementsCollection::write() { return(true); } + +/** + @return always true, since a file-based elements collection can always be + cached. +*/ +bool FileElementsCollection::isCacheable() const { + return(true); +} diff --git a/sources/fileelementscollection.h b/sources/fileelementscollection.h index 68197d63c..a2498be16 100644 --- a/sources/fileelementscollection.h +++ b/sources/fileelementscollection.h @@ -48,6 +48,7 @@ class FileElementsCollection : public ElementsCollection { virtual bool isReadable(); virtual bool isWritable(); virtual bool write(); + virtual bool isCacheable() const; private: void deleteContent(); diff --git a/sources/xmlelementdefinition.cpp b/sources/xmlelementdefinition.cpp index cc23863ac..2dfe92fc6 100644 --- a/sources/xmlelementdefinition.cpp +++ b/sources/xmlelementdefinition.cpp @@ -213,6 +213,17 @@ void XmlElementDefinition::setFilePath(const QString &) { // une categorie XML n'a pas de chemin de type fichier } +/** + @return a null QDateTime object since an XML element does not have a + modification time. +*/ +/** + @return the time of the last modification (mtime) for this element file +*/ +QDateTime XmlElementDefinition::modificationTime() const { + return QDateTime(); +} + QDomElement XmlElementDefinition::writeXml(QDomDocument &xml_doc) const { QDomElement element_elmt = xml_element_.documentElement(); QDomNode new_node = xml_doc.importNode(element_elmt, true); diff --git a/sources/xmlelementdefinition.h b/sources/xmlelementdefinition.h index a799e5638..9fd5493e2 100644 --- a/sources/xmlelementdefinition.h +++ b/sources/xmlelementdefinition.h @@ -52,6 +52,7 @@ class XmlElementDefinition : public ElementDefinition { virtual bool hasFilePath(); virtual QString filePath(); virtual void setFilePath(const QString &); + virtual QDateTime modificationTime() const; virtual QDomElement writeXml(QDomDocument &) const; signals: diff --git a/sources/xmlelementscollection.cpp b/sources/xmlelementscollection.cpp index 6578c9821..28356763a 100644 --- a/sources/xmlelementscollection.cpp +++ b/sources/xmlelementscollection.cpp @@ -115,6 +115,14 @@ bool XmlElementsCollection::write() { return(true); } +/** + @return always false, since an XMl-based elements collection should never + be cached. +*/ +bool XmlElementsCollection::isCacheable() const { + return(false); +} + QDomElement XmlElementsCollection::writeXml(QDomDocument &xml_doc) const { QDomElement collection_elmt = root -> writeXml(xml_doc); collection_elmt.setTagName("collection"); diff --git a/sources/xmlelementscollection.h b/sources/xmlelementscollection.h index f3112d832..da20f0703 100644 --- a/sources/xmlelementscollection.h +++ b/sources/xmlelementscollection.h @@ -47,6 +47,7 @@ class XmlElementsCollection : public ElementsCollection { virtual bool isReadable(); virtual bool isWritable(); virtual bool write(); + virtual bool isCacheable() const; virtual QDomElement writeXml(QDomDocument &) const;