diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..4b98ab893 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +zeal-build-Desktop-Debug/ +zeal/zeal.pro.user diff --git a/zeal/main.cpp b/zeal/main.cpp new file mode 100644 index 000000000..9e2d83c83 --- /dev/null +++ b/zeal/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/zeal/mainwindow.cpp b/zeal/mainwindow.cpp new file mode 100644 index 000000000..8aa13a13b --- /dev/null +++ b/zeal/mainwindow.cpp @@ -0,0 +1,51 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "zeallistmodel.h" +#include "zealsearchmodel.h" +#include + +#include +using namespace std; + +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + auto dataLocation = QStandardPaths::writableLocation(QStandardPaths::DataLocation); + auto dataDir = QDir(dataLocation); + if(!dataDir.cd("docsets")) { + QMessageBox::critical(this, "No docsets directory found", + QString("'docsets' directory not found in '%1'").arg(dataLocation)); + } else { + for(auto subdir : dataDir.entryInfoList()) { + if(subdir.isDir() && !subdir.isHidden()) { + docsets->addDocset(subdir.absoluteFilePath()); + } + } + } + ui->setupUi(this); + ui->treeView->setModel(&zealList); + ui->treeView->setColumnHidden(1, true); + connect(ui->treeView, &QTreeView::activated, [&](const QModelIndex& index) { + ui->webView->setUrl("file://" + index.sibling(index.row(), 1).data().toString()); + }); + connect(ui->lineEdit, &QLineEdit::textChanged, [&](const QString& text) { + if(!text.isEmpty()) { + zealSearch.setQuery(text); + ui->treeView->setModel(&zealSearch); + ui->treeView->reset(); + ui->treeView->setColumnHidden(1, true); + } else { + ui->treeView->setModel(&zealList); + } + }); +} + +MainWindow::~MainWindow() +{ + delete ui; +} diff --git a/zeal/mainwindow.h b/zeal/mainwindow.h new file mode 100644 index 000000000..b0ecdfbc2 --- /dev/null +++ b/zeal/mainwindow.h @@ -0,0 +1,26 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include "zeallistmodel.h" +#include "zealsearchmodel.h" + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private: + Ui::MainWindow *ui; + ZealListModel zealList; + ZealSearchModel zealSearch; +}; + +#endif // MAINWINDOW_H diff --git a/zeal/mainwindow.ui b/zeal/mainwindow.ui new file mode 100644 index 000000000..402973eb2 --- /dev/null +++ b/zeal/mainwindow.ui @@ -0,0 +1,84 @@ + + + MainWindow + + + + 0 + 0 + 1050 + 579 + + + + MainWindow + + + + + + + Qt::Horizontal + + + + + + + + + + QAbstractItemView::SelectItems + + + 10 + + + false + + + + + + + + + about:blank + + + + + + + + + + + 0 + 0 + 1050 + 26 + + + + + + TopToolBarArea + + + false + + + + + + + + QWebView + QWidget +
QtWebKit/QWebView
+
+
+ + +
diff --git a/zeal/zeal.pro b/zeal/zeal.pro new file mode 100644 index 000000000..c8ce5021d --- /dev/null +++ b/zeal/zeal.pro @@ -0,0 +1,28 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2013-01-18T22:28:41 +# +#------------------------------------------------- + +QT += core gui widgets webkitwidgets sql + + +TARGET = zeal +TEMPLATE = app + + +SOURCES += main.cpp\ + mainwindow.cpp \ + zeallistmodel.cpp \ + zealsearchmodel.cpp \ + zealdocsetsregistry.cpp + +HEADERS += mainwindow.h \ + zeallistmodel.h \ + zealsearchmodel.h \ + zealdocsetsregistry.h + +FORMS += mainwindow.ui + + +QMAKE_CXXFLAGS += -std=c++11 diff --git a/zeal/zealdocsetsregistry.cpp b/zeal/zealdocsetsregistry.cpp new file mode 100644 index 000000000..af912aeeb --- /dev/null +++ b/zeal/zealdocsetsregistry.cpp @@ -0,0 +1,14 @@ +#include "zealdocsetsregistry.h" + +ZealDocsetsRegistry* ZealDocsetsRegistry::m_Instance; + +ZealDocsetsRegistry* docsets = ZealDocsetsRegistry::instance(); + +void ZealDocsetsRegistry::addDocset(const QString& path) { + auto dir = QDir(path); + auto db = QSqlDatabase::addDatabase("QSQLITE", dir.dirName()); + db.setDatabaseName(dir.filePath("index.sqlite")); + db.open(); + dbs.insert(dir.dirName(), db); + dirs.insert(dir.dirName(), dir); +} diff --git a/zeal/zealdocsetsregistry.h b/zeal/zealdocsetsregistry.h new file mode 100644 index 000000000..63121e4ea --- /dev/null +++ b/zeal/zealdocsetsregistry.h @@ -0,0 +1,64 @@ +#ifndef ZEALDOCSETSREGISTRY_H +#define ZEALDOCSETSREGISTRY_H + +#include +#include +#include +#include +#include +using namespace std; + +class ZealDocsetsRegistry +{ +public: + static ZealDocsetsRegistry* instance() + { + static QMutex mutex; + if (!m_Instance) + { + mutex.lock(); + + if (!m_Instance) + m_Instance = new ZealDocsetsRegistry; + + mutex.unlock(); + } + + return m_Instance; + } + + void addDocset(const QString& path); + + int count() const { + return dbs.count(); + } + + QSqlDatabase& db(const QString& name) { + return dbs[name]; + } + + const QDir& dir(const QString& name) { + return dirs[name]; + } + + QStringList names() const { + return dbs.keys(); + } + +private: + ZealDocsetsRegistry() { + } + + ZealDocsetsRegistry(const ZealDocsetsRegistry&); // hide copy constructor + ZealDocsetsRegistry& operator=(const ZealDocsetsRegistry&); // hide assign op + // we leave just the declarations, so the compiler will warn us + // if we try to use those two functions by accident + + static ZealDocsetsRegistry* m_Instance; + QMap dbs; + QMap dirs; +}; + +extern ZealDocsetsRegistry* docsets; + +#endif // ZEALDOCSETSREGISTRY_H diff --git a/zeal/zeallistmodel.cpp b/zeal/zeallistmodel.cpp new file mode 100644 index 000000000..b9dc3e0c8 --- /dev/null +++ b/zeal/zeallistmodel.cpp @@ -0,0 +1,130 @@ +#include "zeallistmodel.h" +#include "zealdocsetsregistry.h" + +#include + +#include +#include + +using namespace std; + +ZealListModel::ZealListModel(QObject *parent) : + QAbstractItemModel(parent) +{ + strings = new QSet; + modulesCounts = new QHash; +} + +Qt::ItemFlags ZealListModel::flags(const QModelIndex &index) const +{ + if(!index.isValid()) + return 0; + return QAbstractItemModel::flags(index); +} + +const QHash ZealListModel::getModulesCounts() const +{ + if(!modulesCounts->count()) { + for(auto name : docsets->names()) { + auto db = docsets->db(name); + auto q = db.exec("select count(*) from things where type='module'"); + q.next(); + (*modulesCounts)[name] = q.value(0).toInt(); + } + } + return *modulesCounts; +} + +QModelIndex ZealListModel::index(int row, int column, const QModelIndex &parent) const +{ + if(!parent.isValid()) { + if(row >= docsets->count()) return QModelIndex(); + if(column == 0) { + return createIndex(row, column, (void*)getString(docsets->names().at(row))); + } else if(column == 1) { + return createIndex(row, column, (void*)getString(docsets->dir(docsets->names().at(row)).absoluteFilePath("index.html"))); + } + return QModelIndex(); + } else { + if(i2s(parent)->indexOf('/') == -1) { + if(column == 0) { + return createIndex(row, column, (void*)getString(*i2s(parent)+"/Modules")); + } + } else if(i2s(parent)->endsWith("/Modules")) { + auto name = i2s(parent)->split("/").at(0); + if(row >= getModulesCounts()[name]) return QModelIndex(); + auto db = docsets->db(name); + if(column == 0) { + auto q = db.exec("select name from things where type='module' limit 1 offset " + QString().setNum(row)); + q.next(); + return createIndex(row, column, + (void*)getString(QString("%1/Modules/%2").arg(name, q.value(0).toString()))); + } else if(column == 1) { + auto q = db.exec("select path from things where type='module' limit 1 offset " + QString().setNum(row)); + q.next(); + return createIndex(row, column, (void*)getString(docsets->dir(name).absoluteFilePath(q.value(0).toString()))); + } + } + return QModelIndex(); + } +} + +QVariant ZealListModel::data(const QModelIndex &index, int role) const +{ + if (role != Qt::DisplayRole || !index.isValid()) + return QVariant(); + if(index.column() == 0) + return QVariant(i2s(index)->split('/').last()); + else { + return QVariant(*i2s(index)); + } +} + +QVariant ZealListModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + return QVariant(); +} + +int ZealListModel::rowCount(const QModelIndex &parent) const +{ + if(parent.column() > 0 && !i2s(parent)->endsWith("/Modules")) return 0; + if(!parent.isValid()) { + // root + return docsets->count(); + } else { + auto parentStr = i2s(parent); + if(parentStr->indexOf("/") == -1) { + // docset - one enty - Modules + return 1; + } else if(parentStr->endsWith("/Modules")) { + // modules count + return getModulesCounts()[parentStr->split('/').at(0)]; + } + // module - no sub items + return 0; + } +} + +const QString *ZealListModel::i2s(const QModelIndex &index) const +{ + return static_cast(index.internalPointer()); +} + +QModelIndex ZealListModel::parent(const QModelIndex &child) const +{ + if(child.isValid() && i2s(child)->endsWith("/Modules")) { + return createIndex(0, 0, (void*)getString(i2s(child)->split('/')[0])); + } else if(child.isValid() && i2s(child)->indexOf("/Modules/") != -1) { + return createIndex(0, 0, (void*)getString(i2s(child)->split('/')[0] + "/Modules")); + } else { + return QModelIndex(); + } +} + +int ZealListModel::columnCount(const QModelIndex &parent) const +{ + if(!parent.isValid() || i2s(parent)->endsWith("/Modules")) + return 2; + else + return 1; +} diff --git a/zeal/zeallistmodel.h b/zeal/zeallistmodel.h new file mode 100644 index 000000000..2078a77b9 --- /dev/null +++ b/zeal/zeallistmodel.h @@ -0,0 +1,42 @@ +#ifndef ZEALLISTMODEL_H +#define ZEALLISTMODEL_H + +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +class ZealListModel : public QAbstractItemModel +{ + Q_OBJECT +public: + explicit ZealListModel(QObject *parent = 0); + Qt::ItemFlags flags(const QModelIndex &index) const; + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex parent(const QModelIndex &child) const; + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; +signals: + +public slots: + +private: + const QString* i2s(const QModelIndex &index) const; + QHash *modulesCounts; + const QHash getModulesCounts() const; + QSet *strings; + const QString* getString(const QString& str) const { + if(strings->find(str) == strings->end()) + (*strings).insert(str); + return &*strings->find(str); + } +}; + +#endif // ZEALLISTMODEL_H diff --git a/zeal/zealsearchmodel.cpp b/zeal/zealsearchmodel.cpp new file mode 100644 index 000000000..281b9317c --- /dev/null +++ b/zeal/zealsearchmodel.cpp @@ -0,0 +1,110 @@ +#include +#include +using namespace std; + +#include +#include + +#include "zealsearchmodel.h" +#include "zealdocsetsregistry.h" + +ZealSearchModel::ZealSearchModel(QObject *parent) : + QAbstractItemModel(parent) +{ + counts = new QHash; + strings = new QSet; +} + +Qt::ItemFlags ZealSearchModel::flags(const QModelIndex &index) const +{ + if(!index.isValid()) + return 0; + return QAbstractItemModel::flags(index); +} + +QVariant ZealSearchModel::data(const QModelIndex &index, int role) const +{ + if (role != Qt::DisplayRole || !index.isValid()) + return QVariant(); + return QVariant(*static_cast(index.internalPointer())); +} + +QVariant ZealSearchModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + return QVariant(); +} + +QModelIndex ZealSearchModel::index(int row, int column, const QModelIndex &parent) const +{ + if(!parent.isValid()) { + QSqlDatabase db; + int found = 0, drow = 0; + QString name; + for(QString name_ : docsets->names()) { + db = docsets->db(name_); + found += getCounts()[name_]; + if(found > row) { + name = name_; + break; + } + // not enough rows - decrement row + drow -= getCounts()[name_]; + } + if(found <= row) return QModelIndex(); + if(column == 0) { + auto q = db.exec(QString("select name, parent from things where name like '%1%' order by name asc, path asc limit 1 offset ").arg(query)+QString().setNum(row+drow)); + q.next(); + if(!q.value(1).isNull()) { + auto qp = db.exec(QString("select name from things where id = %1").arg(q.value(1).toInt())); + qp.next(); + return createIndex(row, column, (void*)getString( + QString("%1 (%2)").arg(q.value(0).toString(), qp.value(0).toString()))); + } else { + return createIndex(row, column, (void*)getString(q.value(0).toString())); + } + + } else if (column == 1) { + auto q = db.exec(QString("select path from things where name like '%1%' order by name asc, path asc limit 1 offset ").arg(query)+QString().setNum(row+drow)); + q.next(); + return createIndex(row, column, (void*)getString(docsets->dir(name).absoluteFilePath(q.value(0).toString()))); + } + } + return QModelIndex(); +} + +QModelIndex ZealSearchModel::parent(const QModelIndex &child) const +{ + return QModelIndex(); +} + +int ZealSearchModel::rowCount(const QModelIndex &parent) const +{ + if(!parent.isValid()) { + return accumulate(getCounts().begin(), getCounts().end(), 0); + } + return 0; +} + +int ZealSearchModel::columnCount(const QModelIndex &parent) const +{ + return 2; +} + +void ZealSearchModel::setQuery(const QString &q) { + query = q; + counts->clear(); + strings->clear(); +} + +const QHash ZealSearchModel::getCounts() const +{ + if(counts->empty()) { + for(auto name : docsets->names()) { + auto db = docsets->db(name); + auto q = db.exec(QString("select count(name) from things where name like '%1%'").arg(query)); + q.next(); + (*counts)[name] = min(40, q.value(0).toInt()); + } + } + return *counts; +} diff --git a/zeal/zealsearchmodel.h b/zeal/zealsearchmodel.h new file mode 100644 index 000000000..150304ecf --- /dev/null +++ b/zeal/zealsearchmodel.h @@ -0,0 +1,38 @@ +#ifndef ZEALSEARCHMODEL_H +#define ZEALSEARCHMODEL_H + +#include +#include +#include + +class ZealSearchModel : public QAbstractItemModel +{ + Q_OBJECT +public: + explicit ZealSearchModel(QObject *parent = 0); + Qt::ItemFlags flags(const QModelIndex &index) const; + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex parent(const QModelIndex &child) const; + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + void setQuery(const QString& q); + +signals: + +public slots: + +private: + QString query; + QHash *counts; + const QHash getCounts() const; + QSet *strings; + const QString* getString(const QString& str) const { + if(strings->find(str) == strings->end()) + (*strings).insert(str); + return &*strings->find(str); + } +}; + +#endif // ZEALSEARCHMODEL_H