Commit 487ce0ef authored by ChangZhuo Chen's avatar ChangZhuo Chen

New upstream version 0.6.1

parent 448f0adc
# Configuration for the Lock Threads app - https://probot.github.io/apps/lock/
daysUntilLock: 180
lockComment: >
This thread has been automatically locked since there has not been
any recent activity after it was closed. Please open a new issue for
a related request.
setLockReason: false
only: issues
......@@ -30,3 +30,9 @@ Makefile*
# CMake
CMakeLists.txt.user
# VS Code
/.vscode
# Linux appdata
/assets/freedesktop/org.zealdocs.Zeal.appdata.xml
cmake_minimum_required(VERSION 3.5.1)
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
project(Zeal VERSION 0.6.0)
project(Zeal VERSION 0.6.1)
set(RELEASE_DATE 2018-09-28)
# Project information.
if(APPLE)
......@@ -11,7 +12,7 @@ else()
endif()
set(PROJECT_COMPANY_NAME "ZealDocs")
string(TIMESTAMP PROJECT_COPYRIGHT "© 2015-%Y Oleg Shparber" UTC)
set(PROJECT_COPYRIGHT "© 2015-2018 Oleg Shparber")
set(PROJECT_DESCRIPTION "A simple documentation browser.")
set(PROJECT_URL "https://zealdocs.org")
......
if(UNIX AND NOT APPLE)
find_package(ECM REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_KDE_MODULE_DIR})
include(KDEInstallDirs)
foreach(_i 16 24 32 64 128)
install(FILES "freedesktop/appicons/${_i}/zeal.png"
DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/${_i}x${_i}/apps"
# TODO: Use `RENAME zeal.png` and get rid of subdirectories.
)
endforeach()
# TODO: Generate via zeal.desktop.in.
install(FILES "freedesktop/zeal.desktop"
DESTINATION ${KDE_INSTALL_APPDIR}
)
endif()
add_subdirectory(freedesktop)
if(UNIX AND NOT APPLE)
find_package(ECM 1.0.0 REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
include(ECMInstallIcons)
include(KDEInstallDirs)
ecm_install_icons(ICONS "16-apps-zeal.png"
"24-apps-zeal.png"
"32-apps-zeal.png"
"64-apps-zeal.png"
"128-apps-zeal.png"
DESTINATION ${KDE_INSTALL_ICONDIR}
)
configure_file(
org.zealdocs.Zeal.appdata.xml.in
org.zealdocs.Zeal.appdata.xml
)
install(FILES ${CMAKE_BINARY_DIR}/assets/freedesktop/org.zealdocs.Zeal.appdata.xml
DESTINATION ${KDE_INSTALL_METAINFODIR}
)
install(FILES "org.zealdocs.Zeal.desktop"
DESTINATION ${KDE_INSTALL_APPDIR}
)
endif()
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop">
<id>org.zealdocs.Zeal.desktop</id>
<name>Zeal</name>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0-only</project_license>
<summary>Documentation browser</summary>
<description>
<p>Zeal is a simple offline documentation browser inspired by Dash.</p>
</description>
<url type="homepage">https://zealdocs.org/</url>
<url type="bugtracker">https://github.com/zealdocs/zeal/issues</url>
<url type="help">https://zealdocs.org/usage.html</url>
<screenshots>
<screenshot type="default">
<image>https://i.imgur.com/qBkZduS.png</image>
</screenshot>
</screenshots>
<provides>
<id>zeal.desktop</id>
</provides>
<releases>
<release date="${RELEASE_DATE}" version="${Zeal_VERSION}" />
</releases>
<update_contact>zeal@zealdocs.org</update_contact>
<content_rating type="oars-1.1">
<content_attribute id="violence-cartoon">none</content_attribute>
<content_attribute id="violence-fantasy">none</content_attribute>
<content_attribute id="violence-realistic">none</content_attribute>
<content_attribute id="violence-bloodshed">none</content_attribute>
<content_attribute id="violence-sexual">none</content_attribute>
<content_attribute id="violence-desecration">none</content_attribute>
<content_attribute id="violence-slavery">none</content_attribute>
<content_attribute id="violence-worship">none</content_attribute>
<content_attribute id="drugs-alcohol">none</content_attribute>
<content_attribute id="drugs-narcotics">none</content_attribute>
<content_attribute id="drugs-tobacco">none</content_attribute>
<content_attribute id="sex-nudity">none</content_attribute>
<content_attribute id="sex-themes">none</content_attribute>
<content_attribute id="sex-homosexuality">none</content_attribute>
<content_attribute id="sex-prostitution">none</content_attribute>
<content_attribute id="sex-adultery">none</content_attribute>
<content_attribute id="sex-appearance">none</content_attribute>
<content_attribute id="language-profanity">none</content_attribute>
<content_attribute id="language-humor">none</content_attribute>
<content_attribute id="language-discrimination">none</content_attribute>
<content_attribute id="social-chat">none</content_attribute>
<content_attribute id="social-info">none</content_attribute>
<content_attribute id="social-audio">none</content_attribute>
<content_attribute id="social-location">none</content_attribute>
<content_attribute id="social-contacts">none</content_attribute>
<content_attribute id="money-purchasing">none</content_attribute>
<content_attribute id="money-gambling">none</content_attribute>
</content_rating>
</component>
......@@ -27,6 +27,8 @@
#include <archive.h>
#include <archive_entry.h>
#include <sys/stat.h>
using namespace Zeal::Core;
Extractor::Extractor(QObject *parent) :
......@@ -34,31 +36,28 @@ Extractor::Extractor(QObject *parent) :
{
}
void Extractor::extract(const QString &filePath, const QString &destination, const QString &root)
void Extractor::extract(const QString &sourceFile, const QString &destination, const QString &root)
{
ExtractInfo info = {
this, // extractor
archive_read_new(), // archiveHandle
filePath, // filePath
QFileInfo(filePath).size(), // totalBytes
sourceFile, // filePath
QFileInfo(sourceFile).size(), // totalBytes
0 // extractedBytes
};
archive_read_support_filter_all(info.archiveHandle);
archive_read_support_format_all(info.archiveHandle);
int r = archive_read_open_filename(info.archiveHandle, qPrintable(filePath), 10240);
int r = archive_read_open_filename(info.archiveHandle, qPrintable(sourceFile), 10240);
if (r) {
emit error(filePath, QString::fromLocal8Bit(archive_error_string(info.archiveHandle)));
emit error(sourceFile, QString::fromLocal8Bit(archive_error_string(info.archiveHandle)));
return;
}
archive_read_extract_set_progress_callback(info.archiveHandle, &Extractor::progressCallback,
&info);
QDir destinationDir(destination);
if (!root.isEmpty())
if (!root.isEmpty()) {
destinationDir = destinationDir.filePath(root);
}
// TODO: Do not strip root directory in archive if it equals to 'root'
archive_entry *entry;
......@@ -69,25 +68,62 @@ void Extractor::extract(const QString &filePath, const QString &destination, con
// TODO: Remove once https://github.com/libarchive/libarchive/issues/587 is resolved.
QString pathname = QString::fromWCharArray(archive_entry_pathname_w(entry));
#endif
if (!root.isEmpty())
if (!root.isEmpty()) {
pathname.remove(0, pathname.indexOf(QLatin1String("/")) + 1);
archive_entry_set_pathname(entry, qPrintable(destinationDir.filePath(pathname)));
archive_read_extract(info.archiveHandle, entry, 0);
}
const QString filePath = destinationDir.absoluteFilePath(pathname);
const auto filetype = archive_entry_filetype(entry);
if (filetype == S_IFDIR) {
QDir().mkpath(QFileInfo(filePath).absolutePath());
continue;
} else if (filetype != S_IFREG) {
qWarning("Unsupported filetype %d for %s!", filetype, qPrintable(pathname));
continue;
}
QScopedPointer<QFile> file(new QFile(filePath));
if (!file->open(QIODevice::WriteOnly)) {
qWarning("Cannot open file for writing: %s", qPrintable(pathname));
continue;
}
const void *buffer;
size_t size;
std::int64_t offset;
for (;;) {
int rc = archive_read_data_block(info.archiveHandle, &buffer, &size, &offset);
if (rc != ARCHIVE_OK) {
if (rc == ARCHIVE_EOF) {
break;
}
qWarning("Cannot read from archive: %s", archive_error_string(info.archiveHandle));
emit error(sourceFile,
QString::fromLocal8Bit(archive_error_string(info.archiveHandle)));
return;
}
file->write(static_cast<const char *>(buffer), size);
}
emitProgress(info);
}
emit completed(filePath);
emit completed(sourceFile);
archive_read_free(info.archiveHandle);
}
void Extractor::progressCallback(void *ptr)
void Extractor::emitProgress(ExtractInfo &info)
{
ExtractInfo *info = static_cast<ExtractInfo *>(ptr);
const qint64 extractedBytes = archive_filter_bytes(info->archiveHandle, -1);
if (extractedBytes == info->extractedBytes)
const qint64 extractedBytes = archive_filter_bytes(info.archiveHandle, -1);
if (extractedBytes == info.extractedBytes) {
return;
}
info->extractedBytes = extractedBytes;
info.extractedBytes = extractedBytes;
emit info->extractor->progress(info->filePath, extractedBytes, info->totalBytes);
emit progress(info.filePath, extractedBytes, info.totalBytes);
}
......@@ -37,7 +37,9 @@ public:
explicit Extractor(QObject *parent = nullptr);
public slots:
void extract(const QString &filePath, const QString &destination, const QString &root = QString());
void extract(const QString &sourceFile,
const QString &destination,
const QString &root = QString());
signals:
void error(const QString &filePath, const QString &message);
......@@ -46,14 +48,13 @@ signals:
private:
struct ExtractInfo {
Extractor *extractor;
archive *archiveHandle;
QString filePath;
qint64 totalBytes;
qint64 extractedBytes;
};
static void progressCallback(void *ptr);
void emitProgress(ExtractInfo &info);
};
} // namespace Core
......
......@@ -408,6 +408,13 @@ void Docset::countSymbols()
while (m_db->next()) {
const QString symbolTypeStr = m_db->value(0).toString();
// A workaround for https://github.com/zealdocs/zeal/issues/980.
if (symbolTypeStr.isEmpty()) {
qWarning("Empty symbol type in the '%s' docset, skipping...", qPrintable(m_name));
continue;
}
const QString symbolType = parseSymbolType(symbolTypeStr);
m_symbolStrings.insertMulti(symbolType, symbolTypeStr);
m_symbolCounts[symbolType] += m_db->value(1).toInt();
......
......@@ -91,23 +91,14 @@ bool SearchQuery::hasKeywords() const
return !m_keywords.isEmpty();
}
bool SearchQuery::hasKeyword(const QString &keyword) const
{
// Temporary workaround for #333
// TODO: Remove once #167 is implemented
for (const QString &kw : m_keywords) {
if (keyword.startsWith(kw, Qt::CaseInsensitive))
return true;
}
return false;
}
bool SearchQuery::hasKeywords(const QStringList &keywords) const
{
for (const QString &keyword : keywords) {
if (m_keywords.contains(keyword))
if (m_keywords.contains(keyword, Qt::CaseInsensitive)) {
return true;
}
}
return false;
}
......
......@@ -66,8 +66,7 @@ public:
/// Returns true if there's a docset filter for the given query
bool hasKeywords() const;
/// Returns true if the docset prefix match the ones given on query
bool hasKeyword(const QString &keyword) const;
/// Returns true if one the query contains one of the @c keywords.
bool hasKeywords(const QStringList &keywords) const;
/// Returns the docset filter raw size for the given query
......
......@@ -75,7 +75,7 @@
<property name="text">
<string>&lt;strong&gt;A simple offline documentation browser&lt;/strong&gt;
&lt;br&gt;&lt;br&gt;
Copyright &amp;copy; Oleg Shparber and other conributors, 2013-2018.
Copyright &amp;copy; Oleg Shparber and other contributors, 2013-2018.
&lt;br&gt;
&lt;a href=&quot;https://zealdocs.org&quot;&gt;zealdocs.org&lt;/a&gt;
&lt;br&gt;
......
......@@ -157,12 +157,19 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) :
setupTabBar();
QShortcut *focusSearch = new QShortcut(QStringLiteral("Ctrl+K"), this);
connect(focusSearch, &QShortcut::activated,
// Setup application wide shortcuts.
// Focus search bar.
QShortcut *shortcut = new QShortcut(QStringLiteral("Ctrl+K"), this);
connect(shortcut, &QShortcut::activated,
ui->lineEdit, static_cast<void (SearchEdit::*)()>(&SearchEdit::setFocus));
QShortcut *duplicate = new QShortcut(QStringLiteral("Ctrl+Alt+T"), this);
connect(duplicate, &QShortcut::activated, this, [this]() { duplicateTab(m_tabBar->currentIndex()); });
shortcut = new QShortcut(QStringLiteral("Ctrl+L"), this);
connect(shortcut, &QShortcut::activated,
ui->lineEdit, static_cast<void (SearchEdit::*)()>(&SearchEdit::setFocus));
// Duplicate current tab.
shortcut = new QShortcut(QStringLiteral("Ctrl+Alt+T"), this);
connect(shortcut, &QShortcut::activated, this, [this]() { duplicateTab(m_tabBar->currentIndex()); });
restoreGeometry(m_settings->windowGeometry);
ui->splitter->restoreState(m_settings->verticalSplitterGeometry);
......@@ -191,7 +198,7 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) :
if (QKeySequence(QKeySequence::Preferences).isEmpty()) {
ui->actionPreferences->setShortcut(QStringLiteral("Ctrl+,"));
} else {
ui->actionPreferences->setShortcut(QKeySequence::Quit);
ui->actionPreferences->setShortcut(QKeySequence::Preferences);
}
connect(ui->actionPreferences, &QAction::triggered, [this]() {
......@@ -879,7 +886,7 @@ void MainWindow::applySettings()
removeTrayIcon();
// Content
QByteArray ba;
QByteArray ba = QByteArrayLiteral("body { background-color: white; }");
if (m_settings->darkModeEnabled) {
QScopedPointer<QFile> file(new QFile(DarkModeCssUrl));
if (file->open(QIODevice::ReadOnly)) {
......
......@@ -143,12 +143,21 @@ void SettingsDialog::chooseCustomCssFile()
void SettingsDialog::chooseDocsetStoragePath()
{
const QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"),
ui->docsetStorageEdit->text());
if (dir.isEmpty())
QString path = QFileDialog::getExistingDirectory(this, tr("Open Directory"),
ui->docsetStorageEdit->text());
if (path.isEmpty()) {
return;
}
#ifdef PORTABLE_BUILD
// Use relative path if selected directory is under the application binary path.
if (path.startsWith(QCoreApplication::applicationDirPath() + QLatin1String("/"))) {
const QDir appDirPath(QCoreApplication::applicationDirPath());
path = appDirPath.relativeFilePath(path);
}
#endif
ui->docsetStorageEdit->setText(QDir::toNativeSeparators(dir));
ui->docsetStorageEdit->setText(QDir::toNativeSeparators(path));
}
void SettingsDialog::loadSettings()
......@@ -256,7 +265,7 @@ void SettingsDialog::saveSettings()
settings->fixedFontFamily = ui->fixedFontComboBox->currentText();
settings->defaultFontSize = ui->fontSizeComboBox->currentData().toInt();
settings->defaultFixedFontSize = ui->fixedFontComboBox->currentData().toInt();
settings->defaultFixedFontSize = ui->fixedFontSizeComboBox->currentData().toInt();
settings->minimumFontSize = ui->minFontSizeComboBox->currentData().toInt();
settings->darkModeEnabled = ui->darkModeCheckBox->isChecked();
......
......@@ -72,7 +72,7 @@ const QVector<int> &WebView::availableZoomLevels()
return zoomLevels;
}
const int WebView::defaultZoomLevel()
const int &WebView::defaultZoomLevel()
{
static const int level = availableZoomLevels().indexOf(100);
return level;
......@@ -122,14 +122,20 @@ void WebView::contextMenuEvent(QContextMenuEvent *event)
const QString linkText = hitTestResult.linkText();
if (linkUrl.isValid()) {
m_contextMenu->addAction(tr("Open Link in New Tab"), this, [this]() {
triggerPageAction(QWebPage::WebAction::OpenLinkInNewWindow);
});
const QString scheme = linkUrl.scheme();
if (linkUrl.scheme() != QLatin1String("qrc")) {
m_contextMenu->addAction(tr("Open Link in Desktop Browser"), this, [linkUrl]() {
QDesktopServices::openUrl(linkUrl);
if (scheme != QLatin1String("javascript")) {
m_contextMenu->addAction(tr("Open Link in New Tab"), this, [this]() {
triggerPageAction(QWebPage::WebAction::OpenLinkInNewWindow);
});
}
if (scheme != QLatin1String("qrc")) {
if (scheme != QLatin1String("javascript")) {
m_contextMenu->addAction(tr("Open Link in Desktop Browser"), this, [linkUrl]() {
QDesktopServices::openUrl(linkUrl);
});
}
m_contextMenu->addAction(pageAction(QWebPage::CopyLinkToClipboard));
}
......@@ -148,6 +154,10 @@ void WebView::contextMenuEvent(QContextMenuEvent *event)
m_contextMenu->addSeparator();
}
m_contextMenu->addAction(pageAction(QWebPage::Back));
m_contextMenu->addAction(pageAction(QWebPage::Forward));
m_contextMenu->addSeparator();
m_contextMenu->addAction(tr("Open Page in Desktop Browser"), this, [this]() {
QDesktopServices::openUrl(url());
});
......@@ -177,7 +187,7 @@ void WebView::mousePressEvent(QMouseEvent *event)
case Qt::LeftButton:
case Qt::MiddleButton:
m_clickedLink = hitTestContent(event->pos()).linkUrl();
if (!m_clickedLink.isValid()) {
if (!m_clickedLink.isValid() || m_clickedLink.scheme() == QLatin1String("javascript")) {
break;
}
......@@ -198,7 +208,8 @@ void WebView::mouseReleaseEvent(QMouseEvent *event)
}
const QUrl clickedLink = hitTestContent(event->pos()).linkUrl();
if (!clickedLink.isValid() || clickedLink != m_clickedLink) {
if (!clickedLink.isValid() || clickedLink != m_clickedLink
|| clickedLink.scheme() == QLatin1String("javascript")) {
QWebView::mouseReleaseEvent(event);
return;
}
......@@ -299,6 +310,11 @@ QWebHitTestResult WebView::hitTestContent(const QPoint &pos) const
bool WebView::isUrlExternal(const QUrl &url)
{
static const QStringList localSchemes = {
QStringLiteral("file"),
QStringLiteral("qrc"),
};
const QString scheme = url.scheme();
return !scheme.isEmpty() && scheme != QLatin1String("file") && scheme != QLatin1String("qrc");
return !scheme.isEmpty() && !localSchemes.contains(scheme);
}
......@@ -41,7 +41,7 @@ public:
void setZoomLevel(int level);
static const QVector<int> &availableZoomLevels();
static const int defaultZoomLevel();
static const int &defaultZoomLevel();
public slots:
void zoomIn();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment