Merge branch 'master' into google
[mdictionary] / trunk / src / base / backbone / backbone.cpp
index 97e302f..10116c7 100644 (file)
     Copyright 2010 Comarch S.A.
 
 *******************************************************************************/
+/*! \file backbone.cpp
+\brief Backbone/core main file \see Backbone
 
-// Created by Bartosz Szatkowski
+
+\author Bartosz Szatkowski <bulislaw@linux.com>
+*/
 
 #include "backbone.h"
+#include "ConfigGenerator.h"
+class ConfigGenerator;
 #include <QDebug>
 
+int Backbone::_searchLimit;
+
+// Sadly QtConcurent::mapped dosent let me use something like calling method of
+// some class with supplied argument; so i have to sin against art and put
+// global function and variable so i could supply function with some parametr
+QString mappedSearch;
+QList<Translation*> mapSearch(CommonDictInterface *dict) {
+    if(dict)
+        return dict->searchWordList(mappedSearch, Backbone::_searchLimit);
+    return QList<Translation*>();
+}
+
+
+
+/*! Smart pointer (kind of) for translation object
+
+    QtConcurent::mapped  use collection of data and one function, what i need is
+    to map signle data object to method calls for multiple objects. TranslationPtr
+    is try to store method call as a data -> moreover QtConcurent allow only for
+    methods without any parameters so TranslationPtr is created with Translation
+    object -> ready to call toHtml() for supplied Translation.
+
+    Another thing is that QtConcurent dont like pointers in data collection
+    so TranslationPtr is way to hide real translation object (pointer for object)
+    */
+class TranslationPtr {
+    Translation* _tr;
+public:
+    TranslationPtr(Translation* tr) :_tr(tr) {}
+
+    /*! \return translation text for corresponding Translation object */
+    QString toHtml() const {
+        QString trans;
+        trans = _tr->toHtml();
+        return trans;
+
+    }
+};
+
 void Backbone::init() {
-   _interval = 250; //msec
-   dryRun = false;
 
+   _dir = QDir::homePath() + "/.mdictionary/";
    if(!_configPath.size())
-       _configPath = QDir::homePath() + "/.mdictionary/mdictionary.config";
+       _configPath = _dir + "mdictionary.config";
    if(!_defaultConfigPath.size())
-       _defaultConfigPath = QDir::homePath() + "/.mdictionary/mdictionary.defaults";
+       _defaultConfigPath = _dir + "mdictionary.defaults";
    if(!_pluginPath.size())
        _pluginPath = "/usr/lib/mdictionary";
    _historyLen = 10;
    _searchLimit = 15;
 
+   //Install default config files
+   ConfigGenerator confGen;
+   confGen.generateCss(_dir + "style.css");
+
+
+
    loadPrefs(_defaultConfigPath);
+
+   // Default configuration are stored in separate config file and we dont want
+   // to update it
    _defaultPluginPath = _pluginPath;
    _defaultHistoryLen = _historyLen;
    _defaultSearchLimit = _searchLimit;
@@ -48,23 +101,37 @@ void Backbone::init() {
    loadDicts(_defaultConfigPath, true);
    loadDicts(_configPath);
 
-   connect(&_timerSearch, SIGNAL(timeout()), this, SLOT(translationReady()));
-   connect(&_timerHtmlSearch, SIGNAL(timeout()), this,
+   connect(&_resultWatcher, SIGNAL(finished()), this, SLOT(translationReady()));
+   connect(&_htmlResultWatcher, SIGNAL(finished()), this,
            SLOT(htmlTranslationReady()));
+   connect(&_bookmarkWatcher, SIGNAL(finished()), this,
+           SLOT(bookmarksListReady()));
+   connect(&_bookmarkSearchWatcher, SIGNAL(finished()), this,
+           SLOT(translationReady()));
+
+   // In common opinion perfect thread count is cores_number+1 (in qt perfect
+   // thread count is set to cores number
+   QThreadPool::globalInstance()->setMaxThreadCount(
+           QThreadPool::globalInstance()->maxThreadCount()+1);
 
    _history = new History(5, this);
+   _dictNum = 0;
 }
 
+
+
 Backbone::Backbone(QString pluginPath, QString configPath, bool dry,
                    QObject *parent)
     : QObject(parent)
 {
     _pluginPath = pluginPath;
     _configPath = configPath;
+
     _defaultConfigPath = configPath;
-    init();
+    dryRun = false;
     if(dry)
         dryRun = true;
+    init();
 }
 
 
@@ -90,7 +157,6 @@ Backbone::~Backbone()
 
 
 Backbone::Backbone(const Backbone &b) :QObject(b.parent()) {
-   // init();
     _dicts = QHash<CommonDictInterface*, bool > (b._dicts);
     _plugins = QList<CommonDictInterface* > (b._plugins);
     _result = QHash<QString, Translation* > (b._result);
@@ -106,63 +172,68 @@ int Backbone::searchLimit() const {
 
 
 
-
 QHash<CommonDictInterface*, bool > Backbone::getDictionaries() {
     return _dicts;
 }
 
 
 
-
 QList<CommonDictInterface* > Backbone::getPlugins() {
     return _plugins;
 }
 
 
 
-
 History* Backbone::history() {
     return _history;
 }
 
 
 
-
 QMultiHash<QString, Translation*> Backbone::result() {
     return _result;
 }
 
 
 
-
 void Backbone::stopSearching() {
-    _timerSearch.stop();
-    _timerHtmlSearch.stop();
+    if(stopped)
+        return;
+
     foreach(CommonDictInterface* dict, _dicts.keys())
         dict->stop();
+    stopped = true;
+    _innerHtmlResult.cancel();
+    _innerResult.cancel();
+    Q_EMIT searchCanceled();
 }
 
 
 
-
-void Backbone::search(QString word) {
-    _timerSearch.stop();
+void Backbone::search(QString word){
     _result.clear();
-    _innerResult.clear();
+    mappedSearch = word.toLower();
 
-    _timerSearch.start(_interval);
+    stopped = false;
 
-    foreach(CommonDictInterface* dict, _dicts.keys())
-        if(_dicts[dict] == 1) {
-            QFuture<QList<Translation*> > tr =
-                    QtConcurrent::run(dict,
-                                  &CommonDictInterface::searchWordList,word,
-                                                         searchLimit());
-            _innerResult.append(tr);
-        }
+    // When dictFin and bookmarkFin is set to true then translationReady()
+    // signal is emited see translationReady(),
+    // so when searching only in one of them, coresponding *Fin is set to false
+    // and other to true so program is waiting only for one translation
+    dictFin = !_searchDicts;
+    bookmarkFin = !_searchBookmarks;
 
-}
+    if (_searchDicts) {
+        _innerResult = QtConcurrent::mapped(activeDicts(), mapSearch);
+        _resultWatcher.setFuture(_innerResult);
+    }
 
+    if(_searchBookmarks) {
+        _innerBookmarks = QtConcurrent::run(_bookmarks,
+                &Bookmarks::searchWordList, word);
+        _bookmarkSearchWatcher.setFuture(_innerBookmarks);
+    }
+}
 
 
 
@@ -185,13 +256,17 @@ void Backbone::addDictionary(CommonDictInterface *dict, bool active) {
 
 
  void Backbone::addInternalDictionary(CommonDictInterface* dict, bool active) {
-     dict->setHash(_dicts.size()+1);
+     dict->setHash(++_dictNum); // Hash must be uniqe in every session but not between
      _dicts[dict] = active;
+
      connect(dict, SIGNAL(settingsChanged()), this, SLOT(dictUpdated()));
+     connect(dict, SIGNAL(notify(Notify::NotifyType,QString)), this,
+             SIGNAL(notify(Notify::NotifyType,QString)));
  }
 
  void Backbone::removeDictionary(CommonDictInterface *dict) {
      _dicts.remove(dict);
+     delete dict;
      dictUpdated();
 
  }
@@ -205,24 +280,33 @@ void Backbone::addDictionary(CommonDictInterface *dict, bool active) {
 
 
 
-int Backbone::activeSearches() const {
-    return _innerResult.size();
-}
-
-
-
 void Backbone::translationReady() {
-    foreach(QFuture<QList<Translation*> > trans, _innerResult) {
-        if(!trans.isFinished())
-            continue;
-        QList<Translation*> tList = trans.result();
-        foreach(Translation* t, tList) {
-            _result.insert(t->key().toLower(), t);
+    bool changed = 0; // prevents doubling ready() signal, when both if's are
+                      //  executed in one translationReady() call then second
+                      // translationReady() call doubles ready*() emit
+
+    if(!dictFin && _innerResult.isFinished()) {
+        changed = 1;
+        dictFin = 1;
+        QFutureIterator<QList<Translation*> > it(_innerResult);
+
+        while(it.hasNext()) {
+            QList<Translation* > list = it.next();
+            foreach(Translation* trans, list)
+                _result.insert(trans->key().toLower(), trans);
         }
-        _innerResult.removeOne(trans);
     }
-    if(!_innerResult.size()) {
-        _timerSearch.stop();
+
+    if(!bookmarkFin && _innerBookmarks.isFinished()) {
+        changed = 1;
+        bookmarkFin = 1;
+        QList<Translation*> list = _innerBookmarks.result();
+
+        foreach(Translation* trans, list)
+                _result.insert(trans->key().toLower(), trans);
+    }
+
+    if(!stopped && bookmarkFin && dictFin && changed) {
         Q_EMIT ready();
     }
 }
@@ -230,7 +314,9 @@ void Backbone::translationReady() {
 QStringList Backbone::getFilesFromDir(QString dir, QStringList nameFilter) {
     QDir plug(QDir::toNativeSeparators(dir));
     if(!plug.exists()) {
-        qDebug() << plug.absolutePath() << " folder dosen't exists";
+        qDebug() << plug.absolutePath() << " folder doesn't exist";
+        Q_EMIT notify(Notify::Warning,
+                QString("%1 folder doesn't exist.").arg(plug.path()));
         return QStringList();
     }
     plug.setFilter(QDir::Files);
@@ -246,20 +332,29 @@ void Backbone::loadPlugins() {
     if(dryRun)
         return;
     QStringList nameFilter;
-    nameFilter << "*.so";
+    nameFilter << "*.so" << "*.so.*";
     QStringList files = getFilesFromDir(_pluginPath, nameFilter);
-    qDebug() << files;
 
     foreach(QString file, files) {
         QPluginLoader loader(file);
         if(!loader.load()) {
+            Q_EMIT notify(Notify::Error,
+                    QString("%1 plugin cannot be loaded: %2.")
+                    .arg(file).arg(loader.errorString()));
             qDebug()<< file << " " << loader.errorString();
             continue;
         }
         QObject *pl = loader.instance();
 
+        bool exists = 0;
         CommonDictInterface *plugin = qobject_cast<CommonDictInterface*>(pl);
-        _plugins.append(plugin);
+        foreach(CommonDictInterface* pl, _plugins)
+            if(pl->type() == plugin->type()) {
+                exists = 1;
+                break;
+           }
+        if(!exists)
+            _plugins.append(plugin);
     }
 }
 
@@ -280,14 +375,19 @@ void Backbone::loadPrefs(QString fileName) {
     QFileInfo file(QDir::toNativeSeparators(fileName));
     QDir confDir(file.dir());
     if(!confDir.exists()){
-        qDebug() << "Configuration file dosn't exists ("
+        qDebug() << "Configuration file doesn't exist ("
                 << file.filePath() << ")";
+        Q_EMIT notify(Notify::Warning,
+                QString("%1 configuration file doesn't exist.")
+                .arg(file.filePath()));
         return;
     }
     QSettings set(file.filePath(), QSettings::IniFormat);
     _pluginPath = set.value("general/plugin_path", _pluginPath).toString();
-    _historyLen = set.value("general/history_length", 10).toInt();
+    _historyLen = set.value("general/history_size", 10).toInt();
     _searchLimit = set.value("general/search_limit", 15).toInt();
+    _searchBookmarks = set.value("general/search_bookmarks",1).toBool();
+    _searchDicts = set.value("general/search_dictionaries",1).toBool();
 }
 
 
@@ -296,8 +396,10 @@ void Backbone::savePrefs(QSettings *set) {
     if(dryRun)
         return;
     set->setValue("general/plugin_path", _pluginPath);
-    set->setValue("general/history_length", _historyLen);
+    set->setValue("general/history_size", _historyLen);
     set->setValue("general/search_limit", _searchLimit);
+    set->setValue("general/search_bookmarks", _searchBookmarks);
+    set->setValue("general/search_dictionaries", _searchDicts);
 }
 
 
@@ -306,7 +408,7 @@ void Backbone::saveDefaultPrefs(QSettings *set) {
     if(dryRun)
         return;
     set->setValue("general/plugin_path", _defaultPluginPath);
-    set->setValue("general/history_length", _defaultHistoryLen);
+    set->setValue("general/history_size", _defaultHistoryLen);
     set->setValue("general/search_limit", _defaultSearchLimit);
 }
 
@@ -315,12 +417,15 @@ void Backbone::saveDefaultPrefs(QSettings *set) {
 void Backbone::loadDicts(QString fileName, bool _default) {
     if(dryRun)
         return;
+
     QFileInfo file(QDir::toNativeSeparators(fileName));
-    qDebug() << file.filePath();
     QDir confDir(file.dir());
     if(!confDir.exists()){
-        qDebug() << "Configuration file dosn't exists ("
+        qDebug() << "Configuration file doesn't exist ("
                 << file.filePath() << ")";
+        Q_EMIT notify(Notify::Warning,
+                QString("%1 configurationfile doesn't exist.")
+                .arg(file.filePath()));
         return;
     }
 
@@ -334,7 +439,10 @@ void Backbone::loadDicts(QString fileName, bool _default) {
         if(!plug) {
             qDebug() << "Config file error: "
                     << set.value(dict + "/type", "").toString()
-                    << " dosen't exists";
+                    << " doesn't exist";
+            Q_EMIT notify(Notify::Warning,
+                    QString("Configuration file error. %2 plugin doesn't exist.")
+                    .arg(set.value(dict + "/type", "").toString()));
             continue;
         }
         Settings* plugSet = new Settings();
@@ -358,7 +466,13 @@ void Backbone::loadDicts(QString fileName, bool _default) {
 void Backbone::dictUpdated() {
     if(dryRun)
         return;
-    qDebug() << "UPDATE";
+
+    // For convienence this function is called for each change in dictionaries
+    // and each call dumps configuration for all dictionaries into file.
+    // Maybe better way would be to store new/changed configuration but
+    // parsing settings file and figuring out what was changed, in my opinion,
+    // would take more time
+    _history->setMaxSize(_historyLen);
     QFileInfo file(QDir::toNativeSeparators(_configPath));
     QDir confDir(file.dir());
     if(!confDir.exists())
@@ -393,6 +507,7 @@ void Backbone::saveState(QSettings* set, Settings* plugSet, bool active
         return;
     if(!set || !plugSet)
         return;
+
     QString section;
     section.append(QString("dictionary_%1").arg(hash));
     QList<QString> keys = plugSet->keys();
@@ -410,27 +525,111 @@ QStringList Backbone::htmls() {
 
 
 void Backbone::searchHtml(QList<Translation *> translations) {
-    _timerHtmlSearch.stop();
     _htmlResult.clear();
-    _innerHtmlResult.clear();
-    _timerHtmlSearch.start();
 
-    foreach(Translation* trans, translations)
-        if(trans)
-           _innerHtmlResult.append(
-                   QtConcurrent::run(trans, &Translation::toHtml));
+    QList<TranslationPtr> dummy;
+    stopped = false;
+    foreach(Translation* tr, translations) {
+         if(containsDict(tr->dict()) || !tr->dict())
+            dummy.append(TranslationPtr(tr));
+  /*      foreach(CommonDictInterface* dict, activeDicts()) {
+            Translation* trans = dict->getTranslationFor(tr->key());
+            if(trans)
+                dummy.append(TranslationPtr(trans));
+        } */
+    }
+    if(translations.size()>0) {
+        Translation *tr = translations.at(0);
+        foreach(CommonDictInterface* dict, activeDicts()) {
+            Translation* trans = dict->getTranslationFor(tr->key());
+            if(trans)
+                dummy.append(TranslationPtr(trans));
+        }
+    }
+
+   _innerHtmlResult = QtConcurrent::mapped(dummy,
+                                            &TranslationPtr::toHtml);
+   _htmlResultWatcher.setFuture(_innerHtmlResult);
 }
 
+
+
 void Backbone::htmlTranslationReady() {
-    foreach(QFuture<QString> res, _innerHtmlResult) {
-        if(!res.isFinished())
-            continue;
-        _htmlResult.append(res.result());
-        _innerHtmlResult.removeOne(res);
-    }
-    if(!_innerHtmlResult.size()) {
-        _timerHtmlSearch.stop();
+
+    QFutureIterator<QString> it(_innerHtmlResult);
+    QSet<QString> uniqe;
+    while(it.hasNext())
+        uniqe.insert(it.next());
+    _htmlResult.clear();
+    _htmlResult = uniqe.toList();
+
+    if(!stopped)
         Q_EMIT htmlReady();
-    }
 
 }
+
+
+QList<CommonDictInterface*> Backbone::activeDicts() {
+    QList<CommonDictInterface*>res;
+    foreach(CommonDictInterface* dict, _dicts.keys())
+        if(_dicts[dict])
+            res.append(dict);
+    return res;
+
+}
+
+
+
+void Backbone::bookmarksListReady() {
+   _bookmarksResult = _innerBookmarks.result();
+   Q_EMIT bookmarksReady();
+}
+
+
+
+
+void Backbone::setSettings(Settings *settings) {
+    _historyLen = settings->value("history_size").toInt();
+    _searchLimit = settings->value("search_limit").toInt();
+    if(settings->value("search_dictionaries") == "true")
+        _searchDicts = 1;
+    else
+        _searchDicts = 0;
+    if(settings->value("search_bookmarks") == "true")
+        _searchBookmarks = 1;
+    else
+        _searchBookmarks = 0;
+    dictUpdated();
+    if(settings)
+        delete settings;
+}
+
+
+
+
+Settings* Backbone::settings() {
+    Settings * settings = new Settings();
+    settings->setValue("history_size", QString("%1").arg(_historyLen));
+    settings->setValue("search_limit", QString("%1").arg(_searchLimit));
+    if(_searchBookmarks)
+        settings->setValue("search_bookmarks", "true");
+    else
+        settings->setValue("search_bookmarks", "false");
+
+    if(_searchDicts)
+        settings->setValue("search_dictionaries", "true");
+    else
+        settings->setValue("search_dictionaries", "false");
+    return settings;
+}
+
+
+bool Backbone::containsDict(uint hash) const {
+    QHashIterator<CommonDictInterface*, bool> it(_dicts);
+    if (!hash)
+        return false;
+    while(it.hasNext())
+        if(it.next().key()->hash() == hash)
+            return true;
+    return false;
+}