Merge pull request #45 from kiwix/contentManager

Content manager
This commit is contained in:
Matthieu Gautier 2018-10-26 13:58:05 +02:00 committed by GitHub
commit bf2c780647
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 12142 additions and 169 deletions

View File

@ -49,13 +49,16 @@ SOURCES += \
src/topwidget.cpp \
src/requestinterceptor.cpp \
src/urlschemehandler.cpp \
src/tabwidget.cpp \
src/webview.cpp \
src/searchbar.cpp \
src/mainmenu.cpp \
src/webpage.cpp \
src/about.cpp \
src/tocsidebar.cpp
src/tocsidebar.cpp \
src/contentmanager.cpp \
src/contentmanagerview.cpp \
src/tabbar.cpp \
src/contentmanagerside.cpp
HEADERS += \
src/mainwindow.h \
@ -66,18 +69,22 @@ HEADERS += \
src/kconstants.h \
src/requestinterceptor.h \
src/urlschemehandler.h \
src/tabwidget.h \
src/webview.h \
src/searchbar.h \
src/mainmenu.h \
src/webpage.h \
src/about.h \
src/tocsidebar.h
src/tocsidebar.h \
src/contentmanager.h \
src/contentmanagerview.h \
src/tabbar.h \
src/contentmanagerside.h
FORMS += \
ui/mainwindow.ui \
ui/about.ui \
src/tocsidebar.ui
src/tocsidebar.ui \
src/contentmanagerside.ui
TRANSLATIONS = "resources/i18n/kiwix-desktop_fr.ts"
CODECFORSRC = UTF-8
@ -116,7 +123,9 @@ LIBS += $$system(pkg-config --libs $$PKGCONFIG_OPTION kiwix)
RESOURCES += \
resources/kiwix.qrc \
resources/translations.qrc
resources/translations.qrc \
resources/contentmanager.qrc \
resources/style.qrc
unix {
system($$QMAKE_LUPDATE -locations relative -no-ui-lines $$_PRO_FILE_)

View File

@ -0,0 +1,6 @@
<RCC>
<qresource prefix="/">
<file>js/vue.js</file>
<file>texts/_contentManager.html</file>
</qresource>
</RCC>

View File

@ -177,3 +177,29 @@ QTabBar::close-button {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
/* ----------------------------------------
Find Search page
*/
#sideBar {
background-color: white;
}
#contentmanagerside QWidget{
background-color: white;
outline: none;
}
#contentmanagerside QRadioButton:checked {
border: none;
}
#contentmanagerside QRadioButton::indicator {
image: none;
}
#contentmanagerside QCheckBox::indicator {
image: none;
}

10947
resources/js/vue.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -61,7 +61,6 @@
<file>fonts/SegoeUI/seguisb.ttf</file>
<file>fonts/SegoeUI/segoeui.ttf</file>
<file>texts/about.html</file>
<file>css/style.css</file>
<file>icons/search_backward.svg</file>
<file>icons/search_forward.svg</file>
</qresource>

5
resources/style.qrc Normal file
View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>css/style.css</file>
</qresource>
</RCC>

View File

@ -0,0 +1,229 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html>
<head>
<script src="qrc:///js/vue.js"></script>
<script src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript">
const units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
function niceBytes(x){
var unitIndex = 0;
var n = parseInt(x, 10) || 0;
while(n >= 1024 && ++unitIndex)
n = n/1024;
return(n.toFixed(n >= 10 || unitIndex < 1 ? 0 : 2) + ' ' + units[unitIndex]);
}
function createDict(keys, values) {
var d = {}
for(var i=0; i<keys.length; i++) {
d[keys[i]] = values[i];
}
return d;
}
const BOOK_KEYS = ["id", "name", "path", "url", "size", "description", "title", "tags", "date", "favicon", "faviconMimeType", "downloadId"];
function addBook(values) {
var b = createDict(BOOK_KEYS, values);
if (b.downloadId && !downloadUpdaters.hasOwnProperty(b.id)) {
downloadUpdaters[b.id] = setInterval(function() { getDownloadInfo(b.id); }, 1000);
}
app.books.push(b);
}
function onBooksChanged () {
app.books = [];
for(var i=0; i<contentManager.bookIds.length; i++) {
var id = contentManager.bookIds[i];
contentManager.getBookInfos(id, BOOK_KEYS, addBook);
}
}
downloadUpdaters = {}
const DOWNLOAD_KEYS = ["id", "status", "followedBy", "path", "totalLength", "completedLength", "downloadSpeed", "verifiedLength"];
function getDownloadInfo(id) {
contentManager.updateDownloadInfos(id, DOWNLOAD_KEYS, function(values) {
if (values.length == 0) {
clearInterval(downloadUpdaters[id]);
return;
}
d = createDict(DOWNLOAD_KEYS, values);
if (d.status == "completed") {
clearInterval(downloadUpdaters[id]);
}
Vue.set(app.downloads, id, createDict(DOWNLOAD_KEYS, values));
});
}
function init() {
new QWebChannel(qt.webChannelTransport, function(channel) {
contentManager = channel.objects.contentManager;
app = new Vue({
el: "#app",
data: {
contentManager: contentManager,
books: [],
downloads: {}
},
methods: {
openBook : function(book) {
contentManager.openBook(book.id, function() {});
},
changePage : function(delta) {
var newPage = contentManager.currentPage+delta;
if (newPage < 0) newPage = 0;
if (newPage > contentManager.nbPages) newPage = contentManager.nbPages;
contentManager.currentPage = newPage;
},
downloadBook : function(book) {
contentManager.downloadBook(book.id, function(did) {
book.downloadId = did;
downloadUpdaters[book.id] = setInterval(function() { getDownloadInfo(book.id); }, 1000);
});
},
niceBytes : niceBytes
}
});
contentManager.booksChanged.connect(onBooksChanged);
onBooksChanged();
});
}
</script>
<style>
body {
padding: 0;
margin: 0;
}
*:focus {
outline: none;
}
#searchInput {
background-image: url('qrc:///icons/search.svg');
background-repeat: no-repeat;
background-position: left top;
background-size: 40px 40px;
padding: 0px;
margin: 0px;
padding-left: 45px;
height: 40px;
width: 90%;
border: 1px solid #EEE;
}
#bookList {
width:100%;
}
.tablerow,
.header {
display: flex;
flex-direction: row;
width: 100%;
}
.header {
color: #555;
margin-top: 20px;
}
.tablecell{
flex-basis:20%;
}
.cell0 {
flex-basis: 60px;
flex-grow: 0;
flex-shrink: 0;
}
.tablerow > .cell1 {
font-weight: bold;
}
.cell0 > img {
width: 24px;
height: 24px;
}
.cell1,
.cell2,
.cell3,
.cell4,
.cell5 {
flex-grow: 1;
}
summary::-webkit-details-marker {
display: none
}
summary {
height: 64px;
}
.book {
border-top: 1px solid #EEE;
padding: 10px;
}
button {
background: transparent;
border: 0px;
}
.tablerow button {
color: blue;
font-weight: bold;
font-size: 14px;
border: 0px;
background: transparent;
border-radius: 2px;
}
.tablerow button:hover {
color: white;
background: blue;
}
</style>
</head>
<body onload="init()">
<div id="app">
<div id="searchBar">
<form>
<input id="searchInput" type="text" placeholder="Search files" readonly/>
</form>
</div>
<div id="bookList">
<div class="header">
<span class="tablecell cell0"></span>
<span class="tablecell cell1">File name</span>
<span class="tablecell cell2">Size</span>
<span class="tablecell cell3">Date</span>
<span class="tablecell cell4">Content type</span>
<span class="tablecell cell5"></span>
</div>
<details v-for="book in books" class="book">
<summary class="tablerow">
<span class="tablecell cell0">
<img v-bind:src="'data:image/png;base64,' + book.favicon"/>
</span>
<span class="tablecell cell1">
{{ book.title }}
</span>
<span class="tablecell cell2">
{{ niceBytes(book.size) }}
</span>
<span class="tablecell cell3">
{{ book.date }}
</span>
<span class="tablecell cell4">
{{ book.tags }}
</span>
<span class="tablecell cell5">
<template v-if="book.downloadId">
<span v-if="downloads[book.id]">
{{ niceBytes(downloads[book.id].completedLength) }} / {{ niceBytes(downloads[book.id].totalLength) }}
</span>
</template>
<button v-else-if="book.path" v-on:click="openBook(book)">Open</button>
<button v-else v-on:click="downloadBook(book)">Download</button>
</span>
</summary>
<p class="content">
{{ book.description }}
</p>
</details>
<div class="footer">
<button v-on:click="contentManager.currentPage = 0">First</button>
<button v-on:click="changePage(-1)">Previous</button>
{{ contentManager.currentPage+1 }} / {{ contentManager.nbPages+1 }}
<button v-on:click="changePage(1)">Next</button>
<button v-on:click="contentManager.currentPage = contentManager.nbPages">Last</button>
</div>
</div>
</div>
</body></html>

238
src/contentmanager.cpp Normal file
View File

@ -0,0 +1,238 @@
#include "contentmanager.h"
#include "kiwixapp.h"
#include <kiwix/common/networkTools.h>
#include <kiwix/common/otherTools.h>
#include <kiwix/manager.h>
#include <QDebug>
#include <QUrlQuery>
#include <QUrl>
ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader, QObject *parent)
: QObject(parent),
mp_library(library),
mp_downloader(downloader)
{
// mp_view will be passed to the tab who will take ownership,
// so, we don't need to delete it.
mp_view = new ContentManagerView();
mp_view->registerObject("contentManager", this);
mp_view->setHtml();
setCurrentLanguage(QLocale().name().split("_").at(0));
connect(mp_library, &Library::booksChanged, this, [=]() {emit(this->booksChanged());});
connect(this, &ContentManager::remoteParamsChanged, this, &ContentManager::updateRemoteLibrary);
}
void ContentManager::setLocal(bool local) {
if (local == m_local) {
return;
}
m_local = local;
m_currentPage = 0;
emit(remoteParamsChanged());
emit(booksChanged());
}
#define ADD_V(KEY, METH) {if(key==KEY) values.append(QString::fromStdString((b.METH())));}
QStringList ContentManager::getBookInfos(QString id, const QStringList &keys)
{
QStringList values;
if (id.endsWith(".zim")) {
id.resize(id.size()-4);
}
kiwix::Book& b = [=]()->kiwix::Book& {
try {
return mp_library->getBookById(id);
} catch (...) {
return m_remoteLibrary.getBookById(id.toStdString());
}
}();
for(auto& key: keys){
ADD_V("id", getId);
ADD_V("path", getPath);
ADD_V("indexPath", getIndexPath);
ADD_V("title", getTitle);
ADD_V("description", getDescription);
ADD_V("language", getLanguage);
ADD_V("creator", getCreator);
ADD_V("publisher", getPublisher);
ADD_V("date", getDate);
ADD_V("url", getUrl);
ADD_V("name", getName);
ADD_V("tags", getTags);
ADD_V("origId", getOrigId);
ADD_V("faviconMimeType", getFaviconMimeType);
ADD_V("downloadId", getDownloadId);
if (key == "favicon") {
auto s = b.getFavicon();
values.append(QByteArray::fromStdString(s).toBase64());
}
if (key == "size") {
values.append(QString::number(b.getSize()));
}
if (key == "articleCount") {
values.append(QString::number(b.getArticleCount()));
}
if (key == "mediaCount") {
values.append(QString::number(b.getMediaCount()));
}
}
return values;
}
#undef ADD_V
void ContentManager::openBook(const QString &id)
{
QUrl url("zim://"+id+".zim/");
KiwixApp::instance()->openUrl(url, true);
}
#define ADD_V(KEY, METH) {if(key==KEY) {values.append(QString::fromStdString((d->METH()))); continue;}}
QStringList ContentManager::updateDownloadInfos(QString id, const QStringList &keys)
{
QStringList values;
if (id.endsWith(".zim")) {
id.resize(id.size()-4);
}
auto& b = mp_library->getBookById(id);
kiwix::Download* d;
try {
d = mp_downloader->getDownload(b.getDownloadId());
} catch(...) {
b.setDownloadId("");
mp_library->save();
emit(mp_library->booksChanged());
return values;
}
d->updateStatus(true);
if (d->getStatus() == kiwix::Download::K_COMPLETE) {
b.setPath(d->getPath());
b.setDownloadId("");
mp_library->save();
emit(mp_library->booksChanged());
}
for(auto& key: keys){
ADD_V("id", getDid);
if(key == "status") {
switch(d->getStatus()){
case kiwix::Download::K_ACTIVE:
values.append("active");
break;
case kiwix::Download::K_WAITING:
values.append("waiting");
break;
case kiwix::Download::K_PAUSED:
values.append("paused");
break;
case kiwix::Download::K_ERROR:
values.append("error");
break;
case kiwix::Download::K_COMPLETE:
values.append("completed");
break;
case kiwix::Download::K_REMOVED:
values.append("removed");
break;
default:
values.append("unknown");
}
continue;
}
ADD_V("followedBy", getFollowedBy);
ADD_V("path", getPath);
if(key == "totalLength") {
values.append(QString::number(d->getTotalLength()));
}
if(key == "completedLength") {
values.append(QString::number(d->getCompletedLength()));
}
if(key == "downloadSpeed") {
values.append(QString::number(d->getDownloadSpeed()));
}
if(key == "verifiedLength") {
values.append(QString::number(d->getVerifiedLength()));
}
}
return values;
}
#undef ADD_V
QString ContentManager::downloadBook(const QString &id)
{
auto& book = [&]()->kiwix::Book& {
try {
return m_remoteLibrary.getBookById(id.toStdString());
} catch (...) {
return mp_library->getBookById(id);
}
}();
auto download = mp_downloader->startDownload(book.getUrl());
book.setDownloadId(download->getDid());
mp_library->addBookToLibrary(book);
mp_library->save();
emit(mp_library->booksChanged());
emit(booksChanged());
return QString::fromStdString(download->getDid());
}
QStringList ContentManager::getDownloadIds()
{
QStringList list;
for(auto& id: mp_downloader->getDownloadIds()) {
list.append(QString::fromStdString(id));
}
return list;
}
void ContentManager::setCurrentLanguage(QString language)
{
if (language.length() == 2) {
try {
language = QString::fromStdString(
kiwix::converta2toa3(language.toStdString()));
} catch (std::out_of_range&) {}
}
m_currentLanguage = language;
emit(currentLangChanged());
}
#define CATALOG_HOST "http://library.kiwix.org"
void ContentManager::updateRemoteLibrary() {
QUrlQuery query;
query.addQueryItem("lang", m_currentLanguage);
query.addQueryItem("count", QString::number(m_booksPerPage));
query.addQueryItem("start", QString::number(getStartBookIndex()));
QUrl url;
url.setScheme("http");
url.setHost("localhost");
url.setPort(8080);
url.setPath("/catalog/search.xml");
url.setQuery(query);
qInfo() << "Downloading" << url;
kiwix::Manager manager(&m_remoteLibrary);
try {
auto allContent = kiwix::download(url.toString().toStdString());
manager.readOpds(allContent, CATALOG_HOST);
} catch (runtime_error&) {}
}
QStringList ContentManager::getBookIds() {
if (m_local) {
return mp_library->getBookIds().mid(getStartBookIndex(), m_booksPerPage);
} else {
auto bookIds = m_remoteLibrary.getBooksIds();
QStringList list;
for(auto i=0; i<m_booksPerPage; i++) {
try{
list.append(QString::fromStdString(bookIds.at(getStartBookIndex()+i)));
} catch (out_of_range& e) {
break;
}
}
return list;
}
}

73
src/contentmanager.h Normal file
View File

@ -0,0 +1,73 @@
#ifndef CONTENTMANAGER_H
#define CONTENTMANAGER_H
#include <QObject>
#include <math.h>
#include "library.h"
#include "contentmanagerview.h"
#include <kiwix/downloader.h>
class ContentManager : public QObject
{
Q_OBJECT
Q_PROPERTY(int booksPerPage MEMBER m_booksPerPage NOTIFY booksChanged)
Q_PROPERTY(int nbPages READ getNbPages NOTIFY booksChanged)
Q_PROPERTY(int currentPage MEMBER m_currentPage WRITE setCurrentPage NOTIFY booksChanged)
Q_PROPERTY(int startBookIndex READ getStartBookIndex NOTIFY booksChanged)
Q_PROPERTY(int endBookIndex READ getEndBookIndex NOTIFY booksChanged)
Q_PROPERTY(QStringList bookIds READ getBookIds NOTIFY booksChanged)
Q_PROPERTY(QStringList downloadIds READ getDownloadIds NOTIFY downloadsChanged)
Q_PROPERTY(QString currentLanguage MEMBER m_currentLanguage WRITE setCurrentLanguage NOTIFY currentLangChanged)
public:
explicit ContentManager(Library* library, kiwix::Downloader *downloader, QObject *parent = nullptr);
virtual ~ContentManager() {}
ContentManagerView* getView() { return mp_view; }
void setLocal(bool local);
QStringList getDownloadIds();
private:
Library* mp_library;
kiwix::Library m_remoteLibrary;
kiwix::Downloader* mp_downloader;
ContentManagerView* mp_view;
int m_booksPerPage = 10;
int m_currentPage = 0;
bool m_local = true;
QString m_currentLanguage;
void setCurrentPage(int currentPage) {
m_currentPage = max(0, min(currentPage, getNbPages()));
emit(booksChanged());
}
void setCurrentLanguage(QString language);
QStringList getBookIds();
signals:
void booksChanged();
void downloadsChanged();
void currentLangChanged();
public slots:
int getNbPages() {
if (m_local) {
return round(float(mp_library->getBookIds().length()) / m_booksPerPage);
} else {
return round(float(m_remoteLibrary.getBooksIds().size()) / m_booksPerPage);
}
}
int getStartBookIndex() {
return m_currentPage * m_booksPerPage;
}
int getEndBookIndex() {
return min((m_currentPage+1) * m_booksPerPage, mp_library->getBookIds().length());
}
QStringList getBookInfos(QString id, const QStringList &keys);
void openBook(const QString& id);
QStringList updateDownloadInfos(QString id, const QStringList& keys);
QString downloadBook(const QString& id);
void updateRemoteLibrary();
};
#endif // CONTENTMANAGER_H

View File

@ -0,0 +1,25 @@
#include "contentmanagerside.h"
#include "ui_contentmanagerside.h"
ContentManagerSide::ContentManagerSide(QWidget *parent) :
QWidget(parent),
mp_ui(new Ui::contentmanagerside)
{
mp_ui->setupUi(this);
connect(mp_ui->allFileButton, &QRadioButton::toggled,
this, [=](bool checked) { this->mp_contentManager->setLocal(!checked); });
connect(mp_ui->localFileButton, &QRadioButton::toggled,
this, [=](bool checked) { this->mp_contentManager->setLocal(checked); });
connect(mp_ui->allFileButton, &QRadioButton::toggled,
this, [=](bool checked) { mp_ui->allFileButton->setStyleSheet(
checked ? "*{font-weight: bold}" : "");});
connect(mp_ui->localFileButton, &QRadioButton::toggled,
this, [=](bool checked) { mp_ui->localFileButton->setStyleSheet(
checked ?"*{font-weight: bold}" : "");});
mp_ui->localFileButton->setStyleSheet("*{font-weight: bold}");
}
ContentManagerSide::~ContentManagerSide()
{
delete mp_ui;
}

26
src/contentmanagerside.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef CONTENTMANAGERSIDE_H
#define CONTENTMANAGERSIDE_H
#include <QWidget>
#include "contentmanager.h"
namespace Ui {
class contentmanagerside;
}
class ContentManagerSide : public QWidget
{
Q_OBJECT
public:
explicit ContentManagerSide(QWidget *parent = 0);
~ContentManagerSide();
void setContentManager(ContentManager* contentManager) { mp_contentManager = contentManager; }
private:
Ui::contentmanagerside *mp_ui;
ContentManager* mp_contentManager;
};
#endif // CONTENTMANAGERSIDE_H

123
src/contentmanagerside.ui Normal file
View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>contentmanagerside</class>
<widget class="QWidget" name="contentmanagerside">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>197</width>
<height>366</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string/>
</property>
<property name="flat">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QRadioButton" name="allFileButton">
<property name="text">
<string>All Files</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="localFileButton">
<property name="text">
<string>Local Files</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="categoryButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Browse By Category</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="languageButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Language</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="contentTypeButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Content Type</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,24 @@
#include "contentmanagerview.h"
#include <QFile>
ContentManagerView::ContentManagerView(QWidget *parent)
: QWebEngineView(parent)
{
page()->setWebChannel(&m_webChannel);
}
void ContentManagerView::registerObject(const QString& id, QObject* object)
{
m_webChannel.registerObject(id, object);
}
void ContentManagerView::setHtml()
{
QFile contentFile(":texts/_contentManager.html");
contentFile.open(QIODevice::ReadOnly);
auto byteContent = contentFile.readAll();
contentFile.close();
QWebEngineView::setHtml(byteContent);
}

17
src/contentmanagerview.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef CONTENTMANAGERVIEW_H
#define CONTENTMANAGERVIEW_H
#include <QWebEngineView>
#include <QWebChannel>
class ContentManagerView : public QWebEngineView
{
public:
ContentManagerView(QWidget *parent = Q_NULLPTR);
void registerObject(const QString &id, QObject *object);
void setHtml();
private:
QWebChannel m_webChannel;
};
#endif // CONTENTMANAGERVIEW_H

View File

@ -12,7 +12,10 @@
#include <QPrintDialog>
KiwixApp::KiwixApp(int& argc, char *argv[])
: QApplication(argc, argv)
: QApplication(argc, argv),
m_library(),
m_downloader(),
m_manager(&m_library, &m_downloader)
{
m_qtTranslator.load(QLocale(), "qt", "_",
QLibraryInfo::location(QLibraryInfo::TranslationsPath));
@ -68,7 +71,10 @@ KiwixApp::KiwixApp(int& argc, char *argv[])
createAction();
mp_mainWindow = new MainWindow;
mp_tabWidget = mp_mainWindow->getTabWidget();
mp_tabWidget = mp_mainWindow->getTabBar();
mp_tabWidget->setContentManagerView(m_manager.getView());
mp_mainWindow->getSideContentManager()->setContentManager(&m_manager);
setSideBar(CONTENTMANAGER_BAR);
postInit();
mp_errorDialog = new QErrorMessage(mp_mainWindow);
@ -77,6 +83,7 @@ KiwixApp::KiwixApp(int& argc, char *argv[])
KiwixApp::~KiwixApp()
{
m_downloader.close();
delete mp_errorDialog;
delete mp_mainWindow;
}
@ -101,7 +108,7 @@ void KiwixApp::openZimFile(const QString &zimfile)
}
QString zimId;
try {
zimId = m_library.openBook(_zimfile);
zimId = m_library.openBookFromPath(_zimfile);
} catch (const std::exception& e) {
showMessage("Cannot open " + _zimfile + ": \n" + e.what());
return;
@ -130,10 +137,29 @@ void KiwixApp::printPage()
}
}
void KiwixApp::openUrl(const QString &url, bool newTab) {
openUrl(QUrl(url), newTab);
}
void KiwixApp::openUrl(const QUrl &url, bool newTab) {
mp_tabWidget->openUrl(url, newTab);
}
void KiwixApp::setSideBar(KiwixApp::SideBarType type)
{
auto sideDockWidget = mp_mainWindow->getSideDockWidget();
switch(type) {
case SEARCH_BAR:
case CONTENTMANAGER_BAR:
sideDockWidget->setCurrentIndex(type);
sideDockWidget->show();
break;
case NONE:
sideDockWidget->hide();
break;
}
}
void KiwixApp::openRandomUrl(bool newTab)
{
auto zimId = mp_tabWidget->currentZimId();
@ -220,6 +246,8 @@ void KiwixApp::createAction()
CREATE_ACTION(FindInPageAction, tr("Find in page"));
SET_SHORTCUT(FindInPageAction, QKeySequence::Find);
connect(mpa_actions[FindInPageAction], &QAction::triggered,
this, [=]() { setSideBar(SEARCH_BAR); });
CREATE_ACTION_ICON(ToggleFullscreenAction, "full-screen-enter", tr("Set fullScreen"));
SET_SHORTCUT(ToggleFullscreenAction, QKeySequence::FullScreen);
@ -277,9 +305,5 @@ void KiwixApp::createAction()
}
void KiwixApp::postInit() {
auto realToggleAction = mp_mainWindow->getSideDockWidget()->toggleViewAction();
auto proxyToggleAction = mpa_actions[FindInPageAction];
connect(proxyToggleAction, &QAction::triggered, realToggleAction, &QAction::trigger);
connect(realToggleAction, &QAction::toggled, proxyToggleAction, &QAction::setChecked);
realToggleAction->toggle();
emit(m_library.booksChanged());
}

View File

@ -2,8 +2,10 @@
#define KIWIXAPP_H
#include "library.h"
#include "contentmanager.h"
#include "mainwindow.h"
#include "tabwidget.h"
#include "kiwix/downloader.h"
#include "tabbar.h"
#include "tocsidebar.h"
#include "urlschemehandler.h"
#include "requestinterceptor.h"
@ -47,12 +49,16 @@ public:
ExitAction,
MAX_ACTION
};
enum SideBarType {
SEARCH_BAR,
CONTENTMANAGER_BAR,
NONE
};
KiwixApp(int& argc, char *argv[]);
virtual ~KiwixApp();
static KiwixApp* instance();
void openUrl(const QUrl& url, bool newTab=true);
void openRandomUrl(bool newTab=true);
void showMessage(const QString& message);
@ -61,11 +67,15 @@ public:
RequestInterceptor* getRequestInterceptor() { return &m_requestInterceptor; }
Library* getLibrary() { return &m_library; }
MainWindow* getMainWindow() { return mp_mainWindow; }
TabWidget* getTabWidget() { return mp_tabWidget; }
kiwix::Downloader* getDownloader() { return &m_downloader; }
TabBar* getTabWidget() { return mp_tabWidget; }
QAction* getAction(Actions action);
public slots:
void openZimFile(const QString& zimfile="");
void openUrl(const QString& url, bool newTab=true);
void openUrl(const QUrl& url, bool newTab=true);
void setSideBar(SideBarType type);
void printPage();
protected:
@ -75,8 +85,11 @@ protected:
private:
QTranslator m_qtTranslator, m_appTranslator;
Library m_library;
kiwix::Downloader m_downloader;
ContentManager m_manager;
MainWindow* mp_mainWindow;
TabWidget* mp_tabWidget;
TabBar* mp_tabWidget;
QWidget* mp_currentSideBar;
QErrorMessage* mp_errorDialog;
UrlSchemeHandler m_schemeHandler;

View File

@ -1,25 +1,65 @@
#include "library.h"
#include <kiwix/manager.h>
#include <QtDebug>
class LibraryManipulator: public kiwix::LibraryManipulator {
public:
LibraryManipulator(Library* p_library)
: mp_library(p_library) {}
bool addBookToLibrary(kiwix::Book book) {
auto ret = mp_library->m_library.addBook(book);
emit(mp_library->booksChanged());
return ret;
}
Library* mp_library;
};
Library::Library()
{
auto manipulator = LibraryManipulator(this);
auto manager = kiwix::Manager(&manipulator);
qInfo() << QString::fromStdString(getDataDirectory());
manager.readFile(appendToDirectory(getDataDirectory(),"library.xml"), false);
qInfo() << getBookIds().length();
emit(booksChanged());
}
QString Library::openBook(const QString &zimPath)
Library::~Library()
{
save();
}
QString Library::openBookFromPath(const QString &zimPath)
{
for(auto it=m_readersMap.begin();
it != m_readersMap.end();
it++)
{
if(QString::fromStdString(it->second->getZimFilePath()) == zimPath)
return it->first;
if(QString::fromStdString(it.value()->getZimFilePath()) == zimPath)
return it.key();
}
qInfo() << "Opening" << zimPath;
auto zimPath_ = zimPath.toStdString();
auto reader = std::shared_ptr<kiwix::Reader>(new kiwix::Reader(zimPath_));
auto id = QString::fromStdString(reader->getId() + ".zim");
auto _id(reader->getId());
auto id = QString::fromStdString(_id + ".zim");
kiwix::Book b;
b.update(*reader);
m_library.addBook(b);
m_readersMap[id] = reader;
save();
emit(booksChanged());
return id;
}
QString Library::openBookById(const QString& _id)
{
auto& b = m_library.getBookById(_id.toStdString());
auto reader = std::shared_ptr<kiwix::Reader>(new kiwix::Reader(b.getPath()));
auto id = _id + ".zim";
m_readersMap[id] = reader;
return id;
}
@ -28,6 +68,40 @@ std::shared_ptr<kiwix::Reader> Library::getReader(const QString &zimId)
{
auto it = m_readersMap.find(zimId);
if (it != m_readersMap.end())
return it->second;
return it.value();
// No reader, try to open the file
try {
QString _id = zimId;
if (_id.endsWith(".zim")) _id.resize(_id.size()-4);
openBookById(_id);
return m_readersMap.find(zimId).value();
} catch(...) {}
return nullptr;
}
QStringList Library::getBookIds()
{
QStringList list;
for(auto& id: m_library.getBooksIds()) {
list.append(QString::fromStdString(id));
}
return list;
}
void Library::addBookToLibrary(kiwix::Book &book)
{
m_library.addBook(book);
}
void Library::save()
{
m_library.writeToFile(appendToDirectory(getDataDirectory(),"library.xml"));
}
kiwix::Book &Library::getBookById(QString id)
{
if (id.endsWith(".zim")) {
id.resize(id.size()-4);
}
return m_library.getBookById(id.toStdString());
}

View File

@ -1,18 +1,47 @@
#ifndef LIBRARY_H
#define LIBRARY_H
#include <kiwix/manager.h>
#include <map>
#include <kiwix/book.h>
#include <kiwix/library.h>
#include <kiwix/reader.h>
#include <qstring.h>
#include <memory>
class Library : public kiwix::Manager
#include <QObject>
#include <QSharedPointer>
#include <QMap>
#define TQS(v) (QString::fromStdString(v))
#define FORWARD_GETTER(METH) QString METH() const { return TQS(mp_book->METH()); }
#undef FORWARD_GETTER
#undef TQS
class LibraryManipulator;
class Library : public QObject
{
Q_OBJECT
Q_PROPERTY(QStringList bookIds READ getBookIds NOTIFY booksChanged)
public:
Library();
QString openBook(const QString& zimPath);
virtual ~Library();
QString openBookFromPath(const QString& zimPath);
std::shared_ptr<kiwix::Reader> getReader(const QString& zimId);
QStringList getBookIds();
void addBookToLibrary(kiwix::Book& book);
void save();
public slots:
QString openBookById(const QString& _id);
kiwix::Book& getBookById(QString id);
signals:
void booksChanged();
private:
std::map<QString, std::shared_ptr<kiwix::Reader>> m_readersMap;
kiwix::Library m_library;
QMap<QString, std::shared_ptr<kiwix::Reader>> m_readersMap;
friend class LibraryManipulator;
};
#endif // LIBRARY_H

View File

@ -16,7 +16,7 @@ int main(int argc, char *argv[])
auto positionalArguments = parser.positionalArguments();
if (positionalArguments.size() >= 1){
zimfile = parser.positionalArguments().at(0);
a.openZimFile(zimfile);
}
a.openZimFile(zimfile);
return a.exec();
}

View File

@ -14,7 +14,8 @@ MainWindow::MainWindow(QWidget *parent) :
mp_about(new About(this))
{
mp_ui->setupUi(this);
mp_ui->tabWidget->tabBar()->setExpanding(false);
mp_ui->tabBar->setExpanding(false);
mp_ui->tabBar->setStackedWidget(mp_ui->mainView);
auto app = KiwixApp::instance();
connect(app->getAction(KiwixApp::ExitAction), &QAction::triggered,
this, &QMainWindow::close);
@ -39,12 +40,17 @@ void MainWindow::toggleFullScreen() {
showFullScreen();
}
TabWidget* MainWindow::getTabWidget()
TabBar* MainWindow::getTabBar()
{
return mp_ui->tabWidget;
return mp_ui->tabBar;
}
QDockWidget* MainWindow::getSideDockWidget()
QStackedWidget *MainWindow::getSideDockWidget()
{
return mp_ui->sideDockWidget;
return mp_ui->sideBar;
}
ContentManagerSide *MainWindow::getSideContentManager()
{
return mp_ui->contentmanagerside;
}

View File

@ -4,9 +4,10 @@
#include <QMainWindow>
#include <QDockWidget>
#include "webview.h"
#include "tabwidget.h"
#include "tabbar.h"
#include "tocsidebar.h"
#include "about.h"
#include "contentmanagerside.h"
namespace Ui {
class MainWindow;
@ -20,8 +21,9 @@ public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
TabWidget* getTabWidget();
QDockWidget* getSideDockWidget();
TabBar* getTabBar();
QStackedWidget* getSideDockWidget();
ContentManagerSide* getSideContentManager();
protected slots:
void toggleFullScreen();

View File

@ -1,4 +1,4 @@
#include "tabwidget.h"
#include "tabbar.h"
#include "kiwixapp.h"
#include <QAction>
@ -7,15 +7,15 @@
#define QUITIFNOTCURRENT(VIEW) if((VIEW)!=currentWidget()) {return;}
#define CURRENTIFNULL(VIEW) if(nullptr==VIEW) { VIEW = currentWidget();}
TabWidget::TabWidget(QWidget *parent) :
QTabWidget(parent)
TabBar::TabBar(QWidget *parent) :
QTabBar(parent)
{
setTabsClosable(true);
setElideMode(Qt::ElideNone);
setDocumentMode(true);
setFocusPolicy(Qt::NoFocus);
connect(this, &QTabWidget::tabCloseRequested, this, &TabWidget::closeTab);
connect(this, &QTabWidget::currentChanged, this, &TabWidget::onCurrentChanged);
connect(this, &QTabBar::tabCloseRequested, this, &TabBar::closeTab);
connect(this, &QTabBar::currentChanged, this, &TabBar::onCurrentChanged);
auto app = KiwixApp::instance();
connect(app->getAction(KiwixApp::NewTabAction), &QAction::triggered,
this, [=]() {
@ -27,10 +27,14 @@ TabWidget::TabWidget(QWidget *parent) :
connect(app->getAction(KiwixApp::CloseTabAction), &QAction::triggered,
this, [=]() {
auto index = this->currentIndex();
if (-1 == index) {
if (index <= 0) {
return;
}
this->closeTab(index);
auto widget = mp_stackedWidget->widget(index);
mp_stackedWidget->removeWidget(widget);
widget->setParent(nullptr);
delete widget;
});
connect(app->getAction(KiwixApp::ZoomInAction), &QAction::triggered,
this, [=]() {
@ -58,7 +62,22 @@ TabWidget::TabWidget(QWidget *parent) :
});
}
WebView* TabWidget::createNewTab(bool setCurrent)
void TabBar::setStackedWidget(QStackedWidget *widget) {
mp_stackedWidget = widget;
connect(this, &QTabBar::currentChanged,
widget, &QStackedWidget::setCurrentIndex);
}
void TabBar::setContentManagerView(ContentManagerView* view)
{
qInfo() << "add widget";
mp_contentManagerView = view;
mp_stackedWidget->addWidget(mp_contentManagerView);
mp_stackedWidget->show();
addTab("contentManager");
}
WebView* TabBar::createNewTab(bool setCurrent)
{
WebView* webView = new WebView();
connect(webView, &WebView::titleChanged, this,
@ -72,14 +91,15 @@ WebView* TabWidget::createNewTab(bool setCurrent)
}
);
// Ownership of webview is passed to the tabWidget
addTab(webView, "");
mp_stackedWidget->addWidget(webView);
auto index = addTab("");
if (setCurrent) {
setCurrentWidget(webView);
setCurrentIndex(index);
}
return webView;
}
void TabWidget::openUrl(const QUrl& url, bool newTab)
void TabBar::openUrl(const QUrl& url, bool newTab)
{
WebView* webView = currentWidget();
if (newTab || !webView) {
@ -89,31 +109,31 @@ void TabWidget::openUrl(const QUrl& url, bool newTab)
webView->setUrl(url);
}
void TabWidget::setTitleOf(const QString& title, WebView* webView)
void TabBar::setTitleOf(const QString& title, WebView* webView)
{
CURRENTIFNULL(webView);
if (title.startsWith("zim://")) {
auto url = QUrl(title);
setTabText(indexOf(webView), url.path());
setTabText(mp_stackedWidget->indexOf(webView), url.path());
} else {
setTabText(indexOf(webView), title);
setTabText(mp_stackedWidget->indexOf(webView), title);
}
}
void TabWidget::setIconOf(const QIcon &icon, WebView *webView)
void TabBar::setIconOf(const QIcon &icon, WebView *webView)
{
CURRENTIFNULL(webView);
setTabIcon(indexOf(webView), icon);
setTabIcon(mp_stackedWidget->indexOf(webView), icon);
}
QString TabWidget::currentZimId()
QString TabBar::currentZimId()
{
if (!currentWidget())
return "";
return currentWidget()->zimId();
}
void TabWidget::triggerWebPageAction(QWebEnginePage::WebAction action, WebView *webView)
void TabBar::triggerWebPageAction(QWebEnginePage::WebAction action, WebView *webView)
{
CURRENTIFNULL(webView);
QUITIFNULL(webView);
@ -121,20 +141,29 @@ void TabWidget::triggerWebPageAction(QWebEnginePage::WebAction action, WebView *
webView->setFocus();
}
void TabWidget::closeTab(int index)
void TabBar::closeTab(int index)
{
if (index == 0)
return;
auto webview = widget(index);
removeTab(index);
webview->close();
delete webview;
}
void TabWidget::onCurrentChanged(int index)
void TabBar::onCurrentChanged(int index)
{
if (index != -1)
if (index == -1)
return;
if (index)
{
auto view = widget(index);
emit webActionEnabledChanged(QWebEnginePage::Back, view->isWebActionEnabled(QWebEnginePage::Back));
emit webActionEnabledChanged(QWebEnginePage::Forward, view->isWebActionEnabled(QWebEnginePage::Forward));
KiwixApp::instance()->setSideBar(KiwixApp::NONE);
} else {
emit webActionEnabledChanged(QWebEnginePage::Back, false);
emit webActionEnabledChanged(QWebEnginePage::Forward, false);
KiwixApp::instance()->setSideBar(KiwixApp::CONTENTMANAGER_BAR);
}
}

View File

@ -1,20 +1,27 @@
#ifndef TABWIDGET_H
#define TABWIDGET_H
#include <QTableWidget>
#include <QTabBar>
#include <QStackedWidget>
#include <memory>
#include "webview.h"
#include "contentmanagerview.h"
class TabWidget : public QTabWidget
class TabBar : public QTabBar
{
Q_OBJECT
Q_PROPERTY(QString currentZimId READ currentZimId NOTIFY currentZimIdChanged)
public:
TabWidget(QWidget* parent=nullptr);
TabBar(QWidget* parent=nullptr);
void setStackedWidget(QStackedWidget* widget);
void setContentManagerView(ContentManagerView* view);
WebView* createNewTab(bool setCurrent);
WebView* widget(int index) { return static_cast<WebView*>(QTabWidget::widget(index)); }
WebView* currentWidget() { return static_cast<WebView*>(QTabWidget::currentWidget()); }
WebView* widget(int index) { return (index != 0) ? static_cast<WebView*>(mp_stackedWidget->widget(index)) : nullptr; }
WebView* currentWidget() { auto current = mp_stackedWidget->currentWidget();
if (current == mp_contentManagerView) return nullptr;
return static_cast<WebView*>(current);
}
void openUrl(const QUrl &url, bool newTab);
// Redirect call to sub-webView
@ -30,6 +37,11 @@ signals:
public slots:
void closeTab(int index);
void onCurrentChanged(int index);
private:
ContentManagerView* mp_contentManagerView;
QStackedWidget* mp_stackedWidget;
};
#endif // TABWIDGET_H

View File

@ -8,6 +8,8 @@ TocSideBar::TocSideBar(QWidget *parent) :
{
mp_ui->setupUi(this);
mp_findLineEdit = mp_ui->findEdit;
connect(mp_ui->hideButton, &QPushButton::released,
this, [=]() { KiwixApp::instance()->setSideBar(KiwixApp::NONE);});
connect(mp_ui->fNextButton, &QPushButton::released,
this, &TocSideBar::findNext);
connect(mp_ui->fPreviousButton, &QPushButton::released,

View File

@ -20,75 +20,97 @@
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetMaximumSize</enum>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0,0">
<property name="spacing">
<number>0</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetMaximumSize</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLineEdit" name="findEdit"/>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="hideButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Hide</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="fNextButton">
<property name="text">
<string/>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0">
<property name="spacing">
<number>0</number>
</property>
<property name="icon">
<iconset resource="../resources/kiwix.qrc">
<normaloff>:/icons/search_forward.svg</normaloff>:/icons/search_forward.svg</iconset>
<property name="sizeConstraint">
<enum>QLayout::SetMaximumSize</enum>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
<item>
<widget class="QLineEdit" name="findEdit"/>
</item>
<item>
<widget class="QPushButton" name="fNextButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../resources/kiwix.qrc">
<normaloff>:/icons/search_forward.svg</normaloff>:/icons/search_forward.svg</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="fPreviousButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../resources/kiwix.qrc">
<normaloff>:/icons/search_backward.svg</normaloff>:/icons/search_backward.svg</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="fPreviousButton">
<property name="text">
<string/>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="icon">
<iconset resource="../resources/kiwix.qrc">
<normaloff>:/icons/search_backward.svg</normaloff>:/icons/search_backward.svg</iconset>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</spacer>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources>

View File

@ -24,6 +24,9 @@
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
@ -37,21 +40,58 @@
<number>0</number>
</property>
<item>
<widget class="TabWidget" name="tabWidget">
<property name="tabShape">
<enum>QTabWidget::Rounded</enum>
<widget class="TabBar" name="tabBar" native="true"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="currentIndex">
<number>-1</number>
</property>
<property name="documentMode">
<bool>false</bool>
</property>
</widget>
<item>
<widget class="QStackedWidget" name="sideBar">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<widget class="TocSideBar" name="tocsidebar"/>
<widget class="ContentManagerSide" name="contentmanagerside">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</widget>
</item>
<item>
<widget class="QStackedWidget" name="mainView"/>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="TopWidget" name="mainToolBar">
<property name="movable">
<bool>false</bool>
</property>
<property name="floatable">
<bool>false</bool>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
@ -60,39 +100,6 @@
</attribute>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<widget class="QDockWidget" name="sideDockWidget">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="floating">
<bool>false</bool>
</property>
<property name="features">
<set>QDockWidget::DockWidgetClosable</set>
</property>
<property name="allowedAreas">
<set>Qt::LeftDockWidgetArea</set>
</property>
<property name="windowTitle">
<string>Find in page</string>
</property>
<attribute name="dockWidgetArea">
<number>1</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="TocSideBar" name="tocPage" native="true"/>
</item>
</layout>
</widget>
</widget>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
@ -101,18 +108,24 @@
<extends>QToolBar</extends>
<header>src/topwidget.h</header>
</customwidget>
<customwidget>
<class>TabWidget</class>
<extends>QTabWidget</extends>
<header>src/tabwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>TocSideBar</class>
<extends>QWidget</extends>
<header>src/tocsidebar.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>TabBar</class>
<extends>QWidget</extends>
<header>src/tabbar.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ContentManagerSide</class>
<extends>QWidget</extends>
<header>src/contentmanagerside.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>