- refresh qtmarkdowntextedit + qtkeychain

This commit is contained in:
Dmytro Bogovych 2023-12-21 09:37:27 +03:00
parent e91a022d97
commit 0b297c038b
81 changed files with 14709 additions and 3438 deletions

View File

@ -1,49 +1,106 @@
cmake_minimum_required(VERSION 3.3)
project(qmarkdowntextedit)
cmake_minimum_required(VERSION 3.16) # Qt requires CMake 3.16
project(qmarkdowntextedit LANGUAGES CXX VERSION 1.0.0)
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
find_package( Qt5Core REQUIRED )
find_package( Qt5Widgets REQUIRED )
find_package( Qt5Gui REQUIRED )
# add option to disable test executable
option(QMARKDOWNTEXTEDIT_EXE "Build test executable" ON)
qt5_wrap_ui(ui_qplaintexteditsearchwidget.h qplaintexteditsearchwidget.ui)
# find qt
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} OPTIONAL_COMPONENTS Quick)
set(RESOURCE_FILES
media.qrc
)
# needed for windows
if(WIN32)
set(INTL_LDFLAGS -lintl)
endif(WIN32)
qt5_add_resources(RESOURCE_ADDED ${RESOURCE_FILES})
# QMarkdownTextEdit library
set(RC_FILES
media.qrc
)
set(SOURCE_FILES
# Translations arent loaded so don't include them
set(TS_FILES
trans/qmarkdowntextedit_de.ts
trans/qmarkdowntextedit_ur.ts
trans/qmarkdowntextedit_zh_CN.ts
)
set(QMARKDOWNTEXTEDIT_SOURCES
${RC_FILES}
linenumberarea.h # We need to keep this here, otherwise the build fails
markdownhighlighter.cpp
markdownhighlighter.h
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
qmarkdowntextedit.cpp
qmarkdowntextedit.h
qplaintexteditsearchwidget.ui
qownlanguagedata.cpp
qownlanguagedata.h
qplaintexteditsearchwidget.cpp
qplaintexteditsearchwidget.ui
)
set(QMARKDOWNTEXTEDIT_HEADERS
markdownhighlighter.h
qmarkdowntextedit.h
qplaintexteditsearchwidget.h
)
add_library(qmarkdowntextedit ${QMARKDOWNTEXTEDIT_SOURCES})
set_target_properties(qmarkdowntextedit PROPERTIES
PUBLIC_HEADER "${QMARKDOWNTEXTEDIT_HEADERS}"
)
target_link_libraries(qmarkdowntextedit PUBLIC
Qt${QT_VERSION_MAJOR}::Widgets
${INTL_LDFLAGS}
)
if (Qt${QT_VERSION_MAJOR}Quick_FOUND)
target_link_libraries(qmarkdowntextedit PUBLIC Qt${QT_VERSION_MAJOR}::Quick)
add_executable(QtQuickExample examples/qml/example.cpp examples/qml/ressources.qrc)
target_link_libraries(QtQuickExample PRIVATE Qt${QT_VERSION_MAJOR}::Quick qmarkdowntextedit)
endif()
# QMarkdownTextEdit executable
if(QMARKDOWNTEXTEDIT_EXE)
set(SOURCE_FILES
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
)
add_executable(qmarkdowntextedit ${SOURCE_FILES} ${RESOURCE_ADDED})
add_executable(qmarkdowntextedit-exe ${SOURCE_FILES})
set_target_properties(qmarkdowntextedit-exe PROPERTIES OUTPUT_NAME "qmarkdowntextedit")
target_link_libraries(qmarkdowntextedit-exe PRIVATE
Qt${QT_VERSION_MAJOR}::Widgets
${INTL_LDFLAGS}
qmarkdowntextedit
)
endif()
include_directories(${Qt5Widgets_INCLUDES})
include(GNUInstallDirs) # Doesn't fail on windows
# We need add -DQT_WIDGETS_LIB when using QtWidgets in Qt 5.
add_definitions(${Qt5Widgets_DEFINITIONS})
# Install the lib
install(TARGETS qmarkdowntextedit
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
# Executables fail to build with Qt 5 in the default configuration
# without -fPIE. We add that here.
set(CMAKE_CXX_FLAGS "${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}")
# Add PkgConfig config file
configure_file(qmarkdowntextedit.pc.in ${CMAKE_BINARY_DIR}/qmarkdowntextedit.pc @ONLY)
install(FILES ${CMAKE_BINARY_DIR}/qmarkdowntextedit.pc DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig)
# The Qt5Widgets_LIBRARIES variable also includes QtGui and QtCore
target_link_libraries(qmarkdowntextedit Qt5::Widgets)
# Install exe
if(QMARKDOWNTEXTEDIT_EXE)
install(TARGETS qmarkdowntextedit-exe DESTINATION bin)
endif()

View File

@ -1,5 +1,5 @@
The MIT License (MIT)
Copyright (c) 2014-2019 Patrizio Bekerle
Copyright (c) 2014-2023 Patrizio Bekerle -- <patrizio@bekerle.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View File

@ -1,31 +1,73 @@
# [QMarkdownTextEdit](https://github.com/pbek/qmarkdowntextedit)
[![Build Status GitHub Actions](https://github.com/pbek/qmarkdowntextedit/workflows/Build/badge.svg?branch=develop)](https://github.com/pbek/qmarkdowntextedit/actions)
[![Build Status Linux/OS X](https://travis-ci.org/pbek/qmarkdowntextedit.svg?branch=develop)](https://travis-ci.org/pbek/qmarkdowntextedit)
[![Build Status Windows](https://ci.appveyor.com/api/projects/status/github/pbek/qmarkdowntextedit)](https://ci.appveyor.com/project/pbek/qmarkdowntextedit)
QMarkdownTextEdit is a C++ Qt [QPlainTextEdit](http://doc.qt.io/qt-5/qtextplainedit.html) widget with [markdown](https://en.wikipedia.org/wiki/Markdown) highlighting and some other goodies.
QMarkdownTextEdit is a C++ Qt [QPlainTextEdit](http://doc.qt.io/qt-5/qplaintextedit.html) widget with [markdown](https://en.wikipedia.org/wiki/Markdown) highlighting and some other goodies.
## Widget Features
- Markdown highlighting
- Code syntax highlighting
- Clickable links with `Ctrl + Click`
- Block indent with `Tab` and `Shift + Tab`
- Duplicate text with `Ctrl + Alt + Down`
- Searching of text with `Ctrl + F`
- Jump between search results with `Up` and `Down`
- Close search field with `Escape`
- Replacing of text with `Ctrl + R`
- You can also replace text with regular expressions or whole words
- Line numbers (Qt >= 5.5)
- Very fast
- And much more...
## Supported Markdown Features
Commonmark compliance is enforced where possible however we are not fully Commonmark compliant yet. Following is a list of features/extensions supported by the highlighter. Please note that this is just a plaintext editor and as such, it only does the highlighting and not rendering of the markdown to HTML.
| Feature | Availablity |
| --------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| Bolds and Italics | Yes |
| Lists (Unordered/Orderered) | Yes |
| Links and Images<br/>(Inline/Reference/Autolinks/E-mail) | Yes (Cannot handle nested links or complex cases yet) |
| Heading (ATX and Setext) | Yes |
| Codeblocks (indented and fenced)<br/> Both backtick and tilde code fences are supported | Yes (Only fenced code block has syntax highlighting) |
| Inline code | Yes |
| Strikethrough | Yes |
| Underline | Yes (Optional) |
| Blockquotes | Yes |
| Table | Yes |
## Features
- markdown highlighting
- clickable links with `Ctrl + Click`
- block indent with `Tab` and `Shift + Tab`
- duplicate text with `Ctrl + Alt + Down`
- searching of text with `Ctrl + F`
- jump between search results with `Up` and `Down`
- close search field with `Escape`
- replacing of text with `Ctrl + R`
- you can also replace text with regular expressions or whole words
- and much more...
## Screenshot
![Screenhot](screenshot.png)
## How to use this widget
- include [qmarkdowntextedit.pri](https://github.com/pbek/qmarkdowntextedit/blob/develop/qmarkdowntextedit.pri)
## Usage
There are multiple ways to use this. You can use the editor directly, or you can subclass it or you can just use the highlighter.
### Using the editor
#### QMake
- Include [qmarkdowntextedit.pri](https://github.com/pbek/qmarkdowntextedit/blob/develop/qmarkdowntextedit.pri)
to your project like this `include (qmarkdowntextedit/qmarkdowntextedit.pri)`
- add a normal `QPlainTextEdit` to your UI and promote it to `QMarkdownTextEdit` (base class `QPlainTextEdit`)
## References
- [QOwnNotes - cross-platform open source plain-text file notepad](http://www.qownnotes.org)
#### CMake
- Include [CMakeLists.txt](https://github.com/pbek/qmarkdowntextedit/blob/develop/CMakeLists.txt)
to your project like this `add_subdirectory(qmarkdowntextedit)`
- add a normal `QPlainTextEdit` to your UI and promote it to `QMarkdownTextEdit` (base class `QPlainTextEdit`)
### Using the highlighter only
Highlighter can work with both `QPlainTextEdit` and `QTextEdit`. Example:
```cpp
auto doc = ui->plainTextEdit->document();
auto *highlighter = new MarkdownHighlighter(doc);
```
## Projects using QMarkdownTextEdit
- [QOwnNotes](https://github.com/pbek/QOwnNotes)
- [Notes](https://github.com/nuttyartist/notes)
- [CuteMarkEd-NG](https://github.com/Waqar144/CuteMarkEd-NG)
## Disclaimer
This SOFTWARE PRODUCT is provided by THE PROVIDER "as is" and "with all faults." THE PROVIDER makes no representations or warranties of any kind concerning the safety, suitability, lack of viruses, inaccuracies, typographical errors, or other harmful components of this SOFTWARE PRODUCT.

View File

@ -0,0 +1,24 @@
#include <QApplication>
#include <QQmlApplicationEngine>
#include "markdownhighlighter.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
qmlRegisterType<MarkdownHighlighter>("MarkdownHighlighter", 1, 0,
"MarkdownHighlighter");
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/example.qml"));
QObject::connect(
&engine, &QQmlApplicationEngine::objectCreated, &app,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl) QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
engine.load(url);
return app.exec();
}

View File

@ -0,0 +1,22 @@
import QtQuick 2.0
import QtQuick.Window 2.0
import MarkdownHighlighter 1.0
Window {
id: mainwindow
width: 640
height: 400
visible: true
title: qsTr("QtQuick Project")
TextEdit {
id: editor
text: "# Hello world!"
focus: true
}
MarkdownHighlighter {
id: syntaxHighlighter
textDocument: editor.textDocument
}
}

View File

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

View File

@ -0,0 +1,124 @@
#ifndef LINENUMBERAREA_H
#define LINENUMBERAREA_H
#include <QDebug>
#include <QPainter>
#include <QScrollBar>
#include <QWidget>
#include "qmarkdowntextedit.h"
class LineNumArea final : public QWidget {
Q_OBJECT
public:
explicit LineNumArea(QMarkdownTextEdit *parent)
: QWidget(parent),
textEdit(parent)
{
Q_ASSERT(parent);
_currentLineColor = QColor(QStringLiteral("#eef067"));
_otherLinesColor = QColor(QStringLiteral("#a6a6a6"));
setHidden(true);
// We always use fixed font to avoid "width" issues
setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
}
void setCurrentLineColor(QColor color) {
_currentLineColor = color;
}
void setOtherLineColor(QColor color) {
_otherLinesColor = std::move(color);
}
int lineNumAreaWidth() const
{
if (!enabled) {
return 0;
}
int digits = 2;
int max = std::max(1, textEdit->blockCount());
while (max >= 10) {
max /= 10;
++digits;
}
#if QT_VERSION >= 0x050B00
int space = 13 + textEdit->fontMetrics().horizontalAdvance(u'9') * digits;
#else
int space = 13 + textEdit->fontMetrics().width(QLatin1Char('9')) * digits;
#endif
return space;
}
bool isLineNumAreaEnabled() const
{
return enabled;
}
void setLineNumAreaEnabled(bool e)
{
enabled = e;
setHidden(!e);
}
QSize sizeHint() const override
{
return {lineNumAreaWidth(), 0};
}
protected:
void paintEvent(QPaintEvent *event) override
{
QPainter painter(this);
painter.fillRect(event->rect(), palette().color(QPalette::Active, QPalette::Window));
auto block = textEdit->firstVisibleBlock();
int blockNumber = block.blockNumber();
qreal top = textEdit->blockBoundingGeometry(block).translated(textEdit->contentOffset()).top();
// Maybe the top is not 0?
top += textEdit->viewportMargins().top();
qreal bottom = top;
const QPen currentLine = _currentLineColor;
const QPen otherLines = _otherLinesColor;
painter.setFont(font());
while (block.isValid() && top <= event->rect().bottom()) {
top = bottom;
bottom = top + textEdit->blockBoundingRect(block).height();
if (block.isVisible() && bottom >= event->rect().top()) {
QString number = QString::number(blockNumber + 1);
auto isCurrentLine = textEdit->textCursor().blockNumber() == blockNumber;
painter.setPen(isCurrentLine ? currentLine : otherLines);
painter.drawText(
-5,
top,
sizeHint().width(),
textEdit->fontMetrics().height(),
Qt::AlignRight,
number
);
}
block = block.next();
++blockNumber;
}
}
private:
bool enabled = false;
QMarkdownTextEdit *textEdit;
QColor _currentLineColor;
QColor _otherLinesColor;
};
#endif // LINENUMBERAREA_H

View File

@ -1,41 +1,35 @@
/*
* Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com
* MIT License
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
* Copyright (c) 2014-2023 Patrizio Bekerle -- <patrizio@bekerle.com>
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "mainwindow.h"
#include <QApplication>
#include <QFileInfo>
#include <QDebug>
int main(int argc, char *argv[])
{
#include "mainwindow.h"
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
QString filename;
if (argc > 1) {
filename = argv[1];
}
if (!filename.isEmpty() && !QFileInfo(filename).isReadable()) {
qWarning() << filename << "is not a readable file";
return 1;
}
MainWindow w;
w.show();
if (!filename.isEmpty()) {
w.loadFile(filename);
}
return a.exec();
}

View File

@ -1,14 +1,25 @@
/*
* Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com
* MIT License
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
* Copyright (c) 2014-2023 Patrizio Bekerle -- <patrizio@bekerle.com>
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* mainwindow.cpp
*
@ -16,103 +27,12 @@
*/
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "qmarkdowntextedit.h"
#include <QDebug>
#include <QFileDialog>
#include <QMessageBox>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow) {
ui->setupUi(this);
QToolBar *toolBar = new QToolBar;
addToolBar(toolBar);
QAction *openAction = new QAction(QIcon::fromTheme("document-open"), tr("Open..."));
openAction->setShortcut(QKeySequence::Open);
connect(openAction, &QAction::triggered, this, &MainWindow::open);
QAction *saveAction = new QAction(QIcon::fromTheme("document-save"), tr("Save"));
saveAction->setShortcut(QKeySequence::Save);
QAction *saveAsAction = new QAction(QIcon::fromTheme("document-save-as"), tr("Save as..."));
saveAsAction->setShortcut(QKeySequence::SaveAs);
QAction *quitAction = new QAction(QIcon::fromTheme("view-close"), tr("Quit"));
quitAction->setShortcut(QKeySequence::Quit);
connect(quitAction, &QAction::triggered, this, &MainWindow::onQuit);
m_loadedContent = ui->textEdit->toPlainText();
toolBar->addActions({openAction, saveAction, saveAsAction, quitAction});
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::loadFile(const QString &filename)
{
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
qWarning() << "Failed to open" << filename;
return;
}
m_filename = filename;
m_loadedContent = QString::fromLocal8Bit(file.readAll());
ui->textEdit->setPlainText(m_loadedContent);
}
void MainWindow::saveToFile(const QString &filename)
{
QFile file(filename);
if (!file.open(QIODevice::WriteOnly)) {
qWarning() << "Failed to open" << filename;
return;
}
m_filename = filename;
m_loadedContent = ui->textEdit->toPlainText();
file.write(m_loadedContent.toLocal8Bit());
}
void MainWindow::open()
{
QString filename = QFileDialog::getOpenFileName();
if (filename.isEmpty()) {
return;
}
loadFile(filename);
}
void MainWindow::save()
{
if (!m_filename.isEmpty()) {
saveAs();
return;
}
saveToFile(m_filename);
}
void MainWindow::saveAs()
{
QString filename = QFileDialog::getSaveFileName();
if (filename.isEmpty()) {
return;
}
saveToFile(filename);
}
void MainWindow::onQuit()
{
if (ui->textEdit->toPlainText() != m_loadedContent) {
if (QMessageBox::question(this, tr("Not saved"), tr("Document not saved, sure you want to quit?")) != QMessageBox::Yes) {
return;
}
}
close();
}
MainWindow::~MainWindow() { delete ui; }

View File

@ -1,15 +1,25 @@
/*
* Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com
* MIT License
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
* Copyright (c) 2014-2023 Patrizio Bekerle -- <patrizio@bekerle.com>
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
@ -20,25 +30,13 @@ namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
class MainWindow : public QMainWindow {
Q_OBJECT
public:
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
void loadFile(const QString &filename);
void saveToFile(const QString &filename);
private slots:
void open();
void save();
void saveAs();
void onQuit();
private:
private:
Ui::MainWindow *ui;
QString m_loadedContent;
QString m_filename;
};

View File

@ -26,7 +26,9 @@
## Features
- markdown highlighting
- syntax highlighting
- clickable links with `Ctrl + Click`
- ~strikedout~ text and `inline code;`
- block indent with `Tab` and `Shift + Tab`
- duplicate text with `Ctrl + Alt + Down`
- searching of text with `Ctrl + F`
@ -36,7 +38,7 @@
## References
- [QOwnNotes - cross-platform open source plain-text notepad](http://www.qownnotes.org)
- [QOwnNotes - cross-platform open source plain-text file markdown note taking](https://www.qownnotes.org)
## Disclaimer
@ -55,7 +57,7 @@ There are inherent dangers in the use of any software, and you are solely respon
<x>0</x>
<y>0</y>
<width>1070</width>
<height>25</height>
<height>23</height>
</rect>
</property>
</widget>

File diff suppressed because it is too large Load Diff

View File

@ -1,52 +1,125 @@
/*
* Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com
* MIT License
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
* Copyright (c) 2014-2023 Patrizio Bekerle -- <patrizio@bekerle.com>
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* QPlainTextEdit markdown highlighter
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* QPlainTextEdit Markdown highlighter
*/
#pragma once
#include <QTextCharFormat>
#include <QSyntaxHighlighter>
#include <QRegularExpression>
#include <QSyntaxHighlighter>
#include <QTextCharFormat>
#ifdef QT_QUICK_LIB
#include <QQuickTextDocument>
#endif
QT_BEGIN_NAMESPACE
class QTextDocument;
QT_END_NAMESPACE
class MarkdownHighlighter : public QSyntaxHighlighter
{
Q_OBJECT
class MarkdownHighlighter : public QSyntaxHighlighter {
Q_OBJECT
public:
enum HighlightingOption{
#ifdef QT_QUICK_LIB
Q_PROPERTY(QQuickTextDocument *textDocument READ textDocument WRITE
setTextDocument NOTIFY textDocumentChanged)
QQuickTextDocument *m_quickDocument = nullptr;
signals:
void textDocumentChanged();
public:
inline QQuickTextDocument *textDocument() const { return m_quickDocument; };
void setTextDocument(QQuickTextDocument *textDocument) {
if (!textDocument) return;
m_quickDocument = textDocument;
setDocument(m_quickDocument->textDocument());
Q_EMIT textDocumentChanged();
};
#endif
public:
enum HighlightingOption {
None = 0,
FullyHighlightedBlockQuote = 0x01
FullyHighlightedBlockQuote = 0x01,
Underline = 0x02
};
Q_DECLARE_FLAGS(HighlightingOptions, HighlightingOption)
MarkdownHighlighter(QTextDocument *parent = nullptr,
HighlightingOptions highlightingOptions =
HighlightingOption::None);
MarkdownHighlighter(
QTextDocument *parent = nullptr,
HighlightingOptions highlightingOptions = HighlightingOption::None);
// we use some predefined numbers here to be compatible with
// the peg-markdown parser
static inline QColor codeBlockBackgroundColor() {
const QBrush brush = _formats[CodeBlock].background();
if (!brush.isOpaque()) {
return QColor(Qt::transparent);
}
return brush.color();
}
static constexpr inline bool isOctal(const char c) {
return (c >= '0' && c <= '7');
}
static constexpr inline bool isHex(const char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F');
}
static constexpr inline bool isCodeBlock(const int state) {
return state == MarkdownHighlighter::CodeBlock ||
state == MarkdownHighlighter::CodeBlockTilde ||
state == MarkdownHighlighter::CodeBlockComment ||
state == MarkdownHighlighter::CodeBlockTildeComment ||
state >= MarkdownHighlighter::CodeCpp;
}
static constexpr inline bool isCodeBlockEnd(const int state) {
return state == MarkdownHighlighter::CodeBlockEnd ||
state == MarkdownHighlighter::CodeBlockTildeEnd;
}
static constexpr inline bool isHeading(const int state) {
return state >= H1 && state <= H6;
}
enum class RangeType { CodeSpan, Emphasis, Link };
QPair<int, int> findPositionInRanges(MarkdownHighlighter::RangeType type, int blockNum, int pos) const;
bool isPosInACodeSpan(int blockNumber, int position) const;
bool isPosInALink(int blockNumber, int position) const;
QPair<int, int> getSpanRange(RangeType rangeType, int blockNumber, int position) const;
// we used some predefined numbers here to be compatible with
// the peg-Markdown parser
enum HighlighterState {
NoState = -1,
Link = 0,
Image = 3,
CodeBlock,
CodeBlockComment,
Italic = 7,
Bold,
List,
@ -64,74 +137,217 @@ public:
MaskedSyntax,
CurrentLineBackgroundColor,
BrokenLink,
FrontmatterBlock,
TrailingSpace,
CheckBoxUnChecked,
CheckBoxChecked,
StUnderline,
// code highlighting
CodeKeyWord = 1000,
CodeString = 1001,
CodeComment = 1002,
CodeType = 1003,
CodeOther = 1004,
CodeNumLiteral = 1005,
CodeBuiltIn = 1006,
// internal
CodeBlockIndented = 96,
CodeBlockTildeEnd = 97,
CodeBlockTilde = 98,
CodeBlockTildeComment,
CodeBlockEnd = 100,
HeadlineEnd
HeadlineEnd,
FrontmatterBlockEnd,
// languages
/*********
* When adding a language make sure that its value is a multiple of 2
* This is because we use the next number as comment for that language
* In case the language doesn't support multiline comments in the
* traditional C++ sense, leave the next value empty. Otherwise mark the
* next value as comment for that language. e.g CodeCpp = 200
* CodeCppComment = 201
*/
CodeCpp = 200,
CodeCppComment = 201,
CodeJs = 202,
CodeJsComment = 203,
CodeC = 204,
CodeCComment = 205,
CodeBash = 206,
CodePHP = 208,
CodePHPComment = 209,
CodeQML = 210,
CodeQMLComment = 211,
CodePython = 212,
CodeRust = 214,
CodeRustComment = 215,
CodeJava = 216,
CodeJavaComment = 217,
CodeCSharp = 218,
CodeCSharpComment = 219,
CodeGo = 220,
CodeGoComment = 221,
CodeV = 222,
CodeVComment = 223,
CodeSQL = 224,
CodeJSON = 226,
CodeXML = 228,
CodeCSS = 230,
CodeCSSComment = 231,
CodeTypeScript = 232,
CodeTypeScriptComment = 233,
CodeYAML = 234,
CodeINI = 236,
CodeTaggerScript = 238,
CodeVex = 240,
CodeVexComment = 241,
CodeCMake = 242,
CodeMake = 244,
CodeNix = 246,
CodeForth = 248,
CodeForthComment = 249
};
Q_ENUM(HighlighterState)
// enum BlockState {
// NoBlockState = 0,
// H1,
// H2,
// H3,
// Table,
// CodeBlock,
// CodeBlockEnd
// };
void setTextFormats(QHash<HighlighterState, QTextCharFormat> formats);
void setTextFormat(HighlighterState state, QTextCharFormat format);
static void setTextFormats(
QHash<HighlighterState, QTextCharFormat> formats);
static void setTextFormat(HighlighterState state, QTextCharFormat format);
void clearDirtyBlocks();
void setHighlightingOptions(HighlightingOptions options);
void setHighlightingOptions(const HighlightingOptions options);
void initHighlightingRules();
signals:
Q_SIGNALS:
void highlightingFinished();
protected slots:
protected Q_SLOTS:
void timerTick();
protected:
protected:
struct HighlightingRule {
HighlightingRule(const HighlighterState state_) : state(state_) {}
explicit HighlightingRule(const HighlighterState state_)
: state(state_) {}
HighlightingRule() = default;
QRegularExpression pattern;
QString shouldContain;
HighlighterState state = NoState;
int capturingGroup = 0;
int maskedGroup = 0;
bool useStateAsCurrentBlockState = false;
bool disableIfCurrentStateIsSet = false;
uint8_t capturingGroup = 0;
uint8_t maskedGroup = 0;
};
struct InlineRange {
int begin;
int end;
RangeType type;
InlineRange() = default;
InlineRange(int begin_, int end_, RangeType type_) :
begin{begin_}, end{end_}, type{type_}
{}
};
void highlightBlock(const QString &text) Q_DECL_OVERRIDE;
void initTextFormats(int defaultFontSize = 12);
void highlightBlock(const QString &text) override;
void highlightMarkdown(QString text);
static void initTextFormats(int defaultFontSize = 12);
void highlightHeadline(QString text);
static void initCodeLangs();
void highlightAdditionalRules(QVector<HighlightingRule> &rules,
QString text);
void highlightMarkdown(const QString &text);
void highlightCodeBlock(QString text);
void formatAndMaskRemaining(int formatBegin, int formatLength,
int beginningText, int endText,
const QTextCharFormat &format);
void highlightCommentBlock(QString text);
/******************************
* BLOCK LEVEL FUNCTIONS
******************************/
void addDirtyBlock(QTextBlock block);
void highlightHeadline(const QString &text);
void highlightSubHeadline(const QString &text, HighlighterState state);
void highlightAdditionalRules(const QVector<HighlightingRule> &rules,
const QString &text);
void highlightFrontmatterBlock(const QString &text);
void highlightCommentBlock(const QString &text);
void highlightThematicBreak(const QString &text);
void highlightLists(const QString &text);
void highlightCheckbox(const QString &text, int curPos);
/******************************
* INLINE FUNCTIONS
******************************/
void highlightInlineRules(const QString &text);
int highlightInlineSpans(const QString &text,
int currentPos, const QChar c);
void highlightEmAndStrong(const QString &text, const int pos);
Q_REQUIRED_RESULT int highlightInlineComment(const QString &text, int pos);
int highlightLinkOrImage(const QString &text, int startIndex);
void setHeadingStyles(MarkdownHighlighter::HighlighterState rule,
const QRegularExpressionMatch &match,
const int capturedGroup);
/******************************
* CODE HIGHLIGHTING FUNCTIONS
******************************/
void highlightIndentedCodeBlock(const QString &text);
void highlightCodeFence(const QString &text);
void highlightCodeBlock(const QString &text,
const QString &opener = QStringLiteral("```"));
void highlightSyntax(const QString &text);
Q_REQUIRED_RESULT int highlightNumericLiterals(const QString &text, int i);
Q_REQUIRED_RESULT int highlightStringLiterals(QChar strType,
const QString &text, int i);
void ymlHighlighter(const QString &text);
void iniHighlighter(const QString &text);
void cssHighlighter(const QString &text);
void xmlHighlighter(const QString &text);
void makeHighlighter(const QString &text);
void forthHighlighter(const QString &text);
void taggerScriptHighlighter(const QString &text);
void addDirtyBlock(const QTextBlock &block);
void reHighlightDirtyBlocks();
QVector<HighlightingRule> _highlightingRulesPre;
QVector<HighlightingRule> _highlightingRulesAfter;
QVector<QTextBlock> _dirtyTextBlocks;
QHash<HighlighterState, QTextCharFormat> _formats;
QTimer *_timer;
void clearRangesForBlock(int blockNumber, RangeType type);
bool _highlightingFinished;
HighlightingOptions _highlightingOptions;
QTimer *_timer;
QVector<QTextBlock> _dirtyTextBlocks;
QVector<QPair<int,int>> _linkRanges;
void setCurrentBlockMargin(HighlighterState state);
QHash<int, QVector<InlineRange>> _ranges;
static QVector<HighlightingRule> _highlightingRules;
static QHash<HighlighterState, QTextCharFormat> _formats;
static QHash<QString, HighlighterState> _langStringToEnum;
static constexpr int tildeOffset = 300;
};

View File

@ -1,15 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<path
transform="translate(1,1)"
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 9 3 C 5.6759952 3 3 5.6759952 3 9 C 3 12.324005 5.6759952 15 9 15 C 10.481205 15 11.830584 14.465318 12.875 13.582031 L 18.292969 19 L 19 18.292969 L 13.582031 12.875 C 14.465318 11.830584 15 10.481205 15 9 C 15 5.6759952 12.324005 3 9 3 z M 9 4 C 11.770005 4 14 6.2299952 14 9 C 14 11.770005 11.770005 14 9 14 C 6.2299952 14 4 11.770005 4 9 C 4 6.2299952 6.2299952 4 9 4 z M 4 17 C 3.4460048 17 3 17.446005 3 18 C 3 18.553995 3.4460048 19 4 19 C 4.5539952 19 5 18.553995 5 18 C 5 17.446005 4.5539952 17 4 17 z M 8 17 C 7.4460048 17 7 17.446005 7 18 C 7 18.553995 7.4460048 19 8 19 C 8.5539952 19 9 18.553995 9 18 C 9 17.446005 8.5539952 17 8 17 z M 12 17 C 11.446005 17 11 17.446005 11 18 C 11 18.553995 11.446005 19 12 19 C 12.553995 19 13 18.553995 13 18 C 13 17.446005 12.553995 17 12 17 z "
class="ColorScheme-Text"
/>
</svg>
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M10 4c-3.324 0-6 2.676-6 6s2.676 6 6 6a5.976 5.976 0 0 0 3.875-1.418L19.293 20l.707-.707-5.418-5.418A5.976 5.976 0 0 0 16 10c0-3.324-2.676-6-6-6zm0 1a4.99 4.99 0 0 1 5 5 4.99 4.99 0 0 1-5 5 4.99 4.99 0 0 1-5-5 4.99 4.99 0 0 1 5-5zM5 18a1 1 0 1 0 0 2 1 1 0 1 0 0-2zm4 0a1 1 0 1 0 0 2 1 1 0 1 0 0-2zm4 0a1 1 0 1 0 0 2 1 1 0 1 0 0-2z" fill="#4d4d4d"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 423 B

View File

@ -1,16 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<a transform="translate(1,1)">
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 14 3 L 14 5 L 14 6 L 14 8 L 14 9 L 15 9 L 17 9 C 18.108 9 19 8.108 19 7 C 19 6.1891 18.518727 5.4980937 17.828125 5.1835938 C 17.934063 4.9776938 18 4.7483 18 4.5 C 18 3.669 17.330999 3 16.5 3 L 15 3 L 14 3 z M 15 4 L 16.5 4 C 16.777 4 17 4.223 17 4.5 C 17 4.777 16.777 5 16.5 5 L 15 5 L 15 4 z M 8.2167969 5 L 3 19 L 4.6875 19 L 6.3554688 14.570312 L 11.917969 14.570312 L 13.451172 19 L 15.140625 19 L 10.210938 5 L 8.2167969 5 z M 15 6 L 16.5 6 L 17 6 C 17.553999 6 18 6.446 18 7 C 18 7.554 17.553999 8 17 8 L 15 8 L 15 6 z M 9.2324219 6.6113281 L 11.361328 13.285156 L 6.8730469 13.285156 L 9.2324219 6.6113281 z "
class="ColorScheme-Text"
/>
</a>
</svg>
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><a transform="translate(1 1)"><path d="M14 3v6h3a1.996 1.996 0 0 0 .828-3.816c.106-.206.172-.435.172-.684 0-.831-.669-1.5-1.5-1.5H15zm1 1h1.5a.5.5 0 1 1 0 1H15zM8.217 5 3 19h1.688l1.668-4.43h5.563L13.451 19h1.689L10.21 5zM15 6h2a1 1 0 1 1 0 2h-2zm-5.768.611 2.129 6.674H6.873z" fill="#4d4d4d"/></a></svg>

Before

Width:  |  Height:  |  Size: 998 B

After

Width:  |  Height:  |  Size: 364 B

View File

@ -1,156 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
id="svg3869"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="go-bottom.svg">
<defs
id="defs3871">
<linearGradient
id="linearGradient3257">
<stop
offset="0"
style="stop-color:#a50000;stop-opacity:1"
id="stop3259" />
<stop
offset="1"
style="stop-color:#e73800;stop-opacity:1"
id="stop3261" />
</linearGradient>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4210">
<rect
y="1024.3622"
x="-7"
height="34"
width="34"
id="rect4212"
style="opacity:1;fill:#0000ff;fill-opacity:0.51376145;stroke:none;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4160">
<rect
style="opacity:1;fill:#aade87;fill-opacity:0.47247709;stroke:none;stroke-opacity:1"
id="rect4162"
width="32"
height="32.000015"
x="-6"
y="1028.3619" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.627416"
inkscape:cx="5.4926209"
inkscape:cy="10.264796"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1366"
inkscape:window-height="709"
inkscape:window-x="-4"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:showpageshadow="false"
inkscape:object-nodes="true"
inkscape:snap-bbox="true">
<inkscape:grid
type="xygrid"
id="grid4132" />
<sodipodi:guide
position="4,18"
orientation="18,0"
id="guide4138" />
<sodipodi:guide
position="5,3"
orientation="0,18"
id="guide4140" />
<sodipodi:guide
position="20,2.0000174"
orientation="-18,0"
id="guide4142" />
<sodipodi:guide
position="2,21"
orientation="0,-18"
id="guide4144" />
<sodipodi:guide
position="3,19.000017"
orientation="16,0"
id="guide4146" />
<sodipodi:guide
position="2,4"
orientation="0,16"
id="guide4148" />
<sodipodi:guide
position="21,20"
orientation="-16,0"
id="guide4150" />
<sodipodi:guide
position="2,20"
orientation="0,-16"
id="guide4152" />
</sodipodi:namedview>
<metadata
id="metadata3874">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Capa 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-326,-532.3622)">
<g
transform="translate(327,507.3622)"
inkscape:label="Layer 1"
id="layer1-68">
<g
inkscape:label="Capa 1"
id="layer1-6"
transform="translate(5e-7,-1006.3622)">
<path
inkscape:connector-curvature="0"
style="fill:#4d4d4d;fill-opacity:1;stroke:none"
d="M 3.7070312,7 3,7.7070312 l 6.125,6.1249998 1.875,1.875 1.875,-1.875 L 19,7.7070312 18.292969,7 12.167969,13.125 11,14.292969 9.8320312,13.125 3.7070312,7 Z"
transform="translate(-5e-7,1030.3622)"
id="rect4176" />
</g>
<rect
y="42"
x="3"
height="1"
width="16"
id="rect4186"
style="opacity:1;fill:#4d4d4d;fill-opacity:1;stroke:none" />
</g>
</g>
</svg>
<svg fill="#4d4d4d" height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M4.707 6 4 6.707l6.125 6.125L12 14.707l1.875-1.875L20 6.707 19.293 6l-6.125 6.125L12 13.293l-1.168-1.168zM4 17h16v1H4z"/></svg>

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 214 B

View File

@ -1,476 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
id="svg3869"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="go-top.svg">
<defs
id="defs3871">
<linearGradient
id="linearGradient3257">
<stop
offset="0"
style="stop-color:#a50000;stop-opacity:1"
id="stop3259" />
<stop
offset="1"
style="stop-color:#e73800;stop-opacity:1"
id="stop3261" />
</linearGradient>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4210">
<rect
y="1024.3622"
x="-7"
height="34"
width="34"
id="rect4212"
style="opacity:1;fill:#0000ff;fill-opacity:0.51376145;stroke:none;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4160">
<rect
style="opacity:1;fill:#aade87;fill-opacity:0.47247709;stroke:none;stroke-opacity:1"
id="rect4162"
width="32"
height="32.000015"
x="-6"
y="1028.3619" />
</clipPath>
<clipPath
id="clipPath4160-4"
clipPathUnits="userSpaceOnUse">
<rect
y="1023.3622"
x="7"
height="1"
width="1"
id="rect4162-5"
style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:none;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath16">
<path
d="m 0,706.465 1490.926,0 L 1490.926,0 0,0 0,706.465 Z"
id="path18" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath24">
<path
d="m 22.1953,686.117 1447.7347,0 0,-667.1902 -1447.7347,0 L 22.1953,686.117 Z"
id="path26" />
</clipPath>
<inkscape:perspective
id="perspective4146-36"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-7" />
<inkscape:perspective
id="perspective4146-0"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-8" />
<inkscape:perspective
id="perspective4146-3"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146" />
<inkscape:perspective
id="perspective4146-36-7"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-7-6" />
<inkscape:perspective
id="perspective4146-0-6"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-8-7" />
<inkscape:perspective
id="perspective4146-3-9"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-2" />
<inkscape:perspective
id="perspective4146-36-4"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-7-7" />
<inkscape:perspective
id="perspective4146-0-0"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-8-6" />
<inkscape:perspective
id="perspective4146-3-81"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-6" />
<inkscape:perspective
id="perspective4146-36-7-2"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-7-6-3" />
<inkscape:perspective
id="perspective4146-0-6-4"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-8-7-6" />
<inkscape:perspective
id="perspective4146-3-9-0"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-2-1" />
<inkscape:perspective
id="perspective4146-36-8"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-7-0" />
<inkscape:perspective
id="perspective4146-0-3"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-8-78" />
<inkscape:perspective
id="perspective4146-3-1"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-05" />
<inkscape:perspective
id="perspective4146-36-3"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-7-8" />
<inkscape:perspective
id="perspective4146-0-9"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-8-3" />
<inkscape:perspective
id="perspective4146-3-4"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-9" />
<inkscape:perspective
id="perspective4146-36-7-6"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-7-6-0" />
<inkscape:perspective
id="perspective4146-0-6-7"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-8-7-7" />
<inkscape:perspective
id="perspective4146-3-9-7"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-2-13" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.627416"
inkscape:cx="5.4926209"
inkscape:cy="10.264796"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1366"
inkscape:window-height="709"
inkscape:window-x="-4"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:showpageshadow="false"
inkscape:object-nodes="true"
inkscape:snap-bbox="true">
<inkscape:grid
type="xygrid"
id="grid4132" />
<sodipodi:guide
position="4,18"
orientation="18,0"
id="guide4138" />
<sodipodi:guide
position="5,3"
orientation="0,18"
id="guide4140" />
<sodipodi:guide
position="20,2.0000174"
orientation="-18,0"
id="guide4142" />
<sodipodi:guide
position="2,21"
orientation="0,-18"
id="guide4144" />
<sodipodi:guide
position="3,19.000017"
orientation="16,0"
id="guide4146" />
<sodipodi:guide
position="2,4"
orientation="0,16"
id="guide4148" />
<sodipodi:guide
position="21,20"
orientation="-16,0"
id="guide4150" />
<sodipodi:guide
position="2,20"
orientation="0,-16"
id="guide4152" />
</sodipodi:namedview>
<metadata
id="metadata3874">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Capa 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-326,-532.3622)">
<g
transform="translate(327,507.3622)"
inkscape:label="Layer 1"
id="layer1-2">
<g
inkscape:label="Capa 1"
id="layer1-6"
transform="matrix(1,0,0,-1,5e-7,1080.3622)">
<path
inkscape:connector-curvature="0"
style="fill:#4d4d4d;fill-opacity:1;stroke:none"
d="M 3.7070312,7 3,7.7070312 l 6.125,6.1249998 1.875,1.875 1.875,-1.875 L 19,7.7070312 18.292969,7 12.167969,13.125 11,14.292969 9.8320312,13.125 3.7070312,7 Z"
transform="translate(-5e-7,1030.3622)"
id="rect4176" />
</g>
<rect
transform="scale(1,-1)"
y="-32"
x="3"
height="1"
width="16"
id="rect4186"
style="opacity:1;fill:#4d4d4d;fill-opacity:1;stroke:none" />
</g>
</g>
</svg>
<svg fill="#4d4d4d" height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M4.707 18 4 17.293l6.125-6.125L12 9.293l1.875 1.875L20 17.293l-.707.707-6.125-6.125L12 10.707l-1.168 1.168zM4 7h16V6H4z"/></svg>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 215 B

View File

@ -1,148 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
id="svg3869"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="window-close.svg">
<defs
id="defs3871">
<linearGradient
id="linearGradient3257">
<stop
offset="0"
style="stop-color:#a50000;stop-opacity:1"
id="stop3259" />
<stop
offset="1"
style="stop-color:#e73800;stop-opacity:1"
id="stop3261" />
</linearGradient>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4210">
<rect
y="1024.3622"
x="-7"
height="34"
width="34"
id="rect4212"
style="opacity:1;fill:#0000ff;fill-opacity:0.51376145;stroke:none;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4160">
<rect
style="opacity:1;fill:#aade87;fill-opacity:0.47247709;stroke:none;stroke-opacity:1"
id="rect4162"
width="32"
height="32.000015"
x="-6"
y="1028.3619" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.627416"
inkscape:cx="5.4926209"
inkscape:cy="10.264796"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1366"
inkscape:window-height="709"
inkscape:window-x="-4"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:showpageshadow="false"
inkscape:object-nodes="true"
inkscape:snap-bbox="true">
<inkscape:grid
type="xygrid"
id="grid4132" />
<sodipodi:guide
position="4,18"
orientation="18,0"
id="guide4138" />
<sodipodi:guide
position="5,3"
orientation="0,18"
id="guide4140" />
<sodipodi:guide
position="20,2.0000174"
orientation="-18,0"
id="guide4142" />
<sodipodi:guide
position="2,21"
orientation="0,-18"
id="guide4144" />
<sodipodi:guide
position="3,19.000017"
orientation="16,0"
id="guide4146" />
<sodipodi:guide
position="2,4"
orientation="0,16"
id="guide4148" />
<sodipodi:guide
position="21,20"
orientation="-16,0"
id="guide4150" />
<sodipodi:guide
position="2,20"
orientation="0,-16"
id="guide4152" />
</sodipodi:namedview>
<metadata
id="metadata3874">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Capa 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-326,-532.3622)">
<g
transform="translate(-94.71429,7.57146)"
id="layer1-7"
inkscape:label="Capa 1">
<circle
id="circle16"
cx="421.71429"
cy="-504.57132"
r="0" />
<path
id="path4144"
d="m 432.71429,528.79074 a 7.9999995,8.0000081 0 0 0 -8,8 7.9999995,8.0000081 0 0 0 8,8 7.9999995,8.0000081 0 0 0 8,-8 7.9999995,8.0000081 0 0 0 -8,-8 z m -3.29297,4 3.29297,3.29297 3.29297,-3.29297 0.70703,0.70703 -3.29297,3.29297 3.29297,3.29297 -0.70703,0.70703 -3.29297,-3.29297 -3.29297,3.29297 -0.70703,-0.70703 3.29297,-3.29297 -3.29297,-3.29297 0.70703,-0.70703 z"
style="opacity:1;fill:#da4453;fill-opacity:1"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M12 4a8 8 0 1 0 0 16 8 8 0 1 0 0-16zM8.707 8 12 11.293 15.293 8l.707.707L12.707 12 16 15.293l-.707.707L12 12.707 8.707 16 8 15.293 11.293 12 8 8.707z" fill="#da4453"/></svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -0,0 +1,17 @@
TARGET = QMarkdownTextedit
TEMPLATE = app
QT += core gui widgets
CONFIG += c++11
SOURCES = main.cpp mainwindow.cpp
HEADERS = mainwindow.h
FORMS = mainwindow.ui
LIBS += -lQMarkdownTextedit -L$$OUT_PWD
win32: LIBS += -L$$OUT_PWD/release -L$$OUT_PWD/debug
target.path = $$[QT_INSTALL_BINS]
INSTALLS += target

View File

@ -1,6 +1,8 @@
INCLUDEPATH += $$PWD/
HEADERS += \
$$PWD/linenumberarea.h \
$$PWD/markdownhighlighter.h \
$$PWD/qmarkdowntextedit.h \
$$PWD/qownlanguagedata.h \
$$PWD/qplaintexteditsearchwidget.h

View File

@ -0,0 +1,34 @@
TARGET = QMarkdownTextedit
TEMPLATE = lib
QT += core gui widgets
CONFIG += c++11 create_prl no_install_prl create_pc
include(qmarkdowntextedit.pri)
TRANSLATIONS += trans/qmarkdowntextedit_de.ts \
trans/qmarkdowntextedit_zh_CN.ts \
trans/qmarkdowntextedit_es.ts
isEmpty(PREFIX):PREFIX=$$[QT_INSTALL_PREFIX]
isEmpty(LIBDIR):LIBDIR=$$[QT_INSTALL_LIBS]
isEmpty(HEADERDIR):HEADERDIR=$${PREFIX}/include/$$TARGET/
isEmpty(DSRDIR):DSRDIR=$${PREFIX}/share/$$TARGET
target.path = $${LIBDIR}
headers.files = $$HEADERS
headers.path = $${HEADERDIR}
license.files = LICENSE
license.path = $${DSRDIR}/licenses/
trans.files = trans/*.qm
trans.path = $${DSRDIR}/translations/
QMAKE_PKGCONFIG_NAME = QMarkdownTextedit
QMAKE_PKGCONFIG_DESCRIPTION = C++ Qt QPlainTextEdit widget with markdown highlighting and some other goodies
QMAKE_PKGCONFIG_INCDIR = $${headers.path}
QMAKE_PKGCONFIG_LIBDIR = $${LIBDIR}
QMAKE_PKGCONFIG_DESTDIR = pkgconfig
INSTALLS += target license headers trans

View File

@ -6,6 +6,7 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
SOURCES += \
$$PWD/markdownhighlighter.cpp \
$$PWD/qmarkdowntextedit.cpp \
$$PWD/qownlanguagedata.cpp \
$$PWD/qplaintexteditsearchwidget.cpp
RESOURCES += \

File diff suppressed because it is too large Load Diff

View File

@ -1,87 +1,146 @@
/*
* Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com
* MIT License
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
* Copyright (c) 2014-2023 Patrizio Bekerle -- <patrizio@bekerle.com>
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <QPlainTextEdit>
#include <QEvent>
#include <QPlainTextEdit>
#include "markdownhighlighter.h"
#include "qplaintexteditsearchwidget.h"
class LineNumArea;
class QMarkdownTextEdit : public QPlainTextEdit
{
class QMarkdownTextEdit : public QPlainTextEdit {
Q_OBJECT
Q_PROPERTY(
bool highlighting READ highlightingEnabled WRITE setHighlightingEnabled)
public:
friend class LineNumArea;
public:
enum AutoTextOption {
None = 0x0000,
// inserts closing characters for brackets and markdown characters
// inserts closing characters for brackets and Markdown characters
BracketClosing = 0x0001,
// removes matching brackets and markdown characters
// removes matching brackets and Markdown characters
BracketRemoval = 0x0002
};
Q_DECLARE_FLAGS(AutoTextOptions, AutoTextOption)
explicit QMarkdownTextEdit(QWidget *parent = 0, bool initHighlighter = true);
explicit QMarkdownTextEdit(QWidget *parent = nullptr,
bool initHighlighter = true);
MarkdownHighlighter *highlighter();
QPlainTextEditSearchWidget *searchWidget();
void setIgnoredClickUrlSchemata(QStringList ignoredUrlSchemata);
virtual void openUrl(QString urlString);
QString getMarkdownUrlAtPosition(QString text, int position);
virtual void openUrl(const QString &urlString);
QString getMarkdownUrlAtPosition(const QString &text, int position);
void initSearchFrame(QWidget *searchFrame, bool darkMode = false);
void setAutoTextOptions(AutoTextOptions options);
void setHighlightingEnabled(bool enabled);
static bool isValidUrl(QString urlString);
static bool isValidUrl(const QString &urlString);
void resetMouseCursor() const;
void setReadOnly(bool ro);
void doSearch(QString &searchText,
QPlainTextEditSearchWidget::SearchMode searchMode =
QPlainTextEditSearchWidget::SearchMode::PlainTextMode);
void hideSearchWidget(bool reset);
void updateSettings();
void setLineNumbersCurrentLineColor(QColor color);
void setLineNumbersOtherLineColor(QColor color);
void setSearchWidgetDebounceDelay(uint debounceDelay);
public slots:
void setHighlightingEnabled(bool enabled);
[[nodiscard]] bool highlightingEnabled() const;
void setHighlightCurrentLine(bool set);
bool highlightCurrentLine();
void setCurrentLineHighlightColor(const QColor &c);
QColor currentLineHighlightColor();
public Q_SLOTS:
void duplicateText();
void setText(const QString & text);
void setPlainText(const QString & text);
void setText(const QString &text);
void setPlainText(const QString &text);
void adjustRightMargin();
void hide();
bool openLinkAtCursorPosition();
bool handleBracketRemoval();
bool handleBackspaceEntered();
void centerTheCursor();
void undo();
void moveTextUpDown(bool up);
void setLineNumberEnabled(bool enabled);
protected:
MarkdownHighlighter *_highlighter;
protected:
QTextCursor _textCursor;
MarkdownHighlighter *_highlighter = nullptr;
bool _highlightingEnabled;
QStringList _ignoredClickUrlSchemata;
QPlainTextEditSearchWidget *_searchWidget;
QWidget *_searchFrame;
AutoTextOptions _autoTextOptions;
QStringList _openingCharacters;
QStringList _closingCharacters;
bool _mouseButtonDown = false;
bool _centerCursor = false;
bool _highlightCurrentLine = false;
QColor _currentLineHighlightColor = QColor();
uint _debounceDelay = 0;
bool eventFilter(QObject *obj, QEvent *event);
bool increaseSelectedTextIndention(bool reverse);
bool handleTabEntered(bool reverse);
QMap<QString, QString> parseMarkdownUrlsFromText(QString text);
bool eventFilter(QObject *obj, QEvent *event) override;
QMargins viewportMargins();
bool increaseSelectedTextIndention(
bool reverse,
const QString &indentCharacters = QChar::fromLatin1('\t'));
bool handleTabEntered(bool reverse, const QString &indentCharacters =
QChar::fromLatin1('\t'));
QMap<QString, QString> parseMarkdownUrlsFromText(const QString &text);
bool handleReturnEntered();
bool handleBracketClosing(QString openingCharacter,
QString closingCharacter = "");
bool bracketClosingCheck(QString openingCharacter,
QString closingCharacter);
bool quotationMarkCheck(QString quotationCharacter);
void focusOutEvent(QFocusEvent *event);
void paintEvent(QPaintEvent *e);
bool handleBracketClosing(const QChar openingCharacter,
QChar closingCharacter = QChar());
bool bracketClosingCheck(const QChar openingCharacter,
QChar closingCharacter);
bool quotationMarkCheck(const QChar quotationCharacter);
void focusOutEvent(QFocusEvent *event) override;
void paintEvent(QPaintEvent *e) override;
bool handleCharRemoval(MarkdownHighlighter::RangeType type, int block, int position);
void resizeEvent(QResizeEvent *event) override;
void setLineNumberLeftMarginOffset(int offset);
int _lineNumberLeftMarginOffset = 0;
LineNumArea *lineNumberArea()
{
return _lineNumArea;
}
void updateLineNumAreaGeometry();
void updateLineNumberArea(const QRect rect, int dy);
Q_SLOT void updateLineNumberAreaWidth(int);
bool _handleBracketClosingUsed;
LineNumArea *_lineNumArea;
signals:
Q_SIGNALS:
void urlClicked(QString url);
void zoomIn();
void zoomOut();
};

View File

@ -0,0 +1,12 @@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=@CMAKE_INSTALL_PREFIX@
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@/
Name: @PROJECT_NAME@
Description: @PROJECT_DESCRIPTION@
Version: @PROJECT_VERSION@
Requires:
Libs: -L${libdir} -lqmarkdowntextedit
Cflags: -I${includedir}

View File

@ -1,23 +1,5 @@
#-------------------------------------------------
#
# Project created by QtCreator 2016-01-11T16:56:21
#
#-------------------------------------------------
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = QMarkdownTextedit
TEMPLATE = app
TRANSLATIONS = trans/qmarkdowntextedit_de.ts
CONFIG += c++11
SOURCES += main.cpp \
mainwindow.cpp \
HEADERS += mainwindow.h
FORMS += mainwindow.ui
include(qmarkdowntextedit.pri)
TEMPLATE = subdirs
SUBDIRS = app lib
app.file = qmarkdowntextedit-app.pro
lib.file = qmarkdowntextedit-lib.pro
app.depends = lib

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,245 @@
/*
* MIT License
*
* Copyright (c) 2019-2021 Waqar Ahmed -- <waqar.17a@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef QOWNLANGUAGEDATA_H
#define QOWNLANGUAGEDATA_H
#include <QMultiHash>
/* ------------------------
* TEMPLATE FOR LANG DATA
* -------------------------
*
* loadXXXData, where XXX is the language
* keywords are the language keywords e.g, const
* types are built-in types i.e, int, char, var
* literals are words like, true false
* builtin are the library functions
* other can contain any other thing, for e.g, in cpp it contains the
preprocessor
static const QMultiHash<char, QLatin1String> xxx_keywords = {
};
static const QMultiHash<char, QLatin1String> xxx_types = {
};
static const QMultiHash<char, QLatin1String> xxx_literals = {
};
static const QMultiHash<char, QLatin1String> xxx_builtin = {
};
static const QMultiHash<char, QLatin1String> xxx_other = {
};
*/
/**********************************************************/
/* C/C++ Data *********************************************/
/**********************************************************/
void loadCppData(QMultiHash<char, QLatin1String> &typess,
QMultiHash<char, QLatin1String> &keywordss,
QMultiHash<char, QLatin1String> &builtins,
QMultiHash<char, QLatin1String> &literalss,
QMultiHash<char, QLatin1String> &others);
/**********************************************************/
/* Shell Data *********************************************/
/**********************************************************/
void loadShellData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/**********************************************************/
/* JS Data *********************************************/
/**********************************************************/
void loadJSData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/**********************************************************/
/* JS Data *********************************************/
/**********************************************************/
void loadNixData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/**********************************************************/
/* PHP Data *********************************************/
/**********************************************************/
void loadPHPData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/**********************************************************/
/* QML Data *********************************************/
/**********************************************************/
void loadQMLData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/**********************************************************/
/* Python Data *********************************************/
/**********************************************************/
void loadPythonData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** Rust DATA ***********************************/
/********************************************************/
void loadRustData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** Java DATA ***********************************/
/********************************************************/
void loadJavaData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** C# DATA *************************************/
/********************************************************/
void loadCSharpData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** Go DATA *************************************/
/********************************************************/
void loadGoData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** V DATA **************************************/
/********************************************************/
void loadVData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** SQL DATA ************************************/
/********************************************************/
void loadSQLData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** JSON DATA ***********************************/
/********************************************************/
void loadJSONData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** CSS DATA ***********************************/
/********************************************************/
void loadCSSData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** Typescript DATA *********************************/
/********************************************************/
void loadTypescriptData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** YAML DATA ***************************************/
/********************************************************/
void loadYAMLData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** VEX DATA ****************************************/
/********************************************************/
void loadVEXData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** CMake DATA **************************************/
/********************************************************/
void loadCMakeData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** Make DATA ***************************************/
/********************************************************/
void loadMakeData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** Forth DATA **************************************/
/********************************************************/
void loadForthData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
#endif

View File

@ -1,46 +1,66 @@
/*
* Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com
* MIT License
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
* Copyright (c) 2014-2023 Patrizio Bekerle -- <patrizio@bekerle.com>
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "qplaintexteditsearchwidget.h"
#include "ui_qplaintexteditsearchwidget.h"
#include <QDebug>
#include <QEvent>
#include <QKeyEvent>
#include <QDebug>
QPlainTextEditSearchWidget::QPlainTextEditSearchWidget(QPlainTextEdit *parent) :
QWidget(parent),
ui(new Ui::QPlainTextEditSearchWidget)
{
#include "ui_qplaintexteditsearchwidget.h"
QPlainTextEditSearchWidget::QPlainTextEditSearchWidget(QPlainTextEdit *parent)
: QWidget(parent),
ui(new Ui::QPlainTextEditSearchWidget),
selectionColor(0, 180, 0, 100) {
ui->setupUi(this);
_textEdit = parent;
_darkMode = false;
hide();
ui->searchCountLabel->setStyleSheet(QStringLiteral("* {color: grey}"));
// hiding will leave a open space in the horizontal layout
ui->searchCountLabel->setEnabled(false);
_currentSearchResult = 0;
_searchResultCount = 0;
QObject::connect(ui->closeButton, SIGNAL(clicked()),
this, SLOT(deactivate()));
QObject::connect(ui->searchLineEdit, SIGNAL(textChanged(const QString &)),
this, SLOT(searchLineEditTextChanged(const QString &)));
QObject::connect(ui->searchDownButton, SIGNAL(clicked()),
this, SLOT(doSearchDown()));
QObject::connect(ui->searchUpButton, SIGNAL(clicked()),
this, SLOT(doSearchUp()));
QObject::connect(ui->replaceToggleButton, SIGNAL(toggled(bool)),
this, SLOT(setReplaceMode(bool)));
QObject::connect(ui->replaceButton, SIGNAL(clicked()),
this, SLOT(doReplace()));
QObject::connect(ui->replaceAllButton, SIGNAL(clicked()),
this, SLOT(doReplaceAll()));
connect(ui->closeButton, &QPushButton::clicked, this,
&QPlainTextEditSearchWidget::deactivate);
connect(ui->searchLineEdit, &QLineEdit::textChanged, this,
&QPlainTextEditSearchWidget::searchLineEditTextChanged);
connect(ui->searchDownButton, &QPushButton::clicked, this,
&QPlainTextEditSearchWidget::doSearchDown);
connect(ui->searchUpButton, &QPushButton::clicked, this,
&QPlainTextEditSearchWidget::doSearchUp);
connect(ui->replaceToggleButton, &QPushButton::toggled, this,
&QPlainTextEditSearchWidget::setReplaceMode);
connect(ui->replaceButton, &QPushButton::clicked, this,
&QPlainTextEditSearchWidget::doReplace);
connect(ui->replaceAllButton, &QPushButton::clicked, this,
&QPlainTextEditSearchWidget::doReplaceAll);
connect(&_debounceTimer, &QTimer::timeout,
this, &QPlainTextEditSearchWidget::performSearch);
installEventFilter(this);
ui->searchLineEdit->installEventFilter(this);
@ -52,7 +72,7 @@ QPlainTextEditSearchWidget::QPlainTextEditSearchWidget(QPlainTextEdit *parent) :
ui->buttonFrame->layout()->setSpacing(9);
// set the margin to 0 for the top buttons for OS X
QString buttonStyle = "QPushButton {margin: 0}";
QString buttonStyle = QStringLiteral("QPushButton {margin: 0}");
ui->closeButton->setStyleSheet(buttonStyle);
ui->searchDownButton->setStyleSheet(buttonStyle);
ui->searchUpButton->setStyleSheet(buttonStyle);
@ -61,25 +81,9 @@ QPlainTextEditSearchWidget::QPlainTextEditSearchWidget(QPlainTextEdit *parent) :
#endif
}
QPlainTextEditSearchWidget::~QPlainTextEditSearchWidget() {
delete ui;
}
QPlainTextEditSearchWidget::~QPlainTextEditSearchWidget() { delete ui; }
void QPlainTextEditSearchWidget::activate() {
setReplaceMode(false);
show();
// preset the selected text as search text if there is any and there is no
// other search text
QString selectedText = _textEdit->textCursor().selectedText();
if (!selectedText.isEmpty() && ui->searchLineEdit->text().isEmpty()) {
ui->searchLineEdit->setText(selectedText);
}
ui->searchLineEdit->setFocus();
ui->searchLineEdit->selectAll();
doSearchDown();
}
void QPlainTextEditSearchWidget::activate() { activate(true); }
void QPlainTextEditSearchWidget::activateReplace() {
// replacing is prohibited if the text edit is readonly
@ -94,7 +98,13 @@ void QPlainTextEditSearchWidget::activateReplace() {
}
void QPlainTextEditSearchWidget::deactivate() {
stopDebounce();
hide();
// Clear the search extra selections when closing the search bar
clearSearchExtraSelections();
_textEdit->setFocus();
}
@ -109,29 +119,32 @@ void QPlainTextEditSearchWidget::setReplaceMode(bool enabled) {
bool QPlainTextEditSearchWidget::eventFilter(QObject *obj, QEvent *event) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
auto *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Escape) {
deactivate();
return true;
} else if ((keyEvent->modifiers().testFlag(Qt::ShiftModifier) &&
(keyEvent->key() == Qt::Key_Return)) ||
(keyEvent->key() == Qt::Key_Up)) {
} else if ((!_debounceTimer.isActive() &&
keyEvent->modifiers().testFlag(Qt::ShiftModifier) &&
(keyEvent->key() == Qt::Key_Return)) ||
(keyEvent->key() == Qt::Key_Up)) {
doSearchUp();
return true;
} else if ((keyEvent->key() == Qt::Key_Return) ||
(keyEvent->key() == Qt::Key_Down)) {
} else if (!_debounceTimer.isActive() &&
((keyEvent->key() == Qt::Key_Return) ||
(keyEvent->key() == Qt::Key_Down))) {
doSearchDown();
return true;
} else if (keyEvent->key() == Qt::Key_F3) {
} else if (!_debounceTimer.isActive() && keyEvent->key() == Qt::Key_F3) {
doSearch(!keyEvent->modifiers().testFlag(Qt::ShiftModifier));
return true;
}
// if ((obj == ui->replaceLineEdit) && (keyEvent->key() == Qt::Key_Tab)
// && ui->replaceToggleButton->isChecked()) {
// ui->replaceLineEdit->setFocus();
// }
// if ((obj == ui->replaceLineEdit) && (keyEvent->key() ==
// Qt::Key_Tab)
// && ui->replaceToggleButton->isChecked()) {
// ui->replaceLineEdit->setFocus();
// }
return false;
}
@ -139,19 +152,75 @@ bool QPlainTextEditSearchWidget::eventFilter(QObject *obj, QEvent *event) {
return QWidget::eventFilter(obj, event);
}
void QPlainTextEditSearchWidget::searchLineEditTextChanged(const QString &arg1) {
Q_UNUSED(arg1);
void QPlainTextEditSearchWidget::searchLineEditTextChanged(
const QString &arg1) {
_searchTerm = arg1;
if (_debounceTimer.interval() != 0 && !_searchTerm.isEmpty()) {
_debounceTimer.start();
ui->searchDownButton->setEnabled(false);
ui->searchUpButton->setEnabled(false);
} else {
performSearch();
}
}
void QPlainTextEditSearchWidget::performSearch()
{
doSearchCount();
updateSearchExtraSelections();
doSearchDown();
}
void QPlainTextEditSearchWidget::doSearchUp() {
doSearch(false);
void QPlainTextEditSearchWidget::clearSearchExtraSelections() {
_searchExtraSelections.clear();
setSearchExtraSelections();
}
void QPlainTextEditSearchWidget::doSearchDown() {
doSearch(true);
void QPlainTextEditSearchWidget::updateSearchExtraSelections() {
_searchExtraSelections.clear();
const auto textCursor = _textEdit->textCursor();
_textEdit->moveCursor(QTextCursor::Start);
const QColor color = selectionColor;
QTextCharFormat extraFmt;
extraFmt.setBackground(color);
int findCounter = 0;
const int searchMode = ui->modeComboBox->currentIndex();
while (doSearch(true, false, false)) {
findCounter++;
// prevent infinite loops from regular expression searches like "$", "^" or "\b"
if (searchMode == RegularExpressionMode && findCounter >= 10000) {
break;
}
QTextEdit::ExtraSelection extra = QTextEdit::ExtraSelection();
extra.format = extraFmt;
extra.cursor = _textEdit->textCursor();
_searchExtraSelections.append(extra);
}
_textEdit->setTextCursor(textCursor);
this->setSearchExtraSelections();
}
void QPlainTextEditSearchWidget::setSearchExtraSelections() const {
this->_textEdit->setExtraSelections(this->_searchExtraSelections);
}
void QPlainTextEditSearchWidget::stopDebounce()
{
_debounceTimer.stop();
ui->searchDownButton->setEnabled(true);
ui->searchUpButton->setEnabled(true);
}
void QPlainTextEditSearchWidget::doSearchUp() { doSearch(false); }
void QPlainTextEditSearchWidget::doSearchDown() { doSearch(true); }
bool QPlainTextEditSearchWidget::doReplace(bool forAll) {
if (_textEdit->isReadOnly()) {
return false;
@ -163,18 +232,18 @@ bool QPlainTextEditSearchWidget::doReplace(bool forAll) {
return false;
}
int searchMode = ui->modeComboBox->currentIndex();
const int searchMode = ui->modeComboBox->currentIndex();
if (searchMode == RegularExpressionMode) {
QString text = cursor.selectedText();
text.replace(QRegExp(ui->searchLineEdit->text()),
ui->replaceLineEdit->text());
text.replace(QRegularExpression(ui->searchLineEdit->text()),
ui->replaceLineEdit->text());
cursor.insertText(text);
} else {
cursor.insertText(ui->replaceLineEdit->text());
}
if (!forAll) {
int position = cursor.position();
const int position = cursor.position();
if (!doSearch(true)) {
// restore the last cursor position if text wasn't found any more
@ -195,72 +264,237 @@ void QPlainTextEditSearchWidget::doReplaceAll() {
_textEdit->moveCursor(QTextCursor::Start);
// replace until everything to the bottom is replaced
while (doSearch(true, false) && doReplace(true)) {}
while (doSearch(true, false) && doReplace(true)) {
}
}
/**
* @brief Searches for text in the text edit
* @returns true if found
*/
bool QPlainTextEditSearchWidget::doSearch(bool searchDown, bool allowRestartAtTop) {
QString text = ui->searchLineEdit->text();
bool QPlainTextEditSearchWidget::doSearch(bool searchDown,
bool allowRestartAtTop,
bool updateUI) {
if (_debounceTimer.isActive()) {
stopDebounce();
}
const QString text = ui->searchLineEdit->text();
if (text.isEmpty()) {
if (updateUI) {
ui->searchLineEdit->setStyleSheet(QLatin1String(""));
}
if (text == "") {
ui->searchLineEdit->setStyleSheet("");
return false;
}
int searchMode = ui->modeComboBox->currentIndex();
const int searchMode = ui->modeComboBox->currentIndex();
const bool caseSensitive = ui->matchCaseSensitiveButton->isChecked();
QFlags<QTextDocument::FindFlag> options = searchDown ?
QTextDocument::FindFlag(0)
: QTextDocument::FindBackward;
QFlags<QTextDocument::FindFlag> options =
searchDown ? QTextDocument::FindFlag(0) : QTextDocument::FindBackward;
if (searchMode == WholeWordsMode) {
options |= QTextDocument::FindWholeWords;
}
if (ui->matchCaseSensitiveButton->isChecked()) {
if (caseSensitive) {
options |= QTextDocument::FindCaseSensitively;
}
bool found;
if (searchMode == RegularExpressionMode) {
found = _textEdit->find(QRegExp(text), options);
} else {
found = _textEdit->find(text, options);
// block signal to reduce too many signals being fired and too many updates
_textEdit->blockSignals(true);
bool found =
searchMode == RegularExpressionMode
?
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
_textEdit->find(
QRegularExpression(
text, caseSensitive
? QRegularExpression::NoPatternOption
: QRegularExpression::CaseInsensitiveOption),
options)
:
#else
_textEdit->find(QRegExp(text, caseSensitive ? Qt::CaseSensitive
: Qt::CaseInsensitive),
options)
:
#endif
_textEdit->find(text, options);
_textEdit->blockSignals(false);
if (found) {
const int result =
searchDown ? ++_currentSearchResult : --_currentSearchResult;
_currentSearchResult = std::min(result, _searchResultCount);
updateSearchCountLabelText();
}
// start at the top (or bottom) if not found
if (!found && allowRestartAtTop) {
_textEdit->moveCursor(
searchDown ? QTextCursor::Start : QTextCursor::End);
found = _textEdit->find(text, options);
_textEdit->moveCursor(searchDown ? QTextCursor::Start
: QTextCursor::End);
found =
searchMode == RegularExpressionMode
?
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
_textEdit->find(
QRegularExpression(
text, caseSensitive
? QRegularExpression::NoPatternOption
: QRegularExpression::CaseInsensitiveOption),
options)
:
#else
_textEdit->find(
QRegExp(text, caseSensitive ? Qt::CaseSensitive
: Qt::CaseInsensitive),
options)
:
#endif
_textEdit->find(text, options);
if (found && updateUI) {
_currentSearchResult = searchDown ? 1 : _searchResultCount;
updateSearchCountLabelText();
}
}
QRect rect = _textEdit->cursorRect();
QMargins margins = _textEdit->layout()->contentsMargins();
int searchWidgetHotArea = _textEdit->height() - this->height();
int marginBottom = (rect.y() > searchWidgetHotArea) ? (this->height() + 10)
: 0;
if (updateUI) {
const QRect rect = _textEdit->cursorRect();
QMargins margins = _textEdit->layout()->contentsMargins();
const int searchWidgetHotArea = _textEdit->height() - this->height();
const int marginBottom =
(rect.y() > searchWidgetHotArea) ? (this->height() + 10) : 0;
// move the search box a bit up if we would block the search result
if (margins.bottom() != marginBottom) {
margins.setBottom(marginBottom);
_textEdit->layout()->setContentsMargins(margins);
// move the search box a bit up if we would block the search result
if (margins.bottom() != marginBottom) {
margins.setBottom(marginBottom);
_textEdit->layout()->setContentsMargins(margins);
}
// add a background color according if we found the text or not
const QString bgColorCode =
_darkMode
? (found ? QStringLiteral("#135a13")
: QStringLiteral("#8d2b36"))
: found ? QStringLiteral("#D5FAE2") : QStringLiteral("#FAE9EB");
const QString fgColorCode =
_darkMode ? QStringLiteral("#cccccc") : QStringLiteral("#404040");
ui->searchLineEdit->setStyleSheet(
QStringLiteral("* { background: ") + bgColorCode +
QStringLiteral("; color: ") + fgColorCode + QStringLiteral("; }"));
// restore the search extra selections after the find command
this->setSearchExtraSelections();
}
// add a background color according if we found the text or not
QString colorCode = found ? "#D5FAE2" : "#FAE9EB";
if (_darkMode) {
colorCode = found ? "#135a13" : "#8d2b36";
}
ui->searchLineEdit->setStyleSheet("* { background: " + colorCode + "; }");
return found;
}
/**
* @brief Counts the search results
*/
void QPlainTextEditSearchWidget::doSearchCount() {
// Note that we are moving the anchor, so the search will start from the top
// again! Alternative: Restore cursor position afterward, but then we will
// not know
// at what _currentSearchResult we currently are
_textEdit->moveCursor(QTextCursor::Start, QTextCursor::MoveAnchor);
bool found;
_searchResultCount = 0;
_currentSearchResult = 0;
const int searchMode = ui->modeComboBox->currentIndex();
do {
found = doSearch(true, false, false);
if (found) {
_searchResultCount++;
}
// prevent infinite loops from regular expression searches like "$", "^" or "\b"
if (searchMode == RegularExpressionMode && _searchResultCount >= 10000) {
break;
}
} while (found);
updateSearchCountLabelText();
}
void QPlainTextEditSearchWidget::setDarkMode(bool enabled) {
_darkMode = enabled;
}
void QPlainTextEditSearchWidget::setSearchText(const QString &searchText) {
ui->searchLineEdit->setText(searchText);
}
void QPlainTextEditSearchWidget::setSearchMode(SearchMode searchMode) {
ui->modeComboBox->setCurrentIndex(searchMode);
}
void QPlainTextEditSearchWidget::setDebounceDelay(uint debounceDelay)
{
_debounceTimer.setInterval(static_cast<int>(debounceDelay));
}
void QPlainTextEditSearchWidget::activate(bool focus) {
setReplaceMode(ui->modeComboBox->currentIndex() !=
SearchMode::PlainTextMode);
show();
// preset the selected text as search text if there is any and there is no
// other search text
const QString selectedText = _textEdit->textCursor().selectedText();
if (!selectedText.isEmpty() && ui->searchLineEdit->text().isEmpty()) {
ui->searchLineEdit->setText(selectedText);
}
if (focus) {
ui->searchLineEdit->setFocus();
}
ui->searchLineEdit->selectAll();
updateSearchExtraSelections();
doSearchDown();
}
void QPlainTextEditSearchWidget::reset() {
ui->searchLineEdit->clear();
setSearchMode(SearchMode::PlainTextMode);
setReplaceMode(false);
ui->searchCountLabel->setEnabled(false);
}
void QPlainTextEditSearchWidget::updateSearchCountLabelText() {
ui->searchCountLabel->setEnabled(true);
ui->searchCountLabel->setText(QStringLiteral("%1/%2").arg(
_currentSearchResult == 0 ? QChar('-')
: QString::number(_currentSearchResult),
_searchResultCount == 0 ? QChar('-')
: QString::number(_searchResultCount)));
}
void QPlainTextEditSearchWidget::setSearchSelectionColor(const QColor &color) {
selectionColor = color;
}
void QPlainTextEditSearchWidget::on_modeComboBox_currentIndexChanged(
int index) {
Q_UNUSED(index)
doSearchCount();
doSearchDown();
}
void QPlainTextEditSearchWidget::on_matchCaseSensitiveButton_toggled(
bool checked) {
Q_UNUSED(checked)
doSearchCount();
doSearchDown();
}

View File

@ -1,51 +1,73 @@
/*
* Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com
* MIT License
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
* Copyright (c) 2014-2023 Patrizio Bekerle -- <patrizio@bekerle.com>
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <QWidget>
#include <QPlainTextEdit>
#include <QTimer>
#include <QWidget>
namespace Ui {
class QPlainTextEditSearchWidget;
}
class QPlainTextEditSearchWidget : public QWidget
{
class QPlainTextEditSearchWidget : public QWidget {
Q_OBJECT
public:
enum SearchMode {
PlainTextMode,
WholeWordsMode,
RegularExpressionMode
};
public:
enum SearchMode { PlainTextMode, WholeWordsMode, RegularExpressionMode };
explicit QPlainTextEditSearchWidget(QPlainTextEdit *parent = 0);
bool doSearch(bool searchDown = true, bool allowRestartAtTop = true);
explicit QPlainTextEditSearchWidget(QPlainTextEdit *parent = nullptr);
bool doSearch(bool searchDown = true, bool allowRestartAtTop = true,
bool updateUI = true);
void setDarkMode(bool enabled);
~QPlainTextEditSearchWidget();
private:
Ui::QPlainTextEditSearchWidget *ui;
void setSearchText(const QString &searchText);
void setSearchMode(SearchMode searchMode);
void setDebounceDelay(uint debounceDelay);
void activate(bool focus);
void clearSearchExtraSelections();
void updateSearchExtraSelections();
protected:
private:
Ui::QPlainTextEditSearchWidget *ui;
int _searchResultCount;
int _currentSearchResult;
QList<QTextEdit::ExtraSelection> _searchExtraSelections;
QColor selectionColor;
QTimer _debounceTimer;
QString _searchTerm;
void setSearchExtraSelections() const;
void stopDebounce();
protected:
QPlainTextEdit *_textEdit;
bool _darkMode;
bool eventFilter(QObject *obj, QEvent *event);
bool eventFilter(QObject *obj, QEvent *event) override;
public slots:
public Q_SLOTS:
void activate();
void deactivate();
void doSearchDown();
@ -54,7 +76,15 @@ public slots:
void activateReplace();
bool doReplace(bool forAll = false);
void doReplaceAll();
void reset();
void doSearchCount();
protected slots:
protected Q_SLOTS:
void searchLineEditTextChanged(const QString &arg1);
void performSearch();
void updateSearchCountLabelText();
void setSearchSelectionColor(const QColor &color);
private Q_SLOTS:
void on_modeComboBox_currentIndexChanged(int index);
void on_matchCaseSensitiveButton_toggled(bool checked);
};

View File

@ -26,10 +26,85 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="3">
<widget class="QLineEdit" name="searchLineEdit">
<property name="placeholderText">
<string>Find in text</string>
</property>
</widget>
</item>
<item row="1" column="3" colspan="2">
<widget class="QLineEdit" name="replaceLineEdit">
<property name="placeholderText">
<string>Replace with</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QLabel" name="searchCountLabel">
<property name="text">
<string notr="true">-/-</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="searchLabel">
<property name="text">
<string>Find:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="6">
<widget class="QPushButton" name="searchUpButton">
<property name="toolTip">
<string>Search backward</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="go-top" resource="media.qrc">
<normaloff>:/media/go-top.svg</normaloff>:/media/go-top.svg</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="replaceLabel">
<property name="text">
<string>Replace:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QPushButton" name="closeButton">
<property name="toolTip">
<string>Close search</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="window-close" resource="media.qrc">
<normaloff>:/media/window-close.svg</normaloff>:/media/window-close.svg</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="7">
<widget class="QPushButton" name="replaceToggleButton">
<property name="toolTip">
<string>replace text</string>
<string>Advanced search / replace text</string>
</property>
<property name="text">
<string/>
@ -46,44 +121,10 @@
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="searchLabel">
<property name="text">
<string>Find:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QPushButton" name="closeButton">
<property name="toolTip">
<string>close search</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="window-close" resource="media.qrc">
<normaloff>:/media/window-close.svg</normaloff>:/media/window-close.svg</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLineEdit" name="searchLineEdit">
<property name="placeholderText">
<string>find in text</string>
</property>
</widget>
</item>
<item row="0" column="4">
<item row="0" column="5">
<widget class="QPushButton" name="searchDownButton">
<property name="toolTip">
<string>search forward</string>
<string>Search forward</string>
</property>
<property name="text">
<string/>
@ -97,41 +138,37 @@
</property>
</widget>
</item>
<item row="0" column="5">
<widget class="QPushButton" name="searchUpButton">
<item row="1" column="5">
<widget class="QPushButton" name="matchCaseSensitiveButton">
<property name="toolTip">
<string>search backward</string>
<string>Match case sensitive</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="go-top" resource="media.qrc">
<normaloff>:/media/go-top.svg</normaloff>:/media/go-top.svg</iconset>
<iconset theme="format-text-superscript" resource="media.qrc">
<normaloff>:/media/format-text-superscript.svg</normaloff>:/media/format-text-superscript.svg</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QLineEdit" name="replaceLineEdit">
<property name="placeholderText">
<string>replace with</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="replaceLabel">
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="modeLabel">
<property name="text">
<string>Replace:</string>
<string>Mode:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="3">
<item row="2" column="3" colspan="2">
<widget class="QFrame" name="buttonFrame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
@ -147,7 +184,7 @@
<number>0</number>
</property>
<property name="bottomMargin">
<number>9</number>
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="modeComboBox">
@ -183,6 +220,9 @@
</item>
<item>
<widget class="QPushButton" name="replaceButton">
<property name="toolTip">
<string>Replace one text occurrence</string>
</property>
<property name="text">
<string>Replace</string>
</property>
@ -193,8 +233,11 @@
</item>
<item>
<widget class="QPushButton" name="replaceAllButton">
<property name="toolTip">
<string>Replace all text occurrences</string>
</property>
<property name="text">
<string>Replace All</string>
<string>Replace all</string>
</property>
<property name="flat">
<bool>false</bool>
@ -204,36 +247,6 @@
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="modeLabel">
<property name="text">
<string>Mode:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QPushButton" name="matchCaseSensitiveButton">
<property name="toolTip">
<string>Match case sensitive</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="format-text-superscript" resource="media.qrc">
<normaloff>:/media/format-text-superscript.svg</normaloff>:/media/format-text-superscript.svg</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,28 @@
#!/bin/bash
#
# A tool to run clang-format on the entire project
#
# Some inspirations were taken from https://github.com/eklitzke/clang-format-all
# Variable that will hold the name of the clang-format command
FMT=""
# Some distros just call it clang-format. Others (e.g. Ubuntu) are insistent
# that the version number be part of the command. We prefer clang-format if
# that's present, otherwise we work backwards from highest version to lowest
# version.
for clangfmt in clang-format{,-{4,3}.{9,8,7,6,5,4,3,2,1,0}}; do
if which "$clangfmt" &>/dev/null; then
FMT="$clangfmt"
break
fi
done
# Check if we found a working clang-format
if [ -z "$FMT" ]; then
echo "failed to find clang-format"
exit 1
fi
$FMT -i *.cpp
$FMT -i *.h

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="es_ES">
<context>
<name>QPlainTextEditSearchWidget</name>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="32"/>
<source>Find in text</source>
<translation>Buscar en el texto</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="39"/>
<source>Replace with</source>
<translation>Reemplazar por</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="53"/>
<source>Find:</source>
<translation>Buscar:</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="63"/>
<source>Search backward</source>
<translation>Buscar hacia atrás</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="80"/>
<source>Replace:</source>
<translation>Reemplazar:</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="90"/>
<source>Close search</source>
<translation>Cerrar búsqueda</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="107"/>
<source>Advanced search / replace text</source>
<translation>Búsqueda avanzada / reemplazar texto</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="127"/>
<source>Search forward</source>
<translation>Buscar hacia adelante</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="144"/>
<source>Match case sensitive</source>
<translation>Distingue mayúsculas y minúsculas</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="164"/>
<source>Mode:</source>
<translation>Modo:</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="193"/>
<source>Plain text</source>
<translation>Texto plano</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="198"/>
<source>Whole words</source>
<translation>Palabras enteras</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="203"/>
<source>Regular expression</source>
<translation>Expresión regular</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="224"/>
<source>Replace one text occurrence</source>
<translation>Reemplazar una ocurrencia del texto</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="227"/>
<source>Replace</source>
<translation>Reemplazar</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="237"/>
<source>Replace all text occurrences</source>
<translation>Reemplazar todas las ocurrencias del texto</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="240"/>
<source>Replace all</source>
<translation>Reemplazar todo</translation>
</message>
</context>
</TS>

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="ur_PK">
<context>
<name>QPlainTextEditSearchWidget</name>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="62"/>
<source>close search</source>
<translation>تلاش بند کریں</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="52"/>
<source>Find:</source>
<translation>تلاش:</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="32"/>
<source>replace text</source>
<translation>ٹیکصٹ بدلیں</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="79"/>
<source>find in text</source>
<translation>متن میں تلاش کریں</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="86"/>
<source>search forward</source>
<translation>آگے تلاش کریں</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="103"/>
<source>search backward</source>
<translation>پیچھے تلاش کریں</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="120"/>
<source>replace with</source>
<translation>بدلیں اس سے</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="127"/>
<source>Replace:</source>
<translation>بدلیں:</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="168"/>
<source>Replace</source>
<translation>بدلیں</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="178"/>
<source>Replace All</source>
<translation>تمام کو بدل دیں</translation>
</message>
</context>
</TS>

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN">
<context>
<name>QPlainTextEditSearchWidget</name>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="32"/>
<source>Find in text</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="39"/>
<source>Replace with</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="53"/>
<source>Find:</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="63"/>
<source>Search backward</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="80"/>
<source>Replace:</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="90"/>
<source>Close search</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="107"/>
<source>Advanced search / replace text</source>
<translation>/</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="127"/>
<source>Search forward</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="144"/>
<source>Match case sensitive</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="164"/>
<source>Mode:</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="193"/>
<source>Plain text</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="198"/>
<source>Whole words</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="203"/>
<source>Regular expression</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="224"/>
<source>Replace one text occurrence</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="227"/>
<source>Replace</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="237"/>
<source>Replace all text occurrences</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="240"/>
<source>Replace all</source>
<translation></translation>
</message>
</context>
</TS>

View File

@ -1,3 +1,76 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
*.res
*.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
*.pyc
# Binaries
# --------
*.dll
*.exe
#CMake files
CMakeCache.txt
CMakeFiles
@ -37,7 +110,6 @@ testclient
install_manifest.txt
*.manifest
*.lib
*.exe
#Mac build files
qtkeychain.xcodeproj
@ -45,6 +117,6 @@ qtkeychain.build
#Temporary files
*.sw?
*~
/build-*/
/compile_commands.json

View File

@ -1,31 +1,44 @@
cmake_minimum_required (VERSION 2.8.11)
project (qtkeychain)
cmake_minimum_required(VERSION 3.16)
include (FindPkgConfig)
set(QTKEYCHAIN_VERSION 0.14.2)
set(QTKEYCHAIN_SOVERSION 1)
project(qtkeychain VERSION ${QTKEYCHAIN_VERSION} LANGUAGES CXX)
# Enable C++11
SET(CMAKE_CXX_STANDARD 11)
include(FindPkgConfig)
###
set (QTKEYCHAIN_VERSION 0.9.90)
set (QTKEYCHAIN_SOVERSION 1)
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${PROJECT_SOURCE_DIR}/cmake/Modules")
include(GNUInstallDirs)
include(GenerateExportHeader)
include(CMakePackageConfigHelpers)
include(ECMSetupVersion)
include(ECMGeneratePriFile)
include(CMakeDependentOption)
###
set (CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${PROJECT_SOURCE_DIR}/cmake/Modules")
include (GNUInstallDirs)
include (GenerateExportHeader)
include (ECMPackageConfigHelpers)
include (ECMSetupVersion)
include (ECMGeneratePriFile)
option (BUILD_WITH_QT4 "Build qtkeychain with Qt4 no matter if Qt5 was found" OFF)
option (BUILD_TEST_APPLICATION "Build test application" OFF)
option (BUILD_TRANSLATIONS "Build translations" ON)
option (QTKEYCHAIN_STATIC "Build static library" ON)
option(BUILD_WITH_QT6 "Build qtkeychain with Qt 6" OFF)
option(BUILD_TEST_APPLICATION "Build test application" OFF)
option(BUILD_TRANSLATIONS "Build translations" OFF)
option(BUILD_SHARED_LIBS "Build dynamic library" OFF)
if(QTKEYCHAIN_STATIC)
set(BUILD_SHARED_LIBS OFF)
message(WARNING "QTKEYCHAIN_STATIC is deprecated. Use BUILD_SHARED_LIBS=OFF instead.")
endif()
CMAKE_DEPENDENT_OPTION(BUILD_TRANSLATIONS_AS_RESOURCES "Bundle translations with the library" OFF
"BUILD_TRANSLATIONS" OFF)
if(CMAKE_SYSTEM_NAME STREQUAL Android)
set(ANDROID 1)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL Haiku)
set(HAIKU 1)
endif()
if (WIN32)
option(USE_CREDENTIAL_STORE "Build with windows CredentialStore support" ON)
@ -34,79 +47,78 @@ if (WIN32)
endif()
endif()
if( NOT BUILD_WITH_QT4 )
# try Qt5 first, and prefer that if found
find_package(Qt5Core QUIET)
if( NOT BUILD_WITH_QT6 )
find_package(Qt5 COMPONENTS Core REQUIRED)
endif()
if (Qt5Core_FOUND AND NOT BUILD_WITH_QT4)
set(QTKEYCHAIN_VERSION_INFIX 5)
if (Qt5Core_FOUND AND NOT BUILD_WITH_QT6)
set(QTKEYCHAIN_VERSION_INFIX 5)
if(UNIX AND NOT APPLE AND NOT ANDROID)
find_package(Qt5DBus REQUIRED)
include_directories(${Qt5DBus_INCLUDE_DIRS})
set(QTDBUS_LIBRARIES ${Qt5DBus_LIBRARIES})
macro(qt_add_dbus_interface)
qt5_add_dbus_interface(${ARGN})
endmacro()
if(ANDROID)
if(Qt5Core_VERSION VERSION_LESS 5.7)
find_package(Qt5 COMPONENTS Core REQUIRED Private)
include_directories(${Qt5Core_PRIVATE_INCLUDE_DIRS})
endif()
if(BUILD_TRANSLATIONS)
find_package(Qt5LinguistTools REQUIRED)
macro(qt_add_translation)
qt5_add_translation(${ARGN})
endmacro(qt_add_translation)
macro(qt_create_translation)
qt5_create_translation(${ARGN})
endmacro(qt_create_translation)
endif()
find_package(Qt5 COMPONENTS AndroidExtras REQUIRED)
include_directories(${Qt5AndroidExtras_INCLUDE_DIRS})
set(QTANDROIDEXTRAS_LIBRARIES ${Qt5AndroidExtras_LIBRARIES})
endif()
macro(qt_wrap_cpp)
qt5_wrap_cpp(${ARGN})
if(UNIX AND NOT APPLE AND NOT ANDROID AND NOT HAIKU)
find_package(Qt5 COMPONENTS DBus REQUIRED)
include_directories(${Qt5DBus_INCLUDE_DIRS})
set(QTDBUS_LIBRARIES ${Qt5DBus_LIBRARIES})
macro(qt_add_dbus_interface)
qt5_add_dbus_interface(${ARGN})
endmacro()
endif()
set(QTCORE_LIBRARIES ${Qt5Core_LIBRARIES})
include_directories (${Qt5Core_INCLUDE_DIRS})
if (NOT Qt5Core_VERSION VERSION_LESS "5.7.0")
if (CMAKE_COMPILER_IS_GNUCXX)
if ((NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.7.0") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "6.1.0"))
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
elseif(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.7.0")
message(FATAL_ERROR "Can't build QtKeychain using g++-${CMAKE_CXX_COMPILER_VERSION} and Qt ${Qt5Core_VERSION}: compiler supporting C++11 is required")
endif()
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
if (NOT ${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 3.3)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
else()
message(FATAL_ERROR "Can't build QtKeychain using clang++-${CMAKE_CXX_COMPILER_VERSION} and Qt ${Qt5Core_VERSION}: compiler supporting C++11 is required")
endif()
elseif ((CMAKE_CXX_COMPILER_ID MATCHES "MSVC") AND (MSVC_VERSION LESS 1700))
message(FATAL_ERROR "Can't build QtKeychain using VC++-${MSVC_VERSION} and Qt ${Qt5Core_VERSION}: compiler supporting C++11 is required")
endif()
endif()
else()
set(QTKEYCHAIN_VERSION_INFIX "")
if(UNIX AND NOT APPLE)
find_package(Qt4 COMPONENTS QtCore QtDBus REQUIRED)
set(QTDBUS_LIBRARIES ${QT_QTDBUS_LIBRARY})
macro(qt_add_dbus_interface)
qt4_add_dbus_interface(${ARGN})
endmacro()
else()
find_package(Qt4 COMPONENTS QtCore REQUIRED)
endif()
include_directories(${QT_INCLUDES})
set(QTCORE_LIBRARIES ${QT_QTCORE_LIBRARY})
if(BUILD_TRANSLATIONS)
find_package(Qt5 COMPONENTS LinguistTools REQUIRED)
macro(qt_add_translation)
qt4_add_translation(${ARGN})
qt5_add_translation(${ARGN})
endmacro(qt_add_translation)
macro(qt_create_translation)
qt4_create_translation(${ARGN})
qt5_create_translation(${ARGN})
endmacro(qt_create_translation)
macro(qt_wrap_cpp)
qt4_wrap_cpp(${ARGN})
endif()
macro(qt_wrap_cpp)
qt5_wrap_cpp(${ARGN})
endmacro()
set(QTCORE_LIBRARIES ${Qt5Core_LIBRARIES})
include_directories(${Qt5Core_INCLUDE_DIRS})
else()
find_package(Qt6 COMPONENTS Core REQUIRED)
set(QTKEYCHAIN_VERSION_INFIX 6)
if(UNIX AND NOT APPLE AND NOT ANDROID AND NOT HAIKU)
find_package(Qt6 COMPONENTS DBus REQUIRED)
include_directories(${Qt6DBus_INCLUDE_DIRS})
set(QTDBUS_LIBRARIES ${Qt6DBus_LIBRARIES})
macro(qt_add_dbus_interface)
qt6_add_dbus_interface(${ARGN})
endmacro()
endif()
if(BUILD_TRANSLATIONS)
find_package(Qt6 COMPONENTS LinguistTools REQUIRED)
macro(qt_add_translation)
qt6_add_translation(${ARGN})
endmacro(qt_add_translation)
macro(qt_create_translation)
qt6_create_translation(${ARGN})
endmacro(qt_create_translation)
endif()
macro(qt_wrap_cpp)
qt6_wrap_cpp(${ARGN})
endmacro()
set(QTCORE_LIBRARIES ${Qt6Core_LIBRARIES})
endif()
@ -117,9 +129,20 @@ set(qtkeychain_SOURCES
keychain.cpp
qkeychain_export.h
keychain.h
)
)
add_definitions( -Wall )
if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
# CMake < 3.15 sneaks in /W# flags for us, so we need a replacement,
# or we'll get a warning (cf. CMP0092)
if (CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
endif()
else()
# MSVC's STL / Qt headers are not MSVC -Wall clean, so don't enable it there
add_definitions( -Wall -Werror=return-type )
endif()
if(WIN32)
list(APPEND qtkeychain_SOURCES keychain_win.cpp)
@ -134,105 +157,139 @@ if(WIN32)
endif()
if(APPLE)
if(IOS)
list(APPEND qtkeychain_SOURCES keychain_ios.cpp)
else()
list(APPEND qtkeychain_SOURCES keychain_mac.cpp)
endif()
find_library(COREFOUNDATION_LIBRARY CoreFoundation REQUIRED)
list(APPEND qtkeychain_LIBRARIES ${COREFOUNDATION_LIBRARY})
find_library(SECURITY_LIBRARY Security REQUIRED)
list(APPEND qtkeychain_LIBRARIES ${SECURITY_LIBRARY})
list(APPEND qtkeychain_SOURCES keychain_apple.mm)
list(APPEND qtkeychain_LIBRARIES "-framework Foundation" "-framework Security")
endif()
if(UNIX AND NOT APPLE AND NOT ANDROID)
option(LIBSECRET_SUPPORT "Build with libsecret support" ON)
if(HAIKU)
list(APPEND qtkeychain_SOURCES keychain_haiku.cpp)
if(LIBSECRET_SUPPORT)
pkg_check_modules(LIBSECRET libsecret-1)
find_library(BE_LIBRARY be REQUIRED)
list(APPEND qtkeychain_LIBRARIES ${BE_LIBRARY})
endif()
if (LIBSECRET_FOUND)
add_definitions(-DHAVE_LIBSECRET=1)
endif()
INCLUDE_DIRECTORIES(${LIBSECRET_INCLUDE_DIRS})
list(APPEND qtkeychain_LIBRARIES ${LIBSECRET_LIBRARIES})
endif()
if(UNIX AND NOT APPLE AND NOT ANDROID AND NOT HAIKU)
option(LIBSECRET_SUPPORT "Build with libsecret support" OFF)
#if(LIBSECRET_SUPPORT)
# pkg_check_modules(LIBSECRET REQUIRED libsecret-1)
# add_definitions(-DHAVE_LIBSECRET=1)
# INCLUDE_DIRECTORIES(${LIBSECRET_INCLUDE_DIRS})
# LINK_DIRECTORIES(${LIBSECRET_LIBRARY_DIRS})
# list(APPEND qtkeychain_LIBRARIES_PRIVATE ${LIBSECRET_LIBRARIES})
#endif()
add_definitions(-DKEYCHAIN_DBUS=1)
list(APPEND qtkeychain_SOURCES keychain_unix.cpp gnomekeyring.cpp libsecret.cpp plaintextstore.cpp)
qt_add_dbus_interface(qtkeychain_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/org.kde.KWallet.xml kwallet_interface KWalletInterface)
list(APPEND qtkeychain_LIBRARIES ${QTDBUS_LIBRARIES} )
endif()
if(ANDROID)
list(APPEND qtkeychain_SOURCES keychain_android.cpp androidkeystore.cpp plaintextstore.cpp)
list(APPEND qtkeychain_LIBRARIES_PRIVATE ${QTANDROIDEXTRAS_LIBRARIES} )
endif()
QT_WRAP_CPP(qtkeychain_MOC_OUTFILES keychain.h keychain_p.h gnomekeyring_p.h)
set(qtkeychain_TR_FILES
translations/qtkeychain_de.ts
translations/qtkeychain_ro.ts
)
translations/qtkeychain_de.ts
translations/qtkeychain_fr.ts
translations/qtkeychain_ro.ts
translations/qtkeychain_ru.ts
translations/qtkeychain_zh.ts
)
set(QTKEYCHAIN_TARGET_NAME qt${QTKEYCHAIN_VERSION_INFIX}keychain)
add_library(${QTKEYCHAIN_TARGET_NAME} ${qtkeychain_SOURCES} ${qtkeychain_MOC_OUTFILES} ${qtkeychain_QM_FILES})
if(WIN32)
set_target_properties( ${QTKEYCHAIN_TARGET_NAME} PROPERTIES DEBUG_POSTFIX "d" )
endif()
file(GLOB qtkeychain_TR_SOURCES *.cpp *.h *.ui)
if ( BUILD_TRANSLATIONS )
qt_create_translation(qtkeychain_MESSAGES ${qtkeychain_TR_SOURCES} ${qtkeychain_TR_FILES})
qt_add_translation(qtkeychain_QM_FILES ${qtkeychain_TR_FILES})
add_custom_target(messages DEPENDS ${qtkeychain_MESSAGES})
add_custom_target(translations DEPENDS ${qtkeychain_QM_FILES})
add_custom_target(translations DEPENDS ${qtkeychain_QM_FILES} messages)
# https://github.com/frankosterfeld/qtkeychain/issues/185
add_dependencies(${QTKEYCHAIN_TARGET_NAME} translations)
if(NOT QT_TRANSLATIONS_DIR)
# If this directory is missing, we are in a Qt5 environment.
# Extract the qmake executable location
get_target_property(QT5_QMAKE_EXECUTABLE Qt5::qmake IMPORTED_LOCATION)
# Ask Qt5 where to put the translations
execute_process( COMMAND ${QT5_QMAKE_EXECUTABLE} -query QT_INSTALL_TRANSLATIONS
OUTPUT_VARIABLE qt_translations_dir OUTPUT_STRIP_TRAILING_WHITESPACE )
# make sure we have / and not \ as qmake gives on windows
file( TO_CMAKE_PATH "${qt_translations_dir}" qt_translations_dir)
set( QT_TRANSLATIONS_DIR ${qt_translations_dir} CACHE PATH
"The location of the Qt translations" FORCE)
if (BUILD_TRANSLATIONS_AS_RESOURCES)
set(QM_FILE_LIST "")
foreach(FILE ${qtkeychain_QM_FILES})
list(APPEND QM_FILE_LIST "<file>${FILE}</file>")
endforeach()
string(REPLACE ";" "" QM_FILE_LIST ${QM_FILE_LIST})
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/translations/translations.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
target_sources(${QTKEYCHAIN_TARGET_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
else()
if(QTKEYCHAIN_VERSION_INFIX EQUAL 5 AND QT_TRANSLATIONS_DIR AND NOT QTKEYCHAIN_TRANSLATIONS_DIR)
# Back compatibility with pre-0.11 versions
message (WARNING "QT_TRANSLATIONS_DIR is deprecated, use QTKEYCHAIN_TRANSLATIONS_DIR instead")
set(QTKEYCHAIN_TRANSLATIONS_DIR ${QT_TRANSLATIONS_DIR}
CACHE PATH "The location of the QtKeychain translations" FORCE)
else()
set(QTKEYCHAIN_TRANSLATIONS_DIR
${CMAKE_INSTALL_DATADIR}/qt${QTKEYCHAIN_VERSION_INFIX}keychain/translations
CACHE PATH "The location of the QtKeychain translations" )
endif()
install(FILES ${qtkeychain_QM_FILES} DESTINATION ${QTKEYCHAIN_TRANSLATIONS_DIR})
endif()
install(FILES ${qtkeychain_QM_FILES}
DESTINATION ${QT_TRANSLATIONS_DIR})
endif( BUILD_TRANSLATIONS )
set(QTKEYCHAIN_TARGET_NAME qtkeychain)
if(NOT QTKEYCHAIN_STATIC)
add_library(${QTKEYCHAIN_TARGET_NAME} SHARED ${qtkeychain_SOURCES} ${qtkeychain_MOC_OUTFILES} ${qtkeychain_QM_FILES})
else()
add_library(${QTKEYCHAIN_TARGET_NAME} STATIC ${qtkeychain_SOURCES} ${qtkeychain_MOC_OUTFILES} ${qtkeychain_QM_FILES})
target_link_libraries(${QTKEYCHAIN_TARGET_NAME} PUBLIC ${qtkeychain_LIBRARIES} PRIVATE ${qtkeychain_LIBRARIES_PRIVATE})
if(NOT INTERFACE_INCLUDE_SUFFIX)
set(INTERFACE_INCLUDE_SUFFIX include)
endif()
target_link_libraries(${QTKEYCHAIN_TARGET_NAME} PUBLIC ${qtkeychain_LIBRARIES})
target_include_directories(${QTKEYCHAIN_TARGET_NAME} PUBLIC $<INSTALL_INTERFACE:include/>)
target_include_directories(${QTKEYCHAIN_TARGET_NAME} PUBLIC $<INSTALL_INTERFACE:${INTERFACE_INCLUDE_SUFFIX}/>)
generate_export_header(${QTKEYCHAIN_TARGET_NAME}
EXPORT_FILE_NAME qkeychain_export.h
EXPORT_MACRO_NAME QKEYCHAIN_EXPORT
)
EXPORT_FILE_NAME qkeychain_export.h
EXPORT_MACRO_NAME QKEYCHAIN_EXPORT
)
set_target_properties(${QTKEYCHAIN_TARGET_NAME} PROPERTIES
VERSION ${QTKEYCHAIN_VERSION}
SOVERSION ${QTKEYCHAIN_SOVERSION}
MACOSX_RPATH 1
INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}"
INSTALL_RPATH_USE_LINK_PATH TRUE
)
)
if (NOT APPLE)
set_target_properties(${QTKEYCHAIN_TARGET_NAME} PROPERTIES
INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}"
)
endif()
install(FILES keychain.h ${CMAKE_CURRENT_BINARY_DIR}/qkeychain_export.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/qt${QTKEYCHAIN_VERSION_INFIX}keychain/
)
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/qt${QTKEYCHAIN_VERSION_INFIX}keychain/
)
install(TARGETS ${QTKEYCHAIN_TARGET_NAME}
EXPORT Qt${QTKEYCHAIN_VERSION_INFIX}KeychainLibraryDepends
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
EXPORT Qt${QTKEYCHAIN_VERSION_INFIX}KeychainLibraryDepends
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
if(BUILD_TEST_APPLICATION)
set( testclient_LIBRARIES ${QTKEYCHAIN_TARGET_NAME} )
if(APPLE)
list(APPEND testclient_LIBRARIES "-framework Cocoa")
if (BUILD_WITH_QT6)
find_package(Qt6 COMPONENTS Gui REQUIRED)
list(APPEND testclient_LIBRARIES Qt6::Gui)
else()
find_package(Qt5 COMPONENTS Gui REQUIRED)
list(APPEND testclient_LIBRARIES Qt5::Gui)
endif()
endif()
add_executable( testclient testclient.cpp )
target_link_libraries( testclient ${QTKEYCHAIN_TARGET_NAME})
target_link_libraries( testclient ${testclient_LIBRARIES})
endif()
@ -240,32 +297,32 @@ endif()
### CMake config file
###
ecm_configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/QtKeychainConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/Qt${QTKEYCHAIN_VERSION_INFIX}KeychainConfig.cmake"
INSTALL_DESTINATION Qt${QTKEYCHAIN_VERSION_INFIX}Keychain)
configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/QtKeychainConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/Qt${QTKEYCHAIN_VERSION_INFIX}KeychainConfig.cmake"
INSTALL_DESTINATION Qt${QTKEYCHAIN_VERSION_INFIX}Keychain)
ecm_setup_version("${QTKEYCHAIN_VERSION}" VARIABLE_PREFIX SNORE
PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/Qt${QTKEYCHAIN_VERSION_INFIX}KeychainConfigVersion.cmake"
SOVERSION ${QTKEYCHAIN_VERSION})
PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/Qt${QTKEYCHAIN_VERSION_INFIX}KeychainConfigVersion.cmake"
SOVERSION ${QTKEYCHAIN_VERSION})
if(UNIX AND NOT APPLE AND NOT ANDROID)
if(UNIX AND NOT APPLE AND NOT ANDROID AND NOT HAIKU)
set(PRI_EXTRA_DEPS "dbus")
endif()
ecm_generate_pri_file(BASE_NAME Qt${QTKEYCHAIN_VERSION_INFIX}Keychain
LIB_NAME ${QTKEYCHAIN_TARGET_NAME}
DEPS "core ${PRI_EXTRA_DEPS}"
INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR}
FILENAME_VAR pri_filename)
LIB_NAME ${QTKEYCHAIN_TARGET_NAME}
DEPS "core ${PRI_EXTRA_DEPS}"
INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR}
FILENAME_VAR pri_filename)
install(FILES ${pri_filename} DESTINATION ${ECM_MKSPECS_INSTALL_DIR})
install(EXPORT Qt${QTKEYCHAIN_VERSION_INFIX}KeychainLibraryDepends
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Qt${QTKEYCHAIN_VERSION_INFIX}Keychain"
)
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/Qt${QTKEYCHAIN_VERSION_INFIX}KeychainConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/Qt${QTKEYCHAIN_VERSION_INFIX}KeychainConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Qt${QTKEYCHAIN_VERSION_INFIX}Keychain
)
${CMAKE_CURRENT_BINARY_DIR}/Qt${QTKEYCHAIN_VERSION_INFIX}KeychainConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Qt${QTKEYCHAIN_VERSION_INFIX}Keychain
)

View File

@ -7,6 +7,9 @@ are met:
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to
endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES

View File

@ -1,6 +1,65 @@
ChangeLog
=========
version 0.14.2 (release 2023-12-17)
- Add support for KWallet 6 (Volker Krause <vkrause@kde.org>)
version 0.14.1 (release 2023-06-01)
- Export QKeychain::isAvailable() to make it usable in a shared build (Volker Krause <vkrause@kde.org>)
- Protect against creating the QtKeychain::QtKeychain alias target twice (Volker Krause <vkrause@kde.org>)
version 0.14.0 (release 2023-05-12)
- Add Qt 6 Android support (Igor Bugaev <freedbrt@gmail.com>)
- Add QtQuick client example ((Igor Bugaev <freedbrt@gmail.com>)
- Added Dutch translation (Heimen Stoffels <vistausss@fastmail.com>)
- Fix potential freezing with Apple keychain (Claudio Cambra <developer@claudiocambra.com>)
- Add API to check whether a secure backend is available at all (Volker Krause <vkrause@kde.org>)
version 0.13.2 (release 2021-11-18)
- CMake: Deprecate QTKEYCHAIN_STATIC in favor of BUILD_SHARED_LIBS (be@mixxx.org)
version 0.13.1 (release 2021-11-08)
- KWallet: Fix deletion of entries (Issue #199)
version 0.13.0 (release 2021-11-07)
- Linux: Require libsecret if not explicitly disabled
- Unify implementations for macOS and iOS
- CMake: lots of fixes
version 0.12.0 (release 2020-12-16)
* Add Qt 6 support, drop Qt 4 support
* Require C++11
* Add Android support (Mathias Hasselmann)
version 0.11.1 (release 2020-09-08)
* Build system fixes
version 0.11.0 (release 2020-09-08)
* Important: Debug builds on Windows now get the "d" suffix
* Various build system fixes
* Add Haiku support (François Revol <revol@free.fr>)
* Translation: Russian (Alexander Gorishnyak <kefir500@gmail.com>)
* Translation: Update French (David Geiger <david.david@mageialinux-online.org>)
version 0.10.0 (release 2019-12-17)
* Detect XFCE desktop correctly. (Sandro Knauß <hefee@debian.org>)
* Windows Use CRED_PERSIST_ENTERPRISE (Olivier Goffart <ogoffart@woboq.com>)
* Windows: Improve CredWrite() error handling (Christian Kamm <mail@ckamm.de>)
* Fix build with Qt 5.12.x (Sergey Ilinykh <rion4ik@gmail.com>)
* Fix Qt 4 build (Robert-André Mauchin <zebob.m@gmail.com>)
* Translation: Mandarin (Taiwan) (Poren Chiang <ren.chiang@gmail.com>)
* Translation: French (François Revol <revol@free.fr>)
version 0.9.1 (release 2018-08-20)
* Windows Credential Store: Use CRED_PERSIST_ENTERPRISE (Olivier Goffart <ogoffart@woboq.com>)
* Secret: Don't match the schema name #114 (Christian Kamm <mail@ckamm.de>)

View File

@ -2,21 +2,27 @@
# It defines the following variables
# QTKEYCHAIN_INCLUDE_DIRS - include directories for QtKeychain
# QTKEYCHAIN_LIBRARIES - libraries to link against
# as well as the following imported targets
# qt5keychain / qt6keychain
# Qt5Keychain::Qt5Keychain / Qt6Keychain::Qt6Keychain
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/Qt@QTKEYCHAIN_VERSION_INFIX@KeychainLibraryDepends.cmake")
include(CMakeFindDependencyMacro)
if("@QTKEYCHAIN_VERSION_INFIX@" STREQUAL "5")
find_dependency(Qt5Core)
if(UNIX AND NOT APPLE)
find_dependency(Qt5DBus)
endif()
else()
find_dependency(Qt4 COMPONENTS QtCore)
find_dependency(Qt@QTKEYCHAIN_VERSION_INFIX@Core)
if(UNIX AND NOT APPLE AND NOT ANDROID)
find_dependency(Qt@QTKEYCHAIN_VERSION_INFIX@DBus)
endif()
set(QTKEYCHAIN_LIBRARIES "@QTKEYCHAIN_TARGET_NAME@")
get_target_property(QTKEYCHAIN_INCLUDE_DIRS "@QTKEYCHAIN_TARGET_NAME@" INTERFACE_INCLUDE_DIRECTORIES)
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.18.0 AND NOT TARGET Qt@QTKEYCHAIN_VERSION_INFIX@Keychain::Qt@QTKEYCHAIN_VERSION_INFIX@Keychain)
add_library(Qt@QTKEYCHAIN_VERSION_INFIX@Keychain::Qt@QTKEYCHAIN_VERSION_INFIX@Keychain ALIAS qt@QTKEYCHAIN_VERSION_INFIX@keychain)
endif()
check_required_components(Qt@QTKEYCHAIN_VERSION_INFIX@Keychain)

View File

@ -1 +0,0 @@
ReadMe.txt

View File

@ -0,0 +1,29 @@
QtKeychain
==========
QtKeychain is a Qt API to store passwords and other secret data securely. How the data is stored depends on the platform:
* **macOS:** Passwords are stored in the macOS Keychain.
* **Linux/Unix:** If running, GNOME Keyring is used, otherwise QtKeychain tries to use KWallet (via D-Bus), if available. Libsecret (common API for desktop-specific solutions)
is also supported.
* **Windows:** By default, the Windows Credential Store is used (requires Windows 7 or newer).
Pass `-DUSE_CREDENTIAL_STORE=OFF` to cmake to disable it. If disabled, QtKeychain uses the Windows API function
[CryptProtectData](http://msdn.microsoft.com/en-us/library/windows/desktop/aa380261%28v=vs.85%29.aspx "CryptProtectData function")
to encrypt the password with the user's logon credentials. The encrypted data is then persisted via QSettings.
* **Android and iOS:** Passwords are stored in the Android keystore system and iOS keychain, respectively.
In unsupported environments QtKeychain will report an error. It will not store any data unencrypted unless explicitly requested (`setInsecureFallback( true )`).
Requirements
------------
QtKeychain 0.12 and newer supports Qt 5 and Qt 6 and requires a compiler with C++11 support. Older versions support Qt 4 and Qt 5.
License
-------
QtKeychain is available under the [Modified BSD License](http://www.gnu.org/licenses/license-list.html#ModifiedBSD). See the file COPYING for details.

View File

@ -1,17 +0,0 @@
QtKeychain
==========
QtKeychain is a Qt API to store passwords and other secret data securely. How the data is stored depends on the platform:
* **Mac OS X:** Passwords are stored in the OS X Keychain.
* **Linux/Unix:** If running, GNOME Keyring is used, otherwise qtkeychain tries to use KWallet (via D-Bus), if available.
* **Windows:** By default, the Windows Credential Store is used (requires Windows 7 or newer).
Pass -DUSE_CREDENTIAL_STORE=OFF to cmake use disable it. If disabled, QtKeychain uses the Windows API function
[CryptProtectData](http://msdn.microsoft.com/en-us/library/windows/desktop/aa380261%28v=vs.85%29.aspx "CryptProtectData function")
to encrypt the password with the user's logon credentials. The encrypted data is then persisted via QSettings.
In unsupported environments QtKeychain will report an error. It will not store any data unencrypted unless explicitly requested (setInsecureFallback( true )).
**License:** QtKeychain is available under the [Modified BSD License](http://www.gnu.org/licenses/license-list.html#ModifiedBSD). See the file COPYING for details.

View File

@ -0,0 +1,73 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
*.res
*.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
*.pyc
# Binaries
# --------
*.dll
*.exe

View File

@ -0,0 +1,29 @@
QT += quick
CONFIG += c++11
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
include(../qtkeychain.pri)
SOURCES += \
keychainclass.cpp \
main.cpp
RESOURCES += qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
HEADERS += \
keychainclass.h

View File

@ -0,0 +1,61 @@
#include <QDebug>
#include "keychainclass.h"
KeyChainClass::KeyChainClass(QObject* parent) :
QObject(parent),
m_readCredentialJob(QLatin1String("keychain.example.project.app")),
m_writeCredentialJob(QLatin1String("keychain.example.project.app")),
m_deleteCredentialJob(QLatin1String("keychain.example.project.app"))
{
m_readCredentialJob.setAutoDelete(false);
m_writeCredentialJob.setAutoDelete(false);
m_deleteCredentialJob.setAutoDelete(false);
}
void KeyChainClass::readKey(const QString &key)
{
m_readCredentialJob.setKey(key);
QObject::connect(&m_readCredentialJob, &QKeychain::ReadPasswordJob::finished, [=](){
if (m_readCredentialJob.error()) {
emit error(tr("Read key failed: %1").arg(qPrintable(m_readCredentialJob.errorString())));
return;
}
emit keyRestored(key, m_readCredentialJob.textData());
});
m_readCredentialJob.start();
}
void KeyChainClass::writeKey(const QString &key, const QString &value)
{
m_writeCredentialJob.setKey(key);
QObject::connect(&m_writeCredentialJob, &QKeychain::WritePasswordJob::finished, [=](){
if (m_writeCredentialJob.error()) {
emit error(tr("Write key failed: %1").arg(qPrintable(m_writeCredentialJob.errorString())));
return;
}
emit keyStored(key);
});
m_writeCredentialJob.setTextData(value);
m_writeCredentialJob.start();
}
void KeyChainClass::deleteKey(const QString &key)
{
m_deleteCredentialJob.setKey(key);
QObject::connect(&m_deleteCredentialJob, &QKeychain::DeletePasswordJob::finished, [=](){
if (m_deleteCredentialJob.error()) {
emit error(tr("Delete key failed: %1").arg(qPrintable(m_deleteCredentialJob.errorString())));
return;
}
emit keyDeleted(key);
});
m_deleteCredentialJob.start();
}

View File

@ -0,0 +1,30 @@
#ifndef KEYCHAINCLASS_H
#define KEYCHAINCLASS_H
#include <QObject>
#include <keychain.h>
class KeyChainClass: public QObject
{
Q_OBJECT
public:
KeyChainClass(QObject* parent = nullptr);
Q_INVOKABLE void readKey(const QString& key);
Q_INVOKABLE void writeKey(const QString& key, const QString& value);
Q_INVOKABLE void deleteKey(const QString& key);
Q_SIGNALS:
void keyStored(const QString& key);
void keyRestored(const QString& key, const QString& value);
void keyDeleted(const QString& key);
void error(const QString& errorText);
private:
QKeychain::ReadPasswordJob m_readCredentialJob;
QKeychain::WritePasswordJob m_writeCredentialJob;
QKeychain::DeletePasswordJob m_deleteCredentialJob;
};
#endif // KEYCHAINCLASS_H

View File

@ -0,0 +1,29 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "keychainclass.h"
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
KeyChainClass keyChainClass;
engine.rootContext()->setContextProperty("KeyChain", &keyChainClass);
engine.load(url);
return app.exec();
}

View File

@ -0,0 +1,131 @@
import QtQuick 2.13
import QtQuick.Window 2.13
import QtQuick.Controls 2.12
Window {
id: root
width: 640
height: 480
visible: true
Column {
anchors {
fill: parent
margins: 50
}
spacing: 20
Label {
text: 'Key name:'
font.pixelSize: 20
}
TextField {
id: keyNameTextField
width: parent.width
height: 50
text: 'default key name'
}
Label {
text: 'Key value:'
font.pixelSize: 20
}
TextField {
id: keyValueTextField
width: parent.width
height: 50
text: 'some value'
}
Label {
id: infoLabel
width: parent.width
wrapMode: Text.Wrap
visible: false
onVisibleChanged: if (visible) hideAnimation.start();
SequentialAnimation {
id: hideAnimation
PauseAnimation {
duration: 10000
}
ScriptAction {
script: infoLabel.visible = false
}
}
Component.onCompleted: {
KeyChain.keyStored.connect((key) => {
infoLabel.text = String("Key '%1' successfully stored").arg(key)
infoLabel.color = 'green'
infoLabel.visible = true
})
KeyChain.keyRestored.connect((key, value) => {
infoLabel.text = String("Key '%1' successfully restored with data '%2'").arg(key).arg(value)
infoLabel.color = 'green'
infoLabel.visible = true
})
KeyChain.keyDeleted.connect((key) => {
infoLabel.text = String("Key '%1' successfully deleted").arg(key)
infoLabel.color = 'green'
infoLabel.visible = true
})
KeyChain.error.connect((errorText) => {
infoLabel.text = errorText
infoLabel.color = 'red'
infoLabel.visible = true
})
}
}
Row {
width: parent.width
height: 50
spacing: 20
Button {
width: 80
height: parent.height
text: 'Store'
onClicked: {
KeyChain.writeKey(keyNameTextField.text.trim(), keyValueTextField.text.trim())
}
}
Button {
width: 80
height: parent.height
text: 'Restore'
onClicked: {
KeyChain.readKey(keyNameTextField.text.trim())
}
}
Button {
width: 80
height: parent.height
text: 'Delete'
onClicked: {
KeyChain.deleteKey(keyNameTextField.text.trim())
}
}
}
}
}

View File

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

View File

@ -0,0 +1,304 @@
#include "androidkeystore_p.h"
#if QT_VERSION < QT_VERSION_CHECK(5, 7, 0)
#include "private/qjni_p.h"
#endif
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QAndroidJniEnvironment>
#endif
using namespace QKeychain;
using namespace android::content;
using namespace android::security;
using namespace java::io;
using namespace java::lang;
using namespace java::math;
using namespace java::util;
using namespace java::security;
using namespace java::security::spec;
using namespace javax::crypto;
using namespace javax::security::auth::x500;
using namespace javax::security::cert;
const BigInteger BigInteger::ONE = BigInteger::getStaticObjectField("java/math/BigInteger", "ONE", "Ljava/math/BigInteger;");
const int Calendar::YEAR = Calendar::getStaticField<jint>("java/util/Calendar", "YEAR");
const int Cipher::DECRYPT_MODE = Cipher::getStaticField<jint>("javax/crypto/Cipher", "DECRYPT_MODE");
const int Cipher::ENCRYPT_MODE = Cipher::getStaticField<jint>("javax/crypto/Cipher", "ENCRYPT_MODE");
namespace {
#if QT_VERSION < QT_VERSION_CHECK(5, 7, 0)
struct JNIObject
{
JNIObject(QSharedPointer<QJNIObjectPrivate> d): d(d) {}
static JNIObject fromLocalRef(jobject o)
{
return JNIObject(QSharedPointer<QJNIObjectPrivate>::create(QJNIObjectPrivate::fromLocalRef(o)));
}
jobject object() const { return d->object(); }
QSharedPointer<QJNIObjectPrivate> d;
};
#else
using JNIObject = QAndroidJniObject;
#endif
QByteArray fromArray(const jbyteArray array)
{
QAndroidJniEnvironment env;
jbyte *const bytes = env->GetByteArrayElements(array, nullptr);
const QByteArray result(reinterpret_cast<const char *>(bytes), env->GetArrayLength(array));
env->ReleaseByteArrayElements(array, bytes, JNI_ABORT);
return result;
}
JNIObject toArray(const QByteArray &bytes)
{
QAndroidJniEnvironment env;
const int length = bytes.length();
JNIObject array = JNIObject::fromLocalRef(env->NewByteArray(length));
env->SetByteArrayRegion(static_cast<jbyteArray>(array.object()),
0, length, reinterpret_cast<const jbyte *>(bytes.constData()));
return array;
}
}
bool Object::handleExceptions()
{
QAndroidJniEnvironment env;
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
return false;
}
return true;
}
KeyPairGenerator KeyPairGenerator::getInstance(const QString &algorithm, const QString &provider)
{
return handleExceptions(callStaticObjectMethod("java/security/KeyPairGenerator", "getInstance",
"(Ljava/lang/String;Ljava/lang/String;)Ljava/security/KeyPairGenerator;",
fromString(algorithm).object(), fromString(provider).object()));
}
KeyPair KeyPairGenerator::generateKeyPair() const
{
return handleExceptions(callObjectMethod("generateKeyPair", "()Ljava/security/KeyPair;"));
}
bool KeyPairGenerator::initialize(const AlgorithmParameterSpec &spec) const
{
callMethod<void>("initialize", "(Ljava/security/spec/AlgorithmParameterSpec;)V", spec.object());
return handleExceptions();
}
bool KeyStore::containsAlias(const QString &alias) const
{
return handleExceptions(callMethod<jboolean>("containsAlias", "(Ljava/lang/String;)Z",
fromString(alias).object()));
}
bool KeyStore::deleteEntry(const QString &alias) const
{
callMethod<void>("deleteEntry", "(Ljava/lang/String;)V", fromString(alias).object());
return handleExceptions();
}
KeyStore KeyStore::getInstance(const QString &type)
{
return handleExceptions(callStaticObjectMethod("java/security/KeyStore", "getInstance",
"(Ljava/lang/String;)Ljava/security/KeyStore;",
fromString(type).object()));
}
KeyStore::Entry KeyStore::getEntry(const QString &alias, const KeyStore::ProtectionParameter &param) const
{
return handleExceptions(callObjectMethod("getEntry",
"(Ljava/lang/String;Ljava/security/KeyStore$ProtectionParameter;)Ljava/security/KeyStore$Entry;",
fromString(alias).object(), param.object()));
}
bool KeyStore::load(const KeyStore::LoadStoreParameter &param) const
{
callMethod<void>("load", "(Ljava/security/KeyStore$LoadStoreParameter;)V", param.object());
return handleExceptions();
}
Calendar Calendar::getInstance()
{
return handleExceptions(callStaticObjectMethod("java/util/Calendar", "getInstance",
"()Ljava/util/Calendar;"));
}
bool Calendar::add(int field, int amount) const
{
callMethod<void>("add", "(II)V", field, amount);
return handleExceptions();
}
Date Calendar::getTime() const
{
return handleExceptions(callObjectMethod("getTime", "()Ljava/util/Date;"));
}
KeyPairGeneratorSpec::Builder::Builder(const Context &context)
: Object(QAndroidJniObject("android/security/KeyPairGeneratorSpec$Builder",
"(Landroid/content/Context;)V",
context.object()))
{
handleExceptions();
}
KeyPairGeneratorSpec::Builder KeyPairGeneratorSpec::Builder::setAlias(const QString &alias) const
{
return handleExceptions(callObjectMethod("setAlias",
"(Ljava/lang/String;)Landroid/security/KeyPairGeneratorSpec$Builder;",
fromString(alias).object()));
}
KeyPairGeneratorSpec::Builder KeyPairGeneratorSpec::Builder::setSubject(const X500Principal &subject) const
{
return handleExceptions(callObjectMethod("setSubject",
"(Ljavax/security/auth/x500/X500Principal;)Landroid/security/KeyPairGeneratorSpec$Builder;",
subject.object()));
}
KeyPairGeneratorSpec::Builder KeyPairGeneratorSpec::Builder::setSerialNumber(const BigInteger &serial) const
{
return handleExceptions(callObjectMethod("setSerialNumber",
"(Ljava/math/BigInteger;)Landroid/security/KeyPairGeneratorSpec$Builder;",
serial.object()));
}
KeyPairGeneratorSpec::Builder KeyPairGeneratorSpec::Builder::setStartDate(const Date &date) const
{
return handleExceptions(callObjectMethod("setStartDate",
"(Ljava/util/Date;)Landroid/security/KeyPairGeneratorSpec$Builder;",
date.object()));
}
KeyPairGeneratorSpec::Builder KeyPairGeneratorSpec::Builder::setEndDate(const Date &date) const
{
return handleExceptions(callObjectMethod("setEndDate",
"(Ljava/util/Date;)Landroid/security/KeyPairGeneratorSpec$Builder;",
date.object()));
}
KeyPairGeneratorSpec KeyPairGeneratorSpec::Builder::build() const
{
return handleExceptions(callObjectMethod("build", "()Landroid/security/KeyPairGeneratorSpec;"));
}
X500Principal::X500Principal(const QString &name)
: Object(QAndroidJniObject("javax/security/auth/x500/X500Principal",
"(Ljava/lang/String;)V",
fromString(name).object()))
{
handleExceptions();
}
Certificate KeyStore::PrivateKeyEntry::getCertificate() const
{
return handleExceptions(callObjectMethod("getCertificate", "()Ljava/security/cert/Certificate;"));
}
PrivateKey KeyStore::PrivateKeyEntry::getPrivateKey() const
{
return handleExceptions(callObjectMethod("getPrivateKey", "()Ljava/security/PrivateKey;"));
}
PublicKey Certificate::getPublicKey() const
{
return handleExceptions(callObjectMethod("getPublicKey", "()Ljava/security/PublicKey;"));
}
ByteArrayInputStream::ByteArrayInputStream(const QByteArray &bytes)
: InputStream(QAndroidJniObject("java/io/ByteArrayInputStream", "([B)V", toArray(bytes).object()))
{
}
ByteArrayOutputStream::ByteArrayOutputStream()
: OutputStream(QAndroidJniObject("java/io/ByteArrayOutputStream"))
{
handleExceptions();
}
QByteArray ByteArrayOutputStream::toByteArray() const
{
const QAndroidJniObject wrapper = callObjectMethod<jbyteArray>("toByteArray");
if (!handleExceptions())
return QByteArray();
return fromArray(static_cast<jbyteArray>(wrapper.object()));
}
int InputStream::read() const
{
return handleExceptions(callMethod<int>("read"), -1);
}
bool OutputStream::write(const QByteArray &bytes) const
{
callMethod<void>("write", "([B)V", toArray(bytes).object());
return handleExceptions();
}
bool OutputStream::close() const
{
callMethod<void>("close");
return handleExceptions();
}
bool OutputStream::flush() const
{
callMethod<void>("flush");
return handleExceptions();
}
Cipher Cipher::getInstance(const QString &transformation)
{
return handleExceptions(callStaticObjectMethod("javax/crypto/Cipher", "getInstance",
"(Ljava/lang/String;)Ljavax/crypto/Cipher;",
fromString(transformation).object()));
}
bool Cipher::init(int opMode, const Key &key) const
{
callMethod<void>("init", "(ILjava/security/Key;)V", opMode, key.object());
return handleExceptions();
}
CipherOutputStream::CipherOutputStream(const OutputStream &stream, const Cipher &cipher)
: FilterOutputStream(QAndroidJniObject("javax/crypto/CipherOutputStream",
"(Ljava/io/OutputStream;Ljavax/crypto/Cipher;)V",
stream.object(), cipher.object()))
{
handleExceptions();
}
CipherInputStream::CipherInputStream(const InputStream &stream, const Cipher &cipher)
: FilterInputStream(QAndroidJniObject("javax/crypto/CipherInputStream",
"(Ljava/io/InputStream;Ljavax/crypto/Cipher;)V",
stream.object(), cipher.object()))
{
handleExceptions();
}

View File

@ -0,0 +1,382 @@
/******************************************************************************
* Copyright (C) 2016 Mathias Hasselmann <mathias.hasselmann@kdab.com> *
* *
* This program is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
* or FITNESS FOR A PARTICULAR PURPOSE. For licensing and distribution *
* details, check the accompanying file 'COPYING'. *
*****************************************************************************/
#ifndef QTKEYCHAIN_ANDROIDKEYSTORE_P_H
#define QTKEYCHAIN_ANDROIDKEYSTORE_P_H
#include <QtGlobal>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QAndroidJniObject>
#else
#include <QJniObject>
#include <QJniEnvironment>
typedef QJniObject QAndroidJniObject;
typedef QJniEnvironment QAndroidJniEnvironment;
#endif
namespace QKeychain {
namespace javax {
namespace security {
namespace auth { namespace x500 { class X500Principal; } }
namespace cert { class Certificate; }
}
}
namespace java {
namespace lang {
class Object : protected QAndroidJniObject
{
public:
inline Object(jobject object) : QAndroidJniObject(object) {}
inline Object(const QAndroidJniObject &object) : QAndroidJniObject(object) {}
inline operator bool() const { return isValid(); }
using QAndroidJniObject::object;
using QAndroidJniObject::toString;
protected:
static bool handleExceptions();
template<typename T>
static T handleExceptions(const T &result, const T &resultOnError = T());
};
template<typename T>
inline T Object::handleExceptions(const T &result, const T &resultOnError)
{
if (!handleExceptions())
return resultOnError;
return result;
}
} // namespace lang
namespace io {
class InputStream : public java::lang::Object
{
public:
using Object::Object;
int read() const;
};
class ByteArrayInputStream : public InputStream
{
public:
using InputStream::InputStream;
explicit ByteArrayInputStream(const QByteArray &bytes);
};
class FilterInputStream : public InputStream
{
public:
using InputStream::InputStream;
};
class OutputStream : public java::lang::Object
{
public:
using Object::Object;
bool write(const QByteArray &bytes) const;
bool flush() const;
bool close() const;
};
class ByteArrayOutputStream : public OutputStream
{
public:
using OutputStream::OutputStream;
ByteArrayOutputStream();
QByteArray toByteArray() const;
};
class FilterOutputStream : public OutputStream
{
public:
using OutputStream::OutputStream;
};
} // namespace io
namespace math {
class BigInteger : public java::lang::Object
{
public:
using Object::Object;
static const BigInteger ZERO;
static const BigInteger ONE;
static const BigInteger TEN;
};
} // namespace math
namespace util {
class Date : public java::lang::Object
{
public:
using Object::Object;
};
class Calendar : public java::lang::Object
{
public:
using Object::Object;
static const int YEAR;
static const int MONTH;
static const int DAY;
static const int HOUR;
static const int MINUTE;
static const int SECOND;
static const int MILLISECOND;
static Calendar getInstance();
bool add(int field, int amount) const;
Date getTime() const;
};
} // namespace util
namespace security {
namespace spec {
class AlgorithmParameterSpec : public java::lang::Object
{
public:
using Object::Object;
};
} // namespace spec
class Key : public java::lang::Object
{
public:
using Object::Object;
};
class PrivateKey : public Key
{
public:
using Key::Key;
PrivateKey(const Key &init): Key(init) {}
};
class PublicKey : public Key
{
public:
using Key::Key;
PublicKey(const Key &init): Key(init) {}
};
class KeyPair : public java::lang::Object
{
public:
using Object::Object;
};
class KeyPairGenerator : public java::lang::Object
{
public:
using Object::Object;
static KeyPairGenerator getInstance(const QString &algorithm, const QString &provider);
KeyPair generateKeyPair() const;
bool initialize(const spec::AlgorithmParameterSpec &spec) const;
};
class KeyStore : public java::lang::Object
{
public:
class Entry : public java::lang::Object
{
public:
using Object::Object;
};
class PrivateKeyEntry : public Entry
{
public:
using Entry::Entry;
inline PrivateKeyEntry(const Entry &init): Entry(init) {}
javax::security::cert::Certificate getCertificate() const;
java::security::PrivateKey getPrivateKey() const;
};
class LoadStoreParameter : public java::lang::Object
{
public:
using Object::Object;
};
class ProtectionParameter : public java::lang::Object
{
public:
using Object::Object;
};
using Object::Object;
bool containsAlias(const QString &alias) const;
bool deleteEntry(const QString &alias) const;
static KeyStore getInstance(const QString &type);
Entry getEntry(const QString &alias, const ProtectionParameter &param = nullptr) const;
bool load(const LoadStoreParameter &param = nullptr) const;
};
namespace interfaces {
class RSAPrivateKey : public PrivateKey
{
public:
using PrivateKey::PrivateKey;
RSAPrivateKey(const PrivateKey &init): PrivateKey(init) {}
};
class RSAPublicKey : public PublicKey
{
public:
using PublicKey::PublicKey;
RSAPublicKey(const PublicKey &init): PublicKey(init) {}
};
} // namespace interfaces
} // namespace security
} // namespace java
namespace android {
namespace content {
class Context : public java::lang::Object
{
public:
using Object::Object;
};
} // namespace content
namespace security {
class KeyPairGeneratorSpec : public java::security::spec::AlgorithmParameterSpec
{
public:
class Builder : public java::lang::Object
{
public:
using Object::Object;
explicit Builder(const android::content::Context &context);
Builder setAlias(const QString &alias) const;
Builder setSubject(const javax::security::auth::x500::X500Principal &subject) const;
Builder setSerialNumber(const java::math::BigInteger &serial) const;
Builder setStartDate(const java::util::Date &date) const;
Builder setEndDate(const java::util::Date &date) const;
KeyPairGeneratorSpec build() const;
};
using AlgorithmParameterSpec::AlgorithmParameterSpec;
};
} // namespace security
} // namespace android
namespace javax {
namespace crypto {
class Cipher : public java::lang::Object
{
public:
static const int DECRYPT_MODE;
static const int ENCRYPT_MODE;
using Object::Object;
static Cipher getInstance(const QString &transformation);
bool init(int opMode, const java::security::Key &key) const;
};
class CipherInputStream : public java::io::FilterInputStream
{
public:
using FilterInputStream::FilterInputStream;
explicit CipherInputStream(const InputStream &stream, const Cipher &cipher);
};
class CipherOutputStream : public java::io::FilterOutputStream
{
public:
using FilterOutputStream::FilterOutputStream;
explicit CipherOutputStream(const OutputStream &stream, const Cipher &cipher);
};
}
namespace security {
namespace auth {
namespace x500 {
class X500Principal;
class X500Principal : public java::lang::Object
{
public:
using Object::Object;
explicit X500Principal(const QString &name);
};
} // namespace x500
} // namespace auth
namespace cert {
class Certificate : public java::lang::Object
{
public:
using Object::Object;
java::security::PublicKey getPublicKey() const;
};
} // namespace cert
} // namespace security
} // namespace javax
} // namespace QKeychain
#endif // QTKEYCHAIN_ANDROIDKEYSTORE_P_H

View File

@ -1,125 +1,124 @@
#.rst:
# ECMGeneratePriFile
# ------------------
# SPDX-FileCopyrightText: 2014 David Faure <faure@kde.org>
#
# Generate a ``.pri`` file for the benefit of qmake-based projects.
#
# As well as the function below, this module creates the cache variable
# ``ECM_MKSPECS_INSTALL_DIR`` and sets the default value to ``mkspecs/modules``.
# This assumes Qt and the current project are both installed to the same
# non-system prefix. Packagers who use ``-DCMAKE_INSTALL_PREFIX=/usr`` will
# certainly want to set ``ECM_MKSPECS_INSTALL_DIR`` to something like
# ``share/qt5/mkspecs/modules``.
#
# The main thing is that this should be the ``modules`` subdirectory of either
# the default qmake ``mkspecs`` directory or of a directory that will be in the
# ``$QMAKEPATH`` environment variable when qmake is run.
#
# ::
#
# ecm_generate_pri_file(BASE_NAME <baseName>
# LIB_NAME <libName>
# [DEPS "<dep> [<dep> [...]]"]
# [FILENAME_VAR <filename_variable>]
# [INCLUDE_INSTALL_DIR <dir>]
# [LIB_INSTALL_DIR <dir>])
#
# If your CMake project produces a Qt-based library, you may expect there to be
# applications that wish to use it that use a qmake-based build system, rather
# than a CMake-based one. Creating a ``.pri`` file will make use of your
# library convenient for them, in much the same way that CMake config files make
# things convenient for CMake-based applications.
#
# ecm_generate_pri_file() generates just such a file. It requires the
# ``PROJECT_VERSION_STRING`` variable to be set. This is typically set by
# :module:`ECMSetupVersion`, although the project() command in CMake 3.0.0 and
# later can also set this.
#
# BASE_NAME specifies the name qmake project (.pro) files should use to refer to
# the library (eg: KArchive). LIB_NAME is the name of the actual library to
# link to (ie: the first argument to add_library()). DEPS is a space-separated
# list of the base names of other libraries (for Qt libraries, use the same
# names you use with the ``QT`` variable in a qmake project file, such as "core"
# for QtCore). FILENAME_VAR specifies the name of a variable to store the path
# to the generated file in.
#
# INCLUDE_INSTALL_DIR is the path (relative to ``CMAKE_INSTALL_PREFIX``) that
# include files will be installed to. It defaults to
# ``${INCLUDE_INSTALL_DIR}/<baseName>`` if the ``INCLUDE_INSTALL_DIR`` variable
# is set. If that variable is not set, the ``CMAKE_INSTALL_INCLUDEDIR`` variable
# is used instead, and if neither are set ``include`` is used. LIB_INSTALL_DIR
# operates similarly for the installation location for libraries; it defaults to
# ``${LIB_INSTALL_DIR}``, ``${CMAKE_INSTALL_LIBDIR}`` or ``lib``, in that order.
#
# Example usage:
#
# .. code-block:: cmake
#
# ecm_generate_pri_file(
# BASE_NAME KArchive
# LIB_NAME KF5KArchive
# DEPS "core"
# FILENAME_VAR pri_filename
# )
# install(FILES ${pri_filename} DESTINATION ${ECM_MKSPECS_INSTALL_DIR})
#
# A qmake-based project that wished to use this would then do::
#
# QT += KArchive
#
# in their ``.pro`` file.
#
# Since pre-1.0.0.
# SPDX-License-Identifier: BSD-3-Clause
#=============================================================================
# Copyright 2014 David Faure <faure@kde.org>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#[=======================================================================[.rst:
ECMGeneratePriFile
------------------
Generate a ``.pri`` file for the benefit of qmake-based projects.
As well as the function below, this module creates the cache variable
``ECM_MKSPECS_INSTALL_DIR`` and sets the default value to ``mkspecs/modules``.
This assumes Qt and the current project are both installed to the same
non-system prefix. Packagers who use ``-DCMAKE_INSTALL_PREFIX=/usr`` will
certainly want to set ``ECM_MKSPECS_INSTALL_DIR`` to something like
``share/qt5/mkspecs/modules``.
The main thing is that this should be the ``modules`` subdirectory of either
the default qmake ``mkspecs`` directory or of a directory that will be in the
``$QMAKEPATH`` environment variable when qmake is run.
::
ecm_generate_pri_file(BASE_NAME <baseName>
LIB_NAME <libName>
[VERSION <version>] # since 5.83
[DEPS "<dep> [<dep> [...]]"]
[FILENAME_VAR <filename_variable>]
[INCLUDE_INSTALL_DIRS <dir> [<dir> [...]]] # since 5.92
[INCLUDE_INSTALL_DIR <dir>] # deprecated since 5.92
[LIB_INSTALL_DIR <dir>])
If your CMake project produces a Qt-based library, you may expect there to be
applications that wish to use it that use a qmake-based build system, rather
than a CMake-based one. Creating a ``.pri`` file will make use of your
library convenient for them, in much the same way that CMake config files make
things convenient for CMake-based applications. ``ecm_generate_pri_file()``
generates just such a file.
``VERSION`` specifies the version of the library the ``.pri`` file describes. If
not set, the value is taken from the context variable ``PROJECT_VERSION``.
This variable is usually set by the ``project(... VERSION ...)`` command or,
if CMake policy CMP0048 is not ``NEW``, by :module:`ECMSetupVersion`.
For backward-compatibility with older ECM versions the
``PROJECT_VERSION_STRING`` variable as set by :module:`ECMSetupVersion`
will be preferred over ``PROJECT_VERSION`` if set, unless the minimum
required version of ECM is 5.83 and newer. Since 5.83.
``BASE_NAME`` specifies the name qmake project (.pro) files should use to refer to
the library (eg: KArchive). ``LIB_NAME`` is the name of the actual library to
link to (ie: the first argument to add_library()). ``DEPS`` is a space-separated
list of the base names of other libraries (for Qt libraries, use the same
names you use with the ``QT`` variable in a qmake project file, such as "core"
for QtCore). ``FILENAME_VAR`` specifies the name of a variable to store the path
to the generated file in.
``INCLUDE_INSTALL_DIRS`` are the paths (relative to ``CMAKE_INSTALL_PREFIX``) that
include files will be installed to. It defaults to
``${INCLUDE_INSTALL_DIR}/<baseName>`` if the ``INCLUDE_INSTALL_DIR`` variable
is set. If that variable is not set, the ``CMAKE_INSTALL_INCLUDEDIR`` variable
is used instead, and if neither are set ``include`` is used. ``LIB_INSTALL_DIR``
operates similarly for the installation location for libraries; it defaults to
``${LIB_INSTALL_DIR}``, ``${CMAKE_INSTALL_LIBDIR}`` or ``lib``, in that order.
``INCLUDE_INSTALL_DIR`` is the old variant of ``INCLUDE_INSTALL_DIRS``, taking only one
directory.
Example usage:
.. code-block:: cmake
ecm_generate_pri_file(
BASE_NAME KArchive
LIB_NAME KF5KArchive
DEPS "core"
FILENAME_VAR pri_filename
VERSION 4.2.0
)
install(FILES ${pri_filename} DESTINATION ${ECM_MKSPECS_INSTALL_DIR})
A qmake-based project that wished to use this would then do::
QT += KArchive
in their ``.pro`` file.
Since pre-1.0.0.
#]=======================================================================]
# Replicate the logic from KDEInstallDirs.cmake as we can't depend on it
# Ask qmake if we're using the same prefix as Qt
set(_askqmake OFF)
set(_should_query_qt OFF)
if(NOT DEFINED KDE_INSTALL_USE_QT_SYS_PATHS)
include(ECMQueryQmake)
query_qmake(qt_install_prefix_dir QT_INSTALL_PREFIX)
include(ECMQueryQt)
ecm_query_qt(qt_install_prefix_dir QT_INSTALL_PREFIX TRY)
if(qt_install_prefix_dir STREQUAL "${CMAKE_INSTALL_PREFIX}")
set(_askqmake ON)
set(_should_query_qt ON)
endif()
endif()
if(KDE_INSTALL_USE_QT_SYS_PATHS OR _askqmake)
include(ECMQueryQmake)
query_qmake(qt_host_data_dir QT_HOST_DATA)
set(ECM_MKSPECS_INSTALL_DIR ${qt_host_data_dir}/mkspecs/modules CACHE PATH "The directory where mkspecs will be installed to.")
if(KDE_INSTALL_USE_QT_SYS_PATHS OR _should_query_qt)
include(ECMQueryQt)
ecm_query_qt(qt_install_prefix_dir QT_INSTALL_PREFIX)
ecm_query_qt(qt_host_data_dir QT_HOST_DATA)
if(qt_install_prefix_dir STREQUAL "${CMAKE_INSTALL_PREFIX}")
file(RELATIVE_PATH qt_host_data_dir ${qt_install_prefix_dir} ${qt_host_data_dir})
endif()
if(qt_host_data_dir STREQUAL "")
set(mkspecs_install_dir mkspecs/modules)
else()
set(mkspecs_install_dir ${qt_host_data_dir}/mkspecs/modules)
endif()
set(ECM_MKSPECS_INSTALL_DIR ${mkspecs_install_dir} CACHE PATH "The directory where mkspecs will be installed to.")
else()
set(ECM_MKSPECS_INSTALL_DIR mkspecs/modules CACHE PATH "The directory where mkspecs will be installed to.")
endif()
function(ECM_GENERATE_PRI_FILE)
set(options )
set(oneValueArgs BASE_NAME LIB_NAME DEPS FILENAME_VAR INCLUDE_INSTALL_DIR LIB_INSTALL_DIR)
set(multiValueArgs )
set(oneValueArgs BASE_NAME LIB_NAME DEPS FILENAME_VAR INCLUDE_INSTALL_DIR LIB_INSTALL_DIR VERSION)
set(multiValueArgs INCLUDE_INSTALL_DIRS)
cmake_parse_arguments(EGPF "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
@ -127,22 +126,42 @@ function(ECM_GENERATE_PRI_FILE)
message(FATAL_ERROR "Unknown keywords given to ECM_GENERATE_PRI_FILE(): \"${EGPF_UNPARSED_ARGUMENTS}\"")
endif()
if(ECM_GLOBAL_FIND_VERSION VERSION_LESS 5.83.0)
set(_support_backward_compat_version_string_var TRUE)
else()
set(_support_backward_compat_version_string_var FALSE)
endif()
if(NOT EGPF_BASE_NAME)
message(FATAL_ERROR "Required argument BASE_NAME missing in ECM_GENERATE_PRI_FILE() call")
endif()
if(NOT EGPF_LIB_NAME)
message(FATAL_ERROR "Required argument LIB_NAME missing in ECM_GENERATE_PRI_FILE() call")
endif()
if(NOT PROJECT_VERSION_STRING)
message(FATAL_ERROR "Required variable PROJECT_VERSION_STRING not set before ECM_GENERATE_PRI_FILE() call. Did you call ecm_setup_version?")
if(NOT EGPF_VERSION)
if(_support_backward_compat_version_string_var)
if(NOT PROJECT_VERSION_STRING AND NOT PROJECT_VERSION)
message(FATAL_ERROR "Required variable PROJECT_VERSION_STRING or PROJECT_VERSION not set before ECM_GENERATE_PRI_FILE() call. Missing call of ecm_setup_version() or project(VERSION)?")
endif()
else()
if(NOT PROJECT_VERSION)
message(FATAL_ERROR "Required variable PROJECT_VERSION not set before ECM_GENERATE_PRI_FILE() call. Missing call of ecm_setup_version() or project(VERSION)?")
endif()
endif()
endif()
if(NOT EGPF_INCLUDE_INSTALL_DIR)
if(EGPF_INCLUDE_INSTALL_DIR)
if(EGPF_INCLUDE_INSTALL_DIRS)
message(FATAL_ERROR "Only one argument of INCLUDE_INSTALL_DIR & INCLUDE_INSTALL_DIRS can be used in ECM_GENERATE_PRI_FILE() call")
endif()
set(EGPF_INCLUDE_INSTALL_DIRS ${EGPF_INCLUDE_INSTALL_DIR})
endif()
if(NOT EGPF_INCLUDE_INSTALL_DIRS)
if(INCLUDE_INSTALL_DIR)
set(EGPF_INCLUDE_INSTALL_DIR "${INCLUDE_INSTALL_DIR}/${EGPF_BASE_NAME}")
set(EGPF_INCLUDE_INSTALL_DIRS "${INCLUDE_INSTALL_DIR}/${EGPF_BASE_NAME}")
elseif(CMAKE_INSTALL_INCLUDEDIR)
set(EGPF_INCLUDE_INSTALL_DIR "${CMAKE_INSTALL_INCLUDEDIR}/${EGPF_BASE_NAME}")
set(EGPF_INCLUDE_INSTALL_DIRS "${CMAKE_INSTALL_INCLUDEDIR}/${EGPF_BASE_NAME}")
else()
set(EGPF_INCLUDE_INSTALL_DIR "include/${EGPF_BASE_NAME}")
set(EGPF_INCLUDE_INSTALL_DIRS "include/${EGPF_BASE_NAME}")
endif()
endif()
if(NOT EGPF_LIB_INSTALL_DIR)
@ -155,22 +174,48 @@ function(ECM_GENERATE_PRI_FILE)
endif()
endif()
string(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+.*" "\\1" PROJECT_VERSION_MAJOR "${PROJECT_VERSION_STRING}")
string(REGEX REPLACE "^[0-9]+\\.([0-9]+)\\.[0-9]+.*" "\\1" PROJECT_VERSION_MINOR "${PROJECT_VERSION_STRING}")
string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" PROJECT_VERSION_PATCH "${PROJECT_VERSION_STRING}")
if(EGPF_VERSION)
set(PRI_VERSION "${EGPF_VERSION}")
else()
if(_support_backward_compat_version_string_var AND PROJECT_VERSION_STRING)
set(PRI_VERSION "${PROJECT_VERSION_STRING}")
if(NOT PROJECT_VERSION_STRING STREQUAL PROJECT_VERSION)
message(DEPRECATION "ECM_GENERATE_PRI_FILE() will no longer support PROJECT_VERSION_STRING when the required minimum version of ECM is 5.83 or newer. Set VERSION parameter or use PROJECT_VERSION instead.")
endif()
else()
set(PRI_VERSION "${PROJECT_VERSION}")
endif()
endif()
string(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+.*" "\\1" PRI_VERSION_MAJOR "${PRI_VERSION}")
string(REGEX REPLACE "^[0-9]+\\.([0-9]+)\\.[0-9]+.*" "\\1" PRI_VERSION_MINOR "${PRI_VERSION}")
string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" PRI_VERSION_PATCH "${PRI_VERSION}")
# Prepare the right number of "../.." to go from ECM_MKSPECS_INSTALL_DIR to the install prefix
# This allows to make the generated pri files relocatable (no absolute paths)
if (IS_ABSOLUTE ${ECM_MKSPECS_INSTALL_DIR})
set(BASEPATH ${CMAKE_INSTALL_PREFIX})
else()
string(REGEX REPLACE "[^/]+" ".." PRI_ROOT_RELATIVE_TO_MKSPECS ${ECM_MKSPECS_INSTALL_DIR})
set(BASEPATH "$$PWD/${PRI_ROOT_RELATIVE_TO_MKSPECS}")
endif()
set(PRI_TARGET_BASENAME ${EGPF_BASE_NAME})
set(PRI_TARGET_LIBNAME ${EGPF_LIB_NAME})
set(PRI_TARGET_QTDEPS ${EGPF_DEPS})
if(IS_ABSOLUTE "${EGPF_INCLUDE_INSTALL_DIR}")
set(PRI_TARGET_INCLUDES "${EGPF_INCLUDE_INSTALL_DIR}")
else()
set(PRI_TARGET_INCLUDES "${CMAKE_INSTALL_PREFIX}/${EGPF_INCLUDE_INSTALL_DIR}")
endif()
set(PRI_TARGET_INCLUDES)
foreach(_dir ${EGPF_INCLUDE_INSTALL_DIRS})
# separate list entries with space
if(IS_ABSOLUTE "${_dir}")
string(APPEND PRI_TARGET_INCLUDES " ${_dir}")
else()
string(APPEND PRI_TARGET_INCLUDES " ${BASEPATH}/${_dir}")
endif()
endforeach()
if(IS_ABSOLUTE "${EGPF_LIB_INSTALL_DIR}")
set(PRI_TARGET_LIBS "${EGPF_LIB_INSTALL_DIR}")
else()
set(PRI_TARGET_LIBS "${CMAKE_INSTALL_PREFIX}/${EGPF_LIB_INSTALL_DIR}")
set(PRI_TARGET_LIBS "${BASEPATH}/${EGPF_LIB_INSTALL_DIR}")
endif()
set(PRI_TARGET_DEFINES "")
@ -179,13 +224,25 @@ function(ECM_GENERATE_PRI_FILE)
set(${EGPF_FILENAME_VAR} ${PRI_FILENAME} PARENT_SCOPE)
endif()
set(PRI_TARGET_MODULE_CONFIG "")
# backward compat: it was not obvious LIB_NAME needs to be a target name,
# and some projects where the target name was not the actual library output name
# passed the output name for LIB_NAME, so .name & .module prperties are correctly set.
# TODO: improve API dox, allow control over module name if target name != output name
if(TARGET ${EGPF_LIB_NAME})
get_target_property(target_type ${EGPF_LIB_NAME} TYPE)
if (target_type STREQUAL "STATIC_LIBRARY")
set(PRI_TARGET_MODULE_CONFIG "staticlib")
endif()
endif()
file(GENERATE
OUTPUT ${PRI_FILENAME}
CONTENT
"QT.${PRI_TARGET_BASENAME}.VERSION = ${PROJECT_VERSION_STRING}
QT.${PRI_TARGET_BASENAME}.MAJOR_VERSION = ${PROJECT_VERSION_MAJOR}
QT.${PRI_TARGET_BASENAME}.MINOR_VERSION = ${PROJECT_VERSION_MINOR}
QT.${PRI_TARGET_BASENAME}.PATCH_VERSION = ${PROJECT_VERSION_PATCH}
"QT.${PRI_TARGET_BASENAME}.VERSION = ${PRI_VERSION}
QT.${PRI_TARGET_BASENAME}.MAJOR_VERSION = ${PRI_VERSION_MAJOR}
QT.${PRI_TARGET_BASENAME}.MINOR_VERSION = ${PRI_VERSION_MINOR}
QT.${PRI_TARGET_BASENAME}.PATCH_VERSION = ${PRI_VERSION_PATCH}
QT.${PRI_TARGET_BASENAME}.name = ${PRI_TARGET_LIBNAME}
QT.${PRI_TARGET_BASENAME}.module = ${PRI_TARGET_LIBNAME}
QT.${PRI_TARGET_BASENAME}.defines = ${PRI_TARGET_DEFINES}
@ -193,6 +250,7 @@ QT.${PRI_TARGET_BASENAME}.includes = ${PRI_TARGET_INCLUDES}
QT.${PRI_TARGET_BASENAME}.private_includes =
QT.${PRI_TARGET_BASENAME}.libs = ${PRI_TARGET_LIBS}
QT.${PRI_TARGET_BASENAME}.depends = ${PRI_TARGET_QTDEPS}
QT.${PRI_TARGET_BASENAME}.module_config = ${PRI_TARGET_MODULE_CONFIG}
"
)
endfunction()

View File

@ -6,7 +6,7 @@
#
# ``write_basic_package_version_file()`` is the same as the one provided by the
# `CMakePackageConfigHelpers
# <http://www.cmake.org/cmake/help/v2.8.12/cmake.html#module:CMakePackageConfigHelpers>`_
# <https://www.cmake.org/cmake/help/v2.8.12/cmake.html#module:CMakePackageConfigHelpers>`_
# module in CMake; see that module's documentation for
# more information.
#
@ -23,7 +23,7 @@
# 2.8.12, except that it adds an extra helper macro: find_dependency(). It is
# highly recommended that you read the `documentation for
# CMakePackageConfigHelpers
# <http://www.cmake.org/cmake/help/v2.8.12/cmake.html#module:CMakePackageConfigHelpers>`_
# <https://www.cmake.org/cmake/help/v2.8.12/cmake.html#module:CMakePackageConfigHelpers>`_
# for more information, particularly with regard to the PATH_VARS argument.
#
# Note that there is no argument that will disable the find_dependency() macro;
@ -50,31 +50,10 @@
# Since pre-1.0.0.
#=============================================================================
# Copyright 2014 Alex Merry <alex.merry@kdemail.net>
# Copyright 2013 Stephen Kelly <steveire@gmail.com>
# SPDX-FileCopyrightText: 2014 Alex Merry <alex.merry@kdemail.net>
# SPDX-FileCopyrightText: 2013 Stephen Kelly <steveire@gmail.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# SPDX-License-Identifier: BSD-3-Clause
include(${CMAKE_ROOT}/Modules/CMakePackageConfigHelpers.cmake)

View File

@ -1,32 +0,0 @@
find_package(Qt5Core QUIET)
if (Qt5Core_FOUND)
set(_qmake_executable_default "qmake-qt5")
endif ()
if (TARGET Qt5::qmake)
get_target_property(_qmake_executable_default Qt5::qmake LOCATION)
endif()
set(QMAKE_EXECUTABLE ${_qmake_executable_default}
CACHE FILEPATH "Location of the Qt5 qmake executable")
# This is not public API (yet)!
function(query_qmake result_variable qt_variable)
if(NOT QMAKE_EXECUTABLE)
set(${result_variable} "" PARENT_SCOPE)
message(WARNING "Should specify a qmake Qt5 binary. Can't check ${qt_variable}")
return()
endif()
execute_process(
COMMAND ${QMAKE_EXECUTABLE} -query "${qt_variable}"
RESULT_VARIABLE return_code
OUTPUT_VARIABLE output
)
if(return_code EQUAL 0)
string(STRIP "${output}" output)
file(TO_CMAKE_PATH "${output}" output_path)
set(${result_variable} "${output_path}" PARENT_SCOPE)
else()
message(WARNING "Failed call: ${QMAKE_EXECUTABLE} -query \"${qt_variable}\"")
message(FATAL_ERROR "QMake call failed: ${return_code}")
endif()
endfunction()

View File

@ -0,0 +1,100 @@
# SPDX-FileCopyrightText: 2014 Rohan Garg <rohan16garg@gmail.com>
# SPDX-FileCopyrightText: 2014 Alex Merry <alex.merry@kde.org>
# SPDX-FileCopyrightText: 2014-2016 Aleix Pol <aleixpol@kde.org>
# SPDX-FileCopyrightText: 2017 Friedrich W. H. Kossebau <kossebau@kde.org>
# SPDX-FileCopyrightText: 2022 Ahmad Samir <a.samir78@gmail.com>
#
# SPDX-License-Identifier: BSD-3-Clause
#[=======================================================================[.rst:
ECMQueryQt
---------------
This module can be used to query the installation paths used by Qt.
For Qt5 this uses ``qmake``, and for Qt6 this used ``qtpaths`` (the latter has built-in
support to query the paths of a target platform when cross-compiling).
This module defines the following function:
::
ecm_query_qt(<result_variable> <qt_variable> [TRY])
Passing ``TRY`` will result in the method not making the build fail if the executable
used for querying has not been found, but instead simply print a warning message and
return an empty string.
Example usage:
.. code-block:: cmake
include(ECMQueryQt)
ecm_query_qt(bin_dir QT_INSTALL_BINS)
If the call succeeds ``${bin_dir}`` will be set to ``<prefix>/path/to/bin/dir`` (e.g.
``/usr/lib64/qt/bin/``).
Since: 5.93
#]=======================================================================]
include(${CMAKE_CURRENT_LIST_DIR}/QtVersionOption.cmake)
include(CheckLanguage)
check_language(CXX)
if (CMAKE_CXX_COMPILER)
# Enable the CXX language to let CMake look for config files in library dirs.
# See: https://gitlab.kitware.com/cmake/cmake/-/issues/23266
enable_language(CXX)
endif()
if (QT_MAJOR_VERSION STREQUAL "5")
# QUIET to accommodate the TRY option
find_package(Qt${QT_MAJOR_VERSION}Core QUIET)
if(TARGET Qt5::qmake)
get_target_property(_qmake_executable_default Qt5::qmake LOCATION)
set(QUERY_EXECUTABLE ${_qmake_executable_default}
CACHE FILEPATH "Location of the Qt5 qmake executable")
set(_exec_name_text "Qt5 qmake")
set(_cli_option "-query")
endif()
elseif(QT_MAJOR_VERSION STREQUAL "6")
# QUIET to accommodate the TRY option
find_package(Qt6 COMPONENTS CoreTools QUIET CONFIG)
if (TARGET Qt6::qtpaths)
get_target_property(_qtpaths_executable Qt6::qtpaths LOCATION)
set(QUERY_EXECUTABLE ${_qtpaths_executable}
CACHE FILEPATH "Location of the Qt6 qtpaths executable")
set(_exec_name_text "Qt6 qtpaths")
set(_cli_option "--query")
endif()
endif()
function(ecm_query_qt result_variable qt_variable)
set(options TRY)
set(oneValueArgs)
set(multiValueArgs)
cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT QUERY_EXECUTABLE)
if(ARGS_TRY)
set(${result_variable} "" PARENT_SCOPE)
message(STATUS "No ${_exec_name_text} executable found. Can't check ${qt_variable}")
return()
else()
message(FATAL_ERROR "No ${_exec_name_text} executable found. Can't check ${qt_variable} as required")
endif()
endif()
execute_process(
COMMAND ${QUERY_EXECUTABLE} ${_cli_option} "${qt_variable}"
RESULT_VARIABLE return_code
OUTPUT_VARIABLE output
)
if(return_code EQUAL 0)
string(STRIP "${output}" output)
file(TO_CMAKE_PATH "${output}" output_path)
set(${result_variable} "${output_path}" PARENT_SCOPE)
else()
message(WARNING "Failed call: ${_command} \"${qt_variable}\"")
message(FATAL_ERROR "${_exec_name_text} call failed: ${return_code}")
endif()
endfunction()

View File

@ -28,7 +28,7 @@
# <prefix>_SOVERSION - <soversion>, or <major> if SOVERSION was not given
#
# If CMake policy CMP0048 is not NEW, the following CMake variables will also
# be set:
# be set::
#
# PROJECT_VERSION_MAJOR - <major>
# PROJECT_VERSION_MINOR - <minor>
@ -75,34 +75,13 @@
#
# Since pre-1.0.0.
#
# COMPATIBLITY option available since 1.6.0.
# COMPATIBILITY option available since 1.6.0.
#=============================================================================
# Copyright 2014 Alex Merry <alex.merry@kde.org>
# Copyright 2012 Alexander Neundorf <neundorf@kde.org>
# SPDX-FileCopyrightText: 2014 Alex Merry <alex.merry@kde.org>
# SPDX-FileCopyrightText: 2012 Alexander Neundorf <neundorf@kde.org>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# SPDX-License-Identifier: BSD-3-Clause
include(CMakePackageConfigHelpers)
@ -154,9 +133,9 @@ function(ecm_setup_version _version)
set(_minor "${PROJECT_VERSION_MINOR}")
set(_patch "${PROJECT_VERSION_PATCH}")
else()
string(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+.*" "\\1" _major "${_version}")
string(REGEX REPLACE "^[0-9]+\\.([0-9]+)\\.[0-9]+.*" "\\1" _minor "${_version}")
string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" _patch "${_version}")
string(REGEX REPLACE "^0*([0-9]+)\\.[0-9]+\\.[0-9]+.*" "\\1" _major "${_version}")
string(REGEX REPLACE "^[0-9]+\\.0*([0-9]+)\\.[0-9]+.*" "\\1" _minor "${_version}")
string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.0*([0-9]+).*" "\\1" _patch "${_version}")
endif()
if(NOT ESV_SOVERSION)

View File

@ -1,188 +0,0 @@
# - Define GNU standard installation directories
# Provides install directory variables as defined for GNU software:
# http://www.gnu.org/prep/standards/html_node/Directory-Variables.html
# Inclusion of this module defines the following variables:
# CMAKE_INSTALL_<dir> - destination for files of a given type
# CMAKE_INSTALL_FULL_<dir> - corresponding absolute path
# where <dir> is one of:
# BINDIR - user executables (bin)
# SBINDIR - system admin executables (sbin)
# LIBEXECDIR - program executables (libexec)
# SYSCONFDIR - read-only single-machine data (etc)
# SHAREDSTATEDIR - modifiable architecture-independent data (com)
# LOCALSTATEDIR - modifiable single-machine data (var)
# LIBDIR - object code libraries (lib or lib64 or lib/<multiarch-tuple> on Debian)
# INCLUDEDIR - C header files (include)
# OLDINCLUDEDIR - C header files for non-gcc (/usr/include)
# DATAROOTDIR - read-only architecture-independent data root (share)
# DATADIR - read-only architecture-independent data (DATAROOTDIR)
# INFODIR - info documentation (DATAROOTDIR/info)
# LOCALEDIR - locale-dependent data (DATAROOTDIR/locale)
# MANDIR - man documentation (DATAROOTDIR/man)
# DOCDIR - documentation root (DATAROOTDIR/doc/PROJECT_NAME)
# Each CMAKE_INSTALL_<dir> value may be passed to the DESTINATION options of
# install() commands for the corresponding file type. If the includer does
# not define a value the above-shown default will be used and the value will
# appear in the cache for editing by the user.
# Each CMAKE_INSTALL_FULL_<dir> value contains an absolute path constructed
# from the corresponding destination by prepending (if necessary) the value
# of CMAKE_INSTALL_PREFIX.
#=============================================================================
# Copyright 2011 Nikita Krupen'ko <krnekit@gmail.com>
# Copyright 2011 Kitware, Inc.
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
# License text for the above reference.)
# Installation directories
#
if(NOT DEFINED CMAKE_INSTALL_BINDIR)
set(CMAKE_INSTALL_BINDIR "bin" CACHE PATH "user executables (bin)")
endif()
if(NOT DEFINED CMAKE_INSTALL_SBINDIR)
set(CMAKE_INSTALL_SBINDIR "sbin" CACHE PATH "system admin executables (sbin)")
endif()
if(NOT DEFINED CMAKE_INSTALL_LIBEXECDIR)
set(CMAKE_INSTALL_LIBEXECDIR "libexec" CACHE PATH "program executables (libexec)")
endif()
if(NOT DEFINED CMAKE_INSTALL_SYSCONFDIR)
set(CMAKE_INSTALL_SYSCONFDIR "etc" CACHE PATH "read-only single-machine data (etc)")
endif()
if(NOT DEFINED CMAKE_INSTALL_SHAREDSTATEDIR)
set(CMAKE_INSTALL_SHAREDSTATEDIR "com" CACHE PATH "modifiable architecture-independent data (com)")
endif()
if(NOT DEFINED CMAKE_INSTALL_LOCALSTATEDIR)
set(CMAKE_INSTALL_LOCALSTATEDIR "var" CACHE PATH "modifiable single-machine data (var)")
endif()
if(NOT DEFINED CMAKE_INSTALL_LIBDIR)
set(_LIBDIR_DEFAULT "lib")
# Override this default 'lib' with 'lib64' iff:
# - we are on Linux system but NOT cross-compiling
# - we are NOT on debian
# - we are on a 64 bits system
# reason is: amd64 ABI: http://www.x86-64.org/documentation/abi.pdf
# For Debian with multiarch, use 'lib/${CMAKE_LIBRARY_ARCHITECTURE}' if
# CMAKE_LIBRARY_ARCHITECTURE is set (which contains e.g. "i386-linux-gnu"
# See http://wiki.debian.org/Multiarch
if(CMAKE_SYSTEM_NAME MATCHES "Linux"
AND NOT CMAKE_CROSSCOMPILING)
if (EXISTS "/etc/debian_version") # is this a debian system ?
if(CMAKE_LIBRARY_ARCHITECTURE)
set(_LIBDIR_DEFAULT "lib/${CMAKE_LIBRARY_ARCHITECTURE}")
endif()
else() # not debian, rely on CMAKE_SIZEOF_VOID_P:
if(NOT DEFINED CMAKE_SIZEOF_VOID_P)
message(AUTHOR_WARNING
"Unable to determine default CMAKE_INSTALL_LIBDIR directory because no target architecture is known. "
"Please enable at least one language before including GNUInstallDirs.")
else()
if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
set(_LIBDIR_DEFAULT "lib64")
endif()
endif()
endif()
endif()
set(CMAKE_INSTALL_LIBDIR "${_LIBDIR_DEFAULT}" CACHE PATH "object code libraries (${_LIBDIR_DEFAULT})")
endif()
if(NOT DEFINED CMAKE_INSTALL_INCLUDEDIR)
set(CMAKE_INSTALL_INCLUDEDIR "include" CACHE PATH "C header files (include)")
endif()
if(NOT DEFINED CMAKE_INSTALL_OLDINCLUDEDIR)
set(CMAKE_INSTALL_OLDINCLUDEDIR "/usr/include" CACHE PATH "C header files for non-gcc (/usr/include)")
endif()
if(NOT DEFINED CMAKE_INSTALL_DATAROOTDIR)
set(CMAKE_INSTALL_DATAROOTDIR "share" CACHE PATH "read-only architecture-independent data root (share)")
endif()
#-----------------------------------------------------------------------------
# Values whose defaults are relative to DATAROOTDIR. Store empty values in
# the cache and store the defaults in local variables if the cache values are
# not set explicitly. This auto-updates the defaults as DATAROOTDIR changes.
if(NOT CMAKE_INSTALL_DATADIR)
set(CMAKE_INSTALL_DATADIR "" CACHE PATH "read-only architecture-independent data (DATAROOTDIR)")
set(CMAKE_INSTALL_DATADIR "${CMAKE_INSTALL_DATAROOTDIR}")
endif()
if(NOT CMAKE_INSTALL_INFODIR)
set(CMAKE_INSTALL_INFODIR "" CACHE PATH "info documentation (DATAROOTDIR/info)")
set(CMAKE_INSTALL_INFODIR "${CMAKE_INSTALL_DATAROOTDIR}/info")
endif()
if(NOT CMAKE_INSTALL_LOCALEDIR)
set(CMAKE_INSTALL_LOCALEDIR "" CACHE PATH "locale-dependent data (DATAROOTDIR/locale)")
set(CMAKE_INSTALL_LOCALEDIR "${CMAKE_INSTALL_DATAROOTDIR}/locale")
endif()
if(NOT CMAKE_INSTALL_MANDIR)
set(CMAKE_INSTALL_MANDIR "" CACHE PATH "man documentation (DATAROOTDIR/man)")
set(CMAKE_INSTALL_MANDIR "${CMAKE_INSTALL_DATAROOTDIR}/man")
endif()
if(NOT CMAKE_INSTALL_DOCDIR)
set(CMAKE_INSTALL_DOCDIR "" CACHE PATH "documentation root (DATAROOTDIR/doc/PROJECT_NAME)")
set(CMAKE_INSTALL_DOCDIR "${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME}")
endif()
#-----------------------------------------------------------------------------
mark_as_advanced(
CMAKE_INSTALL_BINDIR
CMAKE_INSTALL_SBINDIR
CMAKE_INSTALL_LIBEXECDIR
CMAKE_INSTALL_SYSCONFDIR
CMAKE_INSTALL_SHAREDSTATEDIR
CMAKE_INSTALL_LOCALSTATEDIR
CMAKE_INSTALL_LIBDIR
CMAKE_INSTALL_INCLUDEDIR
CMAKE_INSTALL_OLDINCLUDEDIR
CMAKE_INSTALL_DATAROOTDIR
CMAKE_INSTALL_DATADIR
CMAKE_INSTALL_INFODIR
CMAKE_INSTALL_LOCALEDIR
CMAKE_INSTALL_MANDIR
CMAKE_INSTALL_DOCDIR
)
# Result directories
#
foreach(dir
BINDIR
SBINDIR
LIBEXECDIR
SYSCONFDIR
SHAREDSTATEDIR
LOCALSTATEDIR
LIBDIR
INCLUDEDIR
OLDINCLUDEDIR
DATAROOTDIR
DATADIR
INFODIR
LOCALEDIR
MANDIR
DOCDIR
)
if(NOT IS_ABSOLUTE ${CMAKE_INSTALL_${dir}})
set(CMAKE_INSTALL_FULL_${dir} "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_${dir}}")
else()
set(CMAKE_INSTALL_FULL_${dir} "${CMAKE_INSTALL_${dir}}")
endif()
endforeach()

View File

@ -0,0 +1,36 @@
# SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
#
# SPDX-License-Identifier: BSD-3-Clause
#[=======================================================================[.rst:
QtVersionOption
---------------
Adds a build option to select the major Qt version if necessary,
that is, if the major Qt version has not yet been determined otherwise
(e.g. by a corresponding ``find_package()`` call).
This module is typically included by other modules requiring knowledge
about the major Qt version.
``QT_MAJOR_VERSION`` is defined to either be "5" or "6".
Since 5.82.0.
#]=======================================================================]
if (DEFINED QT_MAJOR_VERSION)
return()
endif()
if (TARGET Qt5::Core)
set(QT_MAJOR_VERSION 5)
elseif (TARGET Qt6::Core)
set(QT_MAJOR_VERSION 6)
else()
option(BUILD_WITH_QT6 "Build against Qt 6" OFF)
if (BUILD_WITH_QT6)
set(QT_MAJOR_VERSION 6)
else()
set(QT_MAJOR_VERSION 5)
endif()
endif()

View File

@ -1,6 +1,6 @@
#include "gnomekeyring_p.h"
const char* GnomeKeyring::GNOME_KEYRING_DEFAULT = NULL;
const char* GnomeKeyring::GNOME_KEYRING_DEFAULT = nullptr;
bool GnomeKeyring::isAvailable()
{

View File

@ -47,7 +47,7 @@ class JobPrivate;
class QKEYCHAIN_EXPORT Job : public QObject {
Q_OBJECT
public:
~Job();
~Job() override;
/**
* @return The QSettings instance used as plaintext storage if insecureFallback() is true.
@ -150,7 +150,7 @@ Q_SIGNALS:
void finished( QKeychain::Job* );
protected:
explicit Job( JobPrivate *q, QObject* parent=0 );
explicit Job( JobPrivate *q, QObject* parent=nullptr );
Q_INVOKABLE void doStart();
private:
@ -185,8 +185,8 @@ public:
* @param service The service string used by this job (can be empty).
* @param parent The parent of this job.
*/
explicit ReadPasswordJob( const QString& service, QObject* parent=0 );
~ReadPasswordJob();
explicit ReadPasswordJob( const QString& service, QObject* parent=nullptr );
~ReadPasswordJob() override;
/**
* @return The binary data stored as value of this job's key().
@ -222,8 +222,8 @@ public:
* @param service The service string used by this job (can be empty).
* @param parent The parent of this job.
*/
explicit WritePasswordJob( const QString& service, QObject* parent=0 );
~WritePasswordJob();
explicit WritePasswordJob( const QString& service, QObject* parent=nullptr );
~WritePasswordJob() override;
/**
* Set the @p data that the job will store in the keychain as binary data.
@ -259,13 +259,24 @@ public:
* @param service The service string used by this job (can be empty).
* @param parent The parent of this job.
*/
explicit DeletePasswordJob( const QString& service, QObject* parent=0 );
~DeletePasswordJob();
explicit DeletePasswordJob( const QString& service, QObject* parent=nullptr );
~DeletePasswordJob() override;
private:
friend class QKeychain::DeletePasswordJobPrivate;
};
/**
* Checks whether there is a viable secure backend available.
* This particularly matters on UNIX platforms where multiple different backends
* exist and none might be available.
*
* Note that using the insecure fallback will work even if no secure backend is available.
*
* @since 0.14.0
*/
QKEYCHAIN_EXPORT bool isAvailable();
} // namespace QtKeychain
#endif

View File

@ -0,0 +1,195 @@
/******************************************************************************
* Copyright (C) 2016 Mathias Hasselmann <mathias.hasselmann@kdab.com> *
* *
* This program is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
* or FITNESS FOR A PARTICULAR PURPOSE. For licensing and distribution *
* details, check the accompanying file 'COPYING'. *
*****************************************************************************/
#include "keychain_p.h"
#include "androidkeystore_p.h"
#include "plaintextstore_p.h"
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QtAndroid>
#endif
using namespace QKeychain;
using android::content::Context;
using android::security::KeyPairGeneratorSpec;
using java::io::ByteArrayInputStream;
using java::io::ByteArrayOutputStream;
using java::security::interfaces::RSAPrivateKey;
using java::security::interfaces::RSAPublicKey;
using java::security::KeyPair;
using java::security::KeyPairGenerator;
using java::security::KeyStore;
using java::util::Calendar;
using javax::crypto::Cipher;
using javax::crypto::CipherInputStream;
using javax::crypto::CipherOutputStream;
using javax::security::auth::x500::X500Principal;
namespace {
inline QString makeAlias(const QString &service, const QString &key)
{
return service + QLatin1Char('/') + key;
}
}
void ReadPasswordJobPrivate::scheduledStart()
{
PlainTextStore plainTextStore(q->service(), q->settings());
if (!plainTextStore.contains(q->key())) {
q->emitFinishedWithError(Error::EntryNotFound, tr("Entry not found"));
return;
}
const QByteArray &encryptedData = plainTextStore.readData(q->key());
const KeyStore keyStore = KeyStore::getInstance(QStringLiteral("AndroidKeyStore"));
if (!keyStore || !keyStore.load()) {
q->emitFinishedWithError(Error::AccessDenied, tr("Could not open keystore"));
return;
}
const auto &alias = makeAlias(q->service(), q->key());
const KeyStore::PrivateKeyEntry entry = keyStore.getEntry(alias);
if (!entry) {
q->emitFinishedWithError(Error::AccessDenied, tr("Could not retrieve private key from keystore"));
return;
}
const Cipher cipher = Cipher::getInstance(QStringLiteral("RSA/ECB/PKCS1Padding"));
if (!cipher || !cipher.init(Cipher::DECRYPT_MODE, entry.getPrivateKey())) {
q->emitFinishedWithError(Error::OtherError, tr("Could not create decryption cipher"));
return;
}
QByteArray plainData;
const CipherInputStream inputStream(ByteArrayInputStream(encryptedData), cipher);
for (int nextByte; (nextByte = inputStream.read()) != -1; )
plainData.append(nextByte);
mode = plainTextStore.readMode(q->key());
data = plainData;
q->emitFinished();
}
void WritePasswordJobPrivate::scheduledStart()
{
const KeyStore keyStore = KeyStore::getInstance(QStringLiteral("AndroidKeyStore"));
if (!keyStore || !keyStore.load()) {
q->emitFinishedWithError(Error::AccessDenied, tr("Could not open keystore"));
return;
}
const auto &alias = makeAlias(q->service(), q->key());
if (!keyStore.containsAlias(alias)) {
const Calendar start = Calendar::getInstance();
const Calendar end = Calendar::getInstance();
end.add(Calendar::YEAR, 99);
const KeyPairGeneratorSpec spec =
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
KeyPairGeneratorSpec::Builder(Context(QtAndroid::androidActivity())).
#elif QT_VERSION < QT_VERSION_CHECK(6, 4, 0)
KeyPairGeneratorSpec::Builder(Context(QNativeInterface::QAndroidApplication::context())).
#else
KeyPairGeneratorSpec::Builder(Context((jobject)QNativeInterface::QAndroidApplication::context())).
#endif
setAlias(alias).
setSubject(X500Principal(QStringLiteral("CN=QtKeychain, O=Android Authority"))).
setSerialNumber(java::math::BigInteger::ONE).
setStartDate(start.getTime()).
setEndDate(end.getTime()).
build();
const KeyPairGenerator generator = KeyPairGenerator::getInstance(QStringLiteral("RSA"),
QStringLiteral("AndroidKeyStore"));
if (!generator) {
q->emitFinishedWithError(Error::OtherError, tr("Could not create private key generator"));
return;
}
generator.initialize(spec);
if (!generator.generateKeyPair()) {
q->emitFinishedWithError(Error::OtherError, tr("Could not generate new private key"));
return;
}
}
const KeyStore::PrivateKeyEntry entry = keyStore.getEntry(alias);
if (!entry) {
q->emitFinishedWithError(Error::AccessDenied, tr("Could not retrieve private key from keystore"));
return;
}
const RSAPublicKey publicKey = entry.getCertificate().getPublicKey();
const Cipher cipher = Cipher::getInstance(QStringLiteral("RSA/ECB/PKCS1Padding"));
if (!cipher || !cipher.init(Cipher::ENCRYPT_MODE, publicKey)) {
q->emitFinishedWithError(Error::OtherError, tr("Could not create encryption cipher"));
return;
}
ByteArrayOutputStream outputStream;
CipherOutputStream cipherOutputStream(outputStream, cipher);
if (!cipherOutputStream.write(data) || !cipherOutputStream.close()) {
q->emitFinishedWithError(Error::OtherError, tr("Could not encrypt data"));
return;
}
PlainTextStore plainTextStore(q->service(), q->settings());
plainTextStore.write(q->key(), outputStream.toByteArray(), mode);
if (plainTextStore.error() != NoError)
q->emitFinishedWithError(plainTextStore.error(), plainTextStore.errorString());
else
q->emitFinished();
}
void DeletePasswordJobPrivate::scheduledStart()
{
const KeyStore keyStore = KeyStore::getInstance(QStringLiteral("AndroidKeyStore"));
if (!keyStore || !keyStore.load()) {
q->emitFinishedWithError(Error::AccessDenied, tr("Could not open keystore"));
return;
}
const auto &alias = makeAlias(q->service(), q->key());
if (!keyStore.deleteEntry(alias)) {
q->emitFinishedWithError(Error::OtherError, tr("Could not remove private key from keystore"));
return;
}
PlainTextStore plainTextStore(q->service(), q->settings());
plainTextStore.remove(q->key());
if (plainTextStore.error() != NoError)
q->emitFinishedWithError(plainTextStore.error(), plainTextStore.errorString());
else
q->emitFinished();
}
bool QKeychain::isAvailable()
{
return true;
}

View File

@ -0,0 +1,263 @@
/******************************************************************************
* Copyright (C) 2016 Mathias Hasselmann <mathias.hasselmann@kdab.com> *
* *
* This program is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
* or FITNESS FOR A PARTICULAR PURPOSE. For licensing and distribution *
* details, check the accompanying file 'COPYING'. *
*****************************************************************************/
#include "keychain_p.h"
#import <Foundation/Foundation.h>
#import <Security/Security.h>
using namespace QKeychain;
struct ErrorDescription
{
QKeychain::Error code;
QString message;
ErrorDescription(QKeychain::Error code, const QString &message)
: code(code), message(message) {}
static ErrorDescription fromStatus(OSStatus status)
{
switch(status) {
case errSecSuccess:
return ErrorDescription(QKeychain::NoError, Job::tr("No error"));
case errSecItemNotFound:
return ErrorDescription(QKeychain::EntryNotFound, Job::tr("The specified item could not be found in the keychain"));
case errSecUserCanceled:
return ErrorDescription(QKeychain::AccessDeniedByUser, Job::tr("User canceled the operation"));
case errSecInteractionNotAllowed:
return ErrorDescription(QKeychain::AccessDenied, Job::tr("User interaction is not allowed"));
case errSecNotAvailable:
return ErrorDescription(QKeychain::AccessDenied, Job::tr("No keychain is available. You may need to restart your computer"));
case errSecAuthFailed:
return ErrorDescription(QKeychain::AccessDenied, Job::tr("The user name or passphrase you entered is not correct"));
case errSecVerifyFailed:
return ErrorDescription(QKeychain::AccessDenied, Job::tr("A cryptographic verification failure has occurred"));
case errSecUnimplemented:
return ErrorDescription(QKeychain::NotImplemented, Job::tr("Function or operation not implemented"));
case errSecIO:
return ErrorDescription(QKeychain::OtherError, Job::tr("I/O error"));
case errSecOpWr:
return ErrorDescription(QKeychain::OtherError, Job::tr("Already open with with write permission"));
case errSecParam:
return ErrorDescription(QKeychain::OtherError, Job::tr("Invalid parameters passed to a function"));
case errSecAllocate:
return ErrorDescription(QKeychain::OtherError, Job::tr("Failed to allocate memory"));
case errSecBadReq:
return ErrorDescription(QKeychain::OtherError, Job::tr("Bad parameter or invalid state for operation"));
case errSecInternalComponent:
return ErrorDescription(QKeychain::OtherError, Job::tr("An internal component failed"));
case errSecDuplicateItem:
return ErrorDescription(QKeychain::OtherError, Job::tr("The specified item already exists in the keychain"));
case errSecDecode:
return ErrorDescription(QKeychain::OtherError, Job::tr("Unable to decode the provided data"));
}
return ErrorDescription(QKeychain::OtherError, Job::tr("Unknown error"));
}
};
@interface AppleKeychainInterface : NSObject
- (instancetype)initWithJob:(Job *)job andPrivateJob:(JobPrivate *)privateJob;
- (void)keychainTaskFinished;
- (void)keychainReadTaskFinished:(NSData *)retrievedData;
- (void)keychainTaskFinishedWithError:(OSStatus)status descriptiveMessage:(NSString *)descriptiveMessage;
@end
@interface AppleKeychainInterface()
{
QPointer<Job> _job;
QPointer<JobPrivate> _privateJob;
}
@end
@implementation AppleKeychainInterface
- (instancetype)initWithJob:(Job *)job andPrivateJob:(JobPrivate *)privateJob
{
self = [super init];
if (self) {
_job = job;
_privateJob = privateJob;
}
return self;
}
- (void)dealloc
{
[NSNotificationCenter.defaultCenter removeObserver:self];
[super dealloc];
}
- (void)keychainTaskFinished
{
if (_job) {
_job->emitFinished();
}
}
- (void)keychainReadTaskFinished:(NSData *)retrievedData
{
_privateJob->data.clear();
_privateJob->mode = JobPrivate::Binary;
if (retrievedData != nil) {
if (_privateJob) {
_privateJob->data = QByteArray::fromNSData(retrievedData);
}
}
if (_job) {
_job->emitFinished();
}
}
- (void)keychainTaskFinishedWithError:(OSStatus)status descriptiveMessage:(NSString *)descriptiveMessage
{
const auto localisedDescriptiveMessage = Job::tr([descriptiveMessage UTF8String]);
const ErrorDescription error = ErrorDescription::fromStatus(status);
const auto fullMessage = localisedDescriptiveMessage.isEmpty() ? error.message : QStringLiteral("%1: %2").arg(localisedDescriptiveMessage, error.message);
if (_job) {
_job->emitFinishedWithError(error.code, fullMessage);
}
}
@end
static void StartReadPassword(const QString &service, const QString &key, AppleKeychainInterface * const interface)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSDictionary * const query = @{
(__bridge NSString *)kSecClass: (__bridge NSString *)kSecClassGenericPassword,
(__bridge NSString *)kSecAttrService: service.toNSString(),
(__bridge NSString *)kSecAttrAccount: key.toNSString(),
(__bridge NSString *)kSecReturnData: @YES,
};
CFTypeRef dataRef = nil;
const OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) query, &dataRef);
if (status == errSecSuccess) {
const CFDataRef castedDataRef = (CFDataRef)dataRef;
NSData * const data = (__bridge NSData *)castedDataRef;
dispatch_async(dispatch_get_main_queue(), ^{
[interface keychainReadTaskFinished:data];
[interface release];
});
} else {
NSString * const descriptiveErrorString = @"Could not retrieve private key from keystore";
dispatch_async(dispatch_get_main_queue(), ^{
[interface keychainTaskFinishedWithError:status descriptiveMessage:descriptiveErrorString];
[interface release];
});
}
if (dataRef) {
CFRelease(dataRef);
}
});
}
static void StartWritePassword(const QString &service, const QString &key, const QByteArray &data, AppleKeychainInterface * const interface)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSDictionary * const query = @{
(__bridge NSString *)kSecClass: (__bridge NSString *)kSecClassGenericPassword,
(__bridge NSString *)kSecAttrService: service.toNSString(),
(__bridge NSString *)kSecAttrAccount: key.toNSString(),
};
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, nil);
if (status == errSecSuccess) {
NSDictionary * const update = @{
(__bridge NSString *)kSecValueData: data.toNSData(),
};
status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update);
} else {
NSDictionary * const insert = @{
(__bridge NSString *)kSecClass: (__bridge NSString *)kSecClassGenericPassword,
(__bridge NSString *)kSecAttrService: service.toNSString(),
(__bridge NSString *)kSecAttrAccount: key.toNSString(),
(__bridge NSString *)kSecValueData: data.toNSData(),
};
status = SecItemAdd((__bridge const CFDictionaryRef)insert, nil);
}
if (status == errSecSuccess) {
dispatch_async(dispatch_get_main_queue(), ^{
[interface keychainTaskFinished];
[interface release];
});
} else {
NSString * const descriptiveErrorString = @"Could not store data in settings";
dispatch_async(dispatch_get_main_queue(), ^{
[interface keychainTaskFinishedWithError:status descriptiveMessage:descriptiveErrorString];
[interface release];
});
}
});
}
static void StartDeletePassword(const QString &service, const QString &key, AppleKeychainInterface * const interface)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSDictionary * const query = @{
(__bridge NSString *)kSecClass: (__bridge NSString *)kSecClassGenericPassword,
(__bridge NSString *)kSecAttrService: service.toNSString(),
(__bridge NSString *)kSecAttrAccount: key.toNSString(),
};
const OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
if (status == errSecSuccess) {
dispatch_async(dispatch_get_main_queue(), ^{
[interface keychainTaskFinished];
[interface release];
});
} else {
NSString * const descriptiveErrorString = @"Could not remove private key from keystore";
dispatch_async(dispatch_get_main_queue(), ^{
[interface keychainTaskFinishedWithError:status descriptiveMessage:descriptiveErrorString];
[interface release];
});
}
});
}
void ReadPasswordJobPrivate::scheduledStart()
{
AppleKeychainInterface * const interface = [[AppleKeychainInterface alloc] initWithJob:q andPrivateJob:this];
StartReadPassword(service, key, interface);
}
void WritePasswordJobPrivate::scheduledStart()
{
AppleKeychainInterface * const interface = [[AppleKeychainInterface alloc] initWithJob:q andPrivateJob:this];
StartWritePassword(service, key, data, interface);
}
void DeletePasswordJobPrivate::scheduledStart()
{
AppleKeychainInterface * const interface = [[AppleKeychainInterface alloc] initWithJob:q andPrivateJob:this];
StartDeletePassword(service, key, interface);
}
bool QKeychain::isAvailable()
{
return true;
}

View File

@ -0,0 +1,192 @@
/******************************************************************************
* Copyright (C) 2018 François Revol <revol@free.fr> *
* *
* This program is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
* or FITNESS FOR A PARTICULAR PURPOSE. For licensing and distribution *
* details, check the accompanying file 'COPYING'. *
*****************************************************************************/
#include "keychain_p.h"
#include <KeyStore.h>
#include <Application.h>
#include <AppFileInfo.h>
#include <File.h>
#include <QDebug>
#include <QCoreApplication>
#include <QString>
using namespace QKeychain;
class AutoApp {
public:
AutoApp();
~AutoApp();
BApplication *app;
};
AutoApp::AutoApp()
: app(nullptr)
{
if (be_app)
return;
// no BApplication object, probably using QCoreApplication
// but we need one around
QString appSignature;
char signature[B_MIME_TYPE_LENGTH];
signature[0] = '\0';
QString appPath = QCoreApplication::applicationFilePath();
BFile appFile(appPath.toUtf8(), B_READ_ONLY);
if (appFile.InitCheck() == B_OK) {
BAppFileInfo info(&appFile);
if (info.InitCheck() == B_OK) {
if (info.GetSignature(signature) != B_OK)
signature[0] = '\0';
}
}
if (signature[0] != '\0')
appSignature = QLatin1String(signature);
else
appSignature = QLatin1String("application/x-vnd.qtkeychain-") +
QCoreApplication::applicationName().remove("_x86");
app = new BApplication(appSignature.toUtf8().constData());
}
AutoApp::~AutoApp()
{
delete app;
}
static QString strForStatus( status_t os ) {
const char * const buf = strerror(os) ;
return QObject::tr( "error 0x%1: %2" )
.arg( os, 8, 16 ).arg( QString::fromUtf8( buf, strlen( buf ) ) );
}
void ReadPasswordJobPrivate::scheduledStart()
{
AutoApp aa;
QString errorString;
Error error = NoError;
BKeyStore keyStore;
BPasswordKey password;
status_t result = keyStore.GetKey(B_KEY_TYPE_PASSWORD,
q->service().toUtf8().constData(),
q->key().toUtf8().constData(),
false, password);
data = QByteArray(reinterpret_cast<const char*>(password.Data()), password.DataLength());
switch ( result ) {
case B_OK:
q->emitFinished();
return;
case B_ENTRY_NOT_FOUND:
errorString = tr("Password not found");
error = EntryNotFound;
break;
default:
errorString = strForStatus( result );
error = OtherError;
break;
}
q->emitFinishedWithError( error, errorString );
}
void WritePasswordJobPrivate::scheduledStart()
{
AutoApp aa;
QString errorString;
Error error = NoError;
BKeyStore keyStore;
BPasswordKey password(data.constData(),
B_KEY_PURPOSE_GENERIC,
q->service().toUtf8().constData(),
q->key().toUtf8().constData());
status_t result = B_OK;
// re-add as binary if it's not text
if (mode == Binary)
result = password.SetData(reinterpret_cast<const uint8*>(data.constData()), data.size());
if (result == B_OK)
result = keyStore.AddKey(password);
if (result == B_NAME_IN_USE) {
BPasswordKey old_password;
result = keyStore.GetKey(B_KEY_TYPE_PASSWORD,
q->service().toUtf8().constData(),
q->key().toUtf8().constData(),
false, old_password);
if (result == B_OK)
result = keyStore.RemoveKey(old_password);
if (result == B_OK)
result = keyStore.AddKey(password);
}
switch ( result ) {
case B_OK:
q->emitFinished();
return;
case B_ENTRY_NOT_FOUND:
errorString = tr("Password not found");
error = EntryNotFound;
break;
default:
errorString = strForStatus( result );
error = OtherError;
break;
}
q->emitFinishedWithError( error, errorString );
}
void DeletePasswordJobPrivate::scheduledStart()
{
AutoApp aa;
QString errorString;
Error error = NoError;
BKeyStore keyStore;
BPasswordKey password;
status_t result = keyStore.GetKey(B_KEY_TYPE_PASSWORD,
q->service().toUtf8().constData(),
q->key().toUtf8().constData(),
false, password);
if (result == B_OK)
result = keyStore.RemoveKey(password);
switch ( result ) {
case B_OK:
q->emitFinished();
return;
case B_ENTRY_NOT_FOUND:
errorString = tr("Password not found");
error = EntryNotFound;
break;
default:
errorString = strForStatus( result );
error = CouldNotDeleteEntry;
break;
}
q->emitFinishedWithError( error, errorString );
}
bool QKeychain::isAvailable()
{
return true;
}

View File

@ -1,146 +0,0 @@
/******************************************************************************
* Copyright (C) 2016 Mathias Hasselmann <mathias.hasselmann@kdab.com> *
* *
* This program is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
* or FITNESS FOR A PARTICULAR PURPOSE. For licensing and distribution *
* details, check the accompanying file 'COPYING'. *
*****************************************************************************/
#include "keychain_p.h"
#import <Foundation/Foundation.h>
#import <Security/Security.h>
using namespace QKeychain;
struct ErrorDescription
{
QKeychain::Error code;
QString message;
ErrorDescription(QKeychain::Error code, const QString &message)
: code(code), message(message) {}
static ErrorDescription fromStatus(OSStatus status)
{
switch(status) {
case errSecSuccess:
return ErrorDescription(QKeychain::NoError, Job::tr("No error"));
case errSecItemNotFound:
return ErrorDescription(QKeychain::EntryNotFound, Job::tr("The specified item could not be found in the keychain"));
case errSecUserCanceled:
return ErrorDescription(QKeychain::AccessDeniedByUser, Job::tr("User canceled the operation"));
case errSecInteractionNotAllowed:
return ErrorDescription(QKeychain::AccessDenied, Job::tr("User interaction is not allowed"));
case errSecNotAvailable:
return ErrorDescription(QKeychain::AccessDenied, Job::tr("No keychain is available. You may need to restart your computer"));
case errSecAuthFailed:
return ErrorDescription(QKeychain::AccessDenied, Job::tr("The user name or passphrase you entered is not correct"));
case errSecVerifyFailed:
return ErrorDescription(QKeychain::AccessDenied, Job::tr("A cryptographic verification failure has occurred"));
case errSecUnimplemented:
return ErrorDescription(QKeychain::NotImplemented, Job::tr("Function or operation not implemented"));
case errSecIO:
return ErrorDescription(QKeychain::OtherError, Job::tr("I/O error"));
case errSecOpWr:
return ErrorDescription(QKeychain::OtherError, Job::tr("Already open with with write permission"));
case errSecParam:
return ErrorDescription(QKeychain::OtherError, Job::tr("Invalid parameters passed to a function"));
case errSecAllocate:
return ErrorDescription(QKeychain::OtherError, Job::tr("Failed to allocate memory"));
case errSecBadReq:
return ErrorDescription(QKeychain::OtherError, Job::tr("Bad parameter or invalid state for operation"));
case errSecInternalComponent:
return ErrorDescription(QKeychain::OtherError, Job::tr("An internal component failed"));
case errSecDuplicateItem:
return ErrorDescription(QKeychain::OtherError, Job::tr("The specified item already exists in the keychain"));
case errSecDecode:
return ErrorDescription(QKeychain::OtherError, Job::tr("Unable to decode the provided data"));
}
return ErrorDescription(QKeychain::OtherError, Job::tr("Unknown error"));
}
};
void ReadPasswordJobPrivate::scheduledStart()
{
NSDictionary *const query = @{
(__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword,
(__bridge id) kSecAttrService: (__bridge NSString *) service.toCFString(),
(__bridge id) kSecAttrAccount: (__bridge NSString *) key.toCFString(),
(__bridge id) kSecReturnData: @YES,
};
CFTypeRef dataRef = nil;
const OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) query, &dataRef);
data.clear();
mode = Binary;
if (status == errSecSuccess) {
if (dataRef)
data = QByteArray::fromCFData((CFDataRef) dataRef);
q->emitFinished();
} else {
const ErrorDescription error = ErrorDescription::fromStatus(status);
q->emitFinishedWithError(error.code, Job::tr("Could not retrieve private key from keystore: %1").arg(error.message));
}
if (dataRef)
[dataRef release];
}
void WritePasswordJobPrivate::scheduledStart()
{
NSDictionary *const query = @{
(__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword,
(__bridge id) kSecAttrService: (__bridge NSString *) service.toCFString(),
(__bridge id) kSecAttrAccount: (__bridge NSString *) key.toCFString(),
};
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) query, nil);
if (status == errSecSuccess) {
NSDictionary *const update = @{
(__bridge id) kSecValueData: (__bridge NSData *) data.toCFData(),
};
status = SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef) update);
} else {
NSDictionary *const insert = @{
(__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword,
(__bridge id) kSecAttrService: (__bridge NSString *) service.toCFString(),
(__bridge id) kSecAttrAccount: (__bridge NSString *) key.toCFString(),
(__bridge id) kSecValueData: (__bridge NSData *) data.toCFData(),
};
status = SecItemAdd((__bridge CFDictionaryRef) insert, nil);
}
if (status == errSecSuccess) {
q->emitFinished();
} else {
const ErrorDescription error = ErrorDescription::fromStatus(status);
q->emitFinishedWithError(error.code, tr("Could not store data in settings: %1").arg(error.message));
}
}
void DeletePasswordJobPrivate::scheduledStart()
{
const NSDictionary *const query = @{
(__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword,
(__bridge id) kSecAttrService: (__bridge NSString *) service.toCFString(),
(__bridge id) kSecAttrAccount: (__bridge NSString *) key.toCFString(),
};
const OSStatus status = SecItemDelete((__bridge CFDictionaryRef) query);
if (status == errSecSuccess) {
q->emitFinished();
} else {
const ErrorDescription error = ErrorDescription::fromStatus(status);
q->emitFinishedWithError(error.code, Job::tr("Could not remove private key from keystore: %1").arg(error.message));
}
}

View File

@ -1,163 +0,0 @@
/******************************************************************************
* Copyright (C) 2011-2015 Frank Osterfeld <frank.osterfeld@gmail.com> *
* *
* This program is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
* or FITNESS FOR A PARTICULAR PURPOSE. For licensing and distribution *
* details, check the accompanying file 'COPYING'. *
*****************************************************************************/
#include "keychain_p.h"
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#include <QDebug>
using namespace QKeychain;
template <typename T>
struct Releaser {
explicit Releaser( const T& v ) : value( v ) {}
~Releaser() {
CFRelease( value );
}
const T value;
};
static QString strForStatus( OSStatus os ) {
const Releaser<CFStringRef> str( SecCopyErrorMessageString( os, 0 ) );
const char * const buf = CFStringGetCStringPtr( str.value, kCFStringEncodingUTF8 );
if ( !buf )
return QObject::tr( "OS X Keychain error (OSStatus %1)" ).arg( os );
return QObject::tr( "%1 (OSStatus %2)" )
.arg( QString::fromUtf8( buf, strlen( buf ) ) ).arg( os );
}
static OSStatus readPw( QByteArray* pw,
const QString& service,
const QString& account,
SecKeychainItemRef* ref ) {
Q_ASSERT( pw );
pw->clear();
const QByteArray serviceData = service.toUtf8();
const QByteArray accountData = account.toUtf8();
void* data = 0;
UInt32 len = 0;
const OSStatus ret = SecKeychainFindGenericPassword( NULL, // default keychain
serviceData.size(),
serviceData.constData(),
accountData.size(),
accountData.constData(),
&len,
&data,
ref );
if ( ret == noErr ) {
*pw = QByteArray( reinterpret_cast<const char*>( data ), len );
const OSStatus ret2 = SecKeychainItemFreeContent ( 0, data );
if ( ret2 != noErr )
qWarning() << "Could not free item content: " << strForStatus( ret2 );
}
return ret;
}
void ReadPasswordJobPrivate::scheduledStart()
{
QString errorString;
Error error = NoError;
const OSStatus ret = readPw( &data, q->service(), q->key(), 0 );
switch ( ret ) {
case noErr:
break;
case errSecItemNotFound:
errorString = tr("Password not found");
error = EntryNotFound;
break;
default:
errorString = strForStatus( ret );
error = OtherError;
break;
}
q->emitFinishedWithError( error, errorString );
}
static QKeychain::Error deleteEntryImpl( const QString& service, const QString& account, QString* err ) {
SecKeychainItemRef ref;
QByteArray pw;
const OSStatus ret1 = readPw( &pw, service, account, &ref );
if ( ret1 == errSecItemNotFound )
return NoError; // No item stored, we're done
if ( ret1 != noErr ) {
*err = strForStatus( ret1 );
//TODO map error code, set errstr
return OtherError;
}
const Releaser<SecKeychainItemRef> releaser( ref );
const OSStatus ret2 = SecKeychainItemDelete( ref );
if ( ret2 == noErr )
return NoError;
//TODO map error code
*err = strForStatus( ret2 );
return CouldNotDeleteEntry;
}
static QKeychain::Error writeEntryImpl( const QString& service,
const QString& account,
const QByteArray& data,
QString* err ) {
Q_ASSERT( err );
err->clear();
const QByteArray serviceData = service.toUtf8();
const QByteArray accountData = account.toUtf8();
const OSStatus ret = SecKeychainAddGenericPassword( NULL, //default keychain
serviceData.size(),
serviceData.constData(),
accountData.size(),
accountData.constData(),
data.size(),
data.constData(),
NULL //item reference
);
if ( ret != noErr ) {
switch ( ret ) {
case errSecDuplicateItem:
{
Error derr = deleteEntryImpl( service, account, err );
if ( derr != NoError )
return CouldNotDeleteEntry;
else
return writeEntryImpl( service, account, data, err );
}
default:
*err = strForStatus( ret );
return OtherError;
}
}
return NoError;
}
void WritePasswordJobPrivate::scheduledStart()
{
QString errorString;
Error error = NoError;
error = writeEntryImpl( q->service(), key, data, &errorString );
q->emitFinishedWithError( error, errorString );
}
void DeletePasswordJobPrivate::scheduledStart()
{
QString errorString;
Error error = NoError;
const Error derr = deleteEntryImpl( q->service(), key, &errorString );
if ( derr != NoError )
error = CouldNotDeleteEntry;
q->emitFinishedWithError( error, errorString );
}

View File

@ -15,7 +15,7 @@
#include <QSettings>
#include <QQueue>
#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID)
#if defined(KEYCHAIN_DBUS)
#include <QDBusPendingCallWatcher>
@ -49,7 +49,7 @@ public:
Mode mode;
QByteArray data;
#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID)
#if defined(KEYCHAIN_DBUS)
org::kde::KWallet* iface;
int walletHandle;
@ -91,20 +91,20 @@ class ReadPasswordJobPrivate : public JobPrivate {
Q_OBJECT
public:
explicit ReadPasswordJobPrivate( const QString &service_, ReadPasswordJob* qq );
void scheduledStart();
void scheduledStart() override;
#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID)
void fallbackOnError(const QDBusError& err);
#if defined(KEYCHAIN_DBUS)
void fallbackOnError(const QDBusError& err) override;
private Q_SLOTS:
void kwalletOpenFinished( QDBusPendingCallWatcher* watcher );
void kwalletOpenFinished( QDBusPendingCallWatcher* watcher ) override;
void kwalletEntryTypeFinished( QDBusPendingCallWatcher* watcher );
void kwalletFinished( QDBusPendingCallWatcher* watcher );
void kwalletFinished( QDBusPendingCallWatcher* watcher ) override;
#else //moc's too dumb to respect above macros, so just define empty slot implementations
private Q_SLOTS:
void kwalletOpenFinished( QDBusPendingCallWatcher* ) {}
void kwalletOpenFinished( QDBusPendingCallWatcher* ) override {}
void kwalletEntryTypeFinished( QDBusPendingCallWatcher* ) {}
void kwalletFinished( QDBusPendingCallWatcher* ) {}
void kwalletFinished( QDBusPendingCallWatcher* ) override {}
#endif
friend class ReadPasswordJob;
@ -114,10 +114,10 @@ class WritePasswordJobPrivate : public JobPrivate {
Q_OBJECT
public:
explicit WritePasswordJobPrivate( const QString &service_, WritePasswordJob* qq );
void scheduledStart();
void scheduledStart() override;
#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID)
void fallbackOnError(const QDBusError& err);
#if defined(KEYCHAIN_DBUS)
void fallbackOnError(const QDBusError& err) override;
#endif
friend class WritePasswordJob;
@ -128,10 +128,10 @@ class DeletePasswordJobPrivate : public JobPrivate {
public:
explicit DeletePasswordJobPrivate( const QString &service_, DeletePasswordJob* qq );
void scheduledStart();
void scheduledStart() override;
#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID)
void fallbackOnError(const QDBusError& err);
#if defined(KEYCHAIN_DBUS)
void fallbackOnError(const QDBusError& err) override;
#endif
protected:

View File

@ -19,24 +19,35 @@ enum KeyringBackend {
Backend_LibSecretKeyring,
Backend_GnomeKeyring,
Backend_Kwallet4,
Backend_Kwallet5
Backend_Kwallet5,
Backend_Kwallet6,
};
enum DesktopEnvironment {
DesktopEnv_Gnome,
DesktopEnv_Kde4,
DesktopEnv_Plasma5,
DesktopEnv_Plasma6,
DesktopEnv_Unity,
DesktopEnv_Xfce,
DesktopEnv_Other
};
static constexpr const char KWALLET6_DBUS_IFACE[] = "org.kde.kwalletd6";
static constexpr const char KWALLET6_DBUS_PATH[] = "/modules/kwalletd6";
static constexpr const char KWALLET5_DBUS_IFACE[] = "org.kde.kwalletd5";
static constexpr const char KWALLET5_DBUS_PATH[] = "/modules/kwalletd5";
static constexpr const char KWALLET4_DBUS_IFACE[] = "org.kde.kwalletd";
static constexpr const char KWALLET4_DBUS_PATH[] = "/modules/kwalletd";
// the following detection algorithm is derived from chromium,
// licensed under BSD, see base/nix/xdg_util.cc
static DesktopEnvironment getKdeVersion() {
QByteArray value = qgetenv("KDE_SESSION_VERSION");
if ( value == "5" ) {
if ( value == "6" ) {
return DesktopEnv_Plasma6;
} else if ( value == "5" ) {
return DesktopEnv_Plasma5;
} else if (value == "4" ) {
return DesktopEnv_Kde4;
@ -78,14 +89,14 @@ static DesktopEnvironment detectDesktopEnvironment() {
return DesktopEnv_Other;
}
static bool isKwallet5Available()
static bool isKwalletAvailable(const char *dbusIface, const char *dbusPath)
{
if (!QDBusConnection::sessionBus().isConnected())
return false;
org::kde::KWallet iface(
QLatin1String("org.kde.kwalletd5"),
QLatin1String("/modules/kwalletd5"),
QLatin1String(dbusIface),
QLatin1String(dbusPath),
QDBusConnection::sessionBus());
// At this point iface.isValid() can return false even though the
@ -93,7 +104,7 @@ static bool isKwallet5Available()
// a wallet can be opened.
iface.setTimeout(500);
QDBusMessage reply = iface.call(QStringLiteral("networkWallet"));
QDBusMessage reply = iface.call(QLatin1String("networkWallet"));
return reply.type() == QDBusMessage::ReplyMessage;
}
@ -118,7 +129,7 @@ static KeyringBackend detectKeyringBackend()
return Backend_Kwallet4;
case DesktopEnv_Plasma5:
if (isKwallet5Available()) {
if (isKwalletAvailable(KWALLET5_DBUS_IFACE, KWALLET5_DBUS_PATH)) {
return Backend_Kwallet5;
}
if (LibSecretKeyring::isAvailable()) {
@ -130,6 +141,19 @@ static KeyringBackend detectKeyringBackend()
// During startup the keychain backend might just not have started yet
return Backend_Kwallet5;
case DesktopEnv_Plasma6:
if (isKwalletAvailable(KWALLET6_DBUS_IFACE, KWALLET6_DBUS_PATH)) {
return Backend_Kwallet6;
}
if (LibSecretKeyring::isAvailable()) {
return Backend_LibSecretKeyring;
}
if (GnomeKeyring::isAvailable()) {
return Backend_GnomeKeyring;
}
// During startup the keychain backend might just not have started yet
return Backend_Kwallet6;
case DesktopEnv_Gnome:
case DesktopEnv_Unity:
case DesktopEnv_Xfce:
@ -141,7 +165,10 @@ static KeyringBackend detectKeyringBackend()
if (GnomeKeyring::isAvailable()) {
return Backend_GnomeKeyring;
}
if (isKwallet5Available()) {
if (isKwalletAvailable(KWALLET6_DBUS_IFACE, KWALLET6_DBUS_PATH)) {
return Backend_Kwallet6;
}
if (isKwalletAvailable(KWALLET5_DBUS_IFACE, KWALLET5_DBUS_PATH)) {
return Backend_Kwallet5;
}
// During startup the keychain backend might just not have started yet
@ -198,10 +225,13 @@ void ReadPasswordJobPrivate::scheduledStart() {
break;
case Backend_Kwallet4:
kwalletReadPasswordScheduledStartImpl("org.kde.kwalletd", "/modules/kwalletd", this);
kwalletReadPasswordScheduledStartImpl(KWALLET4_DBUS_IFACE, KWALLET4_DBUS_PATH, this);
break;
case Backend_Kwallet5:
kwalletReadPasswordScheduledStartImpl("org.kde.kwalletd5", "/modules/kwalletd5", this);
kwalletReadPasswordScheduledStartImpl(KWALLET5_DBUS_IFACE, KWALLET5_DBUS_PATH, this);
break;
case Backend_Kwallet6:
kwalletReadPasswordScheduledStartImpl(KWALLET6_DBUS_IFACE, KWALLET6_DBUS_PATH, this);
break;
}
}
@ -454,10 +484,13 @@ void WritePasswordJobPrivate::scheduledStart() {
break;
case Backend_Kwallet4:
kwalletWritePasswordScheduledStart("org.kde.kwalletd", "/modules/kwalletd", this);
kwalletWritePasswordScheduledStart(KWALLET4_DBUS_IFACE, KWALLET4_DBUS_PATH, this);
break;
case Backend_Kwallet5:
kwalletWritePasswordScheduledStart("org.kde.kwalletd5", "/modules/kwalletd5", this);
kwalletWritePasswordScheduledStart(KWALLET5_DBUS_IFACE, KWALLET5_DBUS_PATH, this);
break;
case Backend_Kwallet6:
kwalletWritePasswordScheduledStart(KWALLET6_DBUS_IFACE, KWALLET6_DBUS_PATH, this);
break;
}
}
@ -512,12 +545,16 @@ void JobPrivate::kwalletOpenFinished( QDBusPendingCallWatcher* watcher ) {
QDBusPendingReply<int> nextReply;
if ( mode == Text )
nextReply = iface->writePassword( handle, q->service(), key, QString::fromUtf8(data), q->service() );
else if ( mode == Binary )
nextReply = iface->writeEntry( handle, q->service(), key, data, q->service() );
else
if ( !data.isNull() ) {
if ( mode == Text ) {
nextReply = iface->writePassword( handle, q->service(), key, QString::fromUtf8(data), q->service() );
} else {
Q_ASSERT( mode == Binary );
nextReply = iface->writeEntry( handle, q->service(), key, data, q->service() );
}
} else {
nextReply = iface->removeEntry( handle, q->service(), key, q->service() );
}
QDBusPendingCallWatcher* nextWatcher = new QDBusPendingCallWatcher( nextReply, this );
connect( nextWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletFinished(QDBusPendingCallWatcher*)) );
@ -558,10 +595,13 @@ void DeletePasswordJobPrivate::scheduledStart() {
break;
case Backend_Kwallet4:
kwalletWritePasswordScheduledStart("org.kde.kwalletd", "/modules/kwalletd", this);
kwalletWritePasswordScheduledStart(KWALLET4_DBUS_IFACE, KWALLET4_DBUS_PATH, this);
break;
case Backend_Kwallet5:
kwalletWritePasswordScheduledStart("org.kde.kwalletd5", "/modules/kwalletd5", this);
kwalletWritePasswordScheduledStart(KWALLET5_DBUS_IFACE, KWALLET5_DBUS_PATH, this);
break;
case Backend_Kwallet6:
kwalletWritePasswordScheduledStart(KWALLET6_DBUS_IFACE, KWALLET6_DBUS_PATH, this);
break;
}
}
@ -584,3 +624,10 @@ void DeletePasswordJobPrivate::fallbackOnError(const QDBusError &err) {
q->emitFinished();
}
bool QKeychain::isAvailable()
{
return LibSecretKeyring::isAvailable() || GnomeKeyring::isAvailable()
|| isKwalletAvailable(KWALLET6_DBUS_IFACE, KWALLET6_DBUS_PATH)
|| isKwalletAvailable(KWALLET5_DBUS_IFACE, KWALLET5_DBUS_PATH);
}

View File

@ -24,20 +24,20 @@ void ReadPasswordJobPrivate::scheduledStart() {
PCREDENTIALW cred;
if (!CredReadW(name, CRED_TYPE_GENERIC, 0, &cred)) {
Error error;
Error err;
QString msg;
switch(GetLastError()) {
case ERROR_NOT_FOUND:
error = EntryNotFound;
err = EntryNotFound;
msg = tr("Password entry not found");
break;
default:
error = OtherError;
err = OtherError;
msg = tr("Could not decrypt data");
break;
}
q->emitFinishedWithError( error, msg );
q->emitFinishedWithError( err, msg );
return;
}
@ -96,20 +96,20 @@ void DeletePasswordJobPrivate::scheduledStart() {
LPCWSTR name = (LPCWSTR)key.utf16();
if (!CredDeleteW(name, CRED_TYPE_GENERIC, 0)) {
Error error;
Error err;
QString msg;
switch(GetLastError()) {
case ERROR_NOT_FOUND:
error = EntryNotFound;
err = EntryNotFound;
msg = tr("Password entry not found");
break;
default:
error = OtherError;
err = OtherError;
msg = tr("Could not decrypt data");
break;
}
q->emitFinishedWithError( error, msg );
q->emitFinishedWithError( err, msg );
} else {
q->emitFinished();
}
@ -129,10 +129,10 @@ void ReadPasswordJobPrivate::scheduledStart() {
blob_in.cbData = encrypted.size();
const BOOL ret = CryptUnprotectData( &blob_in,
NULL,
NULL,
NULL,
NULL,
nullptr,
nullptr,
nullptr,
nullptr,
0,
&blob_out );
if ( !ret ) {
@ -153,9 +153,9 @@ void WritePasswordJobPrivate::scheduledStart() {
blob_in.cbData = data.size();
const BOOL res = CryptProtectData( &blob_in,
L"QKeychain-encrypted data",
NULL,
NULL,
NULL,
nullptr,
nullptr,
nullptr,
0,
&blob_out );
if ( !res ) {
@ -186,3 +186,8 @@ void DeletePasswordJobPrivate::scheduledStart() {
}
}
#endif
bool QKeychain::isAvailable()
{
return true;
}

View File

@ -5,7 +5,6 @@
#include "libsecret_p.h"
#include <QLibrary>
#include <QDebug>
#if defined(HAVE_LIBSECRET)
const SecretSchema* qtkeychainSchema(void) {
@ -54,14 +53,14 @@ typedef gboolean (*secret_password_clear_finish_t) (GAsyncResult *result,
typedef void (*secret_password_free_t) (gchar *password);
typedef GQuark (*secret_error_get_quark_t) (void) G_GNUC_CONST;
static secret_password_lookup_t secret_password_lookup_fn = NULL;
static secret_password_lookup_finish_t secret_password_lookup_finish_fn = NULL;
static secret_password_store_t secret_password_store_fn = NULL;
static secret_password_store_finish_t secret_password_store_finish_fn = NULL;
static secret_password_clear_t secret_password_clear_fn = NULL;
static secret_password_clear_finish_t secret_password_clear_finish_fn = NULL;
static secret_password_free_t secret_password_free_fn = NULL;
static secret_error_get_quark_t secret_error_get_quark_fn = NULL;
static secret_password_lookup_t secret_password_lookup_fn = nullptr;
static secret_password_lookup_finish_t secret_password_lookup_finish_fn = nullptr;
static secret_password_store_t secret_password_store_fn = nullptr;
static secret_password_store_finish_t secret_password_store_finish_fn = nullptr;
static secret_password_clear_t secret_password_clear_fn = nullptr;
static secret_password_clear_finish_t secret_password_clear_finish_fn = nullptr;
static secret_password_free_t secret_password_free_fn = nullptr;
static secret_error_get_quark_t secret_error_get_quark_fn = nullptr;
static QKeychain::Error gerrorToCode(const GError *error) {
if (error->domain != secret_error_get_quark_fn()) {
@ -83,7 +82,7 @@ on_password_lookup (GObject *source,
GAsyncResult *result,
gpointer inst)
{
GError *error = NULL;
GError *error = nullptr;
callbackArg *arg = (callbackArg*)inst;
gchar *password = secret_password_lookup_finish_fn (result, &error);
@ -95,7 +94,7 @@ on_password_lookup (GObject *source,
arg->self->q->emitFinishedWithError( code, QString::fromUtf8(error->message) );
} else {
if (password != NULL) {
if (password) {
QByteArray raw = QByteArray(password);
switch(arg->self->mode) {
case QKeychain::JobPrivate::Binary:
@ -109,12 +108,12 @@ on_password_lookup (GObject *source,
arg->self->q->emitFinished();
} else if (arg->self->mode == QKeychain::JobPrivate::Text) {
arg->self->mode = QKeychain::JobPrivate::Binary;
secret_password_lookup_fn (qtkeychainSchema(), NULL,
secret_password_lookup_fn (qtkeychainSchema(), nullptr,
on_password_lookup, arg,
"user", arg->user.toUtf8().constData(),
"server", arg->server.toUtf8().constData(),
"type", "base64",
NULL);
nullptr);
return;
} else {
arg->self->q->emitFinishedWithError( QKeychain::EntryNotFound, QObject::tr("Entry not found") );
@ -139,7 +138,7 @@ on_password_stored (GObject *source,
GAsyncResult *result,
gpointer inst)
{
GError *error = NULL;
GError *error = nullptr;
QKeychain::JobPrivate *self = (QKeychain::JobPrivate*)inst;
Q_UNUSED(source);
@ -147,14 +146,14 @@ on_password_stored (GObject *source,
secret_password_store_finish_fn (result, &error);
if (self) {
if (error != NULL) {
if (error) {
self->q->emitFinishedWithError( gerrorToCode(error),
QString::fromUtf8(error->message) );
} else {
self->q->emitFinished();
}
}
if (error != NULL) {
if (error) {
g_error_free (error);
}
}
@ -164,7 +163,7 @@ on_password_cleared (GObject *source,
GAsyncResult *result,
gpointer inst)
{
GError *error = NULL;
GError *error = nullptr;
QKeychain::JobPrivate *self = (QKeychain::JobPrivate*)inst;
gboolean removed = secret_password_clear_finish_fn (result, &error);
@ -178,7 +177,7 @@ on_password_cleared (GObject *source,
self->q->emitFinished();
}
}
if (error != NULL) {
if (error) {
g_error_free (error);
}
}
@ -198,21 +197,21 @@ bool LibSecretKeyring::isAvailable() {
const LibSecretKeyring& keyring = instance();
if (!keyring.isLoaded())
return false;
if (secret_password_lookup_fn == NULL)
if (secret_password_lookup_fn == nullptr)
return false;
if (secret_password_lookup_finish_fn == NULL)
if (secret_password_lookup_finish_fn == nullptr)
return false;
if (secret_password_store_fn == NULL)
if (secret_password_store_fn == nullptr)
return false;
if (secret_password_store_finish_fn == NULL)
if (secret_password_store_finish_fn == nullptr)
return false;
if (secret_password_clear_fn == NULL)
if (secret_password_clear_fn == nullptr)
return false;
if (secret_password_clear_finish_fn == NULL)
if (secret_password_clear_finish_fn == nullptr)
return false;
if (secret_password_free_fn == NULL)
if (secret_password_free_fn == nullptr)
return false;
if (secret_error_get_quark_fn == NULL)
if (secret_error_get_quark_fn == nullptr)
return false;
return true;
#else
@ -236,14 +235,16 @@ bool LibSecretKeyring::findPassword(const QString &user, const QString &server,
arg->user = user;
arg->server = server;
qDebug() << Q_FUNC_INFO;
secret_password_lookup_fn (qtkeychainSchema(), NULL, on_password_lookup, arg,
secret_password_lookup_fn (qtkeychainSchema(), nullptr, on_password_lookup, arg,
"user", user.toUtf8().constData(),
"server", server.toUtf8().constData(),
"type", "plaintext",
NULL);
nullptr);
return true;
#else
Q_UNUSED(user)
Q_UNUSED(server)
Q_UNUSED(self)
return false;
#endif
}
@ -271,16 +272,21 @@ bool LibSecretKeyring::writePassword(const QString &display_name,
break;
}
qDebug() << Q_FUNC_INFO;
secret_password_store_fn (qtkeychainSchema(), SECRET_COLLECTION_DEFAULT,
display_name.toUtf8().constData(),
pwd.constData(), NULL, on_password_stored, self,
pwd.constData(), nullptr, on_password_stored, self,
"user", user.toUtf8().constData(),
"server", server.toUtf8().constData(),
"type", type.toUtf8().constData(),
NULL);
nullptr);
return true;
#else
Q_UNUSED(display_name)
Q_UNUSED(user)
Q_UNUSED(server)
Q_UNUSED(mode)
Q_UNUSED(password)
Q_UNUSED(self)
return false;
#endif
}
@ -293,19 +299,21 @@ bool LibSecretKeyring::deletePassword(const QString &key, const QString &service
return false;
}
qDebug() << Q_FUNC_INFO;
secret_password_clear_fn (qtkeychainSchema(), NULL, on_password_cleared, self,
secret_password_clear_fn (qtkeychainSchema(), nullptr, on_password_cleared, self,
"user", key.toUtf8().constData(),
"server", service.toUtf8().constData(),
NULL);
nullptr);
return true;
#else
Q_UNUSED(key)
Q_UNUSED(service)
Q_UNUSED(self)
return false;
#endif
}
LibSecretKeyring::LibSecretKeyring()
: QLibrary(QStringLiteral("secret-1"), 0)
: QLibrary(QLatin1String("secret-1"), 0)
{
#ifdef HAVE_LIBSECRET
if (load()) {

View File

@ -156,7 +156,7 @@
</method>
<method name="readEntryList">
<arg type="a{sv}" direction="out"/>
<annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
<arg name="handle" type="i" direction="in"/>
<arg name="folder" type="s" direction="in"/>
<arg name="key" type="s" direction="in"/>
@ -164,7 +164,7 @@
</method>
<method name="readMapList">
<arg type="a{sv}" direction="out"/>
<annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
<arg name="handle" type="i" direction="in"/>
<arg name="folder" type="s" direction="in"/>
<arg name="key" type="s" direction="in"/>
@ -172,7 +172,7 @@
</method>
<method name="readPasswordList">
<arg type="a{sv}" direction="out"/>
<annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
<arg name="handle" type="i" direction="in"/>
<arg name="folder" type="s" direction="in"/>
<arg name="key" type="s" direction="in"/>

View File

@ -2,23 +2,27 @@
# This file is provided as is without any warranty.
# It can break at anytime or be removed without notice.
QT5KEYCHAIN_PWD = $$PWD
lessThan(QT_MAJOR_VERSION, 5) {
error("qtkeychain requires Qt 5 or later")
}
QTKEYCHAIN_PWD = $$PWD
CONFIG += depend_includepath
DEFINES += QTKEYCHAIN_NO_EXPORT
INCLUDEPATH += \
$$PWD/.. \
$$QT5KEYCHAIN_PWD
$$QTKEYCHAIN_PWD
HEADERS += \
$$QT5KEYCHAIN_PWD/keychain_p.h \
$$QT5KEYCHAIN_PWD/keychain.h
$$QTKEYCHAIN_PWD/keychain_p.h \
$$QTKEYCHAIN_PWD/keychain.h
SOURCES += \
$$QT5KEYCHAIN_PWD/keychain.cpp
$$QTKEYCHAIN_PWD/keychain.cpp
unix:!macx:!ios {
unix:!android:!macx:!ios {
# Remove the following LIBSECRET_SUPPORT line
# to build without libsecret support.
DEFINES += LIBSECRET_SUPPORT
@ -37,19 +41,34 @@ unix:!macx:!ios {
}
# Generate D-Bus interface:
DEFINES += KEYCHAIN_DBUS
QT += dbus
kwallet_interface.files = $$PWD/org.kde.KWallet.xml
DBUS_INTERFACES += kwallet_interface
HEADERS += \
$$QT5KEYCHAIN_PWD/gnomekeyring_p.h \
$$QT5KEYCHAIN_PWD/plaintextstore_p.h \
$$QT5KEYCHAIN_PWD/libsecret_p.h
$$QTKEYCHAIN_PWD/gnomekeyring_p.h \
$$QTKEYCHAIN_PWD/plaintextstore_p.h \
$$QTKEYCHAIN_PWD/libsecret_p.h
SOURCES += \
$$QT5KEYCHAIN_PWD/keychain_unix.cpp \
$$QT5KEYCHAIN_PWD/plaintextstore.cpp \
$$QT5KEYCHAIN_PWD/gnomekeyring.cpp \
$$QT5KEYCHAIN_PWD/libsecret.cpp
$$QTKEYCHAIN_PWD/keychain_unix.cpp \
$$QTKEYCHAIN_PWD/plaintextstore.cpp \
$$QTKEYCHAIN_PWD/gnomekeyring.cpp \
$$QTKEYCHAIN_PWD/libsecret.cpp
}
android {
lessThan(QT_MAJOR_VERSION, 6) {
QT += androidextras
}
HEADERS += \
$$QTKEYCHAIN_PWD/androidkeystore_p.h \
$$QTKEYCHAIN_PWD/plaintextstore_p.h
SOURCES += \
$$QTKEYCHAIN_PWD/androidkeystore.cpp \
$$QTKEYCHAIN_PWD/keychain_android.cpp \
$$QTKEYCHAIN_PWD/plaintextstore.cpp
}
win32 {
@ -59,25 +78,20 @@ win32 {
DEFINES += USE_CREDENTIAL_STORE
contains(DEFINES, USE_CREDENTIAL_STORE) {
!build_pass:message("Windows Credential Store support: on")
LIBS += -lAdvapi32
LIBS += -ladvapi32
} else {
!build_pass:message("Windows Credential Store support: off")
LIBS += -lCrypt32
HEADERS += $$QT5KEYCHAIN_PWD/plaintextstore_p.h
SOURCES += $$QT5KEYCHAIN_PWD/plaintextstore.cpp
LIBS += -lcrypt32
HEADERS += $$QTKEYCHAIN_PWD/plaintextstore_p.h
SOURCES += $$QTKEYCHAIN_PWD/plaintextstore.cpp
}
HEADERS += $$QT5KEYCHAIN_PWD/libsecret_p.h
HEADERS += $$QTKEYCHAIN_PWD/libsecret_p.h
SOURCES += \
$$QT5KEYCHAIN_PWD/keychain_win.cpp \
$$QT5KEYCHAIN_PWD/libsecret.cpp
$$QTKEYCHAIN_PWD/keychain_win.cpp \
$$QTKEYCHAIN_PWD/libsecret.cpp
}
macx:!ios {
LIBS += "-framework Security" "-framework Foundation"
SOURCES += $$QT5KEYCHAIN_PWD/keychain_mac.cpp
}
ios {
LIBS += "-framework Security" "-framework Foundation"
OBJECTIVE_SOURCES += $$QT5KEYCHAIN_PWD/keychain_ios.mm
macx|ios {
LIBS += -framework Security -framework Foundation
OBJECTIVE_SOURCES += $$QTKEYCHAIN_PWD/keychain_apple.mm
}

View File

@ -6,7 +6,14 @@
* or FITNESS FOR A PARTICULAR PURPOSE. For licensing and distribution *
* details, check the accompanying file 'COPYING'. *
*****************************************************************************/
#include <QtGlobal>
#ifdef Q_OS_DARWIN
#include <QGuiApplication>
#else
#include <QCoreApplication>
#endif
#include <QStringList>
#include "keychain.h"
@ -22,7 +29,14 @@ static int printUsage() {
}
int main( int argc, char** argv ) {
#ifdef Q_OS_DARWIN
// Since we use NSNotificationCenter under the hood in keychain_apple,
// we use QGuiApplication to automatically configure the platform
// integration stuff done in this class and not in QCoreApplication
QGuiApplication app( argc, argv );
#else
QCoreApplication app( argc, argv );
#endif
const QStringList args = app.arguments();
if ( args.count() < 2 )
return printUsage();

View File

@ -1,239 +1,175 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de_DE">
<context>
<name>QKeychain::DeletePasswordJobPrivate</name>
<message>
<location filename="../keychain_win.cpp" line="104"/>
<source>Password entry not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="108"/>
<source>Could not decrypt data</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="548"/>
<location filename="../keychain_unix.cpp" line="556"/>
<source>Unknown error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="574"/>
<source>Could not open wallet: %1; %2</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QKeychain::JobPrivate</name>
<message>
<location filename="../keychain_unix.cpp" line="265"/>
<source>Unknown error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="509"/>
<source>Access to keychain denied</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QKeychain::PlainTextStore</name>
<message>
<location filename="../plaintextstore.cpp" line="65"/>
<source>Could not store data in settings: access error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../plaintextstore.cpp" line="67"/>
<source>Could not store data in settings: format error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../plaintextstore.cpp" line="85"/>
<source>Could not delete data from settings: access error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../plaintextstore.cpp" line="87"/>
<source>Could not delete data from settings: format error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../plaintextstore.cpp" line="104"/>
<source>Entry not found</source>
<translation type="unfinished"></translation>
</message>
</context>
<TS version="2.0" language="de_DE">
<context>
<name>QKeychain::ReadPasswordJobPrivate</name>
<message>
<location filename="../keychain_win.cpp" line="32"/>
<source>Password entry not found</source>
<translation type="unfinished"></translation>
<location filename="../keychain_unix.cpp" line="119"/>
<source>Unknown error</source>
<translation>Unbekannter Fehler</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="36"/>
<location filename="../keychain_win.cpp" line="139"/>
<source>Could not decrypt data</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="178"/>
<location filename="../keychain_unix.cpp" line="133"/>
<source>D-Bus is not running</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="187"/>
<location filename="../keychain_unix.cpp" line="197"/>
<source>Unknown error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="286"/>
<location filename="../keychain_unix.cpp" line="210"/>
<source>No keychain service available</source>
<translation type="unfinished"></translation>
<translation>Kein Schlüsselbund-Dienst verfügbar</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="288"/>
<location filename="../keychain_unix.cpp" line="212"/>
<source>Could not open wallet: %1; %2</source>
<translation type="unfinished"></translation>
<translation>Konnte Brieftasche nicht öffnen: %1; %2</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="333"/>
<location filename="../keychain_unix.cpp" line="258"/>
<source>Access to keychain denied</source>
<translation type="unfinished"></translation>
<translation>Zugriff auf Schlüsselbund verweigert</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="354"/>
<location filename="../keychain_unix.cpp" line="279"/>
<source>Could not determine data type: %1; %2</source>
<translation type="unfinished"></translation>
<translation>Datentyp kann nicht ermittelt werden: %1: %2</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="363"/>
<source>Entry not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="372"/>
<location filename="../keychain_unix.cpp" line="297"/>
<source>Unsupported entry type &apos;Map&apos;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="375"/>
<location filename="../keychain_unix.cpp" line="300"/>
<source>Unknown kwallet entry type &apos;%1&apos;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_mac.cpp" line="75"/>
<location filename="../keychain_unix.cpp" line="315"/>
<source>Could not read password: %1; %2</source>
<translation>Passwort konnte nicht ausgelesen werden: %1; %2</translation>
</message>
<message>
<location filename="../keychain_mac.cpp" line="76"/>
<source>Password not found</source>
<translation type="unfinished"></translation>
<translation>Passwort nicht gefunden</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="288"/>
<location filename="../keychain_win.cpp" line="27"/>
<source>Entry not found</source>
<translation>Eintrag nicht gefunden</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="44"/>
<source>Could not decrypt data</source>
<translation>Kann Daten nicht entschlüsseln</translation>
</message>
</context>
<context>
<name>QKeychain::WritePasswordJobPrivate</name>
<message>
<location filename="../keychain_win.cpp" line="78"/>
<source>Credential size exceeds maximum size of %1</source>
<translation type="unfinished"></translation>
<location filename="../keychain_unix.cpp" line="336"/>
<location filename="../keychain_unix.cpp" line="344"/>
<source>Unknown error</source>
<translation>Unbekannter Fehler</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="87"/>
<source>Credential key exceeds maximum size of %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="92"/>
<source>Writing credentials failed: Win32 error code %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="162"/>
<source>Encryption failed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="415"/>
<location filename="../keychain_unix.cpp" line="359"/>
<source>D-Bus is not running</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="425"/>
<location filename="../keychain_unix.cpp" line="452"/>
<source>Unknown error</source>
<translation type="unfinished"></translation>
<location filename="../keychain_unix.cpp" line="400"/>
<location filename="../keychain_unix.cpp" line="485"/>
<source>Could not open wallet: %1; %2</source>
<translation>Konnte Brieftasche nicht öffnen: %1; %2</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="468"/>
<source>Could not open wallet: %1; %2</source>
<translation type="unfinished"></translation>
<location filename="../keychain_unix.cpp" line="463"/>
<source>Access to keychain denied</source>
<translation>Zugriff auf Schlüsselbund verweigert</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="64"/>
<source>Could not delete encrypted data from settings: access error</source>
<translation>Kann verschlüsselte Daten nicht aus den Einstellungen entfernen: Zugriffsfehler</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="65"/>
<source>Could not delete encrypted data from settings: format error</source>
<translation>Kann verschlüsselte Daten nicht aus den Einstellungen entfernen: Formatfehler</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="85"/>
<source>Encryption failed</source>
<translation>Verschlüsselung fehlgeschlagen</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="100"/>
<source>Could not store encrypted data in settings: access error</source>
<translation>Kann verschlüsselte Daten nicht in den Einstellungen speichern: Zugriffsfehler</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="101"/>
<source>Could not store encrypted data in settings: format error</source>
<translation>Kann verschlüsselte Daten nicht in den Einstellungen speichern: Formatfehler</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<location filename="../libsecret.cpp" line="120"/>
<source>Entry not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="225"/>
<location filename="../keychain_unix.cpp" line="155"/>
<source>Access to keychain denied</source>
<translation type="unfinished"></translation>
<translation>Zugriff auf Schlüsselbund verweigert</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="227"/>
<location filename="../keychain_unix.cpp" line="157"/>
<source>No keyring daemon</source>
<translation type="unfinished"></translation>
<translation>Kein Schlüsselbund-Dienst </translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="229"/>
<location filename="../keychain_unix.cpp" line="159"/>
<source>Already unlocked</source>
<translation type="unfinished"></translation>
<translation>Bereits entsperrt</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="231"/>
<location filename="../keychain_unix.cpp" line="161"/>
<source>No such keyring</source>
<translation type="unfinished"></translation>
<translation>Kein solcher Schlüsselbund</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="233"/>
<location filename="../keychain_unix.cpp" line="163"/>
<source>Bad arguments</source>
<translation type="unfinished"></translation>
<translation>Ungültige Argumente</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="235"/>
<location filename="../keychain_unix.cpp" line="165"/>
<source>I/O error</source>
<translation type="unfinished"></translation>
<translation>Ein-/Ausgabe-Fehler</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="237"/>
<location filename="../keychain_unix.cpp" line="167"/>
<source>Cancelled</source>
<translation type="unfinished"></translation>
<translation>Abgebrochen</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="239"/>
<location filename="../keychain_unix.cpp" line="169"/>
<source>Keyring already exists</source>
<translation type="unfinished"></translation>
<translation>Schlüsselbund existiert bereits</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="241"/>
<location filename="../keychain_unix.cpp" line="171"/>
<source>No match</source>
<translation type="unfinished"></translation>
<translation>Kein Treffer</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="246"/>
<location filename="../keychain_unix.cpp" line="176"/>
<source>Unknown error</source>
<translation type="unfinished"></translation>
<translation>Unbekannter Fehler</translation>
</message>
<message>
<location filename="../keychain_mac.cpp" line="31"/>
<source>OS X Keychain error (OSStatus %1)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_mac.cpp" line="32"/>
<location filename="../keychain_mac.cpp" line="33"/>
<source>%1 (OSStatus %2)</source>
<translation type="unfinished"></translation>
</message>

View File

@ -0,0 +1,267 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="fr_FR">
<context>
<name>QKeychain::DeletePasswordJobPrivate</name>
<message>
<location filename="../keychain_win.cpp" line="79"/>
<source>Password entry not found</source>
<translation>Mot de passe introuvable</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="83"/>
<source>Could not decrypt data</source>
<translation>Impossible de déchiffrer les données</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="546"/>
<location filename="../keychain_unix.cpp" line="554"/>
<source>Unknown error</source>
<translation>Erreur inconnue</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="572"/>
<source>Could not open wallet: %1; %2</source>
<translation>Impossible d&apos;ouvrir le portefeuille : %1; %2</translation>
</message>
<message>
<location filename="../keychain_haiku.cpp" line="120"/>
<source>Password not found</source>
<translation>Mot de passe introuvable</translation>
</message>
</context>
<context>
<name>QKeychain::JobPrivate</name>
<message>
<location filename="../keychain_unix.cpp" line="263"/>
<source>Unknown error</source>
<translation>Erreur inconnue</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="507"/>
<source>Access to keychain denied</source>
<translation>Accès au trousseau refusé</translation>
</message>
</context>
<context>
<name>QKeychain::PlainTextStore</name>
<message>
<location filename="../plaintextstore.cpp" line="65"/>
<source>Could not store data in settings: access error</source>
<translation>Impossible de stocker les données dans les paramètres : Erreur d&apos;accès</translation>
</message>
<message>
<location filename="../plaintextstore.cpp" line="67"/>
<source>Could not store data in settings: format error</source>
<translation>Impossible de stocker les données dans les paramètres : Erreur de format</translation>
</message>
<message>
<location filename="../plaintextstore.cpp" line="85"/>
<source>Could not delete data from settings: access error</source>
<translation>Impossible de supprimer les données depuis les paramètres : Erreur d&apos;accès</translation>
</message>
<message>
<location filename="../plaintextstore.cpp" line="87"/>
<source>Could not delete data from settings: format error</source>
<translation>Impossible de supprimer les données depuis les paramètres : Erreur de format</translation>
</message>
<message>
<location filename="../plaintextstore.cpp" line="104"/>
<source>Entry not found</source>
<translation>Entrée introuvable</translation>
</message>
</context>
<context>
<name>QKeychain::ReadPasswordJobPrivate</name>
<message>
<location filename="../keychain_unix.cpp" line="185"/>
<location filename="../keychain_unix.cpp" line="195"/>
<source>Unknown error</source>
<translation>Erreur inconnue</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="176"/>
<source>D-Bus is not running</source>
<translation>D-Bus n&apos;est pas en cours d&apos;exécution</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="284"/>
<source>No keychain service available</source>
<translation>Aucun service de trousseau disponible</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="286"/>
<source>Could not open wallet: %1; %2</source>
<translation>Impossible d&apos;ouvrir le trousseau : %1; %2</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="331"/>
<source>Access to keychain denied</source>
<translation>Accès au trousseau refusé</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="352"/>
<source>Could not determine data type: %1; %2</source>
<translation>Impossible de déterminer le type de données : %1: %2</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="370"/>
<source>Unsupported entry type &apos;Map&apos;</source>
<translation>Type d&apos;entrée non supporté &apos;Map&apos;</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="373"/>
<source>Unknown kwallet entry type &apos;%1&apos;</source>
<translation>Type de trousseau inconnu &apos;%1&apos;</translation>
</message>
<message>
<source>Could not read password: %1; %2</source>
<translation type="vanished">Impossible de lire le mot de passe : %1; %2</translation>
</message>
<message>
<location filename="../keychain_mac.cpp" line="75"/>
<location filename="../keychain_haiku.cpp" line="41"/>
<source>Password not found</source>
<translation>Mot de passe introuvable</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="361"/>
<source>Entry not found</source>
<translation>Entrée introuvable</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="32"/>
<source>Password entry not found</source>
<translation>Entrée de mot de passe introuvable</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="36"/>
<location filename="../keychain_win.cpp" line="114"/>
<source>Could not decrypt data</source>
<translation>Impossible de déchiffrer les données</translation>
</message>
</context>
<context>
<name>QKeychain::WritePasswordJobPrivate</name>
<message>
<location filename="../keychain_unix.cpp" line="423"/>
<location filename="../keychain_unix.cpp" line="450"/>
<source>Unknown error</source>
<translation>Erreur inconnue</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="413"/>
<source>D-Bus is not running</source>
<translation>D-Bus n&apos;est pas en cours d&apos;exécution</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="466"/>
<source>Could not open wallet: %1; %2</source>
<translation>Impossible d&apos;ouvrir le trousseau : %1; %2</translation>
</message>
<message>
<source>Access to keychain denied</source>
<translation type="vanished">Accès au trousseau refusé</translation>
</message>
<message>
<source>Could not delete encrypted data from settings: access error</source>
<translation type="vanished">Impossible de supprimer des données chiffrées dans les paramètres : Erreur d&apos;accès</translation>
</message>
<message>
<source>Could not delete encrypted data from settings: format error</source>
<translation type="vanished">Impossible de supprimer des données chiffrées dans les paramètres : Erreur de format</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="64"/>
<location filename="../keychain_win.cpp" line="137"/>
<source>Encryption failed</source>
<translation>Le chiffrement a échoué</translation>
</message>
<message>
<source>Could not store encrypted data in settings: access error</source>
<translation type="vanished">Impossible de stocker des données chiffrées dans les paramètres : Erreur d&apos;accès</translation>
</message>
<message>
<source>Could not store encrypted data in settings: format error</source>
<translation type="vanished">Impossible de stocker des données chiffrées dans les paramètres : Erreur de format</translation>
</message>
<message>
<location filename="../keychain_haiku.cpp" line="88"/>
<source>Password not found</source>
<translation>Mot de passe introuvable</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<location filename="../keychain_unix.cpp" line="223"/>
<source>Access to keychain denied</source>
<translation>Accès au trousseau refusé</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="225"/>
<source>No keyring daemon</source>
<translation>Aucun démon de trousseau</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="227"/>
<source>Already unlocked</source>
<translation>Déjà déverrouillé</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="229"/>
<source>No such keyring</source>
<translation>Aucun trousseau</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="231"/>
<source>Bad arguments</source>
<translation>Mauvais arguments</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="233"/>
<source>I/O error</source>
<translation>Erreur d&apos;E/S</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="235"/>
<source>Cancelled</source>
<translation>Annulé</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="237"/>
<source>Keyring already exists</source>
<translation>Trousseau déjà existant</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="239"/>
<source>No match</source>
<translation>Aucune correspondance</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="244"/>
<source>Unknown error</source>
<translation>Erreur inconnue</translation>
</message>
<message>
<location filename="../keychain_mac.cpp" line="31"/>
<source>OS X Keychain error (OSStatus %1)</source>
<translation>OS X Keychain error (OSStatus %1)</translation>
</message>
<message>
<location filename="../keychain_mac.cpp" line="32"/>
<source>%1 (OSStatus %2)</source>
<translation>%1 (OSStatus %2)</translation>
</message>
<message>
<location filename="../libsecret.cpp" line="120"/>
<source>Entry not found</source>
<translation>Entrée introuvable</translation>
</message>
<message>
<location filename="../keychain_haiku.cpp" line="18"/>
<source>error 0x%1: %2</source>
<translation>Erreur 0x%1 : %2</translation>
</message>
</context>
</TS>

View File

@ -0,0 +1,320 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="nl_NL">
<context>
<name>KeyChainClass</name>
<message>
<location filename="TestAppExample/keychainclass.cpp" line="22"/>
<source>Read key failed: %1</source>
<translation>De sleutel kan niet worden ingelezen: %1</translation>
</message>
<message>
<location filename="TestAppExample/keychainclass.cpp" line="37"/>
<source>Write key failed: %1</source>
<translation>De sleutel kan niet worden weggeschreven: %1</translation>
</message>
<message>
<location filename="TestAppExample/keychainclass.cpp" line="54"/>
<source>Delete key failed: %1</source>
<translation>De sleutel kan niet worden verwijderd: %1</translation>
</message>
</context>
<context>
<name>QKeychain::DeletePasswordJobPrivate</name>
<message>
<location filename="keychain_android.cpp" line="173"/>
<source>Could not open keystore</source>
<translation>De sleutelbos kan niet worden geopend</translation>
</message>
<message>
<location filename="keychain_android.cpp" line="179"/>
<source>Could not remove private key from keystore</source>
<translation>De privésleutel kan niet worden verwijderd uit de sleutelbos</translation>
</message>
<message>
<location filename="keychain_haiku.cpp" line="177"/>
<source>Password not found</source>
<translation>Het wachtwoord is niet gevonden</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="552"/>
<location filename="keychain_unix.cpp" line="560"/>
<source>Unknown error</source>
<translation>Onbekende foutmelding</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="578"/>
<source>Could not open wallet: %1; %2</source>
<translation>De sleutelbos kan niet worden geopend: %1; %2</translation>
</message>
<message>
<location filename="keychain_win.cpp" line="104"/>
<source>Password entry not found</source>
<translation>Het wachtwoord is niet gevonden</translation>
</message>
<message>
<location filename="keychain_win.cpp" line="108"/>
<source>Could not decrypt data</source>
<translation>De gegevens kunnen niet worden ongrendeld</translation>
</message>
</context>
<context>
<name>QKeychain::JobPrivate</name>
<message>
<location filename="keychain_unix.cpp" line="265"/>
<source>Unknown error</source>
<translation>Onbekende foutmelding</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="509"/>
<source>Access to keychain denied</source>
<translation>U heeft geen toegang tot de sleutelbos</translation>
</message>
</context>
<context>
<name>QKeychain::PlainTextStore</name>
<message>
<location filename="plaintextstore.cpp" line="65"/>
<source>Could not store data in settings: access error</source>
<translation>De gegevens kunnen niet worden opgeslagen in de instellingen: toegangsfout</translation>
</message>
<message>
<location filename="plaintextstore.cpp" line="67"/>
<source>Could not store data in settings: format error</source>
<translation>De gegevens kunnen niet worden opgeslagen in de instellingen: opmaakfout</translation>
</message>
<message>
<location filename="plaintextstore.cpp" line="85"/>
<source>Could not delete data from settings: access error</source>
<translation>De gegevens kunnen niet worden verwijderd uit de instellingen: toegangsfout</translation>
</message>
<message>
<location filename="plaintextstore.cpp" line="87"/>
<source>Could not delete data from settings: format error</source>
<translation>De gegevens kunnen niet worden verwijderd uit de instellingen: opmaakfout</translation>
</message>
<message>
<location filename="plaintextstore.cpp" line="104"/>
<source>Entry not found</source>
<translation>Het item is niet gevonden</translation>
</message>
</context>
<context>
<name>QKeychain::ReadPasswordJobPrivate</name>
<message>
<location filename="keychain_android.cpp" line="52"/>
<location filename="keychain_unix.cpp" line="363"/>
<source>Entry not found</source>
<translation>Het item is niet gevonden</translation>
</message>
<message>
<location filename="keychain_android.cpp" line="60"/>
<source>Could not open keystore</source>
<translation>De sleutelbos kan niet worden geopend</translation>
</message>
<message>
<location filename="keychain_android.cpp" line="68"/>
<source>Could not retrieve private key from keystore</source>
<translation>De privésleutel kan niet worden verwijderd uit de sleutelbos</translation>
</message>
<message>
<location filename="keychain_android.cpp" line="75"/>
<source>Could not create decryption cipher</source>
<translation>De ontgrendelcode kan niet worden aangemaakt</translation>
</message>
<message>
<location filename="keychain_haiku.cpp" line="96"/>
<source>Password not found</source>
<translation>Het wachtwoord is niet gevonden</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="178"/>
<source>D-Bus is not running</source>
<translation>D-Bus is niet actief</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="187"/>
<location filename="keychain_unix.cpp" line="197"/>
<source>Unknown error</source>
<translation>Onbekende foutmelding</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="286"/>
<source>No keychain service available</source>
<translation>De sleutelbosdienst is niet beschikbaar</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="288"/>
<source>Could not open wallet: %1; %2</source>
<translation>De sleutelbos kan niet worden geopend: %1; %2</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="333"/>
<source>Access to keychain denied</source>
<translation>U heeft geen toegang tot de sleutelbos</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="354"/>
<source>Could not determine data type: %1; %2</source>
<translation>De gegevensdrager kan niet worden bepaald: %1; %2</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="372"/>
<source>Unsupported entry type &apos;Map&apos;</source>
<translation>Niet-ondersteund itemtype Map</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="375"/>
<source>Unknown kwallet entry type &apos;%1&apos;</source>
<translation>Onbekend KWallet-item van type %1</translation>
</message>
<message>
<location filename="keychain_win.cpp" line="32"/>
<source>Password entry not found</source>
<translation>Het wachtwoord is niet gevonden</translation>
</message>
<message>
<location filename="keychain_win.cpp" line="36"/>
<location filename="keychain_win.cpp" line="139"/>
<source>Could not decrypt data</source>
<translation>De gegevens kunnen niet worden ongrendeld</translation>
</message>
</context>
<context>
<name>QKeychain::WritePasswordJobPrivate</name>
<message>
<location filename="keychain_android.cpp" line="95"/>
<source>Could not open keystore</source>
<translation>De sleutelbos kan niet worden geopend</translation>
</message>
<message>
<location filename="keychain_android.cpp" line="124"/>
<source>Could not create private key generator</source>
<translation>De privésleutelgenerator kan niet worden gestart</translation>
</message>
<message>
<location filename="keychain_android.cpp" line="131"/>
<source>Could not generate new private key</source>
<translation>Er kan geen nieuwe privésleutel worden gegenereerd</translation>
</message>
<message>
<location filename="keychain_android.cpp" line="139"/>
<source>Could not retrieve private key from keystore</source>
<translation>De privésleutel kan niet worden opgehaald uit de sleutelbos</translation>
</message>
<message>
<location filename="keychain_android.cpp" line="147"/>
<source>Could not create encryption cipher</source>
<translation>De vergrendelcode kan niet worden aangemaakt</translation>
</message>
<message>
<location filename="keychain_android.cpp" line="155"/>
<source>Could not encrypt data</source>
<translation>De gegevens kunnen niet worden ontgrendeld</translation>
</message>
<message>
<location filename="keychain_haiku.cpp" line="144"/>
<source>Password not found</source>
<translation>Het wachtwoord is niet gevonden</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="415"/>
<source>D-Bus is not running</source>
<translation>D-Bus is niet actief</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="425"/>
<location filename="keychain_unix.cpp" line="452"/>
<source>Unknown error</source>
<translation>Onbekende foutmelding</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="468"/>
<source>Could not open wallet: %1; %2</source>
<translation>De sleutelbos kan niet worden geopend: %1; %2</translation>
</message>
<message>
<location filename="keychain_win.cpp" line="78"/>
<source>Credential size exceeds maximum size of %1</source>
<translation>De omvang overschrijdt de maximumomvang van %1</translation>
</message>
<message>
<location filename="keychain_win.cpp" line="87"/>
<source>Credential key exceeds maximum size of %1</source>
<translation>De sleutel overschrijdt de maximumomvang van %1</translation>
</message>
<message>
<location filename="keychain_win.cpp" line="92"/>
<source>Writing credentials failed: Win32 error code %1</source>
<translation>De gegevens kunnen niet worden weggeschreven: Win32-foutcode %1</translation>
</message>
<message>
<location filename="keychain_win.cpp" line="162"/>
<source>Encryption failed</source>
<translation>Het ontgrendelen is mislukt</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<location filename="keychain_haiku.cpp" line="72"/>
<source>error 0x%1: %2</source>
<translation>foutmelding 0x%1: %2</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="225"/>
<source>Access to keychain denied</source>
<translation>U heeft geen toegang tot de sleutelbos</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="227"/>
<source>No keyring daemon</source>
<translation>De sleutelbosdienst is niet actief</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="229"/>
<source>Already unlocked</source>
<translation>De sleutelbos is reeds ontgrendeld</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="231"/>
<source>No such keyring</source>
<translation>De sleutelbos bestaat niet</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="233"/>
<source>Bad arguments</source>
<translation>Onjuiste opties</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="235"/>
<source>I/O error</source>
<translation>I/O-fout</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="237"/>
<source>Cancelled</source>
<translation>Geannuleerd</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="239"/>
<source>Keyring already exists</source>
<translation>De sleutelbos bestaat reeds</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="241"/>
<source>No match</source>
<translation>Er zijn geen overeenkomsten</translation>
</message>
<message>
<location filename="keychain_unix.cpp" line="246"/>
<source>Unknown error</source>
<translation>Onbekende foutmelding</translation>
</message>
<message>
<location filename="libsecret.cpp" line="119"/>
<source>Entry not found</source>
<translation>Het item is niet gevonden</translation>
</message>
</context>
</TS>

View File

@ -1,241 +1,178 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="ro_RO">
<context>
<name>QKeychain::DeletePasswordJobPrivate</name>
<message>
<location filename="../keychain_win.cpp" line="104"/>
<source>Password entry not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="108"/>
<source>Could not decrypt data</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="548"/>
<location filename="../keychain_unix.cpp" line="556"/>
<source>Unknown error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="574"/>
<source>Could not open wallet: %1; %2</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QKeychain::JobPrivate</name>
<message>
<location filename="../keychain_unix.cpp" line="265"/>
<source>Unknown error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="509"/>
<source>Access to keychain denied</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QKeychain::PlainTextStore</name>
<message>
<location filename="../plaintextstore.cpp" line="65"/>
<source>Could not store data in settings: access error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../plaintextstore.cpp" line="67"/>
<source>Could not store data in settings: format error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../plaintextstore.cpp" line="85"/>
<source>Could not delete data from settings: access error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../plaintextstore.cpp" line="87"/>
<source>Could not delete data from settings: format error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../plaintextstore.cpp" line="104"/>
<source>Entry not found</source>
<translation type="unfinished"></translation>
</message>
</context>
<TS version="2.0" language="ro_RO">
<context>
<name>QKeychain::ReadPasswordJobPrivate</name>
<message>
<location filename="../keychain_win.cpp" line="32"/>
<source>Password entry not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="36"/>
<location filename="../keychain_win.cpp" line="139"/>
<source>Could not decrypt data</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="178"/>
<source>D-Bus is not running</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="187"/>
<location filename="../keychain_unix.cpp" line="197"/>
<location filename="../keychain_unix.cpp" line="119"/>
<source>Unknown error</source>
<translation type="unfinished"></translation>
<translation>Eroare necunoscută</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="286"/>
<location filename="../keychain_unix.cpp" line="133"/>
<source>D-Bus is not running</source>
<translation>D-Bus nu rulează</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="210"/>
<source>No keychain service available</source>
<translation type="unfinished"></translation>
<translatorcomment>Nu există niciun serviciu de chei disponibil</translatorcomment>
<translation>Kein Schlüsselbund-Dienst verfügbar</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="212"/>
<source>Could not open wallet: %1; %2</source>
<translation>Nu se poate deschide portofelul: %1; %2</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="258"/>
<source>Access to keychain denied</source>
<translation>Acces interzis la serviciul de chei</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="279"/>
<source>Could not determine data type: %1; %2</source>
<translation>Nu se poate stabili tipul de date: %1: %2</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="297"/>
<source>Unsupported entry type &apos;Map&apos;</source>
<translation>Tip de înregistrare nesuportat &apos;Map&apos;</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="300"/>
<source>Unknown kwallet entry type &apos;%1&apos;</source>
<translation>Tip de înregistrare kwallet necunoscut &apos;%1&apos;</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="315"/>
<source>Could not read password: %1; %2</source>
<translation>Nu se poate citi parola: %1; %2</translation>
</message>
<message>
<location filename="../keychain_mac.cpp" line="76"/>
<source>Password not found</source>
<translation>Parola nu a fost găsită</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="288"/>
<source>Could not open wallet: %1; %2</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="333"/>
<source>Access to keychain denied</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="354"/>
<source>Could not determine data type: %1; %2</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="363"/>
<location filename="../keychain_win.cpp" line="27"/>
<source>Entry not found</source>
<translation type="unfinished"></translation>
<translation>Înregistrarea nu a fost găsită</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="372"/>
<source>Unsupported entry type &apos;Map&apos;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="375"/>
<source>Unknown kwallet entry type &apos;%1&apos;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_mac.cpp" line="75"/>
<source>Password not found</source>
<translation type="unfinished"></translation>
<location filename="../keychain_win.cpp" line="44"/>
<source>Could not decrypt data</source>
<translation>Nu se poate decripta data</translation>
</message>
</context>
<context>
<name>QKeychain::WritePasswordJobPrivate</name>
<message>
<location filename="../keychain_win.cpp" line="78"/>
<source>Credential size exceeds maximum size of %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="87"/>
<source>Credential key exceeds maximum size of %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="92"/>
<source>Writing credentials failed: Win32 error code %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="162"/>
<source>Encryption failed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="415"/>
<source>D-Bus is not running</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="425"/>
<location filename="../keychain_unix.cpp" line="452"/>
<location filename="../keychain_unix.cpp" line="336"/>
<location filename="../keychain_unix.cpp" line="344"/>
<source>Unknown error</source>
<translation type="unfinished"></translation>
<translation>Eroare necunoscută</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="468"/>
<location filename="../keychain_unix.cpp" line="359"/>
<source>D-Bus is not running</source>
<translation>D-Bus nu rulează</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="400"/>
<location filename="../keychain_unix.cpp" line="485"/>
<source>Could not open wallet: %1; %2</source>
<translation type="unfinished"></translation>
<translation>Nu se poate deschide portofelul: %1; %2</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="463"/>
<source>Access to keychain denied</source>
<translation>Acces interzis la serviciul de chei</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="64"/>
<source>Could not delete encrypted data from settings: access error</source>
<translation>Nu se pot șterge datele criptate din setări: eroare de acces</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="65"/>
<source>Could not delete encrypted data from settings: format error</source>
<translation>Nu se pot șterge datele criptate din setări: eroare de format</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="85"/>
<source>Encryption failed</source>
<translation>Criptarea a eșuat</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="100"/>
<source>Could not store encrypted data in settings: access error</source>
<translation>Nu se pot stoca datele criptate în setări: eroare de acces</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="101"/>
<source>Could not store encrypted data in settings: format error</source>
<translation>Nu se pot stoca datele criptate în setări: eroare de format</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<location filename="../libsecret.cpp" line="120"/>
<source>Entry not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="225"/>
<location filename="../keychain_unix.cpp" line="155"/>
<source>Access to keychain denied</source>
<translation type="unfinished"></translation>
<translation>Acces interzis la serviciul de chei</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="227"/>
<location filename="../keychain_unix.cpp" line="157"/>
<source>No keyring daemon</source>
<translation type="unfinished"></translation>
<translation>Niciun demon pentru inelul de chei</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="229"/>
<location filename="../keychain_unix.cpp" line="159"/>
<source>Already unlocked</source>
<translation type="unfinished"></translation>
<translation>Deja deblocat</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="231"/>
<location filename="../keychain_unix.cpp" line="161"/>
<source>No such keyring</source>
<translation type="unfinished"></translation>
<translation>Nu există astfel de inel de chei</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="233"/>
<location filename="../keychain_unix.cpp" line="163"/>
<source>Bad arguments</source>
<translation type="unfinished"></translation>
<translation>Argumente greșite</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="235"/>
<location filename="../keychain_unix.cpp" line="165"/>
<source>I/O error</source>
<translation type="unfinished"></translation>
<translation>Eroare de I/E</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="237"/>
<location filename="../keychain_unix.cpp" line="167"/>
<source>Cancelled</source>
<translation type="unfinished"></translation>
<translation>Anulat</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="239"/>
<location filename="../keychain_unix.cpp" line="169"/>
<source>Keyring already exists</source>
<translation type="unfinished"></translation>
<translation>Inelul de chei deja există</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="241"/>
<location filename="../keychain_unix.cpp" line="171"/>
<source>No match</source>
<translation type="unfinished"></translation>
<translation>Nicio potrivire</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="246"/>
<location filename="../keychain_unix.cpp" line="176"/>
<source>Unknown error</source>
<translation type="unfinished"></translation>
<translation>Eroare necunoscută</translation>
</message>
<message>
<location filename="../keychain_mac.cpp" line="31"/>
<source>OS X Keychain error (OSStatus %1)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../keychain_mac.cpp" line="32"/>
<location filename="../keychain_mac.cpp" line="33"/>
<source>%1 (OSStatus %2)</source>
<translation type="unfinished"></translation>
<translation>%1 (OSStatus %2)</translation>
</message>
</context>
</TS>

View File

@ -0,0 +1,241 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="ru_RU">
<context>
<name>QKeychain::DeletePasswordJobPrivate</name>
<message>
<location filename="../keychain_unix.cpp" line="548"/>
<location filename="../keychain_unix.cpp" line="556"/>
<source>Unknown error</source>
<translation>Неизвестная ошибка</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="574"/>
<source>Could not open wallet: %1; %2</source>
<translation>Не удалось открыть бумажник: %1; %2</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="104"/>
<source>Password entry not found</source>
<translation>Пароль не найден</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="108"/>
<source>Could not decrypt data</source>
<translation>Не удалось расшифровать данные</translation>
</message>
</context>
<context>
<name>QKeychain::JobPrivate</name>
<message>
<location filename="../keychain_unix.cpp" line="265"/>
<source>Unknown error</source>
<translation>Неизвестная ошибка</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="509"/>
<source>Access to keychain denied</source>
<translation>Доступ к связке ключей запрещён</translation>
</message>
</context>
<context>
<name>QKeychain::PlainTextStore</name>
<message>
<location filename="../plaintextstore.cpp" line="65"/>
<source>Could not store data in settings: access error</source>
<translation>Не удалось сохранить данные в настройках: ошибка доступа</translation>
</message>
<message>
<location filename="../plaintextstore.cpp" line="67"/>
<source>Could not store data in settings: format error</source>
<translation>Не удалось сохранить данные в настройках: ошибка формата</translation>
</message>
<message>
<location filename="../plaintextstore.cpp" line="85"/>
<source>Could not delete data from settings: access error</source>
<translation>Не удалось удалить данные из настроек: ошибка доступа</translation>
</message>
<message>
<location filename="../plaintextstore.cpp" line="87"/>
<source>Could not delete data from settings: format error</source>
<translation>Не удалось удалить данные из настроек: ошибка формата</translation>
</message>
<message>
<location filename="../plaintextstore.cpp" line="104"/>
<source>Entry not found</source>
<translation>Запись не найдена</translation>
</message>
</context>
<context>
<name>QKeychain::ReadPasswordJobPrivate</name>
<message>
<location filename="../keychain_mac.cpp" line="75"/>
<source>Password not found</source>
<translation>Пароль не найден</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="178"/>
<source>D-Bus is not running</source>
<translation>D-Bus не запущен</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="187"/>
<location filename="../keychain_unix.cpp" line="197"/>
<source>Unknown error</source>
<translation>Неизвестная ошибка</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="286"/>
<source>No keychain service available</source>
<translation>Служба связки ключей недоступна</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="288"/>
<source>Could not open wallet: %1; %2</source>
<translation>Не удалось открыть кошелёк: %1; %2</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="333"/>
<source>Access to keychain denied</source>
<translation>Доступ к связке ключей запрещён</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="354"/>
<source>Could not determine data type: %1; %2</source>
<translation>Не удалось определить тип данных: %1; %2</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="363"/>
<source>Entry not found</source>
<translation>Запись не найдена</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="372"/>
<source>Unsupported entry type &apos;Map&apos;</source>
<translation>Неподдерживаемый тип записи &apos;Map&apos;</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="375"/>
<source>Unknown kwallet entry type &apos;%1&apos;</source>
<translation>Неизвестный тип записи kwallet &apos;%1&apos;</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="32"/>
<source>Password entry not found</source>
<translation>Пароль не найден</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="36"/>
<location filename="../keychain_win.cpp" line="139"/>
<source>Could not decrypt data</source>
<translation>Не удалось расшифровать данные</translation>
</message>
</context>
<context>
<name>QKeychain::WritePasswordJobPrivate</name>
<message>
<location filename="../keychain_unix.cpp" line="415"/>
<source>D-Bus is not running</source>
<translation>D-Bus не запущен</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="425"/>
<location filename="../keychain_unix.cpp" line="452"/>
<source>Unknown error</source>
<translation>Неизвестная ошибка</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="468"/>
<source>Could not open wallet: %1; %2</source>
<translation>Не удалось открыть кошелёк: %1; %2</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="78"/>
<source>Credential size exceeds maximum size of %1</source>
<translation>Учётные данные превышают максимальный размер %1</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="87"/>
<source>Credential key exceeds maximum size of %1</source>
<translation>Ключ учётных данных превышает максимальный размер %1</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="92"/>
<source>Writing credentials failed: Win32 error code %1</source>
<translation>Не удалось сохранить учётные данные: код ошибки win32 %1</translation>
</message>
<message>
<location filename="../keychain_win.cpp" line="162"/>
<source>Encryption failed</source>
<translation>Шифрование не удалось</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<location filename="../keychain_mac.cpp" line="31"/>
<source>OS X Keychain error (OSStatus %1)</source>
<translation>Ошибка связки ключей OS X (OSStatus %1)</translation>
</message>
<message>
<location filename="../keychain_mac.cpp" line="32"/>
<source>%1 (OSStatus %2)</source>
<translation>%1 (OSStatus %2)</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="225"/>
<source>Access to keychain denied</source>
<translation>Доступ к связке ключей запрещён</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="227"/>
<source>No keyring daemon</source>
<translation>Нет демона связки ключей</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="229"/>
<source>Already unlocked</source>
<translation>Уже разблокировано</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="231"/>
<source>No such keyring</source>
<translation>Связка ключей не найдена</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="233"/>
<source>Bad arguments</source>
<translation>Неверные аргументы</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="235"/>
<source>I/O error</source>
<translation>Ошибка ввода/вывода</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="237"/>
<source>Cancelled</source>
<translation>Отменено</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="239"/>
<source>Keyring already exists</source>
<translation>Связка ключей уже существует</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="241"/>
<source>No match</source>
<translation>Нет совпадений</translation>
</message>
<message>
<location filename="../keychain_unix.cpp" line="246"/>
<source>Unknown error</source>
<translation>Неизвестная ошибка</translation>
</message>
<message>
<location filename="../libsecret.cpp" line="120"/>
<source>Entry not found</source>
<translation>Запись не найдена</translation>
</message>
</context>
</TS>

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/translations">
@QM_FILE_LIST@
</qresource>
</RCC>