refine github pages
6
.gitignore
vendored
@ -1,6 +0,0 @@
|
||||
.DS_Store
|
||||
|
||||
.idea
|
||||
|
||||
VNote.pro.user
|
||||
VNote.pro.user.*
|
6
.gitmodules
vendored
@ -1,6 +0,0 @@
|
||||
[submodule "src/utils/marked"]
|
||||
path = src/utils/marked
|
||||
url = https://github.com/chjj/marked.git
|
||||
[submodule "hoedown"]
|
||||
path = hoedown
|
||||
url = https://github.com/tamlok/hoedown.git
|
@ -1,31 +0,0 @@
|
||||
{
|
||||
"package": {
|
||||
"name": "vnote",
|
||||
"repo": "vnote",
|
||||
"subject": "tamlok",
|
||||
"desc": "Continuous deployment by Travis-CI",
|
||||
"website_url": "https://github.com/tamlok/vnote",
|
||||
"issue_tracker_url": "https://github.com/tamlok/vnote/issues",
|
||||
"vcs_url": "https://github.com/tamlok/vnote.git",
|
||||
"github_use_tag_release_notes": false,
|
||||
"github_release_notes_file": "",
|
||||
"licenses": ["MIT"],
|
||||
"labels": ["Markdown", "Note-Taking", "Office", "Utils", "Efficiency"],
|
||||
"public_download_numbers": true,
|
||||
"public_stats": true
|
||||
},
|
||||
|
||||
"version": {
|
||||
"name": "1.11",
|
||||
"desc": "VNote Releases",
|
||||
"released": "2017-12-17",
|
||||
"vcs_tag": "1.11",
|
||||
"gpgSign": false
|
||||
},
|
||||
|
||||
"files":
|
||||
[{"includePattern": "build/(VNote_.*\\.AppImage)", "uploadPattern": "$1",
|
||||
"matrixParams": {"override": 1 }}],
|
||||
|
||||
"publish": true
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
{
|
||||
"package": {
|
||||
"name": "vnote",
|
||||
"repo": "vnote",
|
||||
"subject": "tamlok",
|
||||
"desc": "Continuous deployment by Travis-CI",
|
||||
"website_url": "https://github.com/tamlok/vnote",
|
||||
"issue_tracker_url": "https://github.com/tamlok/vnote/issues",
|
||||
"vcs_url": "https://github.com/tamlok/vnote.git",
|
||||
"github_use_tag_release_notes": false,
|
||||
"github_release_notes_file": "",
|
||||
"licenses": ["MIT"],
|
||||
"labels": ["Markdown", "Note-Taking", "Office", "Utils", "Efficiency"],
|
||||
"public_download_numbers": true,
|
||||
"public_stats": true
|
||||
},
|
||||
|
||||
"version": {
|
||||
"name": "1.11",
|
||||
"desc": "VNote Releases",
|
||||
"released": "2017-12-17",
|
||||
"vcs_tag": "1.11",
|
||||
"gpgSign": false
|
||||
},
|
||||
|
||||
"files":
|
||||
[{"includePattern": "build/distrib/(VNote_.*\\.dmg)", "uploadPattern": "$1",
|
||||
"matrixParams": {"override": 1 }}],
|
||||
|
||||
"publish": true
|
||||
}
|
59
.travis.yml
@ -1,59 +0,0 @@
|
||||
dist: trusty
|
||||
sudo: required
|
||||
git:
|
||||
depth: 1
|
||||
language: cpp
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
compiler: clang
|
||||
osx_image: xcode8
|
||||
- os: linux
|
||||
compiler: g++
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
before_install:
|
||||
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test; fi
|
||||
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then sudo apt-get -qq update ; fi
|
||||
- export version="1.11"
|
||||
|
||||
install:
|
||||
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install p7zip-full ; fi
|
||||
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install chrpath ; fi
|
||||
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install openssl libssl-dev ; fi
|
||||
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install libgl1-mesa-dev ; fi
|
||||
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install tree ; fi
|
||||
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then sudo apt-get install -qq gcc-6; sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 100 ; fi
|
||||
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then sudo apt-get install -qq g++-6; sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-6 100 ; fi
|
||||
|
||||
before_script:
|
||||
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then chmod +x .travis_linux.sh ; fi
|
||||
- if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then chmod +x .travis_macos.sh ; fi
|
||||
|
||||
script:
|
||||
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then CXX="g++-6" CC="gcc-6" ./.travis_linux.sh ; fi
|
||||
- if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then ./.travis_macos.sh ; fi
|
||||
|
||||
deploy:
|
||||
- provider: bintray
|
||||
file: "./.linux_bintray.json"
|
||||
user: tamlok
|
||||
key:
|
||||
secure: vADlrYSusARnHxK3NTT3r1r6I/lrxyEuFKLOO73KXl6rvDywrrDDi6YsFekL14/o3clZKLOC8LbvA+4BKPVSoGTO4Dpx/i51fXphf+mMFB3QXSGnbqHm4+2ZnhCl62vfVlFeOPBLDO0KUBI8DCP0I+QogOUickXv3zYx69z6AudSxgjY7nPoN5jGSPgyId4r3o8Shrj1Fk1WtN6qciKyPne57v+jmvRlJTkoEfYkvwTRNMBlmaKy8Vj/CSoIvB8TH8bv3rhHLrN4o1QCwePdaX9G7xlImSpXqgRgzQVU3SRFN2bYTj5DZc1aFyA7iNOhmwgCLAN6OrqXyyJCbyywNhylfgad2rQANcfXiZw9ywooKEYafLDRuiK2G6Pk8m+R6Ded9JBoX9MSz/ZDxxfk4NmLO991wS7IK5aFTQfHZz5W8Tmh1rCphFwlDI/Rxs7ExbXu7INA8IhoLpOSmLIA5lfdC4fD8rv8mjy5gfpMkWlP8XATfi2eMH9uGj89aJa6xp7Bc2mfW3M71YHtYP/pyE/7Bk5vVonP8Cq9rK6OruMIUYtNmXoJnpte20ecSXj9yw7oTR27dhSx3XuAKOXiNpIau0CPxtXA5cSm5X0OS1Lt8RWrms0jp9Ch2dgxlNMpr0soF9DmwekhfHnVoOLyFBWBMDwvkx5TWij9FNAAJb4=
|
||||
dry-run: false
|
||||
on:
|
||||
condition: $TRAVIS_OS_NAME = linux
|
||||
branch: master
|
||||
- provider: bintray
|
||||
file: "./.macos_bintray.json"
|
||||
user: tamlok
|
||||
key:
|
||||
secure: vADlrYSusARnHxK3NTT3r1r6I/lrxyEuFKLOO73KXl6rvDywrrDDi6YsFekL14/o3clZKLOC8LbvA+4BKPVSoGTO4Dpx/i51fXphf+mMFB3QXSGnbqHm4+2ZnhCl62vfVlFeOPBLDO0KUBI8DCP0I+QogOUickXv3zYx69z6AudSxgjY7nPoN5jGSPgyId4r3o8Shrj1Fk1WtN6qciKyPne57v+jmvRlJTkoEfYkvwTRNMBlmaKy8Vj/CSoIvB8TH8bv3rhHLrN4o1QCwePdaX9G7xlImSpXqgRgzQVU3SRFN2bYTj5DZc1aFyA7iNOhmwgCLAN6OrqXyyJCbyywNhylfgad2rQANcfXiZw9ywooKEYafLDRuiK2G6Pk8m+R6Ded9JBoX9MSz/ZDxxfk4NmLO991wS7IK5aFTQfHZz5W8Tmh1rCphFwlDI/Rxs7ExbXu7INA8IhoLpOSmLIA5lfdC4fD8rv8mjy5gfpMkWlP8XATfi2eMH9uGj89aJa6xp7Bc2mfW3M71YHtYP/pyE/7Bk5vVonP8Cq9rK6OruMIUYtNmXoJnpte20ecSXj9yw7oTR27dhSx3XuAKOXiNpIau0CPxtXA5cSm5X0OS1Lt8RWrms0jp9Ch2dgxlNMpr0soF9DmwekhfHnVoOLyFBWBMDwvkx5TWij9FNAAJb4=
|
||||
dry-run: false
|
||||
on:
|
||||
condition: $TRAVIS_OS_NAME = osx
|
||||
branch: master
|
@ -1,86 +0,0 @@
|
||||
#!/bin/bash
|
||||
project_dir=$(pwd)
|
||||
|
||||
# Install qt5.9
|
||||
sudo add-apt-repository ppa:george-edison55/cmake-3.x -y
|
||||
sudo add-apt-repository ppa:beineri/opt-qt591-trusty -y
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get -y install qt59base qt59webengine qt59webchannel qt59svg qt59location qt59tools qt59translations
|
||||
source /opt/qt*/bin/qt*-env.sh
|
||||
|
||||
# Compile newer version fcitx-qt5
|
||||
sudo apt-get -y install fcitx-libs-dev libgl1-mesa-dev bison
|
||||
sudo apt-get -y install cmake
|
||||
|
||||
wget http://xkbcommon.org/download/libxkbcommon-0.5.0.tar.xz
|
||||
tar xf libxkbcommon-0.5.0.tar.xz
|
||||
cd libxkbcommon-0.5.0
|
||||
./configure -prefix=/usr -libdir=/usr/lib/x86_64-linux-gnu -disable-x11
|
||||
make -j$(nproc) && sudo make install
|
||||
|
||||
git clone git://anongit.kde.org/extra-cmake-modules
|
||||
cd extra-cmake-modules
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
make -j$(nproc) && sudo make install
|
||||
|
||||
git clone https://github.com/fcitx/fcitx-qt5
|
||||
cd fcitx-qt5
|
||||
git checkout 1.0.5
|
||||
cmake .
|
||||
make -j$(nproc) && sudo make install
|
||||
|
||||
# Copy fcitx-qt5 files to qt
|
||||
sudo cp /usr/local/lib/libFcitxQt5DBusAddons.so* /opt/qt*/lib/
|
||||
sudo cp /usr/local/lib/libFcitxQt5WidgetsAddons.so* /opt/qt*/lib/
|
||||
|
||||
tree /opt/qt59/lib/
|
||||
|
||||
cd ${project_dir}
|
||||
mkdir build
|
||||
cd build
|
||||
qmake -v
|
||||
qmake CONFIG+=release -spec linux-g++-64 ../VNote.pro
|
||||
make -j$(nproc)
|
||||
|
||||
#
|
||||
# Pack AppImage using linuxdeployqt
|
||||
#
|
||||
mkdir dist
|
||||
INSTALL_ROOT=${project_dir}/build/dist make install ; tree dist/
|
||||
|
||||
# Copy SVG module
|
||||
mkdir -p ./dist/usr/plugins/iconengines
|
||||
mkdir -p ./dist/usr/plugins/imageformats
|
||||
mkdir -p ./dist/usr/plugins/platforminputcontexts
|
||||
cp /opt/qt59/plugins/iconengines/* ./dist/usr/plugins/iconengines/
|
||||
cp /opt/qt59/plugins/imageformats/* ./dist/usr/plugins/imageformats/
|
||||
cp /opt/qt59/plugins/platforminputcontexts/* ./dist/usr/plugins/platforminputcontexts/
|
||||
|
||||
# Copy other project files
|
||||
cp "${project_dir}/README.md" "dist/README.md"
|
||||
cp "${project_dir}/LICENSE" "dist/LICENSE"
|
||||
echo ${version} > ./dist/version
|
||||
echo "${TRAVIS_COMMIT}" >> ./dist/version
|
||||
|
||||
# Get linuxdeployqt tool
|
||||
wget -c "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage"
|
||||
chmod a+x linuxdeployqt*.AppImage
|
||||
unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH
|
||||
./linuxdeployqt*.AppImage ./dist/usr/share/applications/*.desktop -bundle-non-qt-libs
|
||||
|
||||
# Copy translations
|
||||
cp /opt/qt59/translations/*_zh_CN.qm ./dist/usr/translations/
|
||||
|
||||
# Package it for the second time.
|
||||
./linuxdeployqt*.AppImage ./dist/usr/share/applications/*.desktop -appimage
|
||||
|
||||
tree dist/
|
||||
|
||||
ls -l *.AppImage
|
||||
|
||||
mv VNote-*.AppImage VNote_x86_64_${version}.AppImage
|
||||
|
||||
cd ..
|
||||
|
||||
exit 0
|
@ -1,49 +0,0 @@
|
||||
#!/bin/bash
|
||||
project_dir=$(pwd)
|
||||
|
||||
brew update > /dev/null
|
||||
brew install qt
|
||||
QTDIR="/usr/local/opt/qt"
|
||||
PATH="$QTDIR/bin:$PATH"
|
||||
LDFLAGS=-L$QTDIR/lib
|
||||
CPPFLAGS=-I$QTDIR/include
|
||||
|
||||
# Build your app
|
||||
cd ${project_dir}
|
||||
mkdir build
|
||||
cd build
|
||||
qmake -v
|
||||
qmake CONFIG-=debug CONFIG+=release ../VNote.pro
|
||||
make -j2
|
||||
|
||||
git clone https://github.com/aurelien-rainone/macdeployqtfix.git
|
||||
|
||||
# Package DMG from build/src/VNote.app directory
|
||||
cd src/
|
||||
|
||||
sed -i -e 's/com.yourcompany.VNote/com.tamlok.VNote/g' VNote.app/Contents/Info.plist
|
||||
$QTDIR/bin/macdeployqt VNote.app
|
||||
python ../macdeployqtfix/macdeployqtfix.py VNote.app/Contents/MacOS/VNote $QTDIR
|
||||
|
||||
# Fix Helpers/QtWebEngineProcess.app
|
||||
cd VNote.app/Contents/Frameworks/QtWebEngineCore.framework/Versions/5/Helpers
|
||||
$QTDIR/bin/macdeployqt QtWebEngineProcess.app
|
||||
python ${project_dir}/build/macdeployqtfix/macdeployqtfix.py QtWebEngineProcess.app/Contents/MacOS/QtWebEngineProcess $QTDIR
|
||||
|
||||
cd ${project_dir}/build
|
||||
mkdir -p distrib/VNote
|
||||
cd distrib/VNote
|
||||
mv ../../src/VNote.app ./
|
||||
cp "${project_dir}/LICENSE" "LICENSE"
|
||||
cp "${project_dir}/README.md" "README.md"
|
||||
echo "${version}" > version
|
||||
echo "${TRAVIS_COMMIT}" >> version
|
||||
|
||||
ln -s /Applications ./Applications
|
||||
|
||||
cd ..
|
||||
hdiutil create -srcfolder ./VNote -format UDBZ ./VNote.dmg
|
||||
mv VNote.dmg VNote_X64_${version}.dmg
|
||||
cd ..
|
||||
|
||||
exit 0
|
21
LICENSE
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Le Tan
|
||||
|
||||
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.
|
223
README.md
@ -1,223 +0,0 @@
|
||||
# VNote
|
||||
[中文 Chinese](./README_zh.md)
|
||||
|
||||
Designed specially for **Markdown**, **VNote** is a Vim-inspired note-taking application, which knows programmers and Markdown better.
|
||||
|
||||

|
||||
|
||||
# Downloads
|
||||
Users from China can download the latest release of VNote from [Baidu Netdisk](http://pan.baidu.com/s/1jI5HROq).
|
||||
|
||||
## Windows
|
||||

|
||||
|
||||
- [Github releases](https://github.com/tamlok/vnote/releases)
|
||||
- Latest builds on master: [  ](https://bintray.com/tamlok/vnote/vnote/_latestVersion)
|
||||
|
||||
**NOT** supported in XP since QtWebEngineProcess used by VNote could not work in XP.
|
||||
|
||||
## Linux
|
||||
|
||||
### AppImage
|
||||
[](https://travis-ci.org/tamlok/vnote)
|
||||
|
||||
There is an AppImage format standalone executable of VNote for major Linux distributions. **Any help for packaging and distribution on Linux is appreciated!**
|
||||
|
||||
- [Github releases](https://github.com/tamlok/vnote/releases)
|
||||
- Latest builds on master: [  ](https://bintray.com/tamlok/vnote/vnote/_latestVersion)
|
||||
|
||||
### openSUSE
|
||||
Currently `vnote` on openSUSE Tumbleweed can be installed from `home:opensuse_zh` project on OBS. You can execute the following command directly:
|
||||
|
||||
```shell
|
||||
sudo zypper ar https://download.opensuse.org/repositories/home:/opensuse_zh/openSUSE_Tumbleweed/ home:opensuse_zh
|
||||
sudo zypper ref
|
||||
sudo zypper in vnote
|
||||
```
|
||||
|
||||
For other architectures, please search for `vnote` at [software.opensuse.org](https://software.opensuse.org).
|
||||
|
||||
We don't support Leap 42 and below due to the Qt version. Please use AppImage or build it yourself.
|
||||
|
||||
## MacOS
|
||||
[](https://travis-ci.org/tamlok/vnote)
|
||||
|
||||
- [Github releases](https://github.com/tamlok/vnote/releases)
|
||||
- Latest builds on master: [  ](https://bintray.com/tamlok/vnote/vnote/_latestVersion)
|
||||
|
||||
# Description
|
||||
**VNote** is a Qt-based, free and open source note-taking application, focusing on Markdown. VNote is designed to provide comfortable edit experience.
|
||||
|
||||
VNote is **NOT** just a simple editor for Markdown. By providing notes management, VNote makes taking notes in Markdown simpler and more pleasant.
|
||||
|
||||
Utilizing Qt, VNote could run on **Linux**, **Windows**, and **macOS**.
|
||||
|
||||

|
||||
|
||||
# Supports
|
||||
- [Github issues](https://github.com/tamlok/vnote/issues);
|
||||
- Email: tamlokveer at gmail.com;
|
||||
- QQ group: 487756074;
|
||||
- WeChat Public Account: VNote笔记 (vnote_md);
|
||||

|
||||
|
||||
# Highlights
|
||||
- Supports inserting images directly from clipboard;
|
||||
- Supports syntax highlights of fenced code blocks in both **edit** and **read** mode;
|
||||
- Supports outline in both edit and read mode;
|
||||
- Supports custom styles in both edit and read mode;
|
||||
- Supports Vim mode and a set of powerful shortcuts;
|
||||
- Supports infinite levels of folders;
|
||||
- Supports multiple tabs and splitting windows;
|
||||
- Supports [Mermaid](http://knsv.github.io/mermaid/), [Flowchart.js](http://flowchart.js.org/), and [MathJax](https://www.mathjax.org/);
|
||||
- Supports HiDPI;
|
||||
- Supports attachments of notes.
|
||||
- Supports themes and dark mode.
|
||||
|
||||
# Why VNote
|
||||
## Markdown Editor & Notes Management
|
||||
VNote tries to be a powerful Markdown editor with notes management, or a note-taking application with pleasant Markdown support. If you are a fan of Markdown and enjoy writing Markdown notes for study, work, and life, VNote is the right tool for you.
|
||||
|
||||
## Pleasant Markdown Experience
|
||||
### Insight About Markdown
|
||||
Markdown, as a simple mark language, unlike rich text, was born with a **gap between edit and read**. There are about three ways to handle this gap:
|
||||
|
||||
1. As one extreme, some editors just tread Markdown as **plain text**. Users may lose themselves in the messy black characters. It is hard to keep track of the information of the note.
|
||||
2. Most Markdown editors use two panels to **edit and preview Markdown notes simultaneously**. It makes things easier since users could see a pleasant typesetting and layout while editing the text. However, two panels may occupy the whole screen and users keep moving eyes left and righti which will be a big distraction.
|
||||
3. As another extreme, some editors convert the Markdown elements in place immediately after the typing, which makes editing Markdown just like editing rich text document in Word.
|
||||
|
||||
Since most editors choose the second way to handle the gap, people always think of preview when it comes to Markdown. It may be a misunderstanding about Markdown. Designed as a simple mark language, Markdown is intended to help to keep track of the information of the text when editing and provide a beautiful typesetting when reading after being converted to HTML.
|
||||
|
||||
### Tradeoff: VNote's Way
|
||||
VNote tries to minimize the gap and provide the best-effort *WYSIWYG* for Markdown via tuned **syntax highlights** and some other features. By helping to keep track of the content, there is no need to preview or alter the text immediately after being typed.
|
||||
|
||||
# Features
|
||||
## Notebook-Based Notes Management
|
||||
VNote uses **notebooks** to hold your notes. Like OneNote, a notebook can be hosted on any location on your system. A notebook is designed to represent one account. For example, you could have one notebook hosted on local file system and another notebook hosted on an OwnCloud server. This is really useful when notes require different levels of security.
|
||||
|
||||
A notebook corresponds to a self-contained folder (called the *Root Folder* of the notebook) in the file system. You could copy the folder to another location (or on another computer) and import it into VNote.
|
||||
|
||||
A notebook could have infinite levels of folders. VNote supports copying or moving folders or notes within or between notebooks.
|
||||
|
||||
## Simple Notes Management
|
||||
All your notes are managed by some plaintext configuration files and stored as plaintext files. You could access your notes without VNote. You could use external file synchronization services to synchronize your notes and import them on another machine.
|
||||
|
||||
VNote supports both Markdown (suffix `md`) and rich text notes.
|
||||
|
||||
## Syntax Highlight
|
||||
VNote supports accurate syntax highlight for Markdown. Tuning the style of the highlight, VNote enables you to keep track of your document easily.
|
||||
|
||||
VNote also supports **syntax highlight for fenced code blocks** in edit mode in Markdown, which is **superior** than almost all current Markdown editors.
|
||||
|
||||

|
||||
|
||||
## Live Image Preview
|
||||
VNote supports previewing the image link in edit mode. With this, you could just stay in edit mode as much as possible.
|
||||
|
||||

|
||||
|
||||
## Pleasant Image Experience
|
||||
Just paste your image into the Markdown note, VNote will manage all other stuffs. VNote stores images in a specified folder in the same folder with the note. VNote will pop up a window to preview the image when you insert an image. Furthermore, VNote will delete the useless image files automatically after you remove the image links.
|
||||
|
||||

|
||||
|
||||
## Interactive Outline Viewer In Read & Edit Mode
|
||||
VNote provides a user-friendly outline viewer for both edit and view mode. The outline viewer is a responsive item tree instead of a segment of HTML.
|
||||
|
||||
## Powerful Shortcuts
|
||||
VNote supports many pleasant and powerful shortcuts which facilitate your editing, including **Vim Mode**, **Captain Mode**, and **Navigation Mode** which enable you to work without the mouse.
|
||||
|
||||
Please refer to the [shortcuts help](src/resources/docs/shortcuts_en.md) in the help menu for more details.
|
||||
|
||||
## Highly Configurable
|
||||
In VNote, almost everything is configurable, such as background color, font, and Markdown style. VNote uses a plaintext file to record all your configuration, so you could just copy that file to initialize a new VNote on another computer.
|
||||
|
||||
## Others
|
||||
VNote also supports many other features, like:
|
||||
|
||||
- Highlight current cursor line;
|
||||
- Highlight selected text;
|
||||
- Powerful search within the note;
|
||||
- Auto indent and auto list;
|
||||
|
||||
# Build & Development
|
||||
VNote needs Qt 5.9.1 or above to build.
|
||||
|
||||
1. Clone & Init
|
||||
```
|
||||
git clone https://github.com/tamlok/vnote.git vnote.git
|
||||
cd vnote.git
|
||||
git submodule update --init
|
||||
```
|
||||
2. Download Qt & Have Fun
|
||||
Download [Qt 5.9.1](http://info.qt.io/download-qt-for-application-development) and open `VNote.pro` as a project.
|
||||
|
||||
## Linux
|
||||
If your distribution does not have Qt 5.9.1 or above, you need to add it from other sources. In Ubuntu, you could do this:
|
||||
|
||||
```
|
||||
sudo add-apt-repository ppa:beineri/opt-qt591-trusty -y
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get -y install qt59base qt59webengine qt59webchannel qt59svg qt59location qt59tools qt59translations
|
||||
source /opt/qt*/bin/qt*-env.sh
|
||||
```
|
||||
|
||||
After Qt and some necessary modules are ready, you could follow these steps to build VNote
|
||||
|
||||
```
|
||||
cd vnote.git
|
||||
mkdir build
|
||||
cd build
|
||||
qmake ../VNote.pro
|
||||
make
|
||||
sudo make install
|
||||
```
|
||||
|
||||
For details, you could reference [.travis_linux.sh](.travis_linux.sh) in the source root.
|
||||
|
||||
## MacOS
|
||||
If you prefer command line on macOS, you could follow these steps.
|
||||
|
||||
1. Install Xcode and Homebrew;
|
||||
2. Install Qt 5.9.1 via Homebrew:
|
||||
```
|
||||
brew install qt@5.9.1
|
||||
```
|
||||
3. In the project directory, create `build_macos.sh` like this:
|
||||
```sh
|
||||
QTDIR="/usr/local/opt/qt@5.9.1"
|
||||
PATH="$QTDIR/bin:$PATH"
|
||||
LDFLAGS=-L$QTDIR/lib
|
||||
CPPFLAGS=-I$QTDIR/include
|
||||
|
||||
mkdir -p build
|
||||
cd build
|
||||
qmake -v
|
||||
qmake CONFIG-=debug CONFIG+=release ../VNote.pro
|
||||
make -j2
|
||||
```
|
||||
4. Make `build_macos.sh` executable and run it:
|
||||
```sh
|
||||
chmod +x build_macos.sh
|
||||
./build_macos.sh
|
||||
```
|
||||
5. Now you got the bundle `path/to/project/build/src/VNote.app`. Enjoy yourself!
|
||||
|
||||
# Dependencies
|
||||
- [Qt 5.9](http://qt-project.org) (L-GPL v3)
|
||||
- [PEG Markdown Highlight](http://hasseg.org/peg-markdown-highlight/) (MIT License)
|
||||
- [Hoedown 3.0.7](https://github.com/hoedown/hoedown/) (ISC License)
|
||||
- [Marked](https://github.com/chjj/marked) (MIT License)
|
||||
- [Highlight.js](https://github.com/isagalaev/highlight.js/) (BSD License)
|
||||
- [Ionicons 2.0.1](https://github.com/driftyco/ionicons/) (MIT License)
|
||||
- [markdown-it 8.3.1](https://github.com/markdown-it/markdown-it) (MIT License)
|
||||
- [markdown-it-headinganchor 1.3.0](https://github.com/adam-p/markdown-it-headinganchor) (MIT License)
|
||||
- [markdown-it-task-lists 1.4.0](https://github.com/revin/markdown-it-task-lists) (ISC License)
|
||||
- [mermaid 7.0.0](https://github.com/knsv/mermaid) (MIT License)
|
||||
- [MathJax](https://www.mathjax.org/) (Apache-2.0)
|
||||
- [showdown](https://github.com/showdownjs/showdown) (Unknown)
|
||||
- [flowchart.js](https://github.com/adrai/flowchart.js) (MIT License)
|
||||
- Icons made by a326703305@qq.com
|
||||
|
||||
# License
|
||||
VNote is licensed under the [MIT license](http://opensource.org/licenses/MIT).
|
229
README_zh.md
@ -1,229 +0,0 @@
|
||||
# VNote
|
||||
[英文 English](./README.md)
|
||||
|
||||
**VNote** 是一个受Vim启发开发的专门为 **Markdown** 而优化、设计的笔记软件。VNote是一个更了解程序员和Markdown的笔记软件。
|
||||
|
||||

|
||||
|
||||
# 下载
|
||||
国内的用户可以尝试在[百度云盘](http://pan.baidu.com/s/1jI5HROq)下载VNote的最新发行版本。
|
||||
|
||||
## Windows
|
||||

|
||||
|
||||
- [Github releases](https://github.com/tamlok/vnote/releases)
|
||||
- master分支的最新构建:[  ](https://bintray.com/tamlok/vnote/vnote/_latestVersion)
|
||||
|
||||
VNote不支持**XP**,因为QtWebEngineProcess无法在XP上运行。
|
||||
|
||||
## Linux
|
||||
|
||||
### AppImage
|
||||
[](https://travis-ci.org/tamlok/vnote)
|
||||
|
||||
VNote当前为主要Linux发行版提供了一个AppImage格式的独立可执行文件。希望了解Linux系统下打包发布的开发人员能提供这方面进一步的帮助!
|
||||
|
||||
- [Github releases](https://github.com/tamlok/vnote/releases)
|
||||
- master分支的最新构建:[  ](https://bintray.com/tamlok/vnote/vnote/_latestVersion)
|
||||
|
||||
### openSUSE
|
||||
目前 openSUSE Tumbleweed 可以通过 OBS 上的 `home:opensuse_zh` 源直接安装。您可以直接执行以下命令:
|
||||
|
||||
```shell
|
||||
sudo zypper ar https://download.opensuse.org/repositories/home:/opensuse_zh/openSUSE_Tumbleweed/ home:opensuse_zh
|
||||
sudo zypper ref
|
||||
sudo zypper in vnote
|
||||
```
|
||||
|
||||
其他架构请直接在 [software.opensuse.org](https://software.opensuse.org) 搜索 `vnote`。
|
||||
|
||||
由于 Leap 42 及以下版本的 Qt 版本过低,我们无法在 OBS 上进行打包。请使用 AppImage 或自行构建。
|
||||
|
||||
## MacOS
|
||||
[](https://travis-ci.org/tamlok/vnote)
|
||||
|
||||
- [Github releases](https://github.com/tamlok/vnote/releases)
|
||||
- master分支的最新构建:[  ](https://bintray.com/tamlok/vnote/vnote/_latestVersion)
|
||||
|
||||
# 简介
|
||||
|
||||
**VNote**是一个基于Qt框架的、免费的开源笔记软件。VNote专注于Markdown的编辑与阅读,以提供舒适的编辑体验为设计目标。
|
||||
|
||||
VNote不是一个简单的Markdown编辑器。通过提供笔记管理功能,VNote使得编写Markdown笔记更简单和舒适!
|
||||
|
||||
基于Qt框架,VNote能够在主流操作系统上运行,包括 **Linux**, **Windows** 以及 **macOS**(由于macOS上很不一样的交互逻辑,VNote在macOS上并没有被充分测试,我们也希望得到更多的反馈以帮助改进VNote)。
|
||||
|
||||

|
||||
|
||||
# 支持
|
||||
- [Github issues](https://github.com/tamlok/vnote/issues);
|
||||
- 邮箱: tamlokveer at gmail.com;
|
||||
- QQ群: 487756074;
|
||||
- 微信公众号: VNote笔记(vnote_md);
|
||||

|
||||
|
||||
# 亮点
|
||||
- 支持直接从剪切板插入图片;
|
||||
- 支持编辑和阅读模式下代码块的语法高亮;
|
||||
- 支持编辑和阅读模式下的大纲;
|
||||
- 支持自定义编辑和阅读模式的样式;
|
||||
- 支持Vim模式以及一系列强大的快捷键;
|
||||
- 支持无限层级的文件夹;
|
||||
- 支持多个标签页和窗口分割;
|
||||
- 支持[Mermaid](http://knsv.github.io/mermaid/), [Flowchart.js](http://flowchart.js.org/) 和 [MathJax](https://www.mathjax.org/);
|
||||
- 支持高分辨率;
|
||||
- 支持笔记附件。
|
||||
- 支持主题以及深色模式;
|
||||
|
||||
# 开发VNote的动机
|
||||
## Markdown编辑器与笔记管理
|
||||
VNote设计为带有笔记管理功能的Markdown编辑器,或者有良好Markdown支持的笔记软件。如果您喜欢Markdown并经常在学习、工作和生活中使用Markdown记录笔记,那么VNote就是一个适合您的工具。
|
||||
|
||||
## 舒适的Markdown体验
|
||||
### Markdown的本质
|
||||
Markdown作为一个简单标记语言,不像富文本,它的编辑和阅读有着与生俱来的隔阂。一般目前大概有三类方法来处理这个隔阂:
|
||||
|
||||
1. 作为一个极端,一些编辑器只是将Markdown作为无格式的纯文本处理。用户很容易在密密麻麻的黑漆漆的一片文字中找不着方向。
|
||||
2. 大部分编辑器使用两个面板来同时编辑和预览Markdown笔记。从而,用户可以在编辑的同时看到优美的排版和布局。但是,两个面板基本会占据了整个屏幕,而用户的目光焦点左右频繁移动,往往也会使得用户无法专注编辑。
|
||||
3. 作为另一个极端,一些编辑器在用户输入文本后立即将Markdown的标记转换为HTML元素,使得编写Markdown如同在Word文档里面编写富文本一样。
|
||||
|
||||
由于几乎所有的编辑器都选择第二种方法来处理隔阂,一提到Markdown人们往往会想起预览。这可能是对Markdown的一个最大的误解了。设计为一个简单的标记语言,Markdown的设计初衷就是为了在编辑的时候方便帮助跟踪文本的信息,而又能在阅读的时候被转换为HTML为发布提供美观的排版输出。所以,Markdown本身就应该在编辑的时候能够方便地跟踪和掌控文本的信息和脉络,而不需要通过预览这种接近饮鸩止渴的方法来方便编辑。
|
||||
|
||||
### 折中:VNote的方案
|
||||
VNote尝试通过精心调配的**语法高亮**和其他一些特性,来最大程度地减小Markdown的这种割裂感,尽可能地提供一个*所见即所得*的编辑体验。用户在编辑的时候就能有效第把握内容脉络,也就没有必要进行预览或者强制更改文本为HTML元素了。
|
||||
|
||||
# 功能
|
||||
## 基于笔记本的管理
|
||||
VNote使用 **笔记本** 来管理笔记。类似于OneNote,一个笔记本可以保存在系统上的任意位置。一个笔记本对应于一个账户的概念。例如,您可以在本地文件系统上有一个笔记本,另外在某台OwnCloud服务器上保存另一个笔记本。当不同的笔记有不同的保密要求时,独立的笔记本就非常适用了。
|
||||
|
||||
一个笔记本对应于文件系统上的一个独立完整的文件夹(称为笔记本的 **根目录** )。您可以将该文件夹拷贝到其他位置(或者另一台计算机上),然后将其导入到VNote中。
|
||||
|
||||
VNote支持一个笔记本中包含无限层级的文件夹。VNote支持在笔记本内或笔记本间拷贝或剪切文件夹和笔记。
|
||||
|
||||
## 直观的笔记管理
|
||||
所有笔记被保存为纯文本而且通过纯文本的配置文件进行管理。即使没有VNote,您也能方便访问您的数据。这样,您也可以使用第三方的文件同步服务来同步您的笔记,并在另一台计算机上导入到VNote中。
|
||||
|
||||
VNote支持Markdown和富文本笔记,其中Markdown笔记必须以`md`为后缀名。
|
||||
|
||||
## 语法高亮
|
||||
VNote支持精确的Markdown语法高亮。通过精心调试的高亮样式,VNote使得您能够轻松跟踪和阅读您的文档。
|
||||
|
||||
VNote还支持Markdown编辑模式中代码块的语法高亮。目前的Markdown编辑器中绝大部分都尚不支持该特性。
|
||||
|
||||

|
||||
|
||||
## 实时图片预览
|
||||
VNote支持在编辑时原地预览图片链接。这样一来,您就能尽可能地留在编辑模式,避免频繁切换。
|
||||
|
||||
如果想要拷贝图片,可以选取该图片,然后复制。
|
||||
|
||||

|
||||
|
||||
## 良好的图片体验
|
||||
编辑时,支持像其他富文本编辑器一样直接粘贴插入图片,VNote会帮您管理所插入的图片。VNote将这些图片保存在和笔记同一目录下的一个指定目录中。插入图片时,VNote会弹出一个窗口预览即将要插入的图片。另外,当您移除笔记中的图片链接时,VNote会自动删除对应的图片文件。
|
||||
|
||||

|
||||
|
||||
## 编辑和阅读模式中的交互式大纲视图
|
||||
VNote为编辑和预览模式都提供了一个用户友好的大纲视图。该大纲视图是一个项目树,而不是简单地插入一段HTML。
|
||||
|
||||
## 强大的快捷键
|
||||
VNote提供很多快捷键,从而提供一个愉悦的编辑体验。其中包括 **Vim模式**、**舰长模式** 和 **导航模式**,它们能让您完全摆脱鼠标进行操作。
|
||||
|
||||
更多细节请参考帮助菜单中的[快捷键帮助](src/resources/docs/shortcuts_zh.md)。
|
||||
|
||||
## 高度可定制
|
||||
VNote中,几乎一切都是可以定制的,例如背景颜色、字体以及Markdown样式等。VNote使用一个纯文本文件来记录您的所有配置,因此通过拷贝该文件就能够很快地在另一台电脑上初始化一个新的VNote。
|
||||
|
||||
## 其他
|
||||
VNote还支持其他很多的功能,比如:
|
||||
|
||||
- 高亮当前行;
|
||||
- 高亮所选择的文本;
|
||||
- 强大的页内查找;
|
||||
- 自动缩进和自动列表;
|
||||
|
||||
# 构建与开发
|
||||
VNote需要5.9.1版本以上的Qt进行构建。
|
||||
|
||||
1. 克隆代码仓库
|
||||
```
|
||||
git clone https://github.com/tamlok/vnote.git vnote.git
|
||||
cd vnote.git
|
||||
git submodule update --init
|
||||
```
|
||||
2. 下载Qt
|
||||
下载[Qt 5.9.1](http://info.qt.io/download-qt-for-application-development),导入`VNote.pro`创建一个工程。
|
||||
|
||||
## Linux
|
||||
如果您的Linux发行版不提供5.9.1以上版本的Qt,那么您需要从其他来源获取Qt。在Ubuntu中,您可以执行以下步骤:
|
||||
|
||||
```
|
||||
sudo add-apt-repository ppa:beineri/opt-qt591-trusty -y
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get -y install qt59base qt59webengine qt59webchannel qt59svg qt59location qt59tools qt59translations
|
||||
source /opt/qt*/bin/qt*-env.sh
|
||||
```
|
||||
|
||||
当Qt和相关的模块准备就绪后,您可以执行以下命令来编译和安装:
|
||||
|
||||
```
|
||||
cd vnote.git
|
||||
mkdir build
|
||||
cd build
|
||||
qmake ../VNote.pro
|
||||
make
|
||||
sudo make install
|
||||
```
|
||||
|
||||
更多细节,您可以参考源代码根目录下的 [.travis_linux.sh](.travis_linux.sh) 。
|
||||
|
||||
## MacOS
|
||||
在macOS下,您可以执行以下步骤来编译:
|
||||
|
||||
1. 安装Xcode和Homebrew:
|
||||
2. 通过Homebrew安装Qt5.9.1:
|
||||
|
||||
```
|
||||
brew install qt@5.9.1
|
||||
```
|
||||
3. 在VNote源码根目录下,新建一个文件`build_macos.sh`:
|
||||
|
||||
```sh
|
||||
QTDIR="/usr/local/opt/qt@5.9.1"
|
||||
PATH="$QTDIR/bin:$PATH"
|
||||
LDFLAGS=-L$QTDIR/lib
|
||||
CPPFLAGS=-I$QTDIR/include
|
||||
|
||||
mkdir -p build
|
||||
cd build
|
||||
qmake -v
|
||||
qmake CONFIG-=debug CONFIG+=release ../VNote.pro
|
||||
make -j2
|
||||
```
|
||||
4. 修改`build_macos.sh`的执行权限,并执行:
|
||||
|
||||
```sh
|
||||
chmod +x build_macos.sh
|
||||
./build_macos.sh
|
||||
```
|
||||
5. 此时得到VNote的Bundle `path/to/project/build/src/VNote.app`,打开即可。
|
||||
|
||||
# 依赖
|
||||
- [Qt 5.9](http://qt-project.org) (L-GPL v3)
|
||||
- [PEG Markdown Highlight](http://hasseg.org/peg-markdown-highlight/) (MIT License)
|
||||
- [Hoedown 3.0.7](https://github.com/hoedown/hoedown/) (ISC License)
|
||||
- [Marked](https://github.com/chjj/marked) (MIT License)
|
||||
- [Highlight.js](https://github.com/isagalaev/highlight.js/) (BSD License)
|
||||
- [Ionicons 2.0.1](https://github.com/driftyco/ionicons/) (MIT License)
|
||||
- [markdown-it 8.3.1](https://github.com/markdown-it/markdown-it) (MIT License)
|
||||
- [markdown-it-headinganchor 1.3.0](https://github.com/adam-p/markdown-it-headinganchor) (MIT License)
|
||||
- [markdown-it-task-lists 1.4.0](https://github.com/revin/markdown-it-task-lists) (ISC License)
|
||||
- [mermaid 7.0.0](https://github.com/knsv/mermaid) (MIT License)
|
||||
- [MathJax](https://www.mathjax.org/) (Apache-2.0)
|
||||
- [showdown](https://github.com/showdownjs/showdown) (Unknown)
|
||||
- [flowchart.js](https://github.com/adrai/flowchart.js) (MIT License)
|
||||
- 图标由九梦岛主(a326703305@qq.com)制作
|
||||
|
||||
# 代码许可
|
||||
VNote使用[MIT许可](http://opensource.org/licenses/MIT)。
|
15
VNote.pro
@ -1,15 +0,0 @@
|
||||
#-------------------------------------------------
|
||||
#
|
||||
# Project created by QtCreator 2016-10-01T11:03:59
|
||||
#
|
||||
#-------------------------------------------------
|
||||
|
||||
TEMPLATE = subdirs
|
||||
|
||||
CONFIG += c++11
|
||||
|
||||
SUBDIRS = hoedown \
|
||||
peg-highlight \
|
||||
src
|
||||
|
||||
src.depends = hoedown peg-highlight
|
@ -1 +0,0 @@
|
||||
theme: jekyll-theme-cayman
|
76
appveyor.yml
@ -1,76 +0,0 @@
|
||||
image: Visual Studio 2015
|
||||
|
||||
version: 1.11.{build}
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
environment:
|
||||
COMPILER: msvc
|
||||
VSVER: 14
|
||||
|
||||
matrix:
|
||||
- QT: C:\Qt\5.9\msvc2015_64
|
||||
PLATFORM: amd64
|
||||
- QT: C:\Qt\5.9\msvc2015
|
||||
PLATFORM: x86
|
||||
|
||||
clone_depth: 1
|
||||
|
||||
# scripts that run after cloning repository
|
||||
install:
|
||||
- set PATH=%QT%\bin\;C:\Qt\Tools\QtCreator\bin\;C:\Qt\QtIFW2.0.1\bin\;%PATH%
|
||||
- git submodule update --init --recursive
|
||||
|
||||
# scripts that run before build
|
||||
before_build:
|
||||
- call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %PLATFORM%
|
||||
# After calling vcvarsall.bat, %PLATFORM% will be X64 or x86
|
||||
- qmake --version
|
||||
- mkdir build
|
||||
- cd build
|
||||
- if "%PLATFORM%" EQU "X64" (qmake -r -spec win32-msvc CONFIG+=x86_64 CONFIG-=debug CONFIG+=release ../VNote.pro)
|
||||
- if "%PLATFORM%" EQU "x86" (qmake -r -spec win32-msvc CONFIG+=Win32 CONFIG-=debug CONFIG+=release ../VNote.pro)
|
||||
|
||||
# custom build scripts
|
||||
build_script:
|
||||
- nmake
|
||||
|
||||
# scripts that run after build
|
||||
after_build:
|
||||
- set vnote_version=1.11
|
||||
# Clone OpenSSL DLLs
|
||||
- git clone https://github.com/tamlok/openssl-utils.git openssl-utils.git
|
||||
- mkdir distrib\VNote
|
||||
- windeployqt.exe --dir .\distrib\VNote %APPVEYOR_BUILD_FOLDER%\build\src\release\VNote.exe
|
||||
- copy "%APPVEYOR_BUILD_FOLDER%\build\src\release\VNote.exe" "distrib\VNote\VNote.exe"
|
||||
- copy "%APPVEYOR_BUILD_FOLDER%\README.md" "distrib\VNote\README.md"
|
||||
- copy "%APPVEYOR_BUILD_FOLDER%\LICENSE" "distrib\VNote\LICENSE.txt"
|
||||
- echo %vnote_version% > "distrib\VNote\version.txt"
|
||||
- echo %APPVEYOR_REPO_COMMIT% >> "distrib\VNote\version.txt"
|
||||
- copy "distrib\VNote\VNote.exe" "distrib\VNote_win_%PLATFORM%.exe"
|
||||
# Copy OpenSSL DLLs
|
||||
- if "%PLATFORM%" EQU "X64" (xcopy "openssl-utils.git\win64\*.dll" "distrib\VNote")
|
||||
- if "%PLATFORM%" EQU "x86" (xcopy "openssl-utils.git\win32\*.dll" "distrib\VNote")
|
||||
- cd distrib
|
||||
- 7z a VNote_win_%PLATFORM%_portable_%vnote_version%.zip VNote
|
||||
|
||||
artifacts:
|
||||
- path: build\distrib\VNote_win_%PLATFORM%_portable_%vnote_version%.zip
|
||||
name: portable
|
||||
- path: build\distrib\VNote_win_%PLATFORM%.exe
|
||||
name: exe_only
|
||||
|
||||
deploy:
|
||||
- provider: BinTray
|
||||
username: tamlok
|
||||
api_key:
|
||||
secure: YJqzsVDlC2NYH2RgzbUMtjZWbSXOdWUiiENOIqImo31hOfeiB0MiVGmBLmKPAHEg
|
||||
subject: tamlok
|
||||
repo: vnote
|
||||
package: vnote
|
||||
version: 1.11
|
||||
publish: true
|
||||
override: true
|
||||
artifact: portable
|
143
changes.md
@ -1,143 +0,0 @@
|
||||
# Changes History
|
||||
## v1.11
|
||||
- Support themes;
|
||||
- Three built-in mordern themes;
|
||||
- One dark mode theme;
|
||||
- Vim mode
|
||||
- Support block cursor in Normal/Visual mode;
|
||||
- `=` to auto-indent selected lines as the first line;
|
||||
- Support custom external editors to open notes;
|
||||
- Enable `Ctrl+C`/`Ctrl+V` in Vim mode to copy/paste;
|
||||
- Support Flash Page to record ideas quickly;
|
||||
- Support previewing inline images;
|
||||
|
||||
## v1.10
|
||||
- Migrate to Qt 5.9.1;
|
||||
- Support Compact mode in main window;
|
||||
- Update icons;
|
||||
- Support custom startup pages;
|
||||
- Remove obsolete title marker when inserting new one;
|
||||
- Support Magic Words;
|
||||
- Vim mode
|
||||
- Share registers among all tabs;
|
||||
- Support `Ctrl+O` in Insert mode;
|
||||
- Add "Code Block", "Insert Link", and "Insert Image" tool bar buttons;
|
||||
- Support `Ctrl+Shift+T` to recover last closed tabs;
|
||||
- Support view read-only files in edit mode;
|
||||
- Refactor editor for speed;
|
||||
- Support templates when creating notes;
|
||||
- Support snippets;
|
||||
- Support file change check;
|
||||
- Support backup file (save changes automatically);
|
||||
|
||||
## v1.9
|
||||
- Support attachments of notes.
|
||||
- Add recycle bin to notebook to hold deleted files.
|
||||
- Refine Vim mode:
|
||||
- Support J and gJ to join line;
|
||||
- Support S, {, and };
|
||||
- <leader>w to save note;
|
||||
- Fix Y and D actions in Visual mode.
|
||||
- Support AppImage package for Linux.
|
||||
- More responsive and efficient syntax highlight and image preview.
|
||||
- More pleasant line distance.
|
||||
- More natural interaction of folder and note management.
|
||||
- Support inserting note name as title.
|
||||
- Support custom default mode to open a note.
|
||||
- Support auto heading sequence.
|
||||
- Support color column in fenced code block in edit mode.
|
||||
- Support line number in code block in both read and edit mode.
|
||||
- Support created time and modified time of notes, folders, and notebooks.
|
||||
- Support custom Markdown-it options, such as auto line break.
|
||||
- Confirm when cleaning up unused images.
|
||||
- Support custom Mathjax location.
|
||||
- Support custom style for code block highlights in read mode.
|
||||
- Double click on a tab to close it.
|
||||
|
||||
## v1.8
|
||||
- Support editing external files. VNote could open files from command line.
|
||||
- Support drag-and-drop to open external files.
|
||||
- Refine tab context menu.
|
||||
- Support system tray icon.
|
||||
- Refine Vim mode.
|
||||
- Make all tool buttons always visible and smaller.
|
||||
- Support custom file type suffix.
|
||||
- Make the name of notebook/folder/note case-insensitive.
|
||||
- Support links to internal notes in notes.
|
||||
|
||||
## v1.7
|
||||
- ATTENTION: please add font-size option to the "editor" section of your custom MDHL style.
|
||||
- Refine Vim mode (more functions, please refer to the shortcuts help).
|
||||
- Support Find in Vim mode.
|
||||
- Refine tab context menu.
|
||||
- Support Flowchart.js for flowchart.
|
||||
- Add toolbar for common text edit functions.
|
||||
- Support line number (both absolute and relative) in edit mode.
|
||||
- Support custom shortcuts.
|
||||
- Support [[, ]], [], ][, [{, ]} to navigate through titles in both edit and read mode.
|
||||
- Many minor bug fixes.
|
||||
|
||||
## v1.6
|
||||
- Support simple but powerful **Vim mode**.
|
||||
- Change the shortcut of ExitAndRead from `Ctrl+R` to `Ctrl+T`.
|
||||
- Add a edit status indicator in the status bar.
|
||||
- Dragging mouse with Ctrl and left button pressed to scroll in read and edit mode.
|
||||
- Refine highlighting cursor line.
|
||||
- Support subscript, superscript and footnote in markdown-it renderer.
|
||||
- Refactor outline logics to not show extra [EMPTY] headers.
|
||||
- Handle HTML comments correctly.
|
||||
- Provide a default root folder when adding notebooks.
|
||||
- Support check for updates.
|
||||
- Redraw app icons.
|
||||
- Many minor bug fixes.
|
||||
|
||||
## v1.5
|
||||
- Support logging in release mode.
|
||||
- Fix Chinese font matching in mdhl.
|
||||
- Fix VimagePreviewer to support optional title.
|
||||
- Refactor local image folder logics.
|
||||
- Support custom local image folder for both notebook scope and global scope.
|
||||
- Support constraining the width of images in read mode.
|
||||
- Fix and refine default style.
|
||||
- Centering the images and display the alt text as caption in read mode.
|
||||
- Support exporting a single note as PDF file.
|
||||
- Add "Open File Location" menu item in folder tree and note list.
|
||||
- Support highlighting trailing space.
|
||||
|
||||
## v1.4
|
||||
- Use `_vnote.json` as the config file.
|
||||
- More user friendly messages.
|
||||
- Delete notebook by deleting root directories one by one.
|
||||
- Refactor image preview logics to support previewing all images in edit mode.
|
||||
- Support constraining the width of previewed images to the edit window.
|
||||
- bugfix.
|
||||
|
||||
## v1.3
|
||||
- Support code block syntax highlight in edit mode.
|
||||
- A more pleasant AutoIndent and AutoList.
|
||||
- `Ctrl+<Num>` instead of `Ctrl+Alt+<Num>` to insert title.
|
||||
- Support custom Markdown CSS styles and editor styles.
|
||||
|
||||
## v1.2
|
||||
- Support **MathJax**.
|
||||
- Fix a crash on macOS.
|
||||
- Change default font family.
|
||||
- Refine tab order.
|
||||
- Better support for HiDPI.
|
||||
- Support zoom in/out page when reading.
|
||||
- Introduce **Captain Mode** and **Navigation Mode**.
|
||||
- A more user friendly popup opened notes list.
|
||||
- Support jumping to specified tab efficiently by num keys.
|
||||
- Add shortcuts documentation.
|
||||
- AutoList and AutoIndent.
|
||||
|
||||
## v1.1
|
||||
- Refine messages and dialogs. Add Chinese translations.
|
||||
- A new application icon.
|
||||
- Support install target for Linux.
|
||||
- Continuous build and deployment for Linux, macOS, and Windows.
|
||||
- Support both X64 and x86 version of Windows.
|
||||
- Add `.md` suffix automatically when creating a note.
|
||||
- A more user friendly insert dialog.
|
||||
- Support **Mermaid** diagram.
|
||||
- Add **markdown-it** as the default renderer. Support task list.
|
262
css/froala_blocks.css
Normal file
@ -0,0 +1,262 @@
|
||||
.btn {
|
||||
padding: 9px 26px;
|
||||
background-color: #528bff;
|
||||
color: #FFF;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
text-decoration: none;
|
||||
border: solid 2px #528bff;
|
||||
border-radius: 2px;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px; }
|
||||
.btn.btn-empty {
|
||||
background-color: transparent;
|
||||
color: #528bff; }
|
||||
.btn:hover {
|
||||
color: #FFF;
|
||||
background-color: #1f69ff;
|
||||
border-color: #1f69ff; }
|
||||
.btn.btn-black {
|
||||
color: #FFFFFF;
|
||||
background-color: #000000;
|
||||
border-color: #000000; }
|
||||
.btn.btn-black.btn-empty {
|
||||
background-color: transparent;
|
||||
color: #000000; }
|
||||
.btn.btn-black:hover {
|
||||
color: #FFF;
|
||||
background-color: #0d0d0d;
|
||||
border-color: #0d0d0d; }
|
||||
.btn.btn-white {
|
||||
color: #000000;
|
||||
background-color: #FFFFFF;
|
||||
border-color: #FFFFFF; }
|
||||
.btn.btn-white.btn-empty {
|
||||
background-color: transparent;
|
||||
color: #FFFFFF; }
|
||||
.btn.btn-white:hover {
|
||||
color: #000000;
|
||||
background-color: #DEDEDE;
|
||||
border-color: #DEDEDE; }
|
||||
.btn.btn-shadow {
|
||||
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
-moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); }
|
||||
.btn.btn-round {
|
||||
border-radius: 20px;
|
||||
-moz-border-radius: 20px;
|
||||
-webkit-border-radius: 20px;
|
||||
min-width: 150px; }
|
||||
|
||||
.team-1.fdb-block img {
|
||||
border-bottom: solid 5px #528bff;
|
||||
border-radius: 0 !important;
|
||||
-moz-border-radius: 0 !important;
|
||||
-webkit-border-radius: 0 !important; }
|
||||
|
||||
.team-1.fdb-block .fdb-box {
|
||||
padding: 0; }
|
||||
.team-1.fdb-block .fdb-box .content {
|
||||
padding: 20px; }
|
||||
|
||||
.team-2.fdb-block img {
|
||||
border-radius: 100% !important;
|
||||
-moz-border-radius: 100% !important;
|
||||
-webkit-border-radius: 100% !important; }
|
||||
|
||||
.team-3.fdb-block img {
|
||||
border-radius: 100% !important;
|
||||
-moz-border-radius: 100% !important;
|
||||
-webkit-border-radius: 100% !important;
|
||||
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
-moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); }
|
||||
|
||||
.fdb-block {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizelegibility;
|
||||
padding: 100px 0;
|
||||
color: #444444;
|
||||
position: relative;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
overflow: hidden;
|
||||
background-color: #FFFFFF; }
|
||||
.fdb-block.fdb-viewport {
|
||||
min-height: calc(100% - 2 * 100px); }
|
||||
.fdb-block.fdb-viewport .container {
|
||||
min-height: calc(100% - 2 * 100px); }
|
||||
.fdb-block.fdb-image-bg {
|
||||
color: #f2f2f2; }
|
||||
.fdb-block .fdb-box {
|
||||
background: #FFFFFF;
|
||||
color: #444444;
|
||||
padding: 60px 40px;
|
||||
border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
-moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
overflow: hidden; }
|
||||
.fdb-block .fdb-touch {
|
||||
border-top: solid 5px #528bff; }
|
||||
.fdb-block h1, .fdb-block .text-h1 {
|
||||
font-size: 2.75rem;
|
||||
margin-bottom: .5em;
|
||||
margin-top: .3em;
|
||||
font-weight: 400; }
|
||||
.fdb-block h2, .fdb-block .text-h2 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: .5em;
|
||||
margin-top: .3em;
|
||||
font-weight: 400; }
|
||||
.fdb-block h3, .fdb-block .text-h3 {
|
||||
font-size: 1.125rem;
|
||||
margin-bottom: .5em;
|
||||
margin-top: .3em;
|
||||
font-weight: 400; }
|
||||
.fdb-block h4, .fdb-block .text-h4 {
|
||||
font-size: 1rem;
|
||||
margin-bottom: .5em;
|
||||
margin-top: .3em;
|
||||
font-weight: 400; }
|
||||
.fdb-block h5, .fdb-block .text-h5 {
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: .5em;
|
||||
margin-top: .3em;
|
||||
font-weight: 400; }
|
||||
.fdb-block h6, .fdb-block .text-h6 {
|
||||
font-size: 0.75rem;
|
||||
margin-bottom: .5em;
|
||||
margin-top: .3em;
|
||||
font-weight: 400; }
|
||||
.fdb-block .font-weight-light {
|
||||
font-weight: 300; }
|
||||
.fdb-block img {
|
||||
border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px; }
|
||||
.fdb-block img + p, .fdb-block img + h3, .fdb-block img + h4 {
|
||||
margin-top: 20px; }
|
||||
.fdb-block img + h1, .fdb-block img + h2 {
|
||||
margin-top: 40px; }
|
||||
.fdb-block .col-fill-left {
|
||||
width: 50%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
z-index: 1; }
|
||||
.fdb-block .col-fill-left + div {
|
||||
position: relative;
|
||||
z-index: 2; }
|
||||
.fdb-block .col-fill-right {
|
||||
width: 50%;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
z-index: 1; }
|
||||
.fdb-block .col-fill-right + div {
|
||||
position: relative;
|
||||
z-index: 2; }
|
||||
.fdb-block img.fdb-icon {
|
||||
width: auto;
|
||||
width: 60px; }
|
||||
.fdb-block img.fdb-icon-round {
|
||||
width: auto;
|
||||
width: 60px;
|
||||
border-radius: 60px;
|
||||
-moz-border-radius: 60px;
|
||||
-webkit-border-radius: 60px; }
|
||||
.fdb-block .row-100 {
|
||||
height: 100px;
|
||||
width: 100%; }
|
||||
.fdb-block .row-50 {
|
||||
height: 50px;
|
||||
width: 100%; }
|
||||
.fdb-block .row-70 {
|
||||
height: 70px;
|
||||
width: 100%; }
|
||||
|
||||
footer {
|
||||
padding: 30px 0 !important; }
|
||||
footer.footer-large {
|
||||
padding: 100px 0 !important; }
|
||||
footer .flex-column .nav-link {
|
||||
padding-left: 0; }
|
||||
footer.bg-dark {
|
||||
background: #2d313c;
|
||||
color: #f2f2f2; }
|
||||
footer.bg-dark a {
|
||||
color: #f2f2f2; }
|
||||
footer a {
|
||||
color: #444444; }
|
||||
|
||||
header {
|
||||
background-color: #FFFFFF; }
|
||||
header .navbar-nav a.nav-link {
|
||||
color: #777; }
|
||||
header .navbar-nav a.nav-link:hover {
|
||||
color: #528bff; }
|
||||
header .navbar-nav .active a.nav-link {
|
||||
color: #444444; }
|
||||
header .navbar-toggler-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); }
|
||||
header.bg-dark .navbar-nav a.nav-link {
|
||||
color: #dcdcdc; }
|
||||
header.bg-dark .navbar-nav a.nav-link:hover {
|
||||
color: #528bff; }
|
||||
header.bg-dark .navbar-nav .active a.nav-link {
|
||||
color: #FFFFFF; }
|
||||
header.bg-dark .navbar-toggler-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#fff' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E"); }
|
||||
|
||||
header + header {
|
||||
border-top: solid 1px #EEE; }
|
||||
|
||||
.bg-dark {
|
||||
background-color: #2d313c;
|
||||
color: #f2f2f2; }
|
||||
|
||||
.bg-gray {
|
||||
background-color: #fafafa;
|
||||
color: #444444; }
|
||||
|
||||
.br {
|
||||
border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
overflow: hidden; }
|
||||
|
||||
.br-0 {
|
||||
border-radius: 0 !important;
|
||||
-moz-border-radius: 0 !important;
|
||||
-webkit-border-radius: 0 !important; }
|
||||
|
||||
.heart {
|
||||
color: #df584e;
|
||||
font-weight: bold; }
|
||||
|
||||
*.text-light {
|
||||
font-weight: 300 !important; }
|
||||
|
||||
.sl-1 {
|
||||
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
-moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
z-index: 2; }
|
||||
|
||||
table tr.no-border th, table tr.no-border td {
|
||||
border-top: 0; }
|
6
debian/changelog
vendored
@ -1,6 +0,0 @@
|
||||
vnote (1.8-0ubuntu1) zesty; urgency=medium
|
||||
|
||||
* Initial release
|
||||
* Adjusted the Makefile to fix $(DESTDIR) problems.
|
||||
|
||||
-- Le Tan <tamlokveer@gmail.com> Sun, 20 Aug 2017 09:32:22 +0800
|
1
debian/compat
vendored
@ -1 +0,0 @@
|
||||
9
|
17
debian/control
vendored
@ -1,17 +0,0 @@
|
||||
Source: vnote
|
||||
Section: editors
|
||||
Priority: optional
|
||||
Maintainer: Le Tan <tamlokveer@gmail.com>
|
||||
Build-Depends: debhelper (>=9), qt5-default (>= 5.7.1), libqt5svg5-dev, qtwebengine5-dev, libqt5webchannel5-dev, qtpositioning5-dev
|
||||
Standards-Version: 3.9.6
|
||||
Homepage: https://tamlok.github.io/vnote
|
||||
Vcs-Browser: https://github.com/tamlok/vnote.git
|
||||
|
||||
Package: vnote
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}, libQt5WebEngineWidgets (>= 5.7.1), libQt5WebEngineCore, libQt5PrintSupport, libQt5Widgets, libQt5Gui, libQt5WebChannel, libQt5Network, libQt5Core
|
||||
Suggests: libssl
|
||||
Description: A Vim-inspired note-taking application for Markdown.
|
||||
VNote is a cross-platform, free note-taking application, designed especially
|
||||
for Markdown. VNote provides both simple note management and pleasant Markdown
|
||||
edit experience.
|
7
debian/copyright
vendored
@ -1,7 +0,0 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: vnote
|
||||
Source: https://tamlok.github.io/vnote
|
||||
|
||||
Files: *
|
||||
Copyright: 2017 Le Tan <tamlokveer@gmail.com>
|
||||
License: MIT License
|
28
debian/rules
vendored
@ -1,28 +0,0 @@
|
||||
#!/usr/bin/make -f
|
||||
# See debhelper(7) (uncomment to enable)
|
||||
# output every command that modifies files on the build system.
|
||||
#export DH_VERBOSE = 1
|
||||
|
||||
|
||||
# see FEATURE AREAS in dpkg-buildflags(1)
|
||||
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||
|
||||
# see ENVIRONMENT in dpkg-buildflags(1)
|
||||
# package maintainers to append CFLAGS
|
||||
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
|
||||
# package maintainers to append LDFLAGS
|
||||
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
|
||||
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
|
||||
# dh_make generated override targets
|
||||
# This is example for Cmake (See https://bugs.debian.org/641051 )
|
||||
#override_dh_auto_configure:
|
||||
# dh_auto_configure -- # -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH)
|
||||
override_dh_shlibdeps:
|
||||
dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info
|
||||
|
||||
export QT_SELECT=qt5
|
1
debian/source/format
vendored
@ -1 +0,0 @@
|
||||
3.0 (quilt)
|
1
hoedown
@ -1 +0,0 @@
|
||||
Subproject commit e63d2160a7d9895defc253d933b58a83ac54f81a
|
1797
imgs/alt_wide_1.svg
Normal file
After Width: | Height: | Size: 274 KiB |
166
imgs/concentration.svg
Normal file
@ -0,0 +1,166 @@
|
||||
<?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="2300"
|
||||
height="1600"
|
||||
viewBox="0 0 608.54165 423.33335"
|
||||
version="1.1"
|
||||
id="svg2731"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
|
||||
sodipodi:docname="concentration.svg">
|
||||
<defs
|
||||
id="defs2725" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.25588427"
|
||||
inkscape:cx="956.37413"
|
||||
inkscape:cy="1156.3016"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:pagecheckerboard="true"
|
||||
showborder="true"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3630" />
|
||||
<sodipodi:guide
|
||||
position="375.70832,150.81251"
|
||||
orientation="0,1"
|
||||
id="guide4854"
|
||||
inkscape:locked="false" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata2728">
|
||||
<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="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,126.33335)"
|
||||
style="display:inline">
|
||||
<g
|
||||
id="Icon_6_"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,236.53747,67.341653)"
|
||||
style="fill:#00897b;fill-opacity:1">
|
||||
<g
|
||||
id="g4770"
|
||||
style="fill:#00897b;fill-opacity:1">
|
||||
<path
|
||||
id="path4768"
|
||||
d="m 64,368 v 80 h 80 L 379.727,212.271 299.728,132.273 Z M 441.602,150.398 c 8.531,-8.531 8.531,-21.334 0,-29.865 L 391.467,70.398 c -8.531,-8.531 -21.334,-8.531 -29.865,0 l -39.468,39.469 79.999,79.998 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g4852"
|
||||
transform="translate(-240.24168,-127.23813)"
|
||||
style="fill:#00897b;fill-opacity:1">
|
||||
<g
|
||||
id="g4836"
|
||||
style="fill:#00897b;fill-opacity:1">
|
||||
<g
|
||||
id="g4793"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,353.61563,66.283319)"
|
||||
style="fill:#00897b;fill-opacity:1">
|
||||
<g
|
||||
id="g4785"
|
||||
style="fill:#00897b;fill-opacity:1">
|
||||
<path
|
||||
id="path4783"
|
||||
d="m 395.74999,172.9 c 0,-77.8 -66.1,-140.9 -144,-140.9 -77.9,0 -144,63.1 -144,141 0,31 13.2,59 30.2,83 h -0.3 c 10.9,15 21.4,27.7 31.5,45 22,37.8 18.6,74.3 18.7,81.6 v 1.4 h 32 V 256 l -32,-64 h 16.6 l 31.4,64 v 128 h 32 V 256 l 31.4,-64 h 16.5 l -32,64 v 128 h 32 v -1.4 c 0,-8.9 -3.6,-43.8 18.4,-81.6 10.1,-17.3 20.6,-30 31.5,-45 h -0.1 c 17,-24 30.2,-52 30.2,-83.1 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
</g>
|
||||
<rect
|
||||
id="rect4787"
|
||||
height="16"
|
||||
width="64"
|
||||
y="464"
|
||||
x="224"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
<rect
|
||||
id="rect4789"
|
||||
height="16"
|
||||
width="96"
|
||||
y="432"
|
||||
x="208"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
<rect
|
||||
id="rect4791"
|
||||
height="16"
|
||||
width="96"
|
||||
y="400"
|
||||
x="208"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g4843"
|
||||
transform="translate(236.00832,-121.8274)"
|
||||
style="fill:#00897b;fill-opacity:1">
|
||||
<g
|
||||
transform="matrix(0.26458333,0,0,0.26458333,120.58386,60.753527)"
|
||||
id="g4816"
|
||||
style="fill:#00897b;fill-opacity:1">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
d="M 400,172.9 C 400,95.1 333.9,32 256,32 178.1,32 112,95.1 112,173 c 0,31 13.2,59 30.2,83 h -0.3 c 10.9,15 21.4,27.7 31.5,45 22,37.8 18.6,74.3 18.7,81.6 v 1.4 h 128 v -1.4 c 0,-8.9 -3.6,-43.8 18.4,-81.6 10.1,-17.3 20.6,-30 31.5,-45 h -0.1 C 386.8,232 400,204 400,172.9 Z m -49,81.5 c -0.6,0.8 -1.1,1.5 -1.7,2.3 -8.1,10.9 -16.5,22.2 -24.7,36.1 -17.3,29.7 -20.4,57.9 -20.8,75.1 H 288 V 256 l 32,-64 H 303.4 L 272,256 V 368 H 240 V 256 L 208.6,192 H 192 l 32,64 v 112 h -15.9 c -0.5,-17.2 -3.9,-45.7 -20.9,-75 -4.5,-7.7 -9.1,-15 -13.7,-21 h 0.2 L 155.1,246.4 C 139.3,224.8 128,199.3 128,172.8 c 0,-33.4 16,-64.8 39.6,-88.4 23.6,-23.6 55,-36.7 88.4,-36.7 33.4,0 64.8,13.1 88.4,36.7 23.6,23.6 39.6,55 39.6,88.4 0,26.5 -11.3,51.9 -27.1,73.5 z"
|
||||
id="path4808"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
<rect
|
||||
x="224"
|
||||
y="464"
|
||||
width="64"
|
||||
height="16"
|
||||
id="rect4810"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
<rect
|
||||
x="208"
|
||||
y="432"
|
||||
width="96"
|
||||
height="16"
|
||||
id="rect4812"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
<rect
|
||||
x="208"
|
||||
y="400"
|
||||
width="96"
|
||||
height="16"
|
||||
id="rect4814"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<style
|
||||
id="style3416"
|
||||
type="text/css"><![CDATA[
|
||||
.st0{fill:#010101;}
|
||||
]]></style>
|
||||
</svg>
|
After Width: | Height: | Size: 5.7 KiB |
119
imgs/cross_platform.svg
Normal file
@ -0,0 +1,119 @@
|
||||
<?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="2300"
|
||||
height="1600"
|
||||
viewBox="0 0 608.54165 423.33335"
|
||||
version="1.1"
|
||||
id="svg2731"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
|
||||
sodipodi:docname="cross_platform.svg">
|
||||
<defs
|
||||
id="defs2725" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.51176853"
|
||||
inkscape:cx="1218.1782"
|
||||
inkscape:cy="503.54243"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:pagecheckerboard="true"
|
||||
showborder="true" />
|
||||
<metadata
|
||||
id="metadata2728">
|
||||
<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="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,126.33335)">
|
||||
<g
|
||||
id="g3388"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,432.60192,10.911989)"
|
||||
style="fill:#00897b;fill-opacity:1">
|
||||
<path
|
||||
id="path3384"
|
||||
d="m 333.6,153.9 c -33.6,0 -47.8,16.5 -71.2,16.5 -24,0 -42.3,-16.4 -71.4,-16.4 -28.5,0 -58.9,17.9 -78.2,48.4 -27.1,43 -22.5,124 21.4,193 15.7,24.7 36.7,52.4 64.2,52.7 0.2,0 0.3,0 0.5,0 23.9,0 31,-16.1 63.9,-16.3 0.2,0 0.3,0 0.5,0 32.4,0 38.9,16.2 62.7,16.2 0.2,0 0.3,0 0.5,0 27.5,-0.3 49.6,-31 65.3,-55.6 11.3,-17.7 15.5,-26.6 24.2,-46.6 -63.5,-24.8 -73.7,-117.4 -10.9,-152.9 -19.2,-24.7 -46.1,-39 -71.5,-39 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
<path
|
||||
id="path3386"
|
||||
d="m 326.2,64 c -20,1.4 -43.3,14.5 -57,31.6 -12.4,15.5 -22.6,38.5 -18.6,60.8 0.5,0 1,0 1.6,0 21.3,0 43.1,-13.2 55.8,-30.1 12.3,-16.1 21.6,-38.9 18.2,-62.3 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
</g>
|
||||
<g
|
||||
id="g3404"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,48.54423,14.566477)"
|
||||
style="fill:#00897b;fill-opacity:1">
|
||||
<path
|
||||
id="path3400"
|
||||
d="m 254.7,101.3 c -0.6,1 -1.2,2 -1.7,3.1 1.8,-3.5 4.3,-6.5 7.4,-8.7 -2.1,1.3 -4.1,3.1 -5.7,5.6 -0.6,1 1.6,-2.5 0,0 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
<path
|
||||
id="path3402"
|
||||
d="m 426.3,396 c -6.7,-4 -13.2,-11 -12,-18.8 2.3,-15.3 2.5,-21.5 -0.2,-25.8 -1.899,-3.2 -5.5,-5 -8.6,-5.801 2,-2.5 3.1,-5.399 3.8,-10.899 1.842,-14.168 -3.239,-30.67 -6.61,-44.243 -4.445,-17.9 -10.319,-34.014 -20.432,-49.522 -16.489,-25.288 -37.253,-46.804 -51.857,-73.435 -9.7,-29 -4.301,-47.1 -4.801,-66.2 C 324.7,64 298.7,32 261,32 h -14 c -37.5,0 -58,26.9 -58,60 0.697,16.666 1.352,33.333 2,50 0.769,19.756 1.98,37.667 -8,55.3 -7.033,12.488 -19.571,22.43 -28.592,33.478 -9.525,11.665 -13.06,23.669 -17.859,37.619 -5.987,17.401 -21.275,30.906 -24.886,48.929 -1.658,8.271 2.726,17.857 -0.83,24.693 -3.698,7.109 -0.813,14.469 -2.833,21.682 -3.505,11.729 -20.731,10.729 -30.215,11.508 -17.602,1.443 -11.051,17.988 -7.843,29.701 5.598,20.435 -19.54,42.078 11.032,50.471 13.989,3.84 28.586,5.13 42.727,8.32 13.981,3.154 26.917,8.612 40.396,13.262 10.469,3.611 20.262,4.146 30.323,-1.232 10.982,-5.87 18.947,-15.083 31.691,-17 24.707,-3.716 54.829,-4.169 79.373,0.63 7.507,1.468 10.075,6.525 14.905,11.766 8.145,8.838 21.514,10.125 32.794,8.22 11.89,-2.01 20.846,-9.466 29.686,-17.112 12.526,-10.835 26.137,-17.996 40.791,-25.547 7.439,-3.833 22.178,-9.388 24.159,-18.903 C 450.151,406.573 433.642,400.383 426.3,396 Z M 202,451.3 c -1.3,13 -13.9,20.3 -25.4,19.3 -19.502,-1.649 -37.86,-12.237 -57.164,-15.75 -9.393,-1.709 -47.351,-3.173 -46.943,-17.079 0.215,-7.361 5.751,-11.666 7.567,-18.415 2.811,-10.449 -5.753,-19.932 -6.32,-30.16 -0.447,-8.081 10.73,-7.607 16.288,-8.053 8.761,-0.702 19.157,-2.012 23.572,-11.043 1.6,-3.3 -0.5,-15 1.9,-21.8 3.527,-10.155 15.489,-8.949 23.3,-4.899 17.762,9.067 28.943,35.546 38.533,51.821 9.725,16.506 26.712,35.621 24.667,56.078 z m 116.3,-65.6 c 2.289,11.787 4.224,24.387 1.7,36.3 -2.016,9.516 -8.562,15.614 -12.7,24 -2.2,-6.8 11.3,-16.7 9.8,-33.7 -0.042,-0.475 -0.947,-10.882 -1.036,-10.834 -1.979,1.082 -3.825,5.075 -5.304,6.73 -4.809,5.383 -10.771,9.658 -16.729,13.677 -13.797,9.306 -29.221,15.058 -45.83,16.627 -21,2 -38,-10.5 -38,-10.5 2.517,8.305 8.992,21 1.1,28.3 1.743,-15.648 -4.876,-28.06 -11.6,-41.7 46.374,-24.639 -41.363,-64.397 -53,-79.8 -10.476,-13.896 -5.553,-31.83 -2.7,-43.5 -3.443,13.775 -3.959,41.956 14,46.8 7.922,2.169 5.605,-37.071 5.984,-41.463 1.58,-18.312 7.721,-35.997 18.343,-51.015 3.399,-4.806 1.863,-9.533 2.673,-15.322 1.693,-10.668 7.295,-20.547 13.488,-29.225 5.169,-7.243 1.735,-12.618 3.014,-21.055 0.727,-4.795 5.118,3.193 5.798,4.279 4,6.5 16.7,22.7 24.7,22 13.277,-1.161 24.692,-15.938 36.015,-22.046 3.591,-1.937 14.41,-5.242 15.866,-9.632 1.768,-5.33 -12.666,-0.161 -14.114,0.583 -10.782,5.535 -31.018,21.448 -43.867,15.295 -8.762,-4.195 -10.691,-14.994 -17.2,-20.5 15.4,13.6 21.5,10.5 27.6,9.1 9.665,-2.182 18.797,-6.486 27.8,-10.5 4.217,-1.88 20.107,-4.041 22.301,-8.7 2.745,-5.834 -5.05,-6.726 -8.068,-3.58 -6.251,6.515 -15.853,8.981 -24.097,11.787 -18.885,6.427 -37.644,9.131 -51.835,-8.607 -8.354,-10.212 15.558,-22.504 22.2,-28.8 0,0 1,-7.2 -0.6,-12.7 -1.9,-6.5 -7.8,-9.3 -11.9,-8.1 -4.1,1.1 -8,5.5 -6.8,14.8 1,8.3 7,11 7,11 0,0 -2.7,3.5 -5.2,4.7 0,0 -0.8,-0.3 -3.5,-6.3 -2.7,-6 -6.6,-19.5 -0.3,-31.1 6.3,-11.6 19.6,-5.2 23.8,3.8 3.9,8.3 2.4,22.7 2.4,22.7 3.358,-0.843 6.835,-1.3 10.3,-1.3 4.242,0 6.814,4.563 10.6,4.8 -0.635,0 -0.564,-18.124 -0.233,-20.078 1.099,-6.49 4.112,-13.619 9.933,-17.222 16.087,-11.491 34.6,3.916 34.6,21.2 0.024,4.486 -0.137,9.215 -1.199,13.6 -0.659,2.637 -1.582,8.469 -5.114,9.177 -1.145,0.23 -10.683,-0.431 -7.985,-3.277 12.807,-12.812 -1.822,-38.207 -17.4,-24.5 -5.601,4.199 -5.483,13.833 -4.801,20 1.098,9.141 20.51,11.541 26.852,13.78 8.224,2.903 7.943,9.626 6.679,17.116 -1.9,11.26 -2.815,18.792 4.381,28.214 7.344,9.616 12.929,20.086 15.915,31.878 1.483,5.859 1.665,10.843 5.621,15.57 5.983,7.151 10.619,14.805 13.291,23.817 5.225,17.621 6.513,35.422 6.162,53.625 -0.5,8.199 -2.1,15 8.3,9.699 4,-2 6.5,-2.899 11,-3.699 3.101,-6.301 4.4,-18.301 4.5,-24.301 0.2,-13.5 -0.3,-41.5 -27.699,-71.5 0,0 28.5,21.7 33,62 2.5,22.301 -2,34.4 -2,34.4 5.3,1.3 10.8,5.3 13.6,9.8 -0.133,-0.22 1.038,1.748 1.179,1.864 -9.811,-8.171 -31.708,-12.859 -39.679,0.236 -2,3.3 -3.1,7.6 -3.2,11.699 -7.1,-1.199 -12.399,0 -16.8,4.9 C 313.5,358.1 316.3,375.3 318.3,385.7 c 2,10.3 -2,-10.4 0,0 z M 220.6,124.9 c -0.2,-4.9 -2.1,-9 -4.7,-10.8 0.3,-0.3 0.8,-0.7 1.7,-0.4 1.4,0.4 3.5,2.7 4.3,6.3 0.7,2.9 0.4,7.7 -1.4,7.7 -0.002,-0.934 0.031,-1.867 0.1,-2.8 -0.2,-4.9 0,0.9 0,0 z m 54.7,-12 c 0.4,-0.3 1.101,-0.6 2.3,-0.3 1.801,0.4 4.4,2.8 5.301,6.2 0.699,2.8 0.1,7.4 -2.2,7.3 -0.005,-0.903 0.062,-1.803 0.2,-2.7 -0.101,-4.6 -2.301,-8.6 -5.601,-10.5 0.4,-0.3 3.3,1.9 0,0 z m 137.1,320 c -17.504,7.553 -31.304,19.968 -47.117,30.19 -13.824,8.937 -38.156,8.313 -40.022,-12.879 -1.565,-17.776 4.184,-35.288 1.84,-53.357 -1.587,-12.239 -8.534,-28.906 -2.301,-40.854 2.7,-5.1 8.101,-5.5 12.5,-4 3.193,13.267 13.932,23.6 28,23.6 17.031,0 23.542,-14.008 34,-24.5 3.4,0.2 7.4,1.101 8.8,4.801 2.301,5.8 -1.899,17.5 -1.8,22.3 0.3,15.7 9,19.2 26.4,29 15.3,8.799 4.2,15.099 -20.3,25.699 -24.1,10.4 24.5,-10.6 0,0 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
</g>
|
||||
<g
|
||||
id="g3426"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,240.67323,20.633494)"
|
||||
style="fill:#00897b;fill-opacity:1">
|
||||
<path
|
||||
id="path3418"
|
||||
d="M 480,265 H 232 v 179 l 248,36 z"
|
||||
class="st0"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
<path
|
||||
id="path3420"
|
||||
d="M 216,265 H 32 v 150 l 184,26.7 z"
|
||||
class="st0"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
<path
|
||||
id="path3422"
|
||||
d="M 480,32 232,67.4 V 249 h 248 z"
|
||||
class="st0"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
<path
|
||||
id="path3424"
|
||||
d="M 216,69.7 32,96 v 153 h 184 z"
|
||||
class="st0"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
<style
|
||||
id="style3416"
|
||||
type="text/css"><![CDATA[
|
||||
.st0{fill:#010101;}
|
||||
]]></style>
|
||||
</svg>
|
After Width: | Height: | Size: 8.9 KiB |
123
imgs/for_programmers.svg
Normal file
@ -0,0 +1,123 @@
|
||||
<?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="2300"
|
||||
height="1600"
|
||||
viewBox="0 0 608.54165 423.33335"
|
||||
version="1.1"
|
||||
id="svg2731"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
|
||||
sodipodi:docname="for_programmers.svg">
|
||||
<defs
|
||||
id="defs2725" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.51176853"
|
||||
inkscape:cx="857.7003"
|
||||
inkscape:cy="873.74957"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:pagecheckerboard="true"
|
||||
showborder="true"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3630" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata2728">
|
||||
<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="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,126.33335)"
|
||||
style="display:inline">
|
||||
<g
|
||||
id="g4604"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,241.90061,17.599853)"
|
||||
style="fill:#00897b;fill-opacity:1">
|
||||
<g
|
||||
id="g4596"
|
||||
style="fill:#00897b;fill-opacity:1">
|
||||
<circle
|
||||
id="circle4590"
|
||||
r="24"
|
||||
cy="256.16599"
|
||||
cx="184.166"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
<circle
|
||||
id="circle4592"
|
||||
r="24"
|
||||
cy="256.16599"
|
||||
cx="256.16599"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
<circle
|
||||
id="circle4594"
|
||||
r="24"
|
||||
cy="256.16599"
|
||||
cx="328.16599"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
</g>
|
||||
<g
|
||||
id="g4602"
|
||||
style="fill:#00897b;fill-opacity:1">
|
||||
<path
|
||||
id="path4598"
|
||||
d="m 168,392 c -6.143,0 -12.285,-2.344 -16.971,-7.029 l -112,-112 c -9.373,-9.373 -9.373,-24.569 0,-33.941 l 112,-112 c 9.373,-9.372 24.568,-9.372 33.941,0 9.371,9.372 9.371,24.568 0,33.941 L 89.941,256 184.97,351.029 c 9.371,9.372 9.371,24.568 0,33.941 -4.687,4.686 -10.827,7.03 -16.97,7.03 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
<path
|
||||
id="path4600"
|
||||
d="m 344,392 c 6.143,0 12.285,-2.344 16.971,-7.029 l 112,-112 c 9.373,-9.373 9.373,-24.569 0,-33.941 l -112,-112 c -9.373,-9.372 -24.568,-9.372 -33.941,0 -9.371,9.372 -9.371,24.568 0,33.941 L 422.059,256 327.03,351.029 c -9.371,9.372 -9.371,24.568 0,33.941 4.687,4.686 10.827,7.03 16.97,7.03 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
id="path4621"
|
||||
d="m 486.91272,85.716955 c 0.15875,-1.42875 0.23812,-2.64583 0.23812,-3.65125 0,-5.18583 -0.87312,-5.21229 -4.23333,-5.21229 h -19.97604 c 1.93146,-3.175 3.04271,-6.45583 3.04271,-9.78958 0,-10.02771 -15.16063,-14.9225 -15.16063,-23.28334 0,-3.09562 1.34938,-5.63562 2.46063,-9.23396 -7.01146,1.85209 -12.54125,8.86355 -12.54125,16.29834 0,12.77937 14.89604,12.88521 14.89604,22.43667 0,1.19062 -0.37042,2.24895 -0.55563,3.57187 h -14.79021 c 0.21167,-0.79375 0.34396,-1.64042 0.34396,-2.46062 0,-6.0325 -10.34521,-8.96938 -10.34521,-13.97 0,-1.85209 0.26459,-3.38667 0.84667,-5.55625 -3.41312,1.34937 -7.48771,5.29166 -7.48771,9.73666 0,7.06438 8.44021,7.75229 9.73667,12.25021 h -26.67 c -3.36021,0 -4.23334,0.0265 -4.23334,5.21229 0,5.18584 2.0373,16.21896 7.48771,29.368745 5.45042,13.14979 11.7475,18.94417 16.1925,22.80709 l 0.0265,-0.0529 c 1.34938,1.21708 3.12209,1.93146 5.08,1.93146 h 27.09334 c 1.95791,0 3.73062,-0.71438 5.08,-1.93146 l 0.0265,0.0529 c 2.38125,-2.06375 5.29166,-4.70959 8.30791,-8.7048 1.24354,0.52917 2.59292,0.97896 4.07459,1.32292 2.2225,0.52917 4.445,0.79375 6.56166,0.79375 6.35,0 12.065,-2.43417 16.08667,-6.82625 3.54541,-3.86292 5.5827,-9.10167 5.5827,-14.34042 0,-10.292285 -7.40833,-18.891245 -17.17145,-20.769785 z m -4.49792,33.549165 c -2.27542,0 -4.1275,-0.3175 -5.84729,-1.11125 1.05833,-2.11667 2.09021,-4.20688 3.09562,-6.64104 2.6723,-6.45584 4.52438,-12.435415 5.715,-17.409585 5.82084,1.13771 10.23938,6.297095 10.23938,12.461875 0,6.00604 -4.55084,12.7 -13.20271,12.7 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="stroke-width:0.26458335;fill:#00897b;fill-opacity:1" />
|
||||
<path
|
||||
id="path4631"
|
||||
d="m 206.74542,26.066655 h -13.75834 c -0.87312,0 -1.5875,0.71437 -1.5875,1.5875 v 1.48166 c -1.05833,1.05834 -2.88395,2.59292 -4.70958,2.59292 -2.77812,0 -3.91583,-4.33917 -6.90562,-4.86833 -2.9898,-0.52917 -7.19667,-0.79375 -10.02771,-0.79375 -2.83104,0 -13.75834,0.18521 -26.9875,5.68854 -13.22917,5.50333 -18.97063,17.17146 -19.12938,21.53708 -0.0794,2.30188 0.71438,5.58271 2.64584,2.93688 1.93145,-2.64584 9.33979,-11.19188 15.26645,-11.82688 5.90021,-0.60854 12.09146,0.21167 16.85396,4.55083 4.57729,4.15396 5.63563,8.14917 5.63563,19.44688 l -1.74625,0.52917 c -1.13771,0.34395 -1.85209,1.50812 -1.85209,2.38125 0,0 -0.0529,1.71979 -0.0529,18.62666 0,25.029585 -2.8575,53.075415 -2.8575,53.075415 0,0.87312 0.71438,1.5875 1.5875,1.5875 h 22.225 c 0.87313,0 1.5875,-0.71438 1.5875,-1.5875 0,0 -2.8575,-28.04583 -2.8575,-53.075415 0,-16.90687 -0.0529,-18.62666 -0.0529,-18.62666 0,-0.87313 -0.60854,-2.06375 -1.85208,-2.38125 l -2.11666,-0.52917 c 0,-10.10708 -0.10583,-14.2875 1.66688,-17.43604 1.85208,-3.28084 5.9002,-6.05896 7.85812,-6.64104 1.56104,-0.47625 4.23333,0.34395 5.82083,1.71979 v 3.83646 c 0,0.87312 0.71438,1.5875 1.5875,1.5875 h 13.75834 c 0.87312,0 1.5875,-0.71438 1.5875,-1.5875 v -22.225 c 0,-0.87313 -0.71438,-1.5875 -1.5875,-1.5875 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="stroke-width:0.26458335;fill:#00897b;fill-opacity:1" />
|
||||
<path
|
||||
id="path4641"
|
||||
d="M 561.32457,222.42865 V -86.035855 H 47.217104 V 222.42865 H 234.65212 v 17.1369 h -68.54766 v 17.13694 h 275.26171 v -17.13694 h -68.54766 v -17.1369 z M 64.354008,-68.898951 H 544.18767 V 205.29174 H 64.354008 Z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="stroke-width:1.07105732;fill:#00897b;fill-opacity:1" />
|
||||
</g>
|
||||
<style
|
||||
id="style3416"
|
||||
type="text/css"><![CDATA[
|
||||
.st0{fill:#010101;}
|
||||
]]></style>
|
||||
</svg>
|
After Width: | Height: | Size: 6.8 KiB |
11475
imgs/highly_customizable.svg
Normal file
After Width: | Height: | Size: 859 KiB |
50
imgs/img_attachment.svg
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<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"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="512px"
|
||||
height="512px"
|
||||
viewBox="0 0 512 512"
|
||||
enable-background="new 0 0 512 512"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="android-attach.svg"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06"><metadata
|
||||
id="metadata4940"><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><defs
|
||||
id="defs4938" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="640"
|
||||
inkscape:window-height="480"
|
||||
id="namedview4936"
|
||||
showgrid="false"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:zoom="2.2617188"
|
||||
inkscape:cx="256"
|
||||
inkscape:cy="256"
|
||||
inkscape:current-layer="Layer_1" /><g
|
||||
id="Icon_3_"
|
||||
style="fill:#00897b;fill-opacity:1"><g
|
||||
id="g4932"
|
||||
style="fill:#00897b;fill-opacity:1"><path
|
||||
d="M341.334,128v234.666C341.334,409.604,302.938,448,256,448c-46.937,0-85.333-38.396-85.333-85.334V117.334 C170.667,87.469,194.135,64,224,64c29.864,0,53.333,23.469,53.333,53.334v245.333c0,11.729-9.605,21.333-21.334,21.333 c-11.729,0-21.333-9.604-21.333-21.333V160h-32v202.667C202.667,392.531,226.135,416,256,416 c29.865,0,53.334-23.469,53.334-53.333V117.334C309.334,70.401,270.938,32,224,32c-46.938,0-85.334,38.401-85.334,85.334v245.332 C138.667,427.729,190.938,480,256,480c65.062,0,117.334-52.271,117.334-117.334V128H341.334z"
|
||||
id="path4930"
|
||||
style="fill:#00897b;fill-opacity:1" /></g></g></svg>
|
After Width: | Height: | Size: 2.3 KiB |
54
imgs/img_codeblock.svg
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<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"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="512px"
|
||||
height="512px"
|
||||
viewBox="0 0 512 512"
|
||||
enable-background="new 0 0 512 512"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="android-attach.svg"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06"><metadata
|
||||
id="metadata4940"><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><defs
|
||||
id="defs4938" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="640"
|
||||
inkscape:window-height="480"
|
||||
id="namedview4936"
|
||||
showgrid="false"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:zoom="2.2617188"
|
||||
inkscape:cx="256"
|
||||
inkscape:cy="238.31434"
|
||||
inkscape:current-layer="Layer_1" /><g
|
||||
id="g5069"
|
||||
transform="translate(0,-5e-4)"
|
||||
style="fill:#00897b;fill-opacity:1"><path
|
||||
id="path5065"
|
||||
d="m 168,392 c -6.143,0 -12.285,-2.344 -16.971,-7.029 l -112,-112 c -9.373,-9.373 -9.373,-24.569 0,-33.941 l 112,-112 c 9.373,-9.372 24.568,-9.372 33.941,0 9.371,9.372 9.371,24.568 0,33.941 L 89.941,256 184.97,351.029 c 9.371,9.373 9.371,24.568 0,33.941 -4.687,4.686 -10.827,7.03 -16.97,7.03 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" /><path
|
||||
id="path5067"
|
||||
d="m 344,392 c 6.143,0 12.285,-2.344 16.971,-7.029 l 112,-112 c 9.373,-9.373 9.373,-24.569 0,-33.941 l -112,-112 c -9.373,-9.372 -24.568,-9.372 -33.941,0 -9.371,9.372 -9.371,24.568 0,33.941 L 422.059,256 327.03,351.029 c -9.371,9.373 -9.371,24.568 0,33.941 4.687,4.686 10.827,7.03 16.97,7.03 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" /></g></svg>
|
After Width: | Height: | Size: 2.4 KiB |
47
imgs/img_expect_more.svg
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<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"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="512px"
|
||||
height="512px"
|
||||
viewBox="0 0 512 512"
|
||||
enable-background="new 0 0 512 512"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="android-attach.svg"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06"><metadata
|
||||
id="metadata4940"><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><defs
|
||||
id="defs4938" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="640"
|
||||
inkscape:window-height="480"
|
||||
id="namedview4936"
|
||||
showgrid="false"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:zoom="2.2617188"
|
||||
inkscape:cx="256"
|
||||
inkscape:cy="238.31434"
|
||||
inkscape:current-layer="Layer_1" /><path
|
||||
id="path5305"
|
||||
d="m 410.06018,31.999998 h -52 c -3.3,0 -6,2.7 -6,6 v 5.6 c -4,4 -10.9,9.8 -17.8,9.8 -10.5,0 -14.8,-16.4 -26.1,-18.4 -11.3,-2 -27.2,-3 -37.9,-3 -10.7,0 -52,0.7 -102,21.5 -50,20.800003 -71.699998,64.900002 -72.299998,81.400002 -0.3,8.7 2.7,21.1 9.999998,11.1 7.3,-10 35.3,-42.3 57.7,-44.7 22.3,-2.299999 45.7,0.8 63.7,17.2 17.3,15.7 21.3,30.8 21.3,73.5 l -6.6,2 c -4.3,1.3 -7,5.7 -7,9 0,0 -0.2,6.5 -0.2,70.4 0,94.6 -10.8,200.6 -10.8,200.6 0,3.3 2.7,6 6,6 h 84 c 3.3,0 6,-2.7 6,-6 0,0 -10.8,-106 -10.8,-200.6 0,-63.9 -0.2,-70.4 -0.2,-70.4 0,-3.3 -2.3,-7.8 -7,-9 l -8,-2 c 0,-38.2 -0.4,-54 6.3,-65.9 7,-12.4 22.3,-22.9 29.7,-25.1 5.9,-1.799999 16,1.3 22,6.5 V 122 c 0,3.3 2.7,6 6,6 h 52 c 3.3,0 6,-2.7 6,-6 V 37.999998 c 0,-3.3 -2.7,-6 -6,-6 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" /></svg>
|
After Width: | Height: | Size: 2.4 KiB |
51
imgs/img_extension.svg
Normal file
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<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"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="512px"
|
||||
height="512px"
|
||||
viewBox="0 0 512 512"
|
||||
enable-background="new 0 0 512 512"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="android-attach.svg"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06"><metadata
|
||||
id="metadata4940"><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><defs
|
||||
id="defs4938" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="640"
|
||||
inkscape:window-height="480"
|
||||
id="namedview4936"
|
||||
showgrid="false"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:zoom="2.2617188"
|
||||
inkscape:cx="256"
|
||||
inkscape:cy="238.31434"
|
||||
inkscape:current-layer="Layer_1" /><g
|
||||
id="Icon_26_"
|
||||
style="fill:#00897b;fill-opacity:1"><g
|
||||
id="g5245"
|
||||
style="fill:#00897b;fill-opacity:1"><path
|
||||
id="path5243"
|
||||
d="m 256,170.136 c 25.499,0 49.567,10.025 67.771,28.229 18.204,18.203 28.229,42.272 28.229,67.771 0,17.621 -4.422,34.031 -13.144,48.773 -8.339,14.098 -20.437,26.036 -34.985,34.526 L 288,358.697 V 377.073 448 h -64 v -70.927 -18.376 l -15.871,-9.262 C 178.442,332.111 160,300.192 160,266.136 c 0,-25.499 10.025,-49.567 28.229,-67.771 18.204,-18.204 42.272,-28.229 67.771,-28.229 M 277.333,32 c -6.396,0 -42.666,0 -42.666,0 v 64 h 42.666 z M 406.396,85.864 368,124.271 397.864,154.136 436.27,115.74 Z m -300.792,0 -29.875,29.875 38.406,38.396 L 144,124.271 Z M 256,138.136 c -70.396,0 -128,57.604 -128,128 0,46.938 25.604,88.531 64,110.938 V 480 H 320 V 377.073 c 38.396,-22.406 64,-62.938 64,-110.938 0,-70.396 -57.604,-127.999 -128,-127.999 z m 224,106.666 h -64 v 42.667 h 64 z m -384,0 H 32 v 42.667 h 64 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" /></g></g></svg>
|
After Width: | Height: | Size: 2.6 KiB |
56
imgs/img_image.svg
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<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"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="512px"
|
||||
height="512px"
|
||||
viewBox="0 0 512 512"
|
||||
enable-background="new 0 0 512 512"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="android-attach.svg"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06"><metadata
|
||||
id="metadata4940"><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><defs
|
||||
id="defs4938" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="640"
|
||||
inkscape:window-height="480"
|
||||
id="namedview4936"
|
||||
showgrid="false"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:zoom="2.2617188"
|
||||
inkscape:cx="256"
|
||||
inkscape:cy="238.31434"
|
||||
inkscape:current-layer="Layer_1" /><g
|
||||
id="g5001"
|
||||
transform="translate(0.01971293,-0.02898186)"
|
||||
style="fill:#00897b;fill-opacity:1"><g
|
||||
id="g4997"
|
||||
style="fill:#00897b;fill-opacity:1"><path
|
||||
id="path4995"
|
||||
d="m 457.6,140.2 -82.5,-4 -4.8,-53.8 C 369.3,71.1 359.2,63.2 347.4,64.1 L 51.4,88.4 C 39.6,89.4 31.1,98.9 32,110.1 l 21.2,235.8 c 1,11.3 11.2,19.2 22.9,18.3 l 15,-1.2 -2.4,45.8 c -0.6,12.6 9.2,22.8 22.4,23.5 L 441.3,448 c 13.2,0.6 24.1,-8.6 24.8,-21.2 L 480,163.5 c 0.6,-12.5 -9.3,-22.7 -22.4,-23.3 z m -355,5.3 -7.1,134.8 -17.4,24.7 -16,-178 c 0,-0.2 0,-0.3 0,-0.5 0,-0.2 0,-0.3 0,-0.5 0.5,-5 4.3,-9 9.5,-9.4 l 261,-21.4 c 5.2,-0.4 9.7,3 10.5,7.9 0,0.2 0.3,0.2 0.3,0.4 0,0.1 0.3,0.2 0.3,0.4 l 2.7,30.8 -219,-10.5 c -13.2,-0.4 -24.2,8.8 -24.8,21.3 z M 436.7,382.4 390,327.1 362.5,294.4 c -2.4,-2.9 -6.3,-5.3 -10.6,-5.5 -4.3,-0.2 -7.5,1.5 -11.1,4.1 l -16.4,11.9 c -3.5,2.1 -6.2,3.5 -9.9,3.3 -3.6,-0.2 -6.8,-1.6 -9.1,-3.8 -0.8,-0.8 -2.3,-2.2 -3.5,-3.4 l -42.8,-48.9 c -3.1,-3.9 -8.2,-6.4 -13.8,-6.7 -5.7,-0.3 -11.2,2.1 -14.8,5.6 l -101.1,108.8 -6.8,7.4 0.3,-6.8 6.8,-128.9 3.3,-62.9 c 0,-0.2 0,-0.4 0,-0.5 0,-0.2 0,-0.4 0,-0.5 1.4,-5.4 6.2,-9.3 11.9,-9 l 204.2,9.8 28.7,1.4 58.3,2.8 c 5.8,0.3 10.3,4.7 10.4,10.2 0,0.2 0.3,0.3 0.3,0.5 0,0.2 0.3,0.3 0.3,0.5 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" /></g><path
|
||||
id="path4999"
|
||||
d="m 373.2,262.3 c 19.4,0 35.2,-15.8 35.2,-35.2 0,-19.4 -15.7,-35.2 -35.2,-35.2 -19.4,0 -35.2,15.7 -35.2,35.2 0,19.5 15.7,35.2 35.2,35.2 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" /></g></svg>
|
After Width: | Height: | Size: 3.1 KiB |
BIN
imgs/img_logo.png
Normal file
After Width: | Height: | Size: 14 KiB |
90
imgs/img_outline.svg
Normal file
@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<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"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="512px"
|
||||
height="512px"
|
||||
viewBox="0 0 512 512"
|
||||
enable-background="new 0 0 512 512"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="android-attach.svg"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06"><metadata
|
||||
id="metadata4940"><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><defs
|
||||
id="defs4938" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="640"
|
||||
inkscape:window-height="480"
|
||||
id="namedview4936"
|
||||
showgrid="false"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:zoom="2.2617188"
|
||||
inkscape:cx="256"
|
||||
inkscape:cy="238.31434"
|
||||
inkscape:current-layer="Layer_1" /><g
|
||||
id="g5172"
|
||||
style="fill:#00897b;fill-opacity:1"><g
|
||||
id="g5164"
|
||||
style="fill:#00897b;fill-opacity:1"><path
|
||||
id="path5150"
|
||||
d="M 432,80 V 432 H 80 V 80 H 432 M 448,64 H 64 v 384 h 384 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" /><g
|
||||
id="g5154"
|
||||
style="fill:#00897b;fill-opacity:1"><rect
|
||||
id="rect5152"
|
||||
height="16"
|
||||
width="192"
|
||||
y="152"
|
||||
x="192"
|
||||
style="fill:#00897b;fill-opacity:1" /></g><g
|
||||
id="g5158"
|
||||
style="fill:#00897b;fill-opacity:1"><rect
|
||||
id="rect5156"
|
||||
height="16"
|
||||
width="192"
|
||||
y="248"
|
||||
x="192"
|
||||
style="fill:#00897b;fill-opacity:1" /></g><g
|
||||
id="g5162"
|
||||
style="fill:#00897b;fill-opacity:1"><rect
|
||||
id="rect5160"
|
||||
height="16"
|
||||
width="192"
|
||||
y="344"
|
||||
x="192"
|
||||
style="fill:#00897b;fill-opacity:1" /></g></g><circle
|
||||
id="circle5166"
|
||||
r="16"
|
||||
cy="160"
|
||||
cx="144"
|
||||
style="fill:#00897b;fill-opacity:1" /><circle
|
||||
id="circle5168"
|
||||
r="16"
|
||||
cy="256"
|
||||
cx="144"
|
||||
style="fill:#00897b;fill-opacity:1" /><circle
|
||||
id="circle5170"
|
||||
r="16"
|
||||
cy="352"
|
||||
cx="144"
|
||||
style="fill:#00897b;fill-opacity:1" /></g></svg>
|
After Width: | Height: | Size: 2.9 KiB |
110
imgs/own_your_data.svg
Normal file
@ -0,0 +1,110 @@
|
||||
<?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="2300"
|
||||
height="1600"
|
||||
viewBox="0 0 608.54165 423.33335"
|
||||
version="1.1"
|
||||
id="svg2731"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
|
||||
sodipodi:docname="own_your_data.svg">
|
||||
<defs
|
||||
id="defs2725" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.25588427"
|
||||
inkscape:cx="108.81765"
|
||||
inkscape:cy="1395.9963"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:pagecheckerboard="true"
|
||||
showborder="true"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3630" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata2728">
|
||||
<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="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,126.33335)"
|
||||
style="display:inline">
|
||||
<path
|
||||
style="fill:#00897b;fill-opacity:1;stroke-width:0.15930605"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 227.15663,82.930803 c -0.11152,-0.461986 -0.31862,-0.796526 -0.55758,-1.083277 L 213.26514,67.239163 c -0.30268,-0.334545 -0.65315,-0.493849 -1.05142,-0.700948 -0.46199,-0.238956 -0.97177,-0.254888 -1.49747,-0.254888 H 185.2432 c -1.97539,0 -3.77555,1.529338 -3.77555,3.648109 v 53.399384 c 0,2.1347 1.80016,4.12602 3.77555,4.12602 h 38.72731 c 1.97539,0 3.37729,-1.99132 3.37729,-4.12602 V 84.44421 c 0,-0.573502 -0.0637,-0.987694 -0.19117,-1.513407 z m -14.94291,-9.223817 9.23975,10.115933 h -9.23975 z M 186.56545,122.37498 V 71.397048 h 20.55047 v 13.015304 c 0,2.357728 2.13471,4.508362 4.47651,4.508362 H 222.25 v 33.454266 z"
|
||||
id="path3677" />
|
||||
<path
|
||||
id="path3736"
|
||||
d="m 290.65663,125.26413 c -0.11152,-0.46198 -0.31862,-0.79652 -0.55758,-1.08327 l -13.33391,-14.60837 c -0.30268,-0.33455 -0.65315,-0.49385 -1.05142,-0.70095 -0.46199,-0.23895 -0.97177,-0.25489 -1.49747,-0.25489 H 248.7432 c -1.97539,0 -3.77555,1.52934 -3.77555,3.64812 v 53.39938 c 0,2.1347 1.80016,4.12602 3.77555,4.12602 h 38.72731 c 1.97539,0 3.37729,-1.99132 3.37729,-4.12602 v -38.88661 c 0,-0.5735 -0.0637,-0.98769 -0.19117,-1.51341 z m -14.94291,-9.22381 9.23975,10.11593 h -9.23975 z m -25.64827,48.668 v -50.97794 h 20.55047 v 13.0153 c 0,2.35773 2.13471,4.50836 4.47651,4.50836 H 285.75 v 33.45428 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1;stroke-width:0.15930605" />
|
||||
<path
|
||||
id="path3738"
|
||||
d="m 315.75785,40.597469 c -0.11152,-0.461986 -0.31862,-0.796526 -0.55758,-1.083277 L 301.86636,24.905829 c -0.30268,-0.334545 -0.65315,-0.493849 -1.05142,-0.700948 -0.46199,-0.238956 -0.97177,-0.254888 -1.49747,-0.254888 h -25.47305 c -1.97539,0 -3.77555,1.529338 -3.77555,3.648109 v 53.399385 c 0,2.134697 1.80016,4.126015 3.77555,4.126015 h 38.72731 c 1.97539,0 3.37729,-1.991318 3.37729,-4.126015 V 42.110876 c 0,-0.573502 -0.0637,-0.987694 -0.19117,-1.513407 z m -14.94291,-9.223817 9.23975,10.115933 h -9.23975 z M 275.16667,80.041651 V 29.063714 h 20.55047 v 13.015304 c 0,2.357728 2.13471,4.508362 4.47651,4.508362 h 10.65757 v 33.454271 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1;stroke-width:0.15930605" />
|
||||
<path
|
||||
style="fill:#00897b;fill-opacity:1;stroke-width:0.15930605"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 363.38285,109.38913 c -0.11152,-0.46198 -0.31862,-0.79652 -0.55758,-1.08327 L 349.49136,93.697489 c -0.30268,-0.334545 -0.65315,-0.493849 -1.05142,-0.700948 -0.46199,-0.238956 -0.97177,-0.254888 -1.49747,-0.254888 h -25.47305 c -1.97539,0 -3.77555,1.529338 -3.77555,3.648113 v 53.399384 c 0,2.1347 1.80016,4.12602 3.77555,4.12602 h 38.72731 c 1.97539,0 3.37729,-1.99132 3.37729,-4.12602 v -38.88661 c 0,-0.5735 -0.0637,-0.98769 -0.19117,-1.51341 z m -14.94291,-9.22381 9.23975,10.11593 h -9.23975 z m -25.64827,48.668 V 97.855376 h 20.55047 v 13.015304 c 0,2.35773 2.13471,4.50836 4.47651,4.50836 h 10.65757 v 33.45428 z"
|
||||
id="path3740" />
|
||||
<g
|
||||
id="Icon_9_"
|
||||
transform="matrix(0.78622752,0,0,0.78622752,102.99659,-115.94093)"
|
||||
style="fill:#00897b;fill-opacity:1">
|
||||
<g
|
||||
id="g3807"
|
||||
style="fill:#00897b;fill-opacity:1">
|
||||
<g
|
||||
id="g3805"
|
||||
style="fill:#00897b;fill-opacity:1">
|
||||
<path
|
||||
id="path3803"
|
||||
d="M 403.001,217.001 C 388.997,148.002 328.998,96 256,96 198.002,96 148.001,128.998 123.003,177 63.002,183.002 16,233.998 16,296 16,361.996 70.004,416 136,416 H 396 C 451,416 496,371 496,315.999 496,263.002 455.003,220 403.001,217.001 Z M 396,376 H 136 c -44.004,0 -80,-35.996 -80,-80 0,-44 35.996,-80 80,-80 h 14.004 c 12.998,-46 55,-80 105.996,-80 60.996,0 110,49 110,110 v 10 h 30 c 32.998,0 60,27.003 60,60 0,32.998 -27.002,60 -60,60 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00897b;fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
style="fill:#00897b;fill-opacity:1;stroke-width:0.15930605"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 432.79071,123.3254 c -0.11152,-0.46198 -0.31862,-0.79652 -0.55758,-1.08327 l -13.33391,-14.60837 c -0.30268,-0.33455 -0.65315,-0.49385 -1.05142,-0.70095 -0.46199,-0.23895 -0.97177,-0.25489 -1.49747,-0.25489 h -25.47305 c -1.97539,0 -3.77555,1.52934 -3.77555,3.64812 v 53.39938 c 0,2.1347 1.80016,4.12602 3.77555,4.12602 h 38.72731 c 1.97539,0 3.37729,-1.99132 3.37729,-4.12602 v -38.88661 c 0,-0.5735 -0.0637,-0.98769 -0.19117,-1.51341 z m -14.94291,-9.22381 9.23975,10.11593 h -9.23975 z m -25.64827,48.668 V 111.79165 H 412.75 v 13.0153 c 0,2.35773 2.13471,4.50836 4.47651,4.50836 h 10.65757 v 33.45428 z"
|
||||
id="path3831" />
|
||||
</g>
|
||||
<style
|
||||
id="style3416"
|
||||
type="text/css"><![CDATA[
|
||||
.st0{fill:#010101;}
|
||||
]]></style>
|
||||
</svg>
|
After Width: | Height: | Size: 6.5 KiB |
102
imgs/pleasant_markdown_experience.svg
Normal file
After Width: | Height: | Size: 738 KiB |
107
imgs/simple_management.svg
Normal file
@ -0,0 +1,107 @@
|
||||
<?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="2300"
|
||||
height="1600"
|
||||
viewBox="0 0 608.54165 423.33335"
|
||||
version="1.1"
|
||||
id="svg2731"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
|
||||
sodipodi:docname="simple_management.svg">
|
||||
<defs
|
||||
id="defs2725" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.51176853"
|
||||
inkscape:cx="671.47798"
|
||||
inkscape:cy="844.46563"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:pagecheckerboard="true"
|
||||
showborder="true"
|
||||
showguides="false"
|
||||
inkscape:guide-bbox="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3630" />
|
||||
<sodipodi:guide
|
||||
position="301.62499,251.35418"
|
||||
orientation="0,1"
|
||||
id="guide3632"
|
||||
inkscape:locked="false" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata2728">
|
||||
<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="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,126.33335)"
|
||||
style="display:inline">
|
||||
<path
|
||||
id="path3589"
|
||||
d="m 143.7671,192.16325 c -0.18521,-0.76729 -0.52917,-1.32291 -0.92605,-1.79916 L 120.69543,166.1018 c -0.50271,-0.55563 -1.08479,-0.82021 -1.74625,-1.16417 -0.76729,-0.39687 -1.61396,-0.42333 -2.48708,-0.42333 H 74.155217 c -3.28083,0 -6.27062,2.54 -6.27062,6.05896 v 88.68833 c 0,3.54541 2.98979,6.85269 6.27062,6.85269 h 64.320213 c 3.28083,0 5.60917,-3.30728 5.60917,-6.85269 v -64.5848 c 0,-0.9525 -0.10584,-1.64041 -0.3175,-2.51354 z m -24.81792,-15.31937 15.34583,16.80104 h -15.34583 z m -42.597923,80.83021 v -84.66667 h 34.131253 v 21.61646 c 0,3.91583 3.54542,7.48771 7.4348,7.48771 h 17.70062 v 55.5625 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="stroke-width:0.26458335;fill:#00897b;fill-opacity:1" />
|
||||
<path
|
||||
id="path3599"
|
||||
d="m 336.7842,-115.18166 c -10.83231,0 -20.9341,3.70417 -24.23001,11.77264 -3.58325,-8.06847 -13.39718,-11.77264 -24.22975,-11.77264 -14.60526,0 -26.58163,6.87388 -26.58163,20.902092 v 0.26458 2.37993 69.851315 2.38125 h 1.64439 2.8993 1.31419 c 3.40254,-6.96753 11.70463,-11.1125 20.72349,-11.1125 10.6426,0 19.54662,7.28345 21.59449,16.93334 h 5.216 c 2.04761,-9.64989 10.97889,-16.93334 21.62228,-16.93334 9.01832,0 16.17901,3.08663 20.75101,11.1125 h 1.31418 2.89957 1.6211 v -4.49791 -69.056245 -1.05834 -0.26458 c 0,-14.028212 -11.95308,-20.902092 -26.55861,-20.902092 z m -26.35806,91.790837 c -4.7625,-6.84503 -12.89314,-11.3575 -22.0927,-11.3575 -9.1993,0 -17.06563,2.66039 -22.09271,9.50542 l -0.26459,0.14208 v -69.443325 0 -0.26459 c 0.79375,-11.066202 10.60689,-16.139582 22.30253,-16.139582 11.94911,0 21.7342,4.94982 22.05646,16.353372 -0.005,0.19288 0.004,0.38602 0.004,0.57996 l 0.0868,3.46684 z m 48.68334,-69.565825 v 67.713745 c -4.7625,-6.84503 -13.15799,-9.50542 -22.35729,-9.50542 -9.19957,0 -17.33021,4.51247 -22.09271,11.3575 v -70.359575 -0.26459 c 0,-11.671302 9.96394,-16.933332 22.11573,-16.933332 11.69537,0 21.54052,2.95672 22.33427,14.022922 v 0.26458 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="stroke-width:0.26458335;fill:#00897b;fill-opacity:1" />
|
||||
<path
|
||||
id="path3579"
|
||||
d="m 269.87501,55.699983 h -47.97637 l -11.28924,-12.7 h -36.69031 c -6.2095,0 -11.28871,5.07947 -11.28871,11.28898 v 62.088707 c 0,6.20951 5.07921,11.28898 11.28871,11.28898 h 95.95592 c 6.2095,0 11.28871,-5.07947 11.28871,-11.28898 V 66.988963 c 0,-6.20951 -5.07921,-11.28898 -11.28871,-11.28898 z m 2.82204,60.677687 c 0,1.52982 -1.29249,2.82231 -2.82204,2.82231 h -95.95592 c -1.52982,0 -2.82204,-1.29249 -2.82204,-2.82231 V 64.166653 h 98.77796 c 1.52955,0 2.82204,1.29249 2.82204,2.82231 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="display:inline;stroke-width:0.26458335;fill:#00897b;fill-opacity:1" />
|
||||
<path
|
||||
style="display:inline;stroke-width:0.26458335;fill:#00897b;fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 434.62258,55.699993 h -47.97636 l -11.28924,-12.7 h -36.6903 c -6.2095,0 -11.28871,5.07947 -11.28871,11.28898 v 62.088707 c 0,6.2095 5.07921,11.28897 11.28871,11.28897 h 95.9559 c 6.2095,0 11.28871,-5.07947 11.28871,-11.28897 V 66.988973 c 0,-6.20951 -5.07921,-11.28898 -11.28871,-11.28898 z m 2.82204,60.677687 c 0,1.52981 -1.29249,2.8223 -2.82204,2.8223 h -95.9559 c -1.52982,0 -2.82204,-1.29249 -2.82204,-2.8223 V 64.166663 h 98.77794 c 1.52955,0 2.82204,1.29249 2.82204,2.82231 z"
|
||||
id="path3609" />
|
||||
<path
|
||||
style="stroke-width:0.26458335;fill:#00897b;fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 276.27291,192.16325 c -0.18521,-0.76729 -0.52917,-1.32291 -0.92605,-1.79916 L 253.20124,166.1018 c -0.50271,-0.55563 -1.08479,-0.82021 -1.74625,-1.16417 -0.76729,-0.39687 -1.61396,-0.42333 -2.48708,-0.42333 h -42.30688 c -3.28083,0 -6.27062,2.54 -6.27062,6.05896 v 88.68833 c 0,3.54541 2.98979,6.85269 6.27062,6.85269 h 64.32021 c 3.28083,0 5.60917,-3.30728 5.60917,-6.85269 v -64.5848 c 0,-0.9525 -0.10584,-1.64041 -0.3175,-2.51354 z m -24.81792,-15.31937 15.34583,16.80104 h -15.34583 z m -42.59792,80.83021 v -84.66667 h 34.13125 v 21.61646 c 0,3.91583 3.54542,7.48771 7.4348,7.48771 h 17.70062 v 55.5625 z"
|
||||
id="path3652" />
|
||||
<path
|
||||
id="path3654"
|
||||
d="m 408.77876,192.16325 c -0.18521,-0.76729 -0.52917,-1.32291 -0.92605,-1.79916 L 385.70709,166.1018 c -0.50271,-0.55563 -1.08479,-0.82021 -1.74625,-1.16417 -0.76729,-0.39687 -1.61396,-0.42333 -2.48708,-0.42333 h -42.30688 c -3.28083,0 -6.27062,2.54 -6.27062,6.05896 v 88.68833 c 0,3.54541 2.98979,6.85269 6.27062,6.85269 h 64.32021 c 3.28083,0 5.60917,-3.30728 5.60917,-6.85269 v -64.5848 c 0,-0.9525 -0.10584,-1.64041 -0.3175,-2.51354 z m -24.81792,-15.31937 15.34583,16.80104 h -15.34583 z m -42.59792,80.83021 v -84.66667 h 34.13125 v 21.61646 c 0,3.91583 3.54542,7.48771 7.4348,7.48771 h 17.70062 v 55.5625 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="stroke-width:0.26458335;fill:#00897b;fill-opacity:1" />
|
||||
<path
|
||||
style="stroke-width:0.26458335;fill:#00897b;fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 541.28459,192.16325 c -0.18521,-0.76729 -0.52917,-1.32291 -0.92605,-1.79916 L 518.21292,166.1018 c -0.50271,-0.55563 -1.08479,-0.82021 -1.74625,-1.16417 -0.76729,-0.39687 -1.61396,-0.42333 -2.48708,-0.42333 h -42.30688 c -3.28083,0 -6.27062,2.54 -6.27062,6.05896 v 88.68833 c 0,3.54541 2.98979,6.85269 6.27062,6.85269 h 64.32021 c 3.28083,0 5.60917,-3.30728 5.60917,-6.85269 v -64.5848 c 0,-0.9525 -0.10584,-1.64041 -0.3175,-2.51354 z m -24.81792,-15.31937 15.34583,16.80104 h -15.34583 z m -42.59792,80.83021 V 173.00742 H 508 v 21.61646 c 0,3.91583 3.54542,7.48771 7.4348,7.48771 h 17.70062 v 55.5625 z"
|
||||
id="path3656" />
|
||||
</g>
|
||||
<style
|
||||
id="style3416"
|
||||
type="text/css"><![CDATA[
|
||||
.st0{fill:#010101;}
|
||||
]]></style>
|
||||
</svg>
|
After Width: | Height: | Size: 7.5 KiB |
319
index.html
Normal file
@ -0,0 +1,319 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>VNote</title>
|
||||
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, maximum-scale=1.0" />
|
||||
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:100,100i,300,300i,400,400i,500,500i,700,700i,900,900i" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css">
|
||||
|
||||
<link type="text/css" rel="stylesheet" href="./css/froala_blocks.css">
|
||||
|
||||
<style>
|
||||
#navigator {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#navigator .navbar-toggler-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
#navigator {
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
#navigator a {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#navigator + section {
|
||||
padding: 250px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-expand-md">
|
||||
<a class="navbar-brand" href="https://tamlok.github.io/vnote">
|
||||
<img src="./imgs/img_logo.png" height="30" alt="logo">
|
||||
</a>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav1" aria-controls="navbarNav1" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav1">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="https://tamlok.github.io/vnote">Home <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#features">Features</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="">Screenshots</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="">Documentations</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="fdb-block" style="background-image: url(imgs/alt_wide_1.svg);">
|
||||
<div class="container">
|
||||
<div class="row justify-content-start">
|
||||
<div class="col-12 col-sm-10 col-md-8 text-center text-sm-left">
|
||||
<img alt="image" width="200" src="./imgs/img_logo.png"/>
|
||||
<h1>VNote</h1>
|
||||
<p class="text-h3">A note-taking application with Markdown, which knows programmers and Markdown better.</p>
|
||||
<p class="mt-4">
|
||||
<a class="btn btn-black mr-3 mr-sm-0" href="https://github.com/tamlok/vnote"><i class="fa fa-github"></i> Github Project</a>
|
||||
<a class="btn ml-sm-3 mr-3 mr-sm-0 mt-3 mt-sm-0" href="https://github.com/tamlok/vnote#downloads">Download</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="fdb-block">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12 col-md-6 mb-4 mb-md-0">
|
||||
<img alt="image" class="img-fluid" src="./imgs/concentration.svg">
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-lg-5 ml-md-auto text-left">
|
||||
<h1>Concentration</h1>
|
||||
<p class="text-h3 mb-5">Markdown does <b>NOT</b> need live preview side by side. Focus on your note in both read and edit mode.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="fdb-block">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12 col-md-12 col-lg-6 col-xl-5">
|
||||
<h1>Cross-Platform</h1>
|
||||
<p class="text-h3 mb-5">Built with C++ and Qt, VNote works in major desktop platforms.</p>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-8 m-auto ml-lg-auto mr-lg-0 col-lg-6 pt-5 pt-lg-0">
|
||||
<img alt="image" class="img-fluid" src="./imgs/cross_platform.svg">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="fdb-block">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12 col-md-6 mb-4 mb-md-0">
|
||||
<img alt="image" class="img-fluid" src="./imgs/simple_management.svg">
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-lg-5 ml-md-auto text-left">
|
||||
<h1>Simple Management</h1>
|
||||
<ul>
|
||||
<li>All plain text, no database</li>
|
||||
<li>One directory to hold one <b>notebook</b></li>
|
||||
<li>One notebook contains infinite-level <b>folders</b></li>
|
||||
<li>Folders hold your <b>notes</b></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="fdb-block">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12 col-md-12 col-lg-6 col-xl-5">
|
||||
<h1>Own Your Data</h1>
|
||||
<p class="text-h3 mb-5">All files on your local disk. Utilize third-party synchronization services to work from anywhere seamlessly.</p>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-8 m-auto ml-lg-auto mr-lg-0 col-lg-6 pt-5 pt-lg-0">
|
||||
<img alt="image" class="img-fluid" src="./imgs/own_your_data.svg">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="fdb-block">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12 col-md-6 mb-4 mb-md-0">
|
||||
<img alt="image" class="img-fluid" src="./imgs/pleasant_markdown_experience.svg">
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-lg-5 ml-md-auto text-left">
|
||||
<h1>Pleasant Markdown Experience</h1>
|
||||
<p class="text-h3 mb-5">Minimize the gap between reading and writing Markdown.</p>
|
||||
<ul>
|
||||
<li>Tuned and meaningful syntax highlights for Markdown</li>
|
||||
<li>In-place image links preview</li>
|
||||
<li>Copy/paste images without messy stuff</li>
|
||||
<li>Syntax highlights for code blocks in edit mode</li>
|
||||
<li>Interactive outline</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="fdb-block">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12 col-md-12 col-lg-6 col-xl-5">
|
||||
<h1>Highly Customizable</h1>
|
||||
<p class="text-h3 mb-5">Themes, styles, behaviors. All could be tuned to your taste.</p>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-8 m-auto ml-lg-auto mr-lg-0 col-lg-6 pt-5 pt-lg-0">
|
||||
<img alt="image" class="img-fluid" src="./imgs/highly_customizable.svg">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="fdb-block">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12 col-md-6 mb-4 mb-md-0">
|
||||
<img alt="image" class="img-fluid" src="./imgs/for_programmers.svg">
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-lg-5 ml-md-auto text-left">
|
||||
<h1>By Programmers, For Programmers</h1>
|
||||
<p class="text-h3 mb-5">Utilize the power of code editors for Markdown notes.</p>
|
||||
<ul>
|
||||
<li>Vim mode</li>
|
||||
<li>Line number</li>
|
||||
<li>Multiple tabs</li>
|
||||
<li>Split window</li>
|
||||
<li>Powerful shortcuts</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="fdb-block">
|
||||
<div class="container">
|
||||
<div class="row text-center">
|
||||
<div class="col-12">
|
||||
<h1 id="features">Features</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row text-left mt-5">
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<img alt="image" class="img-fluid" src="./imgs/img_image.svg">
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<h3><strong>Easy Images</strong></h3>
|
||||
<p>Insert images directly from clipboard. Preview images in place in edit mode.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4 pt-3 pt-sm-4 pt-md-0">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<img alt="image" class="img-fluid" src="./imgs/img_codeblock.svg">
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<h3><strong>Code Blocks</strong></h3>
|
||||
<p>Syntax highlights for Code Blocks in both edit and read mode.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4 pt-3 pt-sm-4 pt-md-0">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<img alt="image" class="img-fluid" src="./imgs/img_outline.svg">
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<h3><strong>Interactive Outline</strong></h3>
|
||||
<p>Interactive outline in both edit and read mode.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row text-left pt-3 pt-sm-4 pt-md-5">
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<img alt="image" class="img-fluid" src="./imgs/img_extension.svg">
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<h3><strong>Extensions</strong></h3>
|
||||
<p>Mermaid, Flowchart.js, and MathJax.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4 pt-3 pt-sm-4 pt-md-0">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<img alt="image" class="img-fluid" src="./imgs/img_attachment.svg">
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<h3><strong>Attachments</strong></h3>
|
||||
<p>Link all related files to the notes by attachments.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4 pt-3 pt-sm-4 pt-md-0">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<img alt="image" class="img-fluid" src="./imgs/img_expect_more.svg">
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<h3><strong>We Could Expect More</strong></h3>
|
||||
<p>VNote is in active development. Post your issues or pull requests to shape VNote.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="fdb-block footer-large">
|
||||
<div class="container">
|
||||
<div class="row align-items-top text-center">
|
||||
<div class="col-12 col-sm-6 col-md-4 col-lg-3 text-sm-left">
|
||||
<h3><strong>About VNote</strong></h3>
|
||||
<nav class="nav flex-column">
|
||||
<p>Developed from 2016/10/01</p>
|
||||
<p>QQ Group: 487756074</p>
|
||||
<p>WeChat Public Account: vnote_md</p>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-sm-6 col-md-4 col-lg-3 mt-5 mt-sm-0 text-sm-left">
|
||||
<h3><strong>About Me</strong></h3>
|
||||
<nav class="nav flex-column">
|
||||
<p><a href="https://github.com/tamlok">tamlok@github</a></p>
|
||||
<p><a href="mailto:tamlokveer@gmail.com">tamlokveer@gmail.com</a></p>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,18 +0,0 @@
|
||||
# PEG-Markdown-Highlight
|
||||
# Github: https://github.com/ali-rantakari/peg-markdown-highlight
|
||||
|
||||
QT -= core gui
|
||||
|
||||
TARGET = peg-highlight
|
||||
|
||||
TEMPLATE = lib
|
||||
|
||||
CONFIG += warn_off
|
||||
CONFIG += staticlib
|
||||
|
||||
SOURCES += pmh_parser.c \
|
||||
pmh_styleparser.c
|
||||
|
||||
HEADERS += pmh_parser.h \
|
||||
pmh_styleparser.h \
|
||||
pmh_definitions.h
|
@ -1,125 +0,0 @@
|
||||
/* PEG Markdown Highlight
|
||||
* Copyright 2011-2016 Ali Rantakari -- http://hasseg.org
|
||||
* Licensed under the GPL2+ and MIT licenses (see LICENSE for more info).
|
||||
*
|
||||
* pmh_definitions.h
|
||||
*/
|
||||
|
||||
#ifndef pmh_MARKDOWN_DEFINITIONS
|
||||
#define pmh_MARKDOWN_DEFINITIONS
|
||||
|
||||
/** \file
|
||||
* \brief Global definitions for the parser.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* \brief Element types.
|
||||
*
|
||||
* The first (documented) ones are language element types.
|
||||
*
|
||||
* The last (non-documented) ones are utility types used
|
||||
* by the parser itself.
|
||||
*
|
||||
* \sa pmh_element
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
pmh_LINK, /**< Explicit link */
|
||||
pmh_AUTO_LINK_URL, /**< Implicit URL link */
|
||||
pmh_AUTO_LINK_EMAIL, /**< Implicit email link */
|
||||
pmh_IMAGE, /**< Image definition */
|
||||
pmh_CODE, /**< Code (inline) */
|
||||
pmh_HTML, /**< HTML */
|
||||
pmh_HTML_ENTITY, /**< HTML special entity definition */
|
||||
pmh_EMPH, /**< Emphasized text */
|
||||
pmh_STRONG, /**< Strong text */
|
||||
pmh_LIST_BULLET, /**< Bullet for an unordered list item */
|
||||
pmh_LIST_ENUMERATOR, /**< Enumerator for an ordered list item */
|
||||
pmh_COMMENT, /**< (HTML) Comment */
|
||||
|
||||
// Code assumes that pmh_H1-6 are in order.
|
||||
pmh_H1, /**< Header, level 1 */
|
||||
pmh_H2, /**< Header, level 2 */
|
||||
pmh_H3, /**< Header, level 3 */
|
||||
pmh_H4, /**< Header, level 4 */
|
||||
pmh_H5, /**< Header, level 5 */
|
||||
pmh_H6, /**< Header, level 6 */
|
||||
|
||||
pmh_BLOCKQUOTE, /**< Blockquote */
|
||||
pmh_VERBATIM, /**< Verbatim (e.g. block of code) */
|
||||
pmh_HTMLBLOCK, /**< Block of HTML */
|
||||
pmh_HRULE, /**< Horizontal rule */
|
||||
pmh_REFERENCE, /**< Reference */
|
||||
pmh_NOTE, /**< Note */
|
||||
pmh_STRIKE, /**< Strike-through */
|
||||
|
||||
// Utility types used by the parser itself:
|
||||
|
||||
// List of pmh_RAW element lists, each to be processed separately from
|
||||
// others (for each element in linked lists of this type, `children` points
|
||||
// to a linked list of pmh_RAW elements):
|
||||
pmh_RAW_LIST, /**< Internal to parser. Please ignore. */
|
||||
|
||||
// Span marker for positions in original input to be post-processed
|
||||
// in a second parsing step:
|
||||
pmh_RAW, /**< Internal to parser. Please ignore. */
|
||||
|
||||
// Additional text to be parsed along with spans in the original input
|
||||
// (these may be added to linked lists of pmh_RAW elements):
|
||||
pmh_EXTRA_TEXT, /**< Internal to parser. Please ignore. */
|
||||
|
||||
// Separates linked lists of pmh_RAW elements into parts to be processed
|
||||
// separate from each other:
|
||||
pmh_SEPARATOR, /**< Internal to parser. Please ignore. */
|
||||
|
||||
// Placeholder element used while parsing:
|
||||
pmh_NO_TYPE, /**< Internal to parser. Please ignore. */
|
||||
|
||||
// Linked list of *all* elements created while parsing:
|
||||
pmh_ALL /**< Internal to parser. Please ignore. */
|
||||
} pmh_element_type;
|
||||
|
||||
/**
|
||||
* \brief Number of types in pmh_element_type.
|
||||
* \sa pmh_element_type
|
||||
*/
|
||||
#define pmh_NUM_TYPES 31
|
||||
|
||||
/**
|
||||
* \brief Number of *language element* types in pmh_element_type.
|
||||
* \sa pmh_element_type
|
||||
*/
|
||||
#define pmh_NUM_LANG_TYPES (pmh_NUM_TYPES - 6)
|
||||
|
||||
|
||||
/**
|
||||
* \brief A Language element occurrence.
|
||||
*/
|
||||
struct pmh_Element
|
||||
{
|
||||
pmh_element_type type; /**< \brief Type of element */
|
||||
unsigned long pos; /**< \brief Unicode code point offset marking the
|
||||
beginning of this element in the
|
||||
input. */
|
||||
unsigned long end; /**< \brief Unicode code point offset marking the
|
||||
end of this element in the input. */
|
||||
struct pmh_Element *next; /**< \brief Next element in list */
|
||||
char *label; /**< \brief Label (for links and references) */
|
||||
char *address; /**< \brief Address (for links and references) */
|
||||
};
|
||||
typedef struct pmh_Element pmh_element;
|
||||
|
||||
/**
|
||||
* \brief Bitfield enumeration of supported Markdown extensions.
|
||||
*/
|
||||
enum pmh_extensions
|
||||
{
|
||||
pmh_EXT_NONE = 0, /**< No extensions */
|
||||
pmh_EXT_NOTES = (1 << 0), /**< Footnote syntax:
|
||||
http://pandoc.org/README.html#footnotes */
|
||||
pmh_EXT_STRIKE = (1 << 1) /**< Strike-through syntax:
|
||||
http://pandoc.org/README.html#strikeout */
|
||||
};
|
||||
|
||||
#endif
|
@ -1,91 +0,0 @@
|
||||
/* PEG Markdown Highlight
|
||||
* Copyright 2011-2016 Ali Rantakari -- http://hasseg.org
|
||||
* Licensed under the GPL2+ and MIT licenses (see LICENSE for more info).
|
||||
*
|
||||
* pmh_parser.h
|
||||
*/
|
||||
|
||||
#ifdef Q_CC_GNU
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#endif
|
||||
|
||||
/** \file
|
||||
* \brief Parser public interface.
|
||||
*/
|
||||
|
||||
#ifndef __cplusplus
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include "pmh_definitions.h"
|
||||
|
||||
|
||||
/**
|
||||
* \brief Parse Markdown text, return elements
|
||||
*
|
||||
* Parses the given Markdown text and returns the results as an
|
||||
* array of linked lists of elements, indexed by type.
|
||||
*
|
||||
* \param[in] text The Markdown text to parse for highlighting.
|
||||
* \param[in] extensions The extensions to use in parsing (a bitfield
|
||||
* of pmh_extensions values).
|
||||
* \param[out] out_result A pmh_element array, indexed by type, containing
|
||||
* the results of the parsing (linked lists of elements).
|
||||
* You must pass this to pmh_free_elements() when it's
|
||||
* not needed anymore.
|
||||
*
|
||||
* \sa pmh_element_type
|
||||
*/
|
||||
void pmh_markdown_to_elements(char *text, int extensions,
|
||||
pmh_element **out_result[]);
|
||||
|
||||
/**
|
||||
* \brief Sort elements in list by start offset.
|
||||
*
|
||||
* Sorts the linked lists of elements in the list returned by
|
||||
* pmh_markdown_to_elements() by their start offsets (pos).
|
||||
*
|
||||
* \param[in] element_lists Array of linked lists of elements (output
|
||||
* from pmh_markdown_to_elements()).
|
||||
*
|
||||
* \sa pmh_markdown_to_elements
|
||||
* \sa pmh_element::pos
|
||||
*/
|
||||
void pmh_sort_elements_by_pos(pmh_element *element_lists[]);
|
||||
|
||||
/**
|
||||
* \brief Free pmh_element array
|
||||
*
|
||||
* Frees an pmh_element array returned by pmh_markdown_to_elements().
|
||||
*
|
||||
* \param[in] elems The pmh_element array resulting from calling
|
||||
* pmh_markdown_to_elements().
|
||||
*
|
||||
* \sa pmh_markdown_to_elements
|
||||
*/
|
||||
void pmh_free_elements(pmh_element **elems);
|
||||
|
||||
/**
|
||||
* \brief Get element type name
|
||||
*
|
||||
* \param[in] type The type value to get the name for.
|
||||
*
|
||||
* \return The name of the given type as a null-terminated string.
|
||||
*
|
||||
* \sa pmh_element_type
|
||||
*/
|
||||
char *pmh_element_name_from_type(pmh_element_type type);
|
||||
|
||||
/**
|
||||
* \brief Get element type from a name
|
||||
*
|
||||
* \param[in] name The name of the type.
|
||||
*
|
||||
* \return The element type corresponding to the given name.
|
||||
*
|
||||
* \sa pmh_element_type
|
||||
*/
|
||||
pmh_element_type pmh_element_type_from_name(char *name);
|
||||
|
@ -1,934 +0,0 @@
|
||||
/* PEG Markdown Highlight
|
||||
* Copyright 2011-2016 Ali Rantakari -- http://hasseg.org
|
||||
* Licensed under the GPL2+ and MIT licenses (see LICENSE for more info).
|
||||
*
|
||||
* styleparser.c
|
||||
*
|
||||
* Parser for custom syntax highlighting stylesheets.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "pmh_styleparser.h"
|
||||
#include "pmh_parser.h"
|
||||
|
||||
|
||||
#if pmh_DEBUG_OUTPUT
|
||||
#define pmhsp_PRINTF(x, ...) fprintf(stderr, x, ##__VA_ARGS__)
|
||||
#else
|
||||
#define pmhsp_PRINTF(x, ...)
|
||||
#endif
|
||||
|
||||
|
||||
// vasprintf is not in the C standard nor in POSIX so we provide our own
|
||||
static int our_vasprintf(char **strptr, const char *fmt, va_list argptr)
|
||||
{
|
||||
int ret;
|
||||
va_list argptr2;
|
||||
*strptr = NULL;
|
||||
|
||||
va_copy(argptr2, argptr);
|
||||
ret = vsnprintf(NULL, 0, fmt, argptr2);
|
||||
if (ret <= 0)
|
||||
return ret;
|
||||
|
||||
*strptr = (char *)malloc(ret+1);
|
||||
if (*strptr == NULL)
|
||||
return -1;
|
||||
|
||||
va_copy(argptr2, argptr);
|
||||
ret = vsnprintf(*strptr, ret+1, fmt, argptr2);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Parsing context data
|
||||
typedef struct
|
||||
{
|
||||
char *input;
|
||||
void (*error_callback)(char*,int,void*);
|
||||
void *error_callback_context;
|
||||
int styles_pos;
|
||||
pmh_style_collection *styles;
|
||||
} style_parser_data;
|
||||
|
||||
typedef struct raw_attribute
|
||||
{
|
||||
char *name;
|
||||
char *value;
|
||||
int line_number;
|
||||
struct raw_attribute *next;
|
||||
} raw_attribute;
|
||||
|
||||
static raw_attribute *new_raw_attribute(char *name, char *value,
|
||||
int line_number)
|
||||
{
|
||||
raw_attribute *v = (raw_attribute *)malloc(sizeof(raw_attribute));
|
||||
v->name = name;
|
||||
v->value = value;
|
||||
v->line_number = line_number;
|
||||
v->next = NULL;
|
||||
return v;
|
||||
}
|
||||
|
||||
static void free_raw_attributes(raw_attribute *list)
|
||||
{
|
||||
raw_attribute *cur = list;
|
||||
while (cur != NULL)
|
||||
{
|
||||
raw_attribute *pattr = NULL;
|
||||
if (cur->name != NULL) free(cur->name);
|
||||
if (cur->value != NULL) free(cur->value);
|
||||
pattr = cur;
|
||||
cur = cur->next;
|
||||
free(pattr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void report_error(style_parser_data *p_data,
|
||||
int line_number, char *str, ...)
|
||||
{
|
||||
va_list argptr;
|
||||
if (p_data->error_callback == NULL)
|
||||
return;
|
||||
|
||||
va_start(argptr, str);
|
||||
char *errmsg;
|
||||
our_vasprintf(&errmsg, str, argptr);
|
||||
va_end(argptr);
|
||||
p_data->error_callback(errmsg, line_number,
|
||||
p_data->error_callback_context);
|
||||
free(errmsg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static char *trim_str(char *str)
|
||||
{
|
||||
while (isspace(*str))
|
||||
str++;
|
||||
if (*str == '\0')
|
||||
return str;
|
||||
char *end = str + strlen(str) - 1;
|
||||
while (end > str && isspace(*end))
|
||||
end--;
|
||||
*(end+1) = '\0';
|
||||
return str;
|
||||
}
|
||||
|
||||
static char *trim_str_dup(char *str)
|
||||
{
|
||||
size_t start = 0;
|
||||
while (isspace(*(str + start)))
|
||||
start++;
|
||||
size_t end = strlen(str) - 1;
|
||||
while (start < end && isspace(*(str + end)))
|
||||
end--;
|
||||
|
||||
size_t len = end - start + 1;
|
||||
char *ret = (char *)malloc(sizeof(char)*len + 1);
|
||||
*ret = '\0';
|
||||
strncat(ret, (str + start), len);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static char *strcpy_lower(char *str)
|
||||
{
|
||||
char *low = strdup(str);
|
||||
int i;
|
||||
int len = strlen(str);
|
||||
for (i = 0; i < len; i++)
|
||||
*(low+i) = tolower(*(low+i));
|
||||
return low;
|
||||
}
|
||||
|
||||
static char *standardize_str(char *str)
|
||||
{
|
||||
return strcpy_lower(trim_str(str));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static pmh_attr_argb_color *new_argb_color(int r, int g, int b, int a)
|
||||
{
|
||||
pmh_attr_argb_color *c = (pmh_attr_argb_color *)
|
||||
malloc(sizeof(pmh_attr_argb_color));
|
||||
c->red = r; c->green = g; c->blue = b; c->alpha = a;
|
||||
return c;
|
||||
}
|
||||
static pmh_attr_argb_color *new_argb_from_hex(long long hex, bool has_alpha)
|
||||
{
|
||||
// 0xaarrggbb
|
||||
int a = has_alpha ? ((hex >> 24) & 0xFF) : 255;
|
||||
int r = ((hex >> 16) & 0xFF);
|
||||
int g = ((hex >> 8) & 0xFF);
|
||||
int b = (hex & 0xFF);
|
||||
return new_argb_color(r,g,b,a);
|
||||
}
|
||||
static pmh_attr_argb_color *new_argb_from_hex_str(style_parser_data *p_data,
|
||||
int attr_line_number,
|
||||
char *str)
|
||||
{
|
||||
// "aarrggbb"
|
||||
int len = strlen(str);
|
||||
if (len != 6 && len != 8) {
|
||||
report_error(p_data, attr_line_number,
|
||||
"Value '%s' is not a valid color value: it should be a "
|
||||
"hexadecimal number, 6 or 8 characters long.",
|
||||
str);
|
||||
return NULL;
|
||||
}
|
||||
char *endptr = NULL;
|
||||
long long num = strtoll(str, &endptr, 16);
|
||||
if (*endptr != '\0') {
|
||||
report_error(p_data, attr_line_number,
|
||||
"Value '%s' is not a valid color value: the character "
|
||||
"'%c' is invalid. The color value should be a hexadecimal "
|
||||
"number, 6 or 8 characters long.",
|
||||
str, *endptr);
|
||||
return NULL;
|
||||
}
|
||||
return new_argb_from_hex(num, (len == 8));
|
||||
}
|
||||
|
||||
static pmh_attr_value *new_attr_value()
|
||||
{
|
||||
return (pmh_attr_value *)malloc(sizeof(pmh_attr_value));
|
||||
}
|
||||
|
||||
static pmh_attr_font_styles *new_font_styles()
|
||||
{
|
||||
pmh_attr_font_styles *ret = (pmh_attr_font_styles *)
|
||||
malloc(sizeof(pmh_attr_font_styles));
|
||||
ret->italic = false;
|
||||
ret->bold = false;
|
||||
ret->underlined = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static pmh_attr_font_size *new_font_size()
|
||||
{
|
||||
pmh_attr_font_size *ret = (pmh_attr_font_size *)
|
||||
malloc(sizeof(pmh_attr_font_size));
|
||||
ret->is_relative = false;
|
||||
ret->size_pt = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static pmh_style_attribute *new_attr(char *name, pmh_attr_type type)
|
||||
{
|
||||
pmh_style_attribute *attr = (pmh_style_attribute *)malloc(sizeof(pmh_style_attribute));
|
||||
attr->name = strdup(name);
|
||||
attr->type = type;
|
||||
attr->next = NULL;
|
||||
return attr;
|
||||
}
|
||||
|
||||
static void free_style_attributes(pmh_style_attribute *list)
|
||||
{
|
||||
pmh_style_attribute *cur = list;
|
||||
while (cur != NULL)
|
||||
{
|
||||
if (cur->name != NULL)
|
||||
free(cur->name);
|
||||
if (cur->value != NULL)
|
||||
{
|
||||
if (cur->type == pmh_attr_type_foreground_color
|
||||
|| cur->type == pmh_attr_type_background_color
|
||||
|| cur->type == pmh_attr_type_caret_color
|
||||
|| cur->type == pmh_attr_type_strike_color)
|
||||
free(cur->value->argb_color);
|
||||
else if (cur->type == pmh_attr_type_font_family)
|
||||
free(cur->value->font_family);
|
||||
else if (cur->type == pmh_attr_type_font_style)
|
||||
free(cur->value->font_styles);
|
||||
else if (cur->type == pmh_attr_type_font_size_pt)
|
||||
free(cur->value->font_size);
|
||||
else if (cur->type == pmh_attr_type_other)
|
||||
free(cur->value->string);
|
||||
free(cur->value);
|
||||
}
|
||||
pmh_style_attribute *pattr = cur;
|
||||
cur = cur->next;
|
||||
free(pattr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#define IF_ATTR_NAME(x) if (strcmp(x, name) == 0)
|
||||
pmh_attr_type pmh_attr_type_from_name(char *name)
|
||||
{
|
||||
IF_ATTR_NAME("color") return pmh_attr_type_foreground_color;
|
||||
else IF_ATTR_NAME("foreground") return pmh_attr_type_foreground_color;
|
||||
else IF_ATTR_NAME("foreground-color") return pmh_attr_type_foreground_color;
|
||||
else IF_ATTR_NAME("background") return pmh_attr_type_background_color;
|
||||
else IF_ATTR_NAME("background-color") return pmh_attr_type_background_color;
|
||||
else IF_ATTR_NAME("caret") return pmh_attr_type_caret_color;
|
||||
else IF_ATTR_NAME("caret-color") return pmh_attr_type_caret_color;
|
||||
else IF_ATTR_NAME("strike") return pmh_attr_type_strike_color;
|
||||
else IF_ATTR_NAME("strike-color") return pmh_attr_type_strike_color;
|
||||
else IF_ATTR_NAME("font-size") return pmh_attr_type_font_size_pt;
|
||||
else IF_ATTR_NAME("font-family") return pmh_attr_type_font_family;
|
||||
else IF_ATTR_NAME("font-style") return pmh_attr_type_font_style;
|
||||
return pmh_attr_type_other;
|
||||
}
|
||||
|
||||
char *pmh_attr_name_from_type(pmh_attr_type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case pmh_attr_type_foreground_color:
|
||||
return "foreground-color"; break;
|
||||
case pmh_attr_type_background_color:
|
||||
return "background-color"; break;
|
||||
case pmh_attr_type_caret_color:
|
||||
return "caret-color"; break;
|
||||
case pmh_attr_type_strike_color:
|
||||
return "strike-color"; break;
|
||||
case pmh_attr_type_font_size_pt:
|
||||
return "font-size"; break;
|
||||
case pmh_attr_type_font_family:
|
||||
return "font-family"; break;
|
||||
case pmh_attr_type_font_style:
|
||||
return "font-style"; break;
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typedef struct multi_value
|
||||
{
|
||||
char *value;
|
||||
size_t length;
|
||||
int line_number;
|
||||
struct multi_value *next;
|
||||
} multi_value;
|
||||
|
||||
static multi_value *split_multi_value(char *input, char separator)
|
||||
{
|
||||
multi_value *head = NULL;
|
||||
multi_value *tail = NULL;
|
||||
|
||||
char *c = input;
|
||||
while (*c != '\0')
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; (*(c+i) != '\0' && *(c+i) != separator); i++);
|
||||
|
||||
multi_value *mv = (multi_value *)malloc(sizeof(multi_value));
|
||||
mv->value = (char *)malloc(sizeof(char)*i + 1);
|
||||
mv->length = i;
|
||||
mv->line_number = 0;
|
||||
mv->next = NULL;
|
||||
*mv->value = '\0';
|
||||
strncat(mv->value, c, i);
|
||||
|
||||
if (head == NULL) {
|
||||
head = mv;
|
||||
tail = mv;
|
||||
} else {
|
||||
tail->next = mv;
|
||||
tail = mv;
|
||||
}
|
||||
|
||||
if (*(c+i) == separator)
|
||||
i++;
|
||||
c += i;
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
static void free_multi_value(multi_value *val)
|
||||
{
|
||||
multi_value *cur = val;
|
||||
while (cur != NULL)
|
||||
{
|
||||
multi_value *pvalue = cur;
|
||||
multi_value *next_cur = cur->next;
|
||||
free(pvalue->value);
|
||||
free(pvalue);
|
||||
cur = next_cur;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#define EQUALS(a,b) (strcmp(a, b) == 0)
|
||||
|
||||
static pmh_style_attribute *interpret_attributes(style_parser_data *p_data,
|
||||
pmh_element_type lang_element_type,
|
||||
raw_attribute *raw_attributes)
|
||||
{
|
||||
pmh_style_attribute *attrs = NULL;
|
||||
|
||||
raw_attribute *cur = raw_attributes;
|
||||
while (cur != NULL)
|
||||
{
|
||||
pmh_attr_type atype = pmh_attr_type_from_name(cur->name);
|
||||
pmh_style_attribute *attr = new_attr(cur->name, atype);
|
||||
attr->lang_element_type = lang_element_type;
|
||||
attr->value = new_attr_value();
|
||||
|
||||
if (atype == pmh_attr_type_foreground_color
|
||||
|| atype == pmh_attr_type_background_color
|
||||
|| atype == pmh_attr_type_caret_color
|
||||
|| atype == pmh_attr_type_strike_color)
|
||||
{
|
||||
char *hexstr = trim_str(cur->value);
|
||||
// new_argb_from_hex_str() reports conversion errors
|
||||
attr->value->argb_color =
|
||||
new_argb_from_hex_str(p_data, cur->line_number, hexstr);
|
||||
if (attr->value->argb_color == NULL) {
|
||||
free_style_attributes(attr);
|
||||
attr = NULL;
|
||||
}
|
||||
}
|
||||
else if (atype == pmh_attr_type_font_size_pt)
|
||||
{
|
||||
pmh_attr_font_size *fs = new_font_size();
|
||||
attr->value->font_size = fs;
|
||||
|
||||
char *trimmed_value = trim_str_dup(cur->value);
|
||||
|
||||
fs->is_relative = (*trimmed_value == '+' || *trimmed_value == '-');
|
||||
char *endptr = NULL;
|
||||
fs->size_pt = (int)strtol(cur->value, &endptr, 10);
|
||||
if (endptr == cur->value) {
|
||||
report_error(p_data, cur->line_number,
|
||||
"Value '%s' is invalid for attribute '%s'",
|
||||
cur->value, cur->name);
|
||||
free_style_attributes(attr);
|
||||
attr = NULL;
|
||||
}
|
||||
|
||||
free(trimmed_value);
|
||||
}
|
||||
else if (atype == pmh_attr_type_font_family)
|
||||
{
|
||||
attr->value->font_family = trim_str_dup(cur->value);
|
||||
}
|
||||
else if (atype == pmh_attr_type_font_style)
|
||||
{
|
||||
attr->value->font_styles = new_font_styles();
|
||||
multi_value *values = split_multi_value(cur->value, ',');
|
||||
multi_value *value_cur = values;
|
||||
while (value_cur != NULL)
|
||||
{
|
||||
char *standardized_value = standardize_str(value_cur->value);
|
||||
|
||||
if (EQUALS(standardized_value, "italic"))
|
||||
attr->value->font_styles->italic = true;
|
||||
else if (EQUALS(standardized_value, "bold"))
|
||||
attr->value->font_styles->bold = true;
|
||||
else if (EQUALS(standardized_value, "underlined"))
|
||||
attr->value->font_styles->underlined = true;
|
||||
else {
|
||||
report_error(p_data, cur->line_number,
|
||||
"Value '%s' is invalid for attribute '%s'",
|
||||
standardized_value, cur->name);
|
||||
}
|
||||
|
||||
free(standardized_value);
|
||||
value_cur = value_cur->next;
|
||||
}
|
||||
free_multi_value(values);
|
||||
}
|
||||
else if (atype == pmh_attr_type_other)
|
||||
{
|
||||
attr->value->string = trim_str_dup(cur->value);
|
||||
}
|
||||
|
||||
if (attr != NULL) {
|
||||
// add to linked list
|
||||
attr->next = attrs;
|
||||
attrs = attr;
|
||||
}
|
||||
|
||||
cur = cur->next;
|
||||
}
|
||||
|
||||
return attrs;
|
||||
}
|
||||
|
||||
|
||||
static void interpret_and_add_style(style_parser_data *p_data,
|
||||
char *style_rule_name,
|
||||
int style_rule_line_number,
|
||||
raw_attribute *raw_attributes)
|
||||
{
|
||||
bool isEditorType = false;
|
||||
bool isCurrentLineType = false;
|
||||
bool isSelectionType = false;
|
||||
pmh_element_type type = pmh_element_type_from_name(style_rule_name);
|
||||
if (type == pmh_NO_TYPE)
|
||||
{
|
||||
if (EQUALS(style_rule_name, "editor"))
|
||||
isEditorType = true, type = pmh_NO_TYPE;
|
||||
else if (EQUALS(style_rule_name, "editor-current-line"))
|
||||
isCurrentLineType = true, type = pmh_NO_TYPE;
|
||||
else if (EQUALS(style_rule_name, "editor-selection"))
|
||||
isSelectionType = true, type = pmh_NO_TYPE;
|
||||
else {
|
||||
report_error(p_data, style_rule_line_number,
|
||||
"Style rule '%s' is not a language element type name or "
|
||||
"one of the following: 'editor', 'editor-current-line', "
|
||||
"'editor-selection'",
|
||||
style_rule_name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
pmh_style_attribute *attrs = interpret_attributes(p_data, type, raw_attributes);
|
||||
if (isEditorType)
|
||||
p_data->styles->editor_styles = attrs;
|
||||
else if (isCurrentLineType)
|
||||
p_data->styles->editor_current_line_styles = attrs;
|
||||
else if (isSelectionType)
|
||||
p_data->styles->editor_selection_styles = attrs;
|
||||
else
|
||||
p_data->styles->element_styles[(p_data->styles_pos)++] = attrs;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static bool char_is_whitespace(char c)
|
||||
{
|
||||
return (c == ' ' || c == '\t');
|
||||
}
|
||||
|
||||
static bool char_begins_linecomment(char c)
|
||||
{
|
||||
return (c == '#');
|
||||
}
|
||||
|
||||
static bool line_is_comment(multi_value *line)
|
||||
{
|
||||
char *c;
|
||||
for (c = line->value; *c != '\0'; c++)
|
||||
{
|
||||
if (!char_is_whitespace(*c))
|
||||
return char_begins_linecomment(*c);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool line_is_empty(multi_value *line)
|
||||
{
|
||||
char *c;
|
||||
for (c = line->value; *c != '\0'; c++)
|
||||
{
|
||||
if (!char_is_whitespace(*c))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
typedef struct block
|
||||
{
|
||||
multi_value *lines;
|
||||
struct block *next;
|
||||
} block;
|
||||
|
||||
static block *new_block()
|
||||
{
|
||||
block *ret = (block *)malloc(sizeof(block));
|
||||
ret->next = NULL;
|
||||
ret->lines = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void free_blocks(block *val)
|
||||
{
|
||||
block *cur = val;
|
||||
while (cur != NULL)
|
||||
{
|
||||
block *pblock = cur;
|
||||
block *next = pblock->next;
|
||||
free_multi_value(pblock->lines);
|
||||
free(pblock);
|
||||
cur = next;
|
||||
}
|
||||
}
|
||||
|
||||
static block *get_blocks(char *input)
|
||||
{
|
||||
block *head = NULL;
|
||||
block *tail = NULL;
|
||||
block *current_block = NULL;
|
||||
|
||||
multi_value *discarded_lines = NULL;
|
||||
|
||||
int line_number_counter = 1;
|
||||
|
||||
multi_value *lines = split_multi_value(input, '\n');
|
||||
multi_value *previous_line = NULL;
|
||||
multi_value *line_cur = lines;
|
||||
while (line_cur != NULL)
|
||||
{
|
||||
bool discard_line = false;
|
||||
|
||||
line_cur->line_number = line_number_counter++;
|
||||
|
||||
if (line_is_empty(line_cur))
|
||||
{
|
||||
discard_line = true;
|
||||
|
||||
if (current_block != NULL)
|
||||
{
|
||||
// terminate block
|
||||
if (tail != current_block)
|
||||
tail->next = current_block;
|
||||
tail = current_block;
|
||||
current_block = NULL;
|
||||
previous_line->next = NULL;
|
||||
}
|
||||
}
|
||||
else if (line_is_comment(line_cur))
|
||||
{
|
||||
// Do not discard (i.e. free()) comment lines within blocks:
|
||||
if (current_block == NULL)
|
||||
discard_line = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (current_block == NULL)
|
||||
{
|
||||
// start block
|
||||
current_block = new_block();
|
||||
current_block->lines = line_cur;
|
||||
if (previous_line != NULL)
|
||||
previous_line->next = NULL;
|
||||
}
|
||||
if (head == NULL) {
|
||||
head = current_block;
|
||||
tail = current_block;
|
||||
}
|
||||
}
|
||||
|
||||
multi_value *next_cur = line_cur->next;
|
||||
previous_line = (discard_line) ? NULL : line_cur;
|
||||
|
||||
if (discard_line) {
|
||||
line_cur->next = discarded_lines;
|
||||
discarded_lines = line_cur;
|
||||
}
|
||||
|
||||
line_cur = next_cur;
|
||||
}
|
||||
|
||||
if (current_block != NULL && tail != current_block)
|
||||
tail->next = current_block;
|
||||
|
||||
free_multi_value(discarded_lines);
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
|
||||
#define ASSIGNMENT_OP_UITEXT "':' or '='"
|
||||
#define IS_ASSIGNMENT_OP(c) ((c) == ':' || (c) == '=')
|
||||
#define IS_STYLE_RULE_NAME_CHAR(c) \
|
||||
( (c) != '\0' && !isspace(c) \
|
||||
&& !char_begins_linecomment(c) && !IS_ASSIGNMENT_OP(c) )
|
||||
#define IS_ATTRIBUTE_NAME_CHAR(c) \
|
||||
( (c) != '\0' && !char_begins_linecomment(c) && !IS_ASSIGNMENT_OP(c) )
|
||||
#define IS_ATTRIBUTE_VALUE_CHAR(c) \
|
||||
( (c) != '\0' && !char_begins_linecomment(c) )
|
||||
|
||||
static char *get_style_rule_name(multi_value *line)
|
||||
{
|
||||
char *str = line->value;
|
||||
|
||||
// Scan past leading whitespace:
|
||||
size_t start_index;
|
||||
for (start_index = 0;
|
||||
(*(str+start_index) != '\0' && isspace(*(str+start_index)));
|
||||
start_index++);
|
||||
|
||||
// Scan until style rule name characters end:
|
||||
size_t value_end_index;
|
||||
for (value_end_index = start_index;
|
||||
IS_STYLE_RULE_NAME_CHAR(*(str + value_end_index));
|
||||
value_end_index++);
|
||||
|
||||
// Copy style rule name:
|
||||
size_t value_len = value_end_index - start_index;
|
||||
char *value = (char *)malloc(sizeof(char)*value_len + 1);
|
||||
*value = '\0';
|
||||
strncat(value, (str + start_index), value_len);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static bool parse_attribute_line(style_parser_data *p_data, multi_value *line,
|
||||
char **out_attr_name, char **out_attr_value)
|
||||
{
|
||||
char *str = line->value;
|
||||
|
||||
// Scan past leading whitespace:
|
||||
size_t name_start_index;
|
||||
for (name_start_index = 0;
|
||||
( *(str+name_start_index) != '\0' &&
|
||||
isspace(*(str+name_start_index)) );
|
||||
name_start_index++);
|
||||
|
||||
// Scan until attribute name characters end:
|
||||
size_t name_end_index;
|
||||
for (name_end_index = name_start_index;
|
||||
IS_ATTRIBUTE_NAME_CHAR(*(str + name_end_index));
|
||||
name_end_index++);
|
||||
// Scan backwards to trim trailing whitespace off:
|
||||
while (name_start_index < name_end_index
|
||||
&& isspace(*(str + name_end_index - 1)))
|
||||
name_end_index--;
|
||||
|
||||
// Scan until just after the first assignment operator:
|
||||
size_t assignment_end_index;
|
||||
for (assignment_end_index = name_end_index;
|
||||
( *(str + assignment_end_index) != '\0' &&
|
||||
!IS_ASSIGNMENT_OP(*(str + assignment_end_index)) );
|
||||
assignment_end_index++);
|
||||
|
||||
// Scan over the found assignment operator, or report error:
|
||||
if (IS_ASSIGNMENT_OP(*(str + assignment_end_index)))
|
||||
assignment_end_index++;
|
||||
else
|
||||
{
|
||||
report_error(p_data, line->line_number,
|
||||
"Invalid attribute definition: str does not contain "
|
||||
"an assignment operator (%s): '%s'",
|
||||
ASSIGNMENT_OP_UITEXT, str);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t value_start_index = assignment_end_index;
|
||||
// Scan until attribute value characters end:
|
||||
size_t value_end_index;
|
||||
for (value_end_index = value_start_index;
|
||||
IS_ATTRIBUTE_VALUE_CHAR(*(str + value_end_index));
|
||||
value_end_index++);
|
||||
|
||||
// Copy attribute name:
|
||||
size_t name_len = name_end_index - name_start_index;
|
||||
char *attr_name = (char *)malloc(sizeof(char)*name_len + 1);
|
||||
*attr_name = '\0';
|
||||
strncat(attr_name, (str + name_start_index), name_len);
|
||||
*out_attr_name = attr_name;
|
||||
|
||||
// Copy attribute value:
|
||||
size_t attr_value_len = value_end_index - assignment_end_index;
|
||||
char *attr_value_str = (char *)malloc(sizeof(char)*attr_value_len + 1);
|
||||
*attr_value_str = '\0';
|
||||
strncat(attr_value_str, (str + assignment_end_index), attr_value_len);
|
||||
*out_attr_value = attr_value_str;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#define HAS_UTF8_BOM(x) ( ((*x & 0xFF) == 0xEF)\
|
||||
&& ((*(x+1) & 0xFF) == 0xBB)\
|
||||
&& ((*(x+2) & 0xFF) == 0xBF) )
|
||||
|
||||
// - Removes UTF-8 BOM
|
||||
// - Standardizes line endings to \n
|
||||
static char *strcpy_preformat_style(char *str)
|
||||
{
|
||||
char *new_str = (char *)malloc(sizeof(char) * strlen(str) + 1);
|
||||
|
||||
char *c = str;
|
||||
int i = 0;
|
||||
|
||||
if (HAS_UTF8_BOM(c))
|
||||
c += 3;
|
||||
|
||||
while (*c != '\0')
|
||||
{
|
||||
if (*c == '\r' && *(c+1) == '\n')
|
||||
{
|
||||
*(new_str+i) = '\n';
|
||||
i++;
|
||||
c += 2;
|
||||
}
|
||||
else if (*c == '\r')
|
||||
{
|
||||
*(new_str+i) = '\n';
|
||||
i++;
|
||||
c++;
|
||||
}
|
||||
else
|
||||
{
|
||||
*(new_str+i) = *c;
|
||||
i++;
|
||||
c++;
|
||||
}
|
||||
}
|
||||
*(new_str+i) = '\0';
|
||||
|
||||
return new_str;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void _sty_parse(style_parser_data *p_data)
|
||||
{
|
||||
// We don't have to worry about leaking the original p_data->input;
|
||||
// the user of the library is responsible for that:
|
||||
p_data->input = strcpy_preformat_style(p_data->input);
|
||||
|
||||
block *blocks = get_blocks(p_data->input);
|
||||
|
||||
block *block_cur = blocks;
|
||||
while (block_cur != NULL)
|
||||
{
|
||||
raw_attribute *attributes_head = NULL;
|
||||
raw_attribute *attributes_tail = NULL;
|
||||
|
||||
pmhsp_PRINTF("Block:\n");
|
||||
multi_value *header_line = block_cur->lines;
|
||||
if (header_line == NULL) {
|
||||
block_cur = block_cur->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
pmhsp_PRINTF(" Head line (len %ld): '%s'\n",
|
||||
header_line->length, header_line->value);
|
||||
char *style_rule_name = get_style_rule_name(header_line);
|
||||
pmhsp_PRINTF(" Style rule name: '%s'\n", style_rule_name);
|
||||
|
||||
multi_value *attr_line_cur = header_line->next;
|
||||
if (attr_line_cur == NULL)
|
||||
report_error(p_data, header_line->line_number,
|
||||
"No style attributes defined for style rule '%s'",
|
||||
style_rule_name);
|
||||
|
||||
while (attr_line_cur != NULL)
|
||||
{
|
||||
if (line_is_comment(attr_line_cur))
|
||||
{
|
||||
attr_line_cur = attr_line_cur->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
pmhsp_PRINTF(" Attr line (len %ld): '%s'\n",
|
||||
attr_line_cur->length, attr_line_cur->value);
|
||||
char *attr_name_str;
|
||||
char *attr_value_str;
|
||||
bool success = parse_attribute_line(p_data,
|
||||
attr_line_cur,
|
||||
&attr_name_str,
|
||||
&attr_value_str);
|
||||
if (success)
|
||||
{
|
||||
pmhsp_PRINTF(" Attr: '%s' Value: '%s'\n",
|
||||
attr_name_str, attr_value_str);
|
||||
raw_attribute *attribute =
|
||||
new_raw_attribute(attr_name_str, attr_value_str,
|
||||
attr_line_cur->line_number);
|
||||
if (attributes_head == NULL) {
|
||||
attributes_head = attribute;
|
||||
attributes_tail = attribute;
|
||||
} else {
|
||||
attributes_tail->next = attribute;
|
||||
attributes_tail = attribute;
|
||||
}
|
||||
}
|
||||
|
||||
attr_line_cur = attr_line_cur->next;
|
||||
}
|
||||
|
||||
if (attributes_head != NULL)
|
||||
{
|
||||
interpret_and_add_style(p_data, style_rule_name,
|
||||
header_line->line_number, attributes_head);
|
||||
free_raw_attributes(attributes_head);
|
||||
}
|
||||
|
||||
free(style_rule_name);
|
||||
|
||||
block_cur = block_cur->next;
|
||||
}
|
||||
|
||||
free_blocks(blocks);
|
||||
free(p_data->input);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static pmh_style_collection *new_style_collection()
|
||||
{
|
||||
pmh_style_collection *sc = (pmh_style_collection *)
|
||||
malloc(sizeof(pmh_style_collection));
|
||||
|
||||
sc->element_styles = (pmh_style_attribute**)
|
||||
malloc(sizeof(pmh_style_attribute*)
|
||||
* pmh_NUM_LANG_TYPES);
|
||||
int i;
|
||||
for (i = 0; i < pmh_NUM_LANG_TYPES; i++)
|
||||
sc->element_styles[i] = NULL;
|
||||
|
||||
sc->editor_styles = NULL;
|
||||
sc->editor_current_line_styles = NULL;
|
||||
sc->editor_selection_styles = NULL;
|
||||
|
||||
return sc;
|
||||
}
|
||||
|
||||
void pmh_free_style_collection(pmh_style_collection *coll)
|
||||
{
|
||||
free_style_attributes(coll->editor_styles);
|
||||
free_style_attributes(coll->editor_current_line_styles);
|
||||
free_style_attributes(coll->editor_selection_styles);
|
||||
int i;
|
||||
for (i = 0; i < pmh_NUM_LANG_TYPES; i++)
|
||||
free_style_attributes(coll->element_styles[i]);
|
||||
free(coll->element_styles);
|
||||
free(coll);
|
||||
}
|
||||
|
||||
static style_parser_data *new_style_parser_data(char *input)
|
||||
{
|
||||
style_parser_data *p_data = (style_parser_data*)
|
||||
malloc(sizeof(style_parser_data));
|
||||
p_data->input = input;
|
||||
p_data->styles_pos = 0;
|
||||
p_data->styles = new_style_collection();
|
||||
return p_data;
|
||||
}
|
||||
|
||||
pmh_style_collection *pmh_parse_styles(char *input,
|
||||
void(*error_callback)(char*,int,void*),
|
||||
void *error_callback_context)
|
||||
{
|
||||
style_parser_data *p_data = new_style_parser_data(input);
|
||||
p_data->error_callback = error_callback;
|
||||
p_data->error_callback_context = error_callback_context;
|
||||
|
||||
_sty_parse(p_data);
|
||||
|
||||
pmh_style_collection *ret = p_data->styles;
|
||||
free(p_data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1,148 +0,0 @@
|
||||
/* PEG Markdown Highlight
|
||||
* Copyright 2011-2016 Ali Rantakari -- http://hasseg.org
|
||||
* Licensed under the GPL2+ and MIT licenses (see LICENSE for more info).
|
||||
*
|
||||
* pmh_styleparser.h
|
||||
*
|
||||
* Public interface of a parser for custom syntax highlighting stylesheets.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \brief Style parser public interface.
|
||||
*/
|
||||
|
||||
#include "pmh_definitions.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* \brief Color (ARGB) attribute value.
|
||||
*
|
||||
* All values are 0-255.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
int red; /**< Red color component (0-255) */
|
||||
int green; /**< Green color component (0-255) */
|
||||
int blue; /**< Blue color component (0-255) */
|
||||
int alpha; /**< Alpha (opacity) color component (0-255) */
|
||||
} pmh_attr_argb_color;
|
||||
|
||||
/** \brief Font style attribute value. */
|
||||
typedef struct
|
||||
{
|
||||
bool italic;
|
||||
bool bold;
|
||||
bool underlined;
|
||||
} pmh_attr_font_styles;
|
||||
|
||||
/** \brief Font size attribute value. */
|
||||
typedef struct
|
||||
{
|
||||
int size_pt; /**< The font point size */
|
||||
bool is_relative; /**< Whether the size is relative (i.e. size_pt points
|
||||
larger than the default font) */
|
||||
} pmh_attr_font_size;
|
||||
|
||||
/** \brief Style attribute types. */
|
||||
typedef enum
|
||||
{
|
||||
pmh_attr_type_foreground_color, /**< Foreground color */
|
||||
pmh_attr_type_background_color, /**< Background color */
|
||||
pmh_attr_type_caret_color, /**< Caret (insertion point) color */
|
||||
pmh_attr_type_font_size_pt, /**< Font size (in points) */
|
||||
pmh_attr_type_font_family, /**< Font family */
|
||||
pmh_attr_type_font_style, /**< Font style */
|
||||
pmh_attr_type_strike_color, /**< Strike-through color */
|
||||
pmh_attr_type_other /**< Arbitrary custom attribute */
|
||||
} pmh_attr_type;
|
||||
|
||||
/**
|
||||
* \brief Style attribute value.
|
||||
*
|
||||
* Determine which member to access in this union based on the
|
||||
* 'type' value of the pmh_style_attribute.
|
||||
*
|
||||
* \sa pmh_style_attribute
|
||||
*/
|
||||
typedef union
|
||||
{
|
||||
pmh_attr_argb_color *argb_color; /**< ARGB color */
|
||||
pmh_attr_font_styles *font_styles; /**< Font styles */
|
||||
pmh_attr_font_size *font_size; /**< Font size */
|
||||
char *font_family; /**< Font family */
|
||||
char *string; /**< Arbitrary custom string value
|
||||
(use this if the attribute's type
|
||||
is pmh_attr_type_other) */
|
||||
} pmh_attr_value;
|
||||
|
||||
/** \brief Style attribute. */
|
||||
typedef struct pmh_style_attribute
|
||||
{
|
||||
pmh_element_type lang_element_type; /**< The Markdown language element this
|
||||
style applies to */
|
||||
pmh_attr_type type; /**< The type of the attribute */
|
||||
char *name; /**< The name of the attribute (if type
|
||||
is pmh_attr_type_other, you can
|
||||
use this value to determine what
|
||||
the attribute is) */
|
||||
pmh_attr_value *value; /**< The value of the attribute */
|
||||
struct pmh_style_attribute *next; /**< Next attribute in linked list */
|
||||
} pmh_style_attribute;
|
||||
|
||||
/** \brief Collection of styles. */
|
||||
typedef struct
|
||||
{
|
||||
/** Styles that apply to the editor in general */
|
||||
pmh_style_attribute *editor_styles;
|
||||
|
||||
/** Styles that apply to the line in the editor where the caret (insertion
|
||||
point) resides */
|
||||
pmh_style_attribute *editor_current_line_styles;
|
||||
|
||||
/** Styles that apply to the range of selected text in the editor */
|
||||
pmh_style_attribute *editor_selection_styles;
|
||||
|
||||
/** Styles that apply to specific Markdown language elements */
|
||||
pmh_style_attribute **element_styles;
|
||||
} pmh_style_collection;
|
||||
|
||||
|
||||
/**
|
||||
* \brief Parse stylesheet string, return style collection
|
||||
*
|
||||
* \param[in] input The stylesheet string to parse.
|
||||
* \param[in] error_callback Callback function to be called when errors
|
||||
* occur during parsing. The first argument
|
||||
* to the callback function is the error
|
||||
* message and the second one the line number
|
||||
* in the original input where the error
|
||||
* occurred. The last argument will always
|
||||
* get the value you pass in for the
|
||||
* error_callback_context argument to this
|
||||
* function.
|
||||
* Pass in NULL to suppress error reporting.
|
||||
* \param[in] error_callback_context Arbitrary context pointer for the error
|
||||
* callback function; will be passed in as
|
||||
* the last argument to error_callback.
|
||||
*
|
||||
* \return A pmh_style_collection. You must pass this value to
|
||||
* pmh_free_style_collection() when it's not needed anymore.
|
||||
*/
|
||||
pmh_style_collection *pmh_parse_styles(char *input,
|
||||
void(*error_callback)(char*,int,void*),
|
||||
void *error_callback_context);
|
||||
|
||||
/**
|
||||
* \brief Free a pmh_style_collection.
|
||||
*
|
||||
* Frees a pmh_style_collection value returned by pmh_parse_styles().
|
||||
*
|
||||
* \param[in] collection The collection to free.
|
||||
*/
|
||||
void pmh_free_style_collection(pmh_style_collection *collection);
|
||||
|
||||
|
||||
char *pmh_attr_name_from_type(pmh_attr_type type);
|
||||
|
||||
pmh_attr_type pmh_attr_type_from_name(char *name);
|
||||
|
@ -1,8 +0,0 @@
|
||||
NONEXISTENT_TYPE
|
||||
x: 3
|
||||
|
||||
STRONG
|
||||
font-style: funkadelic, bold, snazzy
|
||||
foreground: 13bx12
|
||||
background: 5
|
||||
|
@ -1,87 +0,0 @@
|
||||
# Styles using 'Solarized' color scheme
|
||||
# by Ethan Schoonover: http://ethanschoonover.com/solarized
|
||||
#
|
||||
# (dark background version)
|
||||
|
||||
editor
|
||||
foreground: 93a1a1 # base1
|
||||
background: 002b36 # base03
|
||||
caret: ffffff
|
||||
font-size: 13
|
||||
|
||||
H1
|
||||
foreground: 6c71c4 # violet
|
||||
font-style: bold
|
||||
font-size: +6
|
||||
|
||||
H2
|
||||
foreground: 6c71c4 # violet
|
||||
font-style: bold
|
||||
font-size: +5
|
||||
|
||||
H3
|
||||
foreground: 6c71c4 # violet
|
||||
font-size: +4
|
||||
|
||||
H4
|
||||
foreground: 268bd2 # blue
|
||||
font-size: +3
|
||||
|
||||
H5
|
||||
foreground: 268bd2 # blue
|
||||
font-size: +2
|
||||
|
||||
H6
|
||||
foreground: 268bd2 # blue
|
||||
font-size: +1
|
||||
|
||||
HRULE
|
||||
foreground: 586e75 # base01
|
||||
|
||||
LIST_BULLET
|
||||
foreground: b58900 # yellow
|
||||
|
||||
LIST_ENUMERATOR
|
||||
foreground: b58900 # yellow
|
||||
|
||||
LINK
|
||||
foreground: 2aa198 # cyan
|
||||
|
||||
AUTO_LINK_URL
|
||||
foreground: 2aa198 # cyan
|
||||
|
||||
AUTO_LINK_EMAIL
|
||||
foreground: 2aa198 # cyan
|
||||
|
||||
IMAGE
|
||||
foreground: d33682 # magenta
|
||||
|
||||
REFERENCE
|
||||
foreground: 80b58900 # yellow, reduced alpha
|
||||
font-size: -2
|
||||
|
||||
CODE
|
||||
foreground: 859900 # green
|
||||
|
||||
EMPH
|
||||
foreground: cb4b16 # orange
|
||||
font-style: italic
|
||||
|
||||
STRONG
|
||||
foreground: dc322f # red
|
||||
font-style: bold
|
||||
|
||||
HTML_ENTITY
|
||||
foreground: 6c71c4 # violet
|
||||
|
||||
COMMENT
|
||||
foreground: 93a1a1 # base1
|
||||
|
||||
VERBATIM
|
||||
foreground: 859900 # green
|
||||
|
||||
BLOCKQUOTE
|
||||
foreground: d33682 # magenta
|
||||
|
||||
STRIKE
|
||||
strike-color: 93a1a1 # base1
|
@ -1 +0,0 @@
|
||||
# linecomment
editor # comment
foreground : 13ff13
background : 000000 # comment
# linecomment
STRONG:
EMPH=
# comment
foreground: 00ff00
comment asd
background: AB0000ff
STRONG :
dog: 1 # something
cat: 4
font-style: underlined, Italic , BoLD #hi, hello
font-size: 14pt
font-family: Courier New, Times
# linecomment
BOO
x: 3
editor-selection:
foreground: abcdef
background: abcdef
editor-current-line:
background: ffffff
|
@ -1,22 +0,0 @@
|
||||
editor :
|
||||
foreground : 13ff13
|
||||
background : 000000
|
||||
caret: ffffff
|
||||
|
||||
EMPH
|
||||
font-style: italic
|
||||
|
||||
STRONG
|
||||
font-style: bold
|
||||
|
||||
LINK
|
||||
font-style: underlined
|
||||
|
||||
editor-selection:
|
||||
foreground: ff0000
|
||||
background: eeeeee
|
||||
font-style: underlined
|
||||
|
||||
editor-current-line:
|
||||
background: ffffff
|
||||
|
@ -1,79 +0,0 @@
|
||||
# Styles using 'Solarized' color scheme
|
||||
# by Ethan Schoonover: http://ethanschoonover.com/solarized
|
||||
#
|
||||
# (dark background version)
|
||||
|
||||
editor
|
||||
foreground: 93a1a1 # base1
|
||||
background: 002b36 # base03
|
||||
caret: ffffff
|
||||
|
||||
H1
|
||||
foreground: 6c71c4 # violet
|
||||
font-style: bold
|
||||
|
||||
H2
|
||||
foreground: 6c71c4 # violet
|
||||
font-style: bold
|
||||
|
||||
H3
|
||||
foreground: 6c71c4 # violet
|
||||
|
||||
H4
|
||||
foreground: 268bd2 # blue
|
||||
|
||||
H5
|
||||
foreground: 268bd2 # blue
|
||||
|
||||
H6
|
||||
foreground: 268bd2 # blue
|
||||
|
||||
HRULE
|
||||
foreground: 586e75 # base01
|
||||
|
||||
LIST_BULLET
|
||||
foreground: b58900 # yellow
|
||||
|
||||
LIST_ENUMERATOR
|
||||
foreground: b58900 # yellow
|
||||
|
||||
LINK
|
||||
foreground: 2aa198 # cyan
|
||||
|
||||
AUTO_LINK_URL
|
||||
foreground: 2aa198 # cyan
|
||||
|
||||
AUTO_LINK_EMAIL
|
||||
foreground: 2aa198 # cyan
|
||||
|
||||
IMAGE
|
||||
foreground: d33682 # magenta
|
||||
|
||||
REFERENCE
|
||||
foreground: 80b58900 # yellow, reduced alpha
|
||||
|
||||
CODE
|
||||
foreground: 859900 # green
|
||||
|
||||
EMPH
|
||||
foreground: cb4b16 # orange
|
||||
font-style: italic
|
||||
|
||||
STRONG
|
||||
foreground: dc322f # red
|
||||
font-style: bold
|
||||
|
||||
HTML_ENTITY
|
||||
foreground: 6c71c4 # violet
|
||||
|
||||
COMMENT
|
||||
foreground: 93a1a1 # base1
|
||||
|
||||
VERBATIM
|
||||
foreground: 859900 # green
|
||||
|
||||
BLOCKQUOTE
|
||||
foreground: d33682 # magenta
|
||||
|
||||
STRIKE
|
||||
strike-color: 93a1a1 # base1
|
@ -1,80 +0,0 @@
|
||||
# Styles using 'Solarized' color scheme
|
||||
# by Ethan Schoonover: http://ethanschoonover.com/solarized
|
||||
#
|
||||
# (light background version)
|
||||
|
||||
editor
|
||||
foreground: 586e75 # base01
|
||||
background: fdf6e3 # base3
|
||||
caret: 000000
|
||||
|
||||
H1
|
||||
foreground: 6c71c4 # violet
|
||||
font-style: bold
|
||||
|
||||
H2
|
||||
foreground: 6c71c4 # violet
|
||||
font-style: bold
|
||||
|
||||
H3
|
||||
foreground: 6c71c4 # violet
|
||||
|
||||
H4
|
||||
foreground: 268bd2 # blue
|
||||
|
||||
H5
|
||||
foreground: 268bd2 # blue
|
||||
|
||||
H6
|
||||
foreground: 268bd2 # blue
|
||||
|
||||
HRULE
|
||||
foreground: 586e75 # base01
|
||||
|
||||
LIST_BULLET
|
||||
foreground: b58900 # yellow
|
||||
|
||||
LIST_ENUMERATOR
|
||||
foreground: b58900 # yellow
|
||||
|
||||
LINK
|
||||
foreground: 2aa198 # cyan
|
||||
|
||||
AUTO_LINK_URL
|
||||
foreground: 2aa198 # cyan
|
||||
|
||||
AUTO_LINK_EMAIL
|
||||
foreground: 2aa198 # cyan
|
||||
|
||||
IMAGE
|
||||
foreground: d33682 # magenta
|
||||
|
||||
REFERENCE
|
||||
foreground: 80b58900 # yellow, reduced alpha
|
||||
|
||||
CODE
|
||||
foreground: 859900 # green
|
||||
|
||||
EMPH
|
||||
foreground: cb4b16 # orange
|
||||
font-style: italic
|
||||
|
||||
STRONG
|
||||
foreground: dc322f # red
|
||||
font-style: bold
|
||||
|
||||
HTML_ENTITY
|
||||
foreground: 6c71c4 # violet
|
||||
|
||||
COMMENT
|
||||
foreground: 93a1a1 # base1
|
||||
|
||||
VERBATIM
|
||||
foreground: 859900 # green
|
||||
|
||||
BLOCKQUOTE
|
||||
foreground: d33682 # magenta
|
||||
|
||||
STRIKE
|
||||
strike-color: 586e75 # base01
|
||||
|
@ -1,33 +0,0 @@
|
||||
# linecomment
|
||||
|
||||
editor # comment
|
||||
foreground : 13ff13
|
||||
background : 000000 # comment
|
||||
|
||||
# linecomment
|
||||
STRONG:
|
||||
|
||||
EMPH=
|
||||
# comment
|
||||
foreground: 00ff00
|
||||
comment asd
|
||||
background: AB0000ff
|
||||
|
||||
STRONG :
|
||||
dog: 1 # something
|
||||
cat: 4
|
||||
font-style: underlined, Italic , BoLD #hi, hello
|
||||
font-size: 14pt
|
||||
font-family: Courier New, Times
|
||||
|
||||
# linecomment
|
||||
BOO
|
||||
x: 3
|
||||
|
||||
editor-selection:
|
||||
foreground: abcdef
|
||||
background: abcdef
|
||||
|
||||
editor-current-line:
|
||||
background: ffffff
|
||||
|
@ -1,33 +0,0 @@
|
||||
# linecomment
|
||||
|
||||
editor # comment
|
||||
foreground : 13ff13
|
||||
background : 000000 # comment
|
||||
|
||||
# linecomment
|
||||
STRONG:
|
||||
|
||||
EMPH=
|
||||
# comment
|
||||
foreground: 00ff00
|
||||
comment asd
|
||||
background: AB0000ff
|
||||
|
||||
STRONG :
|
||||
dog: 1 # something
|
||||
cat: 4
|
||||
font-style: underlined, Italic , BoLD #hi, hello
|
||||
font-size: 14pt
|
||||
font-family: Courier New, Times
|
||||
|
||||
# linecomment
|
||||
BOO
|
||||
x: 3
|
||||
|
||||
editor-selection:
|
||||
foreground: abcdef
|
||||
background: abcdef
|
||||
|
||||
editor-current-line:
|
||||
background: ffffff
|
||||
|
@ -1,199 +0,0 @@
|
||||
|
||||
The Syntax of PEG Markdown Highlight Stylesheets
|
||||
================================================
|
||||
|
||||
[PEG Markdown Highlight][pmh] includes a parser for stylesheets that define how different Markdown language elements are to be highlighted. This document describes the syntax of these stylesheets.
|
||||
|
||||
[pmh]: http://hasseg.org/peg-markdown-highlight/
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
Here is a quick, simple example of a stylesheet:
|
||||
|
||||
<style>
|
||||
.codetable { border-collapse: collapse; }
|
||||
.codetable .left { text-align: right; padding-right: 10px; }
|
||||
.codetable .right { text-align: left; padding-left: 10px; }
|
||||
.codetable .content { font-family: monospace; background: #eee; padding: 0 5px; }
|
||||
.codetable .comment { color: #174EB3; }
|
||||
.codetable .rule { color: #491B8F; }
|
||||
.codetable .attrname { color: #48B317; }
|
||||
.codetable .attrvalue { color: #A65C1F; }
|
||||
</style>
|
||||
|
||||
<table class="codetable">
|
||||
<tr>
|
||||
<td class="left"></td>
|
||||
<td class="content"># The first comment lines</td>
|
||||
<td class="right"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left"></td>
|
||||
<td class="content"># describe the stylesheet.</td>
|
||||
<td class="right"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left"></td>
|
||||
<td class="content"> </td>
|
||||
<td class="right"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left"><span class="rule">Style rule →</span></td>
|
||||
<td class="content"><span class="rule">editor:</span></td>
|
||||
<td class="right"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left"></td>
|
||||
<td class="content"> foreground: ff0000 <span class="comment"># red text</span></td>
|
||||
<td class="right"><span class="comment">← Comment</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left"><span class="attrname">Attribute name →</span></td>
|
||||
<td class="content"> <span class="attrname">font-family</span>: <span class="attrvalue">Consolas</span></td>
|
||||
<td class="right"><span class="attrvalue">← Attribute value</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left"></td>
|
||||
<td class="content"> </td>
|
||||
<td class="right"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left"></td>
|
||||
<td class="content">EMPH:</td>
|
||||
<td class="right"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left"></td>
|
||||
<td class="content"> font-size: 14</td>
|
||||
<td class="right"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left"></td>
|
||||
<td class="content"> font-style: bold, underlined</td>
|
||||
<td class="right"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
Style Rules
|
||||
-----------
|
||||
|
||||
A stylesheet is composed of one or more *rules*. Rules are separated from each other by **empty lines** like so:
|
||||
|
||||
H2:
|
||||
foreground: ff0000
|
||||
|
||||
H3:
|
||||
foreground: 00ff00
|
||||
|
||||
Each begins with the ***name* of the rule**, which is always on its own line, and may be one of the following:
|
||||
|
||||
- **`editor`**: Styles that apply to the whole document/editor
|
||||
- **`editor-current-line`**: Styles that apply to the current line in the editor (i.e. the line where the caret is)
|
||||
- **`editor-selection`**: Styles that apply to the selected range in the editor when the user makes a selection in the text
|
||||
- A Markdown element type (like `EMPH`, `REFERENCE` or `H1`): Styles that apply to occurrences of that particular element. The supported element types are:
|
||||
- **`LINK`:** Explicit link (like `[click here][ref]`)
|
||||
- **`AUTO_LINK_URL`:** Implicit URL link (like `<http://google.com>`)
|
||||
- **`AUTO_LINK_EMAIL`:** Implicit email link (like `<first.last@google.com>`)
|
||||
- **`IMAGE`:** Image definition
|
||||
- **`REFERENCE`:** Reference (like `[id]: http://www.google.com`)
|
||||
- **`CODE`:** Inline code
|
||||
- **`EMPH`:** Emphasized text
|
||||
- **`STRONG`:** Strong text
|
||||
- **`LIST_BULLET`:** Bullet for an unordered list item
|
||||
- **`LIST_ENUMERATOR`:** Enumerator for an ordered list item
|
||||
- **`H1`:** Header, level 1
|
||||
- **`H2`:** Header, level 2
|
||||
- **`H3`:** Header, level 3
|
||||
- **`H4`:** Header, level 4
|
||||
- **`H5`:** Header, level 5
|
||||
- **`H6`:** Header, level 6
|
||||
- **`BLOCKQUOTE`:** Blockquote marker
|
||||
- **`VERBATIM`:** Block of code
|
||||
- **`HRULE`:** Horizontal rule
|
||||
- **`HTML`:** HTML tag
|
||||
- **`HTML_ENTITY`:** HTML special entity definition (like `…`)
|
||||
- **`HTMLBLOCK`:** Block of HTML
|
||||
- **`COMMENT`:** (HTML) Comment
|
||||
- **`NOTE`:** Note
|
||||
- **`STRIKE`:** Strike-through
|
||||
|
||||
The name may be optionally followed by an assignment operator (either `:` or `=`):
|
||||
|
||||
H1:
|
||||
foreground: ff00ff
|
||||
|
||||
H2 =
|
||||
foreground: ff0000
|
||||
|
||||
H3
|
||||
foreground: 00ff00
|
||||
|
||||
The **order of style rules is significant**; it defines the order in which different language elements should be highlighted. *(Of course applications that use PEG Markdown Highlight and the style parser may disregard this and highlight elements in whatever order they desire.)*
|
||||
|
||||
After the name of the rule, there can be one or more *attributes*.
|
||||
|
||||
|
||||
Style Attributes
|
||||
----------------
|
||||
|
||||
Attribute assignments are each on their own line, and they consist of the *name* of the attribute as well as the *value* assigned to it. An assignment operator (either `:` or `=`) separates the name from the value:
|
||||
|
||||
attribute-name: value
|
||||
attribute-name= value
|
||||
|
||||
Attribute assignment lines **may be indented**.
|
||||
|
||||
### Attribute Names and Types
|
||||
|
||||
The following is a list of the names of predefined attributes, and the values they may be assigned:
|
||||
|
||||
- `foreground-color` *(aliases: `foreground` and `color`)*
|
||||
- See the *Color Attribute Values* subsection for information about valid values for this attribute.
|
||||
- `background-color` *(alias: `background`)*
|
||||
- See the *Color Attribute Values* subsection for information about valid values for this attribute.
|
||||
- `caret-color` *(alias: `caret`)*
|
||||
- See the *Color Attribute Values* subsection for information about valid values for this attribute.
|
||||
- `strike-color` *(alias: `strike`)*
|
||||
- See the *Color Attribute Values* subsection for information about valid values for this attribute.
|
||||
- `font-size`
|
||||
- An integer value for the font size, *in points* (i.e. not in pixels). The number may have a textual suffix such as `pt`.
|
||||
- If the value begins with `+` or `-`, it is considered *relative* to some base font size (as defined by the host application). For example, the value `3` defines the font size as 3 (absolute) while `+3` defines it as +3 (relative), i.e. 3 point sizes larger than the base font size.
|
||||
- `font-family`
|
||||
- A comma-separated list of one or more arbitrary font family names. *(It is up to the application that uses the PEG Markdown Highlight library to resolve this string to actual fonts on the system.)*
|
||||
- `font-style`
|
||||
- A comma-separated list of one or more of the following:
|
||||
- `italic`
|
||||
- `bold`
|
||||
- `underlined`
|
||||
|
||||
Applications may also include support for any **custom attribute names and values** they desire — attributes other than the ones listed above will be included in the style parser results, with their values stored as strings.
|
||||
|
||||
|
||||
## Color Attribute Values
|
||||
|
||||
Colors can be specified either in **RGB** (red, green, blue) or **ARGB** (alpha, red, green, blue) formats. In both, each component is a two-character hexadecimal value (from `00` to `FF`):
|
||||
|
||||
foreground: ff00ee # red = ff, green = 00, blue = ee (and implicitly, alpha = ff)
|
||||
background: 99ff00ee # alpha = 99, red = ff, green = 00, blue = ee
|
||||
|
||||
|
||||
Comments
|
||||
--------
|
||||
|
||||
Each line in a stylesheet may have a comment. The `#` character begins a line comment that continues until the end of the line:
|
||||
|
||||
# this line has only this comment
|
||||
H1: # this line has a style rule name and then a comment
|
||||
foreground: ff0000 # this line has an attribute and then a comment
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 198 KiB |
Before Width: | Height: | Size: 533 KiB |
Before Width: | Height: | Size: 601 KiB |
Before Width: | Height: | Size: 291 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 7.9 KiB |
@ -1,248 +0,0 @@
|
||||
#include "vconfirmdeletiondialog.h"
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
#include "utils/vutils.h"
|
||||
#include "vconfigmanager.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
ConfirmItemWidget::ConfirmItemWidget(bool p_checked,
|
||||
const QString &p_file,
|
||||
const QString &p_tip,
|
||||
int p_index,
|
||||
QWidget *p_parent)
|
||||
: QWidget(p_parent), m_index(p_index)
|
||||
{
|
||||
setupUI();
|
||||
|
||||
m_checkBox->setChecked(p_checked);
|
||||
m_fileLabel->setText(p_file);
|
||||
if (!p_tip.isEmpty()) {
|
||||
m_fileLabel->setToolTip(p_tip);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfirmItemWidget::setupUI()
|
||||
{
|
||||
m_checkBox = new QCheckBox;
|
||||
connect(m_checkBox, &QCheckBox::stateChanged,
|
||||
this, &ConfirmItemWidget::checkStateChanged);
|
||||
|
||||
m_fileLabel = new QLabel;
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
mainLayout->addWidget(m_checkBox);
|
||||
mainLayout->addWidget(m_fileLabel);
|
||||
mainLayout->addStretch();
|
||||
mainLayout->setContentsMargins(3, 0, 0, 0);
|
||||
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
bool ConfirmItemWidget::isChecked() const
|
||||
{
|
||||
return m_checkBox->isChecked();
|
||||
}
|
||||
|
||||
int ConfirmItemWidget::getIndex() const
|
||||
{
|
||||
return m_index;
|
||||
}
|
||||
|
||||
VConfirmDeletionDialog::VConfirmDeletionDialog(const QString &p_title,
|
||||
const QString &p_text,
|
||||
const QString &p_info,
|
||||
const QVector<ConfirmItemInfo> &p_items,
|
||||
bool p_enableAskAgain,
|
||||
bool p_askAgainEnabled,
|
||||
bool p_enablePreview,
|
||||
QWidget *p_parent)
|
||||
: QDialog(p_parent),
|
||||
m_enableAskAgain(p_enableAskAgain),
|
||||
m_askAgainEnabled(p_askAgainEnabled),
|
||||
m_enablePreview(p_enablePreview),
|
||||
m_items(p_items)
|
||||
{
|
||||
setupUI(p_title, p_text, p_info);
|
||||
|
||||
initItems();
|
||||
|
||||
updateCountLabel();
|
||||
}
|
||||
|
||||
void VConfirmDeletionDialog::setupUI(const QString &p_title,
|
||||
const QString &p_text,
|
||||
const QString &p_info)
|
||||
{
|
||||
QLabel *textLabel = NULL;
|
||||
if (!p_text.isEmpty()) {
|
||||
textLabel = new QLabel(p_text);
|
||||
textLabel->setWordWrap(true);
|
||||
}
|
||||
|
||||
QLabel *infoLabel = NULL;
|
||||
if (!p_info.isEmpty()) {
|
||||
infoLabel = new QLabel(p_info);
|
||||
infoLabel->setWordWrap(true);
|
||||
}
|
||||
|
||||
m_countLabel = new QLabel("Items");
|
||||
QHBoxLayout *labelLayout = new QHBoxLayout;
|
||||
labelLayout->addWidget(m_countLabel);
|
||||
labelLayout->addStretch();
|
||||
labelLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
m_listWidget = new QListWidget();
|
||||
m_listWidget->setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
connect(m_listWidget, &QListWidget::currentRowChanged,
|
||||
this, &VConfirmDeletionDialog::currentFileChanged);
|
||||
connect(m_listWidget, &QListWidget::itemActivated,
|
||||
this, [this](QListWidgetItem *p_item) {
|
||||
// Open it using resource manager.
|
||||
if (!p_item) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConfirmItemWidget *widget = getItemWidget(p_item);
|
||||
Q_ASSERT(widget);
|
||||
QString filePath = m_items[widget->getIndex()].m_path;
|
||||
|
||||
if (!filePath.isEmpty()) {
|
||||
QUrl url = QUrl::fromLocalFile(filePath);
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
});
|
||||
|
||||
m_previewer = new QLabel();
|
||||
|
||||
m_askAgainCB = new QCheckBox(tr("Do not ask for confirmation again"));
|
||||
m_askAgainCB->setChecked(!m_askAgainEnabled);
|
||||
m_askAgainCB->setVisible(m_enableAskAgain);
|
||||
|
||||
// Ok is the default button.
|
||||
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
m_btnBox->button(QDialogButtonBox::Ok)->setProperty("DangerBtn", true);
|
||||
|
||||
QHBoxLayout *midLayout = new QHBoxLayout;
|
||||
midLayout->addWidget(m_listWidget);
|
||||
if (m_enablePreview) {
|
||||
midLayout->addStretch();
|
||||
midLayout->addWidget(m_previewer);
|
||||
midLayout->addStretch();
|
||||
} else {
|
||||
midLayout->addWidget(m_previewer);
|
||||
m_previewer->setVisible(false);
|
||||
}
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
if (textLabel) {
|
||||
mainLayout->addWidget(textLabel);
|
||||
}
|
||||
|
||||
if (infoLabel) {
|
||||
mainLayout->addWidget(infoLabel);
|
||||
}
|
||||
|
||||
mainLayout->addWidget(m_askAgainCB);
|
||||
mainLayout->addWidget(m_btnBox);
|
||||
mainLayout->addLayout(labelLayout);
|
||||
mainLayout->addLayout(midLayout);
|
||||
|
||||
setLayout(mainLayout);
|
||||
setWindowTitle(p_title);
|
||||
}
|
||||
|
||||
QVector<ConfirmItemInfo> VConfirmDeletionDialog::getConfirmedItems() const
|
||||
{
|
||||
QVector<ConfirmItemInfo> confirmedItems;
|
||||
|
||||
for (int i = 0; i < m_listWidget->count(); ++i) {
|
||||
ConfirmItemWidget *widget = getItemWidget(m_listWidget->item(i));
|
||||
if (widget->isChecked()) {
|
||||
confirmedItems.push_back(m_items[widget->getIndex()]);
|
||||
}
|
||||
}
|
||||
|
||||
return confirmedItems;
|
||||
}
|
||||
|
||||
void VConfirmDeletionDialog::initItems()
|
||||
{
|
||||
m_listWidget->clear();
|
||||
|
||||
for (int i = 0; i < m_items.size(); ++i) {
|
||||
ConfirmItemWidget *itemWidget = new ConfirmItemWidget(true,
|
||||
m_items[i].m_name,
|
||||
m_items[i].m_tip,
|
||||
i,
|
||||
this);
|
||||
connect(itemWidget, &ConfirmItemWidget::checkStateChanged,
|
||||
this, &VConfirmDeletionDialog::updateCountLabel);
|
||||
|
||||
QListWidgetItem *item = new QListWidgetItem();
|
||||
QSize size = itemWidget->sizeHint();
|
||||
size.setHeight(size.height() * 2);
|
||||
item->setSizeHint(size);
|
||||
|
||||
m_listWidget->addItem(item);
|
||||
m_listWidget->setItemWidget(item, itemWidget);
|
||||
}
|
||||
|
||||
m_listWidget->setMinimumSize(m_listWidget->sizeHint());
|
||||
m_listWidget->setCurrentRow(-1);
|
||||
m_listWidget->setCurrentRow(0);
|
||||
}
|
||||
|
||||
bool VConfirmDeletionDialog::getAskAgainEnabled() const
|
||||
{
|
||||
return !m_askAgainCB->isChecked();
|
||||
}
|
||||
|
||||
void VConfirmDeletionDialog::currentFileChanged(int p_row)
|
||||
{
|
||||
bool succeed = false;
|
||||
if (p_row > -1 && m_enablePreview) {
|
||||
ConfirmItemWidget *widget = getItemWidget(m_listWidget->item(p_row));
|
||||
if (widget) {
|
||||
int idx = widget->getIndex();
|
||||
Q_ASSERT(idx < m_items.size());
|
||||
QPixmap image(m_items[idx].m_path);
|
||||
if (!image.isNull()) {
|
||||
int width = 512 * VUtils::calculateScaleFactor();
|
||||
QSize previewSize(width, width);
|
||||
if (image.width() > width || image.height() > width) {
|
||||
m_previewer->setPixmap(image.scaled(previewSize, Qt::KeepAspectRatio));
|
||||
} else {
|
||||
m_previewer->setPixmap(image);
|
||||
}
|
||||
|
||||
succeed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_previewer->setVisible(succeed);
|
||||
}
|
||||
|
||||
ConfirmItemWidget *VConfirmDeletionDialog::getItemWidget(QListWidgetItem *p_item) const
|
||||
{
|
||||
QWidget *wid = m_listWidget->itemWidget(p_item);
|
||||
return dynamic_cast<ConfirmItemWidget *>(wid);
|
||||
}
|
||||
|
||||
void VConfirmDeletionDialog::updateCountLabel()
|
||||
{
|
||||
int total = m_listWidget->count();
|
||||
int checked = 0;
|
||||
|
||||
for (int i = 0; i < total; ++i) {
|
||||
ConfirmItemWidget *widget = getItemWidget(m_listWidget->item(i));
|
||||
if (widget->isChecked()) {
|
||||
++checked;
|
||||
}
|
||||
}
|
||||
|
||||
m_countLabel->setText(tr("%1/%2 Items").arg(checked).arg(total));
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
#ifndef VCONFIRMDELETIONDIALOG_H
|
||||
#define VCONFIRMDELETIONDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QVector>
|
||||
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
class QDialogButtonBox;
|
||||
class QListWidget;
|
||||
class QListWidgetItem;
|
||||
class ConfirmItemWidget;
|
||||
class QCheckBox;
|
||||
|
||||
// Information about a deletion item needed to confirm.
|
||||
struct ConfirmItemInfo
|
||||
{
|
||||
ConfirmItemInfo()
|
||||
: m_data(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
ConfirmItemInfo(const QString &p_name,
|
||||
const QString &p_tip,
|
||||
const QString &p_path,
|
||||
void *p_data)
|
||||
: m_name(p_name), m_tip(p_tip), m_path(p_path), m_data(p_data)
|
||||
{
|
||||
}
|
||||
|
||||
QString m_name;
|
||||
QString m_tip;
|
||||
QString m_path;
|
||||
void *m_data;
|
||||
};
|
||||
|
||||
class ConfirmItemWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ConfirmItemWidget(bool p_checked,
|
||||
const QString &p_file,
|
||||
const QString &p_tip,
|
||||
int p_index,
|
||||
QWidget *p_parent = NULL);
|
||||
|
||||
bool isChecked() const;
|
||||
|
||||
int getIndex() const;
|
||||
|
||||
signals:
|
||||
// Emit when item's check state changed.
|
||||
void checkStateChanged(int p_state);
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
QCheckBox *m_checkBox;
|
||||
QLabel *m_fileLabel;
|
||||
|
||||
int m_index;
|
||||
};
|
||||
|
||||
class VConfirmDeletionDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VConfirmDeletionDialog(const QString &p_title,
|
||||
const QString &p_text,
|
||||
const QString &p_info,
|
||||
const QVector<ConfirmItemInfo> &p_items,
|
||||
bool p_enableAskAgain,
|
||||
bool p_askAgainEnabled,
|
||||
bool p_enablePreview,
|
||||
QWidget *p_parent = 0);
|
||||
|
||||
QVector<ConfirmItemInfo> getConfirmedItems() const;
|
||||
|
||||
bool getAskAgainEnabled() const;
|
||||
|
||||
private slots:
|
||||
void currentFileChanged(int p_row);
|
||||
|
||||
void updateCountLabel();
|
||||
|
||||
private:
|
||||
void setupUI(const QString &p_title,
|
||||
const QString &p_text,
|
||||
const QString &p_info);
|
||||
|
||||
void initItems();
|
||||
|
||||
ConfirmItemWidget *getItemWidget(QListWidgetItem *p_item) const;
|
||||
|
||||
QLabel *m_countLabel;
|
||||
QListWidget *m_listWidget;
|
||||
QLabel *m_previewer;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
QCheckBox *m_askAgainCB;
|
||||
|
||||
bool m_enableAskAgain;
|
||||
// Init value if m_enableAskAgain is true.
|
||||
bool m_askAgainEnabled;
|
||||
|
||||
bool m_enablePreview;
|
||||
|
||||
QVector<ConfirmItemInfo> m_items;
|
||||
};
|
||||
|
||||
#endif // VCONFIRMDELETIONDIALOG_H
|
@ -1,125 +0,0 @@
|
||||
#include <QtWidgets>
|
||||
#include "vdeletenotebookdialog.h"
|
||||
#include "vconfigmanager.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
VDeleteNotebookDialog::VDeleteNotebookDialog(const QString &p_title,
|
||||
const VNotebook *p_notebook,
|
||||
QWidget *p_parent)
|
||||
: QDialog(p_parent), m_notebook(p_notebook)
|
||||
{
|
||||
setupUI(p_title, m_notebook->getName());
|
||||
}
|
||||
|
||||
void VDeleteNotebookDialog::setupUI(const QString &p_title, const QString &p_name)
|
||||
{
|
||||
QLabel *infoLabel = new QLabel(tr("Are you sure to delete notebook <span style=\"%1\">%2</span>?")
|
||||
.arg(g_config->c_dataTextStyle).arg(p_name));
|
||||
m_warningLabel = new QLabel();
|
||||
m_warningLabel->setWordWrap(true);
|
||||
|
||||
m_deleteCheck = new QCheckBox(tr("Delete files from disk"), this);
|
||||
m_deleteCheck->setChecked(false);
|
||||
m_deleteCheck->setToolTip(tr("When checked, VNote will delete all files (including Recycle Bin) of this notebook from disk"));
|
||||
connect(m_deleteCheck, &QCheckBox::stateChanged,
|
||||
this, &VDeleteNotebookDialog::deleteCheckChanged);
|
||||
|
||||
deleteCheckChanged(false);
|
||||
|
||||
// Ok is the default button.
|
||||
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setProperty("DangerBtn", true);
|
||||
|
||||
// Standard Warning icon.
|
||||
QLabel *iconLabel = new QLabel();
|
||||
QPixmap pixmap = standardIcon(QMessageBox::Warning);
|
||||
if (pixmap.isNull()) {
|
||||
iconLabel->hide();
|
||||
} else {
|
||||
iconLabel->setPixmap(pixmap);
|
||||
}
|
||||
|
||||
QVBoxLayout *iconLayout = new QVBoxLayout();
|
||||
iconLayout->addStretch();
|
||||
iconLayout->addWidget(iconLabel);
|
||||
iconLayout->addStretch();
|
||||
|
||||
QVBoxLayout *infoLayout = new QVBoxLayout();
|
||||
infoLayout->addWidget(infoLabel);
|
||||
infoLayout->addWidget(m_deleteCheck);
|
||||
infoLayout->addWidget(m_warningLabel);
|
||||
|
||||
QHBoxLayout *topLayout = new QHBoxLayout();
|
||||
topLayout->addLayout(iconLayout);
|
||||
topLayout->addLayout(infoLayout);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
mainLayout->addLayout(topLayout);
|
||||
mainLayout->addWidget(m_btnBox);
|
||||
|
||||
setLayout(mainLayout);
|
||||
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
setWindowTitle(p_title);
|
||||
}
|
||||
|
||||
bool VDeleteNotebookDialog::getDeleteFiles() const
|
||||
{
|
||||
return m_deleteCheck->isChecked();
|
||||
}
|
||||
|
||||
QPixmap VDeleteNotebookDialog::standardIcon(QMessageBox::Icon p_icon)
|
||||
{
|
||||
QStyle *style = this->style();
|
||||
int iconSize = style->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, this);
|
||||
QIcon tmpIcon;
|
||||
switch (p_icon) {
|
||||
case QMessageBox::Information:
|
||||
tmpIcon = style->standardIcon(QStyle::SP_MessageBoxInformation, 0, this);
|
||||
break;
|
||||
case QMessageBox::Warning:
|
||||
tmpIcon = style->standardIcon(QStyle::SP_MessageBoxWarning, 0, this);
|
||||
break;
|
||||
case QMessageBox::Critical:
|
||||
tmpIcon = style->standardIcon(QStyle::SP_MessageBoxCritical, 0, this);
|
||||
break;
|
||||
case QMessageBox::Question:
|
||||
tmpIcon = style->standardIcon(QStyle::SP_MessageBoxQuestion, 0, this);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!tmpIcon.isNull()) {
|
||||
QWindow *window = this->windowHandle();
|
||||
if (!window) {
|
||||
if (const QWidget *nativeParent = this->nativeParentWidget()) {
|
||||
window = nativeParent->windowHandle();
|
||||
}
|
||||
}
|
||||
return tmpIcon.pixmap(window, QSize(iconSize, iconSize));
|
||||
}
|
||||
return QPixmap();
|
||||
}
|
||||
|
||||
void VDeleteNotebookDialog::deleteCheckChanged(int p_state)
|
||||
{
|
||||
if (!p_state) {
|
||||
m_warningLabel->setText(tr("VNote won't delete files in directory <span style=\"%1\">%2</span>.")
|
||||
.arg(g_config->c_dataTextStyle).arg(m_notebook->getPath()));
|
||||
} else {
|
||||
m_warningLabel->setText(tr("<span style=\"%1\">WARNING</span>: "
|
||||
"VNote may delete <b>ANY</b> files in directory <span style=\"%2\">%3</span> "
|
||||
"and directory <span style=\"%2\">%4</span>!<br>"
|
||||
"VNote will try to delete all the root folders within this notebook one by one.<br>"
|
||||
"It may be UNRECOVERABLE!")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(m_notebook->getPath())
|
||||
.arg(m_notebook->getRecycleBinFolderPath()));
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
#ifndef VDELETENOTEBOOKDIALOG_H
|
||||
#define VDELETENOTEBOOKDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QMessageBox>
|
||||
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QString;
|
||||
class QCheckBox;
|
||||
class QDialogButtonBox;
|
||||
class VNotebook;
|
||||
|
||||
class VDeleteNotebookDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VDeleteNotebookDialog(const QString &p_title,
|
||||
const VNotebook *p_notebook,
|
||||
QWidget *p_parent = 0);
|
||||
|
||||
// Whether delete files from disk.
|
||||
bool getDeleteFiles() const;
|
||||
|
||||
private slots:
|
||||
void deleteCheckChanged(int p_state);
|
||||
|
||||
private:
|
||||
void setupUI(const QString &p_title, const QString &p_name);
|
||||
QPixmap standardIcon(QMessageBox::Icon p_icon);
|
||||
|
||||
const VNotebook *m_notebook;
|
||||
QLabel *m_warningLabel;
|
||||
QCheckBox *m_deleteCheck;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
};
|
||||
|
||||
#endif // VDELETENOTEBOOKDIALOG_H
|
@ -1,116 +0,0 @@
|
||||
#include <QtWidgets>
|
||||
#include "vdirinfodialog.h"
|
||||
#include "vdirectory.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "vlineedit.h"
|
||||
#include "utils/vutils.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
VDirInfoDialog::VDirInfoDialog(const QString &title,
|
||||
const QString &info,
|
||||
const VDirectory *directory,
|
||||
VDirectory *parentDirectory,
|
||||
QWidget *parent)
|
||||
: QDialog(parent), title(title), info(info),
|
||||
m_directory(directory), m_parentDirectory(parentDirectory)
|
||||
{
|
||||
setupUI();
|
||||
|
||||
connect(m_nameEdit, &QLineEdit::textChanged, this, &VDirInfoDialog::handleInputChanged);
|
||||
|
||||
handleInputChanged();
|
||||
}
|
||||
|
||||
void VDirInfoDialog::setupUI()
|
||||
{
|
||||
QLabel *infoLabel = NULL;
|
||||
if (!info.isEmpty()) {
|
||||
infoLabel = new QLabel(info);
|
||||
}
|
||||
|
||||
m_nameEdit = new VLineEdit(m_directory->getName());
|
||||
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
|
||||
m_nameEdit);
|
||||
m_nameEdit->setValidator(validator);
|
||||
m_nameEdit->selectAll();
|
||||
|
||||
// Created time.
|
||||
QString createdTimeStr = VUtils::displayDateTime(m_directory->getCreatedTimeUtc().toLocalTime());
|
||||
QLabel *createdTimeLabel = new QLabel(createdTimeStr);
|
||||
|
||||
QFormLayout *topLayout = new QFormLayout();
|
||||
topLayout->addRow(tr("Folder &name:"), m_nameEdit);
|
||||
topLayout->addRow(tr("Created time:"), createdTimeLabel);
|
||||
|
||||
m_warnLabel = new QLabel();
|
||||
m_warnLabel->setWordWrap(true);
|
||||
m_warnLabel->hide();
|
||||
|
||||
// Ok is the default button.
|
||||
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setProperty("SpecialBtn", true);
|
||||
m_nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
if (infoLabel) {
|
||||
mainLayout->addWidget(infoLabel);
|
||||
}
|
||||
mainLayout->addLayout(topLayout);
|
||||
mainLayout->addWidget(m_warnLabel);
|
||||
mainLayout->addWidget(m_btnBox);
|
||||
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
setLayout(mainLayout);
|
||||
|
||||
setWindowTitle(title);
|
||||
}
|
||||
|
||||
void VDirInfoDialog::handleInputChanged()
|
||||
{
|
||||
bool showWarnLabel = false;
|
||||
QString name = m_nameEdit->getEvaluatedText();
|
||||
bool nameOk = !name.isEmpty();
|
||||
if (nameOk && name != m_directory->getName()) {
|
||||
// Check if the name conflicts with existing directory name.
|
||||
// Case-insensitive when creating note.
|
||||
const VDirectory *directory = m_parentDirectory->findSubDirectory(name, false);
|
||||
QString warnText;
|
||||
if (directory && directory != m_directory) {
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
} else if (!VUtils::checkFileNameLegal(name)) {
|
||||
// Check if evaluated name contains illegal characters.
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name <span style=\"%2\">%3</span> contains illegal characters "
|
||||
"(after magic word evaluation).")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
}
|
||||
|
||||
if (!nameOk) {
|
||||
showWarnLabel = true;
|
||||
m_warnLabel->setText(warnText);
|
||||
}
|
||||
}
|
||||
|
||||
m_warnLabel->setVisible(showWarnLabel);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setEnabled(nameOk);
|
||||
}
|
||||
|
||||
QString VDirInfoDialog::getNameInput() const
|
||||
{
|
||||
return m_nameEdit->getEvaluatedText();
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
#ifndef VDIRINFODIALOG_H
|
||||
#define VDIRINFODIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class QLabel;
|
||||
class VLineEdit;
|
||||
class QDialogButtonBox;
|
||||
class QString;
|
||||
class VDirectory;
|
||||
|
||||
class VDirInfoDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VDirInfoDialog(const QString &title,
|
||||
const QString &info,
|
||||
const VDirectory *directory,
|
||||
VDirectory *parentDirectory,
|
||||
QWidget *parent = 0);
|
||||
|
||||
QString getNameInput() const;
|
||||
|
||||
private slots:
|
||||
void handleInputChanged();
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
VLineEdit *m_nameEdit;
|
||||
QLabel *m_warnLabel;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
|
||||
QString title;
|
||||
QString info;
|
||||
|
||||
const VDirectory *m_directory;
|
||||
VDirectory *m_parentDirectory;
|
||||
};
|
||||
#endif // VDIRINFODIALOG_H
|
@ -1,309 +0,0 @@
|
||||
#include "veditsnippetdialog.h"
|
||||
#include <QtWidgets>
|
||||
|
||||
#include "utils/vutils.h"
|
||||
#include "vlineedit.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vmetawordmanager.h"
|
||||
|
||||
extern VMetaWordManager *g_mwMgr;
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
VEditSnippetDialog::VEditSnippetDialog(const QString &p_title,
|
||||
const QString &p_info,
|
||||
const QVector<VSnippet> &p_snippets,
|
||||
const VSnippet &p_snippet,
|
||||
QWidget *p_parent)
|
||||
: QDialog(p_parent),
|
||||
m_snippets(p_snippets),
|
||||
m_snippet(p_snippet)
|
||||
{
|
||||
setupUI(p_title, p_info);
|
||||
|
||||
handleInputChanged();
|
||||
}
|
||||
|
||||
void VEditSnippetDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
{
|
||||
QLabel *infoLabel = NULL;
|
||||
if (!p_info.isEmpty()) {
|
||||
infoLabel = new QLabel(p_info);
|
||||
infoLabel->setWordWrap(true);
|
||||
}
|
||||
|
||||
// Name.
|
||||
m_nameEdit = new VLineEdit(m_snippet.getName());
|
||||
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
|
||||
m_nameEdit);
|
||||
m_nameEdit->setValidator(validator);
|
||||
|
||||
// Type.
|
||||
m_typeCB = VUtils::getComboBox();
|
||||
for (int i = 0; i < VSnippet::Type::Invalid; ++i) {
|
||||
m_typeCB->addItem(VSnippet::typeStr(static_cast<VSnippet::Type>(i)), i);
|
||||
}
|
||||
|
||||
int typeIdx = m_typeCB->findData((int)m_snippet.getType());
|
||||
Q_ASSERT(typeIdx > -1);
|
||||
m_typeCB->setCurrentIndex(typeIdx);
|
||||
|
||||
// Shortcut.
|
||||
m_shortcutCB = VUtils::getComboBox();
|
||||
m_shortcutCB->addItem(tr("None"), QChar());
|
||||
auto shortcuts = getAvailableShortcuts();
|
||||
for (auto it : shortcuts) {
|
||||
m_shortcutCB->addItem(it, it);
|
||||
}
|
||||
|
||||
QChar sh = m_snippet.getShortcut();
|
||||
if (sh.isNull()) {
|
||||
m_shortcutCB->setCurrentIndex(0);
|
||||
} else {
|
||||
int shortcutIdx = m_shortcutCB->findData(sh);
|
||||
m_shortcutCB->setCurrentIndex(shortcutIdx < 0 ? 0 : shortcutIdx);
|
||||
}
|
||||
|
||||
// Cursor mark.
|
||||
m_cursorMarkEdit = new QLineEdit(m_snippet.getCursorMark());
|
||||
m_cursorMarkEdit->setToolTip(tr("String in the content to mark the cursor position"));
|
||||
|
||||
// Selection mark.
|
||||
m_selectionMarkEdit = new QLineEdit(m_snippet.getSelectionMark());
|
||||
m_selectionMarkEdit->setToolTip(tr("String in the content to be replaced with selected text"));
|
||||
|
||||
// Auto Indent.
|
||||
m_autoIndentCB = new QCheckBox(tr("Auto indent"), this);
|
||||
m_autoIndentCB->setToolTip(tr("Auto indent the content according to the first line"));
|
||||
m_autoIndentCB->setChecked(m_snippet.getAutoIndent());
|
||||
|
||||
// Content.
|
||||
m_contentEdit = new QTextEdit();
|
||||
m_contentEdit->setProperty("LineEdit", true);
|
||||
setContentEditByType();
|
||||
|
||||
QFormLayout *topLayout = new QFormLayout();
|
||||
topLayout->addRow(tr("Snippet &name:"), m_nameEdit);
|
||||
topLayout->addRow(tr("Snippet &type:"), m_typeCB);
|
||||
topLayout->addRow(tr("Shortc&ut:"), m_shortcutCB);
|
||||
topLayout->addRow(tr("Cursor &mark:"), m_cursorMarkEdit);
|
||||
topLayout->addRow(tr("&Selection mark:"), m_selectionMarkEdit);
|
||||
topLayout->addWidget(m_autoIndentCB);
|
||||
topLayout->addRow(tr("&Content:"), m_contentEdit);
|
||||
|
||||
m_warnLabel = new QLabel();
|
||||
m_warnLabel->setWordWrap(true);
|
||||
m_warnLabel->hide();
|
||||
|
||||
// Ok is the default button.
|
||||
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setProperty("SpecialBtn", true);
|
||||
m_nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
if (infoLabel) {
|
||||
mainLayout->addWidget(infoLabel);
|
||||
}
|
||||
|
||||
mainLayout->addLayout(topLayout);
|
||||
mainLayout->addWidget(m_warnLabel);
|
||||
mainLayout->addWidget(m_btnBox);
|
||||
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
setLayout(mainLayout);
|
||||
|
||||
setWindowTitle(p_title);
|
||||
|
||||
connect(m_nameEdit, &QLineEdit::textChanged,
|
||||
this, &VEditSnippetDialog::handleInputChanged);
|
||||
|
||||
connect(m_typeCB, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
this, &VEditSnippetDialog::handleInputChanged);
|
||||
|
||||
connect(m_cursorMarkEdit, &QLineEdit::textChanged,
|
||||
this, &VEditSnippetDialog::handleInputChanged);
|
||||
|
||||
connect(m_selectionMarkEdit, &QLineEdit::textChanged,
|
||||
this, &VEditSnippetDialog::handleInputChanged);
|
||||
|
||||
connect(m_contentEdit, &QTextEdit::textChanged,
|
||||
this, &VEditSnippetDialog::handleInputChanged);
|
||||
}
|
||||
|
||||
void VEditSnippetDialog::handleInputChanged()
|
||||
{
|
||||
bool showWarnLabel = false;
|
||||
QString name = m_nameEdit->getEvaluatedText();
|
||||
bool nameOk = !name.isEmpty();
|
||||
if (nameOk && name != m_snippet.getName()) {
|
||||
// Check if the name conflicts with existing snippet name.
|
||||
// Case-insensitive.
|
||||
QString lowerName = name.toLower();
|
||||
bool conflicted = false;
|
||||
for (auto const & item : m_snippets) {
|
||||
if (item.getName() == name) {
|
||||
conflicted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QString warnText;
|
||||
if (conflicted) {
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
} else if (!VUtils::checkFileNameLegal(name)) {
|
||||
// Check if evaluated name contains illegal characters.
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name <span style=\"%2\">%3</span> contains illegal characters "
|
||||
"(after magic word evaluation).")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
}
|
||||
|
||||
if (!nameOk) {
|
||||
showWarnLabel = true;
|
||||
m_warnLabel->setText(warnText);
|
||||
}
|
||||
}
|
||||
|
||||
QString cursorMark = m_cursorMarkEdit->text();
|
||||
bool cursorMarkOk = true;
|
||||
if (nameOk && !cursorMark.isEmpty()) {
|
||||
// Check if the mark appears more than once in the content.
|
||||
QString selectionMark = m_selectionMarkEdit->text();
|
||||
QString content = getContentEditByType();
|
||||
content = g_mwMgr->evaluate(content);
|
||||
QString warnText;
|
||||
if (content.count(cursorMark) > 1) {
|
||||
cursorMarkOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Cursor mark <span style=\"%2\">%3</span> occurs more than once "
|
||||
"in the content (after magic word evaluation). "
|
||||
"Please choose another mark.")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(cursorMark);
|
||||
} else if ((cursorMark == selectionMark
|
||||
|| cursorMark.contains(selectionMark)
|
||||
|| selectionMark.contains(cursorMark))
|
||||
&& !selectionMark.isEmpty()) {
|
||||
cursorMarkOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Cursor mark <span style=\"%2\">%3</span> conflicts with selection mark. "
|
||||
"Please choose another mark.")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(cursorMark);
|
||||
}
|
||||
|
||||
if (!cursorMarkOk) {
|
||||
showWarnLabel = true;
|
||||
m_warnLabel->setText(warnText);
|
||||
}
|
||||
}
|
||||
|
||||
m_warnLabel->setVisible(showWarnLabel);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setEnabled(nameOk && cursorMarkOk);
|
||||
}
|
||||
|
||||
void VEditSnippetDialog::setContentEditByType()
|
||||
{
|
||||
switch (m_snippet.getType()) {
|
||||
case VSnippet::Type::PlainText:
|
||||
m_contentEdit->setPlainText(m_snippet.getContent());
|
||||
break;
|
||||
|
||||
case VSnippet::Type::Html:
|
||||
m_contentEdit->setHtml(m_snippet.getContent());
|
||||
break;
|
||||
|
||||
default:
|
||||
m_contentEdit->setPlainText(m_snippet.getContent());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QString VEditSnippetDialog::getContentEditByType() const
|
||||
{
|
||||
if (m_typeCB->currentIndex() == -1) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
switch (static_cast<VSnippet::Type>(m_typeCB->currentData().toInt())) {
|
||||
case VSnippet::Type::PlainText:
|
||||
return m_contentEdit->toPlainText();
|
||||
|
||||
case VSnippet::Type::Html:
|
||||
return m_contentEdit->toHtml();
|
||||
|
||||
default:
|
||||
return m_contentEdit->toPlainText();
|
||||
}
|
||||
}
|
||||
|
||||
QString VEditSnippetDialog::getNameInput() const
|
||||
{
|
||||
return m_nameEdit->getEvaluatedText();
|
||||
}
|
||||
|
||||
VSnippet::Type VEditSnippetDialog::getTypeInput() const
|
||||
{
|
||||
return static_cast<VSnippet::Type>(m_typeCB->currentData().toInt());
|
||||
}
|
||||
|
||||
QString VEditSnippetDialog::getCursorMarkInput() const
|
||||
{
|
||||
return m_cursorMarkEdit->text();
|
||||
}
|
||||
|
||||
QString VEditSnippetDialog::getSelectionMarkInput() const
|
||||
{
|
||||
return m_selectionMarkEdit->text();
|
||||
}
|
||||
|
||||
QString VEditSnippetDialog::getContentInput() const
|
||||
{
|
||||
return getContentEditByType();
|
||||
}
|
||||
|
||||
QChar VEditSnippetDialog::getShortcutInput() const
|
||||
{
|
||||
return m_shortcutCB->currentData().toChar();
|
||||
}
|
||||
|
||||
bool VEditSnippetDialog::getAutoIndentInput() const
|
||||
{
|
||||
return m_autoIndentCB->isChecked();
|
||||
}
|
||||
|
||||
QVector<QChar> VEditSnippetDialog::getAvailableShortcuts() const
|
||||
{
|
||||
QVector<QChar> ret = VSnippet::getAllShortcuts();
|
||||
|
||||
QChar curCh = m_snippet.getShortcut();
|
||||
// Remove those have already been assigned to snippets.
|
||||
for (auto const & snip : m_snippets) {
|
||||
QChar ch = snip.getShortcut();
|
||||
if (ch.isNull()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch != curCh) {
|
||||
ret.removeOne(ch);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
#ifndef VEDITSNIPPETDIALOG_H
|
||||
#define VEDITSNIPPETDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QVector>
|
||||
|
||||
#include "vsnippet.h"
|
||||
|
||||
class VLineEdit;
|
||||
class QLineEdit;
|
||||
class QLabel;
|
||||
class QDialogButtonBox;
|
||||
class QComboBox;
|
||||
class QTextEdit;
|
||||
class QCheckBox;
|
||||
|
||||
|
||||
class VEditSnippetDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VEditSnippetDialog(const QString &p_title,
|
||||
const QString &p_info,
|
||||
const QVector<VSnippet> &p_snippets,
|
||||
const VSnippet &p_snippet,
|
||||
QWidget *p_parent = nullptr);
|
||||
|
||||
QString getNameInput() const;
|
||||
|
||||
VSnippet::Type getTypeInput() const;
|
||||
|
||||
QString getCursorMarkInput() const;
|
||||
|
||||
QString getSelectionMarkInput() const;
|
||||
|
||||
QString getContentInput() const;
|
||||
|
||||
QChar getShortcutInput() const;
|
||||
|
||||
bool getAutoIndentInput() const;
|
||||
|
||||
private slots:
|
||||
void handleInputChanged();
|
||||
|
||||
private:
|
||||
void setupUI(const QString &p_title, const QString &p_info);
|
||||
|
||||
void setContentEditByType();
|
||||
|
||||
QString getContentEditByType() const;
|
||||
|
||||
QVector<QChar> getAvailableShortcuts() const;
|
||||
|
||||
VLineEdit *m_nameEdit;
|
||||
QComboBox *m_typeCB;
|
||||
QComboBox *m_shortcutCB;
|
||||
QLineEdit *m_cursorMarkEdit;
|
||||
QLineEdit *m_selectionMarkEdit;
|
||||
QCheckBox *m_autoIndentCB;
|
||||
QTextEdit *m_contentEdit;
|
||||
|
||||
QLabel *m_warnLabel;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
|
||||
const QVector<VSnippet> &m_snippets;
|
||||
|
||||
const VSnippet &m_snippet;
|
||||
};
|
||||
|
||||
#endif // VEDITSNIPPETDIALOG_H
|
@ -1,147 +0,0 @@
|
||||
#include <QtWidgets>
|
||||
#include "vfileinfodialog.h"
|
||||
#include "vdirectory.h"
|
||||
#include "vnotefile.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vlineedit.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
VFileInfoDialog::VFileInfoDialog(const QString &title,
|
||||
const QString &info,
|
||||
VDirectory *directory,
|
||||
const VNoteFile *file,
|
||||
QWidget *parent)
|
||||
: QDialog(parent), m_directory(directory), m_file(file)
|
||||
{
|
||||
setupUI(title, info);
|
||||
|
||||
connect(m_nameEdit, &QLineEdit::textChanged, this, &VFileInfoDialog::handleInputChanged);
|
||||
|
||||
handleInputChanged();
|
||||
}
|
||||
|
||||
void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
{
|
||||
QLabel *infoLabel = NULL;
|
||||
if (!p_info.isEmpty()) {
|
||||
infoLabel = new QLabel(p_info);
|
||||
}
|
||||
|
||||
// File name.
|
||||
QString name = m_file->getName();
|
||||
m_nameEdit = new VLineEdit(name);
|
||||
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
|
||||
m_nameEdit);
|
||||
m_nameEdit->setValidator(validator);
|
||||
int baseStart = 0, baseLength = name.size();
|
||||
int dotIdx = name.lastIndexOf('.');
|
||||
if (dotIdx != -1) {
|
||||
baseLength = dotIdx;
|
||||
}
|
||||
|
||||
// Select without suffix.
|
||||
m_nameEdit->setSelection(baseStart, baseLength);
|
||||
|
||||
// Attachment folder.
|
||||
QLineEdit *attachmentFolderEdit = new QLineEdit(m_file->getAttachmentFolder());
|
||||
attachmentFolderEdit->setPlaceholderText(tr("Will be assigned when adding attachments"));
|
||||
attachmentFolderEdit->setToolTip(tr("The folder to hold attachments of this note"));
|
||||
attachmentFolderEdit->setReadOnly(true);
|
||||
|
||||
// Created time.
|
||||
QString createdTimeStr = VUtils::displayDateTime(m_file->getCreatedTimeUtc().toLocalTime());
|
||||
QLabel *createdTimeLabel = new QLabel(createdTimeStr);
|
||||
|
||||
// Modified time.
|
||||
createdTimeStr = VUtils::displayDateTime(m_file->getModifiedTimeUtc().toLocalTime());
|
||||
QLabel *modifiedTimeLabel = new QLabel(createdTimeStr);
|
||||
modifiedTimeLabel->setToolTip(tr("Last modified time within VNote"));
|
||||
|
||||
QFormLayout *topLayout = new QFormLayout();
|
||||
topLayout->addRow(tr("Note &name:"), m_nameEdit);
|
||||
topLayout->addRow(tr("Attachment folder:"), attachmentFolderEdit);
|
||||
topLayout->addRow(tr("Created time:"), createdTimeLabel);
|
||||
topLayout->addRow(tr("Modified time:"), modifiedTimeLabel);
|
||||
|
||||
m_warnLabel = new QLabel();
|
||||
m_warnLabel->setWordWrap(true);
|
||||
m_warnLabel->hide();
|
||||
|
||||
// Ok is the default button.
|
||||
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setProperty("SpecialBtn", true);
|
||||
m_nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
if (infoLabel) {
|
||||
mainLayout->addWidget(infoLabel);
|
||||
}
|
||||
|
||||
mainLayout->addLayout(topLayout);
|
||||
mainLayout->addWidget(m_warnLabel);
|
||||
mainLayout->addWidget(m_btnBox);
|
||||
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
setLayout(mainLayout);
|
||||
|
||||
setWindowTitle(p_title);
|
||||
}
|
||||
|
||||
void VFileInfoDialog::handleInputChanged()
|
||||
{
|
||||
bool showWarnLabel = false;
|
||||
QString name = m_nameEdit->getEvaluatedText();
|
||||
bool nameOk = !name.isEmpty();
|
||||
if (nameOk && name != m_file->getName()) {
|
||||
// Check if the name conflicts with existing note name.
|
||||
// Case-insensitive when creating note.
|
||||
const VNoteFile *file = m_directory->findFile(name, false);
|
||||
QString warnText;
|
||||
if (file && file != m_file) {
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
} else if (m_file->getDocType() != DocType::Unknown
|
||||
&& VUtils::docTypeFromName(name) != m_file->getDocType()) {
|
||||
// Check if the name change the doc type.
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Changing type of the note is not supported. "
|
||||
"Please use the same suffix as the old one.")
|
||||
.arg(g_config->c_warningTextStyle);
|
||||
} else if (!VUtils::checkFileNameLegal(name)) {
|
||||
// Check if evaluated name contains illegal characters.
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name <span style=\"%2\">%3</span> contains illegal characters "
|
||||
"(after magic word evaluation).")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
}
|
||||
|
||||
if (!nameOk) {
|
||||
showWarnLabel = true;
|
||||
m_warnLabel->setText(warnText);
|
||||
}
|
||||
}
|
||||
|
||||
m_warnLabel->setVisible(showWarnLabel);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setEnabled(nameOk);
|
||||
}
|
||||
|
||||
QString VFileInfoDialog::getNameInput() const
|
||||
{
|
||||
return m_nameEdit->getEvaluatedText();
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
#ifndef VFILEINFODIALOG_H
|
||||
#define VFILEINFODIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class QLabel;
|
||||
class VLineEdit;
|
||||
class QDialogButtonBox;
|
||||
class QString;
|
||||
class VDirectory;
|
||||
class VNoteFile;
|
||||
|
||||
// File information dialog for internal note file.
|
||||
class VFileInfoDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VFileInfoDialog(const QString &title,
|
||||
const QString &info,
|
||||
VDirectory *directory,
|
||||
const VNoteFile *file,
|
||||
QWidget *parent = 0);
|
||||
|
||||
QString getNameInput() const;
|
||||
|
||||
private slots:
|
||||
void handleInputChanged();
|
||||
|
||||
private:
|
||||
void setupUI(const QString &p_title, const QString &p_info);
|
||||
|
||||
VLineEdit *m_nameEdit;
|
||||
QLabel *m_warnLabel;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
|
||||
VDirectory *m_directory;
|
||||
const VNoteFile *m_file;
|
||||
};
|
||||
|
||||
#endif // VFILEINFODIALOG_H
|
@ -1,308 +0,0 @@
|
||||
#include "vfindreplacedialog.h"
|
||||
#include <QtWidgets>
|
||||
|
||||
#include "utils/viconutils.h"
|
||||
|
||||
VFindReplaceDialog::VFindReplaceDialog(QWidget *p_parent)
|
||||
: QWidget(p_parent), m_options(0), m_replaceAvailable(true)
|
||||
{
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void VFindReplaceDialog::setupUI()
|
||||
{
|
||||
QLabel *titleLabel = new QLabel(tr("Find/Replace"));
|
||||
titleLabel->setProperty("TitleLabel", true);
|
||||
m_closeBtn = new QPushButton(VIconUtils::titleIcon(":/resources/icons/close.svg"), "");
|
||||
m_closeBtn->setProperty("TitleBtn", true);
|
||||
QHBoxLayout *titleLayout = new QHBoxLayout();
|
||||
titleLayout->addWidget(titleLabel);
|
||||
titleLayout->addWidget(m_closeBtn);
|
||||
titleLayout->setStretch(0, 1);
|
||||
titleLayout->setStretch(1, 0);
|
||||
titleLayout->setContentsMargins(0, 0, 0, 0);
|
||||
titleLayout->setSpacing(0);
|
||||
QWidget *titleWidget = new QWidget();
|
||||
titleWidget->setObjectName("FindReplaceTitleWidget");
|
||||
titleWidget->setLayout(titleLayout);
|
||||
|
||||
// Find
|
||||
QLabel *findLabel = new QLabel(tr("Find:"));
|
||||
m_findEdit = new QLineEdit();
|
||||
m_findEdit->setPlaceholderText(tr("Enter text to search"));
|
||||
findLabel->setBuddy(m_findEdit);
|
||||
m_findNextBtn = new QPushButton(tr("Find &Next"));
|
||||
m_findNextBtn->setProperty("FlatBtn", true);
|
||||
m_findNextBtn->setDefault(true);
|
||||
m_findPrevBtn = new QPushButton(tr("Find &Previous"));
|
||||
m_findPrevBtn->setProperty("FlatBtn", true);
|
||||
|
||||
// Replace
|
||||
QLabel *replaceLabel = new QLabel(tr("&Replace with:"));
|
||||
m_replaceEdit = new QLineEdit();
|
||||
m_replaceEdit->setPlaceholderText(tr("Enter text to replace with"));
|
||||
replaceLabel->setBuddy(m_replaceEdit);
|
||||
m_replaceBtn = new QPushButton(tr("Replace"));
|
||||
m_replaceBtn->setProperty("FlatBtn", true);
|
||||
m_replaceFindBtn = new QPushButton(tr("Replace && Fin&d"));
|
||||
m_replaceFindBtn->setProperty("FlatBtn", true);
|
||||
m_replaceAllBtn = new QPushButton(tr("Replace A&ll"));
|
||||
m_replaceAllBtn->setProperty("FlatBtn", true);
|
||||
m_advancedBtn = new QPushButton(tr("&Advanced >>"));
|
||||
m_advancedBtn->setProperty("FlatBtn", true);
|
||||
m_advancedBtn->setCheckable(true);
|
||||
|
||||
// Options
|
||||
m_caseSensitiveCheck = new QCheckBox(tr("&Case sensitive"), this);
|
||||
connect(m_caseSensitiveCheck, &QCheckBox::stateChanged,
|
||||
this, &VFindReplaceDialog::optionBoxToggled);
|
||||
m_wholeWordOnlyCheck = new QCheckBox(tr("&Whole word only"), this);
|
||||
connect(m_wholeWordOnlyCheck, &QCheckBox::stateChanged,
|
||||
this, &VFindReplaceDialog::optionBoxToggled);
|
||||
m_regularExpressionCheck = new QCheckBox(tr("Re&gular expression"), this);
|
||||
connect(m_regularExpressionCheck, &QCheckBox::stateChanged,
|
||||
this, &VFindReplaceDialog::optionBoxToggled);
|
||||
m_incrementalSearchCheck = new QCheckBox(tr("&Incremental search"), this);
|
||||
connect(m_incrementalSearchCheck, &QCheckBox::stateChanged,
|
||||
this, &VFindReplaceDialog::optionBoxToggled);
|
||||
|
||||
QGridLayout *gridLayout = new QGridLayout();
|
||||
gridLayout->addWidget(findLabel, 0, 0);
|
||||
gridLayout->addWidget(m_findEdit, 0, 1);
|
||||
gridLayout->addWidget(m_findNextBtn, 0, 2);
|
||||
gridLayout->addWidget(m_findPrevBtn, 0, 3);
|
||||
gridLayout->addWidget(replaceLabel, 1, 0);
|
||||
gridLayout->addWidget(m_replaceEdit, 1, 1);
|
||||
gridLayout->addWidget(m_replaceBtn, 1, 2);
|
||||
gridLayout->addWidget(m_replaceFindBtn, 1, 3);
|
||||
gridLayout->addWidget(m_replaceAllBtn, 1, 4);
|
||||
gridLayout->addWidget(m_advancedBtn, 1, 5);
|
||||
gridLayout->addWidget(m_caseSensitiveCheck, 2, 1);
|
||||
gridLayout->addWidget(m_wholeWordOnlyCheck, 2, 2);
|
||||
gridLayout->addWidget(m_regularExpressionCheck, 3, 1);
|
||||
gridLayout->addWidget(m_incrementalSearchCheck, 3, 2);
|
||||
gridLayout->setColumnStretch(0, 0);
|
||||
gridLayout->setColumnStretch(1, 4);
|
||||
gridLayout->setColumnStretch(2, 1);
|
||||
gridLayout->setColumnStretch(3, 1);
|
||||
gridLayout->setColumnStretch(4, 1);
|
||||
gridLayout->setColumnStretch(5, 1);
|
||||
gridLayout->setColumnStretch(6, 3);
|
||||
|
||||
QMargins margin = gridLayout->contentsMargins();
|
||||
margin.setLeft(3);
|
||||
gridLayout->setContentsMargins(margin);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
mainLayout->addWidget(titleWidget);
|
||||
mainLayout->addLayout(gridLayout);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
setLayout(mainLayout);
|
||||
|
||||
setTabOrder(m_findEdit, m_findNextBtn);
|
||||
setTabOrder(m_findNextBtn, m_findPrevBtn);
|
||||
setTabOrder(m_findPrevBtn, m_replaceEdit);
|
||||
setTabOrder(m_replaceEdit, m_replaceBtn);
|
||||
setTabOrder(m_replaceBtn, m_replaceFindBtn);
|
||||
setTabOrder(m_replaceFindBtn, m_replaceAllBtn);
|
||||
setTabOrder(m_replaceAllBtn, m_advancedBtn);
|
||||
setTabOrder(m_advancedBtn, m_caseSensitiveCheck);
|
||||
setTabOrder(m_caseSensitiveCheck, m_wholeWordOnlyCheck);
|
||||
setTabOrder(m_wholeWordOnlyCheck, m_regularExpressionCheck);
|
||||
setTabOrder(m_regularExpressionCheck, m_incrementalSearchCheck);
|
||||
setTabOrder(m_incrementalSearchCheck, m_closeBtn);
|
||||
|
||||
m_caseSensitiveCheck->hide();
|
||||
m_wholeWordOnlyCheck->hide();
|
||||
m_regularExpressionCheck->hide();
|
||||
m_incrementalSearchCheck->hide();
|
||||
|
||||
// Signals
|
||||
connect(m_closeBtn, &QPushButton::clicked,
|
||||
this, &VFindReplaceDialog::closeDialog);
|
||||
connect(m_findEdit, &QLineEdit::textChanged,
|
||||
this, &VFindReplaceDialog::handleFindTextChanged);
|
||||
connect(m_advancedBtn, &QPushButton::toggled,
|
||||
this, &VFindReplaceDialog::advancedBtnToggled);
|
||||
connect(m_findNextBtn, SIGNAL(clicked(bool)),
|
||||
this, SLOT(findNext()));
|
||||
connect(m_findPrevBtn, SIGNAL(clicked(bool)),
|
||||
this, SLOT(findPrevious()));
|
||||
connect(m_replaceBtn, SIGNAL(clicked(bool)),
|
||||
this, SLOT(replace()));
|
||||
connect(m_replaceFindBtn, SIGNAL(clicked(bool)),
|
||||
this, SLOT(replaceFind()));
|
||||
connect(m_replaceAllBtn, SIGNAL(clicked(bool)),
|
||||
this, SLOT(replaceAll()));
|
||||
}
|
||||
|
||||
void VFindReplaceDialog::closeDialog()
|
||||
{
|
||||
if (this->isVisible()) {
|
||||
hide();
|
||||
emit dialogClosed();
|
||||
}
|
||||
}
|
||||
|
||||
void VFindReplaceDialog::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
switch (event->key()) {
|
||||
case Qt::Key_Escape:
|
||||
event->accept();
|
||||
closeDialog();
|
||||
return;
|
||||
|
||||
case Qt::Key_Return:
|
||||
{
|
||||
int modifiers = event->modifiers();
|
||||
bool shift = false;
|
||||
if (modifiers == Qt::ShiftModifier) {
|
||||
shift = true;
|
||||
} else if (modifiers != Qt::NoModifier) {
|
||||
break;
|
||||
}
|
||||
if (!m_findEdit->hasFocus() && !m_replaceEdit->hasFocus()) {
|
||||
break;
|
||||
}
|
||||
event->accept();
|
||||
if (shift) {
|
||||
findPrevious();
|
||||
} else {
|
||||
findNext();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
QWidget::keyPressEvent(event);
|
||||
}
|
||||
|
||||
void VFindReplaceDialog::openDialog(QString p_text)
|
||||
{
|
||||
show();
|
||||
if (!p_text.isEmpty()) {
|
||||
m_findEdit->setText(p_text);
|
||||
}
|
||||
m_findEdit->setFocus();
|
||||
m_findEdit->selectAll();
|
||||
}
|
||||
|
||||
void VFindReplaceDialog::handleFindTextChanged(const QString &p_text)
|
||||
{
|
||||
emit findTextChanged(p_text, m_options);
|
||||
}
|
||||
|
||||
void VFindReplaceDialog::advancedBtnToggled(bool p_checked)
|
||||
{
|
||||
if (p_checked) {
|
||||
m_advancedBtn->setText("B&asic <<");
|
||||
} else {
|
||||
m_advancedBtn->setText("&Advanced <<");
|
||||
}
|
||||
|
||||
m_caseSensitiveCheck->setVisible(p_checked);
|
||||
m_wholeWordOnlyCheck->setVisible(p_checked);
|
||||
m_regularExpressionCheck->setVisible(p_checked);
|
||||
m_incrementalSearchCheck->setVisible(p_checked);
|
||||
}
|
||||
|
||||
void VFindReplaceDialog::optionBoxToggled(int p_state)
|
||||
{
|
||||
QObject *obj = sender();
|
||||
FindOption opt = FindOption::CaseSensitive;
|
||||
if (obj == m_caseSensitiveCheck) {
|
||||
opt = FindOption::CaseSensitive;
|
||||
} else if (obj == m_wholeWordOnlyCheck) {
|
||||
opt = FindOption::WholeWordOnly;
|
||||
} else if (obj == m_regularExpressionCheck) {
|
||||
opt = FindOption::RegularExpression;
|
||||
} else {
|
||||
opt = FindOption::IncrementalSearch;
|
||||
}
|
||||
|
||||
if (p_state) {
|
||||
m_options |= opt;
|
||||
} else {
|
||||
m_options &= ~opt;
|
||||
}
|
||||
emit findOptionChanged(m_options);
|
||||
}
|
||||
|
||||
void VFindReplaceDialog::setOption(FindOption p_opt, bool p_enabled)
|
||||
{
|
||||
if (p_opt == FindOption::CaseSensitive) {
|
||||
m_caseSensitiveCheck->setChecked(p_enabled);
|
||||
} else if (p_opt == FindOption::WholeWordOnly) {
|
||||
m_wholeWordOnlyCheck->setChecked(p_enabled);
|
||||
} else if (p_opt == FindOption::RegularExpression) {
|
||||
m_regularExpressionCheck->setChecked(p_enabled);
|
||||
} else if (p_opt == FindOption::IncrementalSearch) {
|
||||
m_incrementalSearchCheck->setChecked(p_enabled);
|
||||
} else {
|
||||
Q_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
void VFindReplaceDialog::findNext()
|
||||
{
|
||||
QString text = m_findEdit->text();
|
||||
if (text.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
emit findNext(text, m_options, true);
|
||||
}
|
||||
|
||||
void VFindReplaceDialog::findPrevious()
|
||||
{
|
||||
QString text = m_findEdit->text();
|
||||
if (text.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
emit findNext(text, m_options, false);
|
||||
}
|
||||
|
||||
void VFindReplaceDialog::replace()
|
||||
{
|
||||
QString text = m_findEdit->text();
|
||||
if (text.isEmpty() || !m_replaceAvailable) {
|
||||
return;
|
||||
}
|
||||
QString replaceText = m_replaceEdit->text();
|
||||
emit replace(text, m_options, replaceText, false);
|
||||
}
|
||||
|
||||
void VFindReplaceDialog::replaceFind()
|
||||
{
|
||||
QString text = m_findEdit->text();
|
||||
if (text.isEmpty() || !m_replaceAvailable) {
|
||||
return;
|
||||
}
|
||||
QString replaceText = m_replaceEdit->text();
|
||||
emit replace(text, m_options, replaceText, true);
|
||||
}
|
||||
|
||||
void VFindReplaceDialog::replaceAll()
|
||||
{
|
||||
QString text = m_findEdit->text();
|
||||
if (text.isEmpty() || !m_replaceAvailable) {
|
||||
return;
|
||||
}
|
||||
QString replaceText = m_replaceEdit->text();
|
||||
emit replaceAll(text, m_options, replaceText);
|
||||
}
|
||||
|
||||
void VFindReplaceDialog::updateState(DocType p_docType, bool p_editMode)
|
||||
{
|
||||
if (p_editMode || p_docType == DocType::Html) {
|
||||
m_wholeWordOnlyCheck->setEnabled(true);
|
||||
m_regularExpressionCheck->setEnabled(true);
|
||||
} else if (p_docType == DocType::Markdown) {
|
||||
m_wholeWordOnlyCheck->setEnabled(false);
|
||||
m_regularExpressionCheck->setEnabled(false);
|
||||
}
|
||||
|
||||
m_replaceAvailable = p_editMode;
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
#ifndef VFINDREPLACEDIALOG_H
|
||||
#define VFINDREPLACEDIALOG_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QString>
|
||||
#include "vconstants.h"
|
||||
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class QCheckBox;
|
||||
|
||||
class VFindReplaceDialog : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VFindReplaceDialog(QWidget *p_parent = 0);
|
||||
void setOption(FindOption p_opt, bool p_enabled);
|
||||
// Update the options enabled/disabled state according to current
|
||||
// edit tab.
|
||||
void updateState(DocType p_docType, bool p_editMode);
|
||||
|
||||
signals:
|
||||
void dialogClosed();
|
||||
void findTextChanged(const QString &p_text, uint p_options);
|
||||
void findOptionChanged(uint p_options);
|
||||
void findNext(const QString &p_text, uint p_options, bool p_forward);
|
||||
void replace(const QString &p_text, uint p_options,
|
||||
const QString &p_replaceText, bool p_findNext);
|
||||
void replaceAll(const QString &p_text, uint p_options,
|
||||
const QString &p_replaceText);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
|
||||
|
||||
public slots:
|
||||
void closeDialog();
|
||||
void openDialog(QString p_text = "");
|
||||
void findNext();
|
||||
void findPrevious();
|
||||
void replace();
|
||||
void replaceFind();
|
||||
void replaceAll();
|
||||
|
||||
private slots:
|
||||
void handleFindTextChanged(const QString &p_text);
|
||||
void advancedBtnToggled(bool p_checked);
|
||||
void optionBoxToggled(int p_state);
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
// Bit OR of FindOption
|
||||
uint m_options;
|
||||
bool m_replaceAvailable;
|
||||
|
||||
QLineEdit *m_findEdit;
|
||||
QLineEdit *m_replaceEdit;
|
||||
QPushButton *m_findNextBtn;
|
||||
QPushButton *m_findPrevBtn;
|
||||
QPushButton *m_replaceBtn;
|
||||
QPushButton *m_replaceFindBtn;
|
||||
QPushButton *m_replaceAllBtn;
|
||||
QPushButton *m_advancedBtn;
|
||||
QPushButton *m_closeBtn;
|
||||
QCheckBox *m_caseSensitiveCheck;
|
||||
QCheckBox *m_wholeWordOnlyCheck;
|
||||
QCheckBox *m_regularExpressionCheck;
|
||||
QCheckBox *m_incrementalSearchCheck;
|
||||
};
|
||||
|
||||
#endif // VFINDREPLACEDIALOG_H
|
@ -1,267 +0,0 @@
|
||||
#include <QtWidgets>
|
||||
#include <QValidator>
|
||||
#include <QRegExp>
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
#include "vinsertimagedialog.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vlineedit.h"
|
||||
#include "vdownloader.h"
|
||||
|
||||
VInsertImageDialog::VInsertImageDialog(const QString &p_title,
|
||||
const QString &p_imageTitle,
|
||||
const QString &p_imagePath,
|
||||
bool p_browsable,
|
||||
QWidget *p_parent)
|
||||
: QDialog(p_parent),
|
||||
m_image(NULL),
|
||||
m_browsable(p_browsable),
|
||||
m_timer(NULL)
|
||||
{
|
||||
setupUI(p_title, p_imageTitle, p_imagePath);
|
||||
|
||||
connect(m_imageTitleEdit, &QLineEdit::textChanged,
|
||||
this, &VInsertImageDialog::handleInputChanged);
|
||||
|
||||
if (m_browsable) {
|
||||
m_timer = new QTimer(this);
|
||||
m_timer->setSingleShot(true);
|
||||
m_timer->setInterval(500 /* ms */);
|
||||
connect(m_timer, &QTimer::timeout,
|
||||
this, &VInsertImageDialog::handlePathEditChanged);
|
||||
|
||||
connect(m_pathEdit, &QLineEdit::textChanged,
|
||||
this, [this]() {
|
||||
m_timer->stop();
|
||||
|
||||
setImage(QImage());
|
||||
if (m_pathEdit->text().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_timer->start();
|
||||
});
|
||||
|
||||
connect(browseBtn, &QPushButton::clicked,
|
||||
this, &VInsertImageDialog::handleBrowseBtnClicked);
|
||||
|
||||
fetchImageFromClipboard();
|
||||
}
|
||||
|
||||
handleInputChanged();
|
||||
}
|
||||
|
||||
VInsertImageDialog::~VInsertImageDialog()
|
||||
{
|
||||
if (m_image) {
|
||||
delete m_image;
|
||||
m_image = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void VInsertImageDialog::setupUI(const QString &p_title,
|
||||
const QString &p_imageTitle,
|
||||
const QString &p_imagePath)
|
||||
{
|
||||
QLabel *pathLabel = new QLabel(tr("&From:"));
|
||||
m_pathEdit = new QLineEdit(p_imagePath);
|
||||
pathLabel->setBuddy(m_pathEdit);
|
||||
browseBtn = new QPushButton(tr("&Browse"));
|
||||
m_pathEdit->setReadOnly(!m_browsable);
|
||||
browseBtn->setEnabled(m_browsable);
|
||||
|
||||
QLabel *imageTitleLabel = new QLabel(tr("&Image title:"));
|
||||
m_imageTitleEdit = new VLineEdit(p_imageTitle);
|
||||
m_imageTitleEdit->selectAll();
|
||||
imageTitleLabel->setBuddy(m_imageTitleEdit);
|
||||
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_imageTitleRegExp),
|
||||
m_imageTitleEdit);
|
||||
m_imageTitleEdit->setValidator(validator);
|
||||
|
||||
QGridLayout *topLayout = new QGridLayout();
|
||||
topLayout->addWidget(pathLabel, 0, 0);
|
||||
topLayout->addWidget(m_pathEdit, 0, 1);
|
||||
topLayout->addWidget(browseBtn, 0, 2);
|
||||
topLayout->addWidget(imageTitleLabel, 1, 0);
|
||||
topLayout->addWidget(m_imageTitleEdit, 1, 1, 1, 2);
|
||||
topLayout->setColumnStretch(0, 0);
|
||||
topLayout->setColumnStretch(1, 1);
|
||||
topLayout->setColumnStretch(2, 0);
|
||||
|
||||
// Ok is the default button.
|
||||
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
m_btnBox->button(QDialogButtonBox::Ok)->setProperty("SpecialBtn", true);
|
||||
|
||||
imagePreviewLabel = new QLabel();
|
||||
imagePreviewLabel->setVisible(false);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
mainLayout->addLayout(topLayout);
|
||||
mainLayout->addWidget(m_btnBox);
|
||||
mainLayout->addWidget(imagePreviewLabel);
|
||||
setLayout(mainLayout);
|
||||
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
setWindowTitle(p_title);
|
||||
|
||||
m_imageTitleEdit->setFocus();
|
||||
}
|
||||
|
||||
void VInsertImageDialog::handleInputChanged()
|
||||
{
|
||||
QString title = m_imageTitleEdit->getEvaluatedText();
|
||||
QRegExp reg(VUtils::c_imageTitleRegExp);
|
||||
bool titleOk = reg.exactMatch(title);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setEnabled(titleOk && m_image);
|
||||
}
|
||||
|
||||
QString VInsertImageDialog::getImageTitleInput() const
|
||||
{
|
||||
return m_imageTitleEdit->getEvaluatedText();
|
||||
}
|
||||
|
||||
QString VInsertImageDialog::getPathInput() const
|
||||
{
|
||||
return m_pathEdit->text();
|
||||
}
|
||||
|
||||
void VInsertImageDialog::handleBrowseBtnClicked()
|
||||
{
|
||||
static QString lastPath = QDir::homePath();
|
||||
QString filePath = QFileDialog::getOpenFileName(this, tr("Select The Image To Be Inserted"),
|
||||
lastPath, tr("Images (*.png *.xpm *.jpg *.bmp *.gif)"));
|
||||
if (filePath.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update lastPath
|
||||
lastPath = QFileInfo(filePath).path();
|
||||
|
||||
m_imageType = ImageType::LocalFile;
|
||||
|
||||
m_pathEdit->setText(filePath);
|
||||
|
||||
m_imageTitleEdit->setFocus();
|
||||
}
|
||||
|
||||
void VInsertImageDialog::setImage(const QImage &image)
|
||||
{
|
||||
if (image.isNull()) {
|
||||
imagePreviewLabel->setVisible(false);
|
||||
if (m_image) {
|
||||
delete m_image;
|
||||
m_image = NULL;
|
||||
}
|
||||
|
||||
handleInputChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
int width = 512 * VUtils::calculateScaleFactor();
|
||||
QSize previewSize(width, width);
|
||||
if (!m_image) {
|
||||
m_image = new QImage(image);
|
||||
} else {
|
||||
*m_image = image;
|
||||
}
|
||||
|
||||
QPixmap pixmap;
|
||||
if (image.width() > width || image.height() > width) {
|
||||
pixmap = QPixmap::fromImage(m_image->scaled(previewSize, Qt::KeepAspectRatio));
|
||||
} else {
|
||||
pixmap = QPixmap::fromImage(*m_image);
|
||||
}
|
||||
|
||||
imagePreviewLabel->setPixmap(pixmap);
|
||||
imagePreviewLabel->setVisible(true);
|
||||
|
||||
handleInputChanged();
|
||||
}
|
||||
|
||||
void VInsertImageDialog::imageDownloaded(const QByteArray &data)
|
||||
{
|
||||
m_imageType = ImageType::ImageData;
|
||||
setImage(QImage::fromData(data));
|
||||
}
|
||||
|
||||
QImage VInsertImageDialog::getImage() const
|
||||
{
|
||||
if (!m_image) {
|
||||
return QImage();
|
||||
} else return *m_image;
|
||||
}
|
||||
|
||||
void VInsertImageDialog::fetchImageFromClipboard()
|
||||
{
|
||||
if (!m_browsable || !m_pathEdit->text().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(!m_image);
|
||||
|
||||
QClipboard *clipboard = QApplication::clipboard();
|
||||
const QMimeData *mimeData = clipboard->mimeData();
|
||||
|
||||
QUrl url;
|
||||
|
||||
if (mimeData->hasImage()) {
|
||||
QImage im = qvariant_cast<QImage>(mimeData->imageData());
|
||||
if (im.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setImage(im);
|
||||
m_imageType = ImageType::ImageData;
|
||||
qDebug() << "fetch image data from clipboard to insert";
|
||||
return;
|
||||
} else if (mimeData->hasUrls()) {
|
||||
QList<QUrl> urls = mimeData->urls();
|
||||
if (urls.size() != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
url = urls[0];
|
||||
} else if (mimeData->hasText()) {
|
||||
url = QUrl(mimeData->text());
|
||||
}
|
||||
|
||||
if (url.isValid()) {
|
||||
if (url.isLocalFile()) {
|
||||
m_pathEdit->setText(url.toLocalFile());
|
||||
} else {
|
||||
m_pathEdit->setText(url.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VInsertImageDialog::handlePathEditChanged()
|
||||
{
|
||||
QString text = m_pathEdit->text();
|
||||
QUrl url = QUrl::fromUserInput(text);
|
||||
if (!url.isValid()) {
|
||||
setImage(QImage());
|
||||
return;
|
||||
}
|
||||
|
||||
QImage image(text);
|
||||
if (image.isNull()) {
|
||||
setImage(QImage());
|
||||
// Try to treat it as network image.
|
||||
m_imageType = ImageType::ImageData;
|
||||
VDownloader *downloader = new VDownloader(this);
|
||||
connect(downloader, &VDownloader::downloadFinished,
|
||||
this, &VInsertImageDialog::imageDownloaded);
|
||||
downloader->download(url.toString());
|
||||
qDebug() << "try to fetch network image to insert" << text;
|
||||
} else {
|
||||
// Local image path.
|
||||
setImage(image);
|
||||
m_imageType = ImageType::LocalFile;
|
||||
qDebug() << "fetch local file image to insert" << text;
|
||||
}
|
||||
|
||||
handleInputChanged();
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
#ifndef VINSERTIMAGEDIALOG_H
|
||||
#define VINSERTIMAGEDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QImage>
|
||||
#include <QString>
|
||||
#include <QByteArray>
|
||||
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class VLineEdit;
|
||||
class QPushButton;
|
||||
class QDialogButtonBox;
|
||||
class QTimer;
|
||||
|
||||
class VInsertImageDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum ImageType
|
||||
{
|
||||
LocalFile = 0,
|
||||
ImageData
|
||||
};
|
||||
|
||||
VInsertImageDialog(const QString &p_title,
|
||||
const QString &p_imageTitle,
|
||||
const QString &p_imagePath,
|
||||
bool p_browsable = true,
|
||||
QWidget *p_parent = nullptr);
|
||||
|
||||
~VInsertImageDialog();
|
||||
|
||||
QString getImageTitleInput() const;
|
||||
|
||||
QString getPathInput() const;
|
||||
|
||||
void setImage(const QImage &image);
|
||||
|
||||
QImage getImage() const;
|
||||
|
||||
VInsertImageDialog::ImageType getImageType() const;
|
||||
|
||||
public slots:
|
||||
void imageDownloaded(const QByteArray &data);
|
||||
|
||||
private slots:
|
||||
void handleInputChanged();
|
||||
|
||||
void handleBrowseBtnClicked();
|
||||
|
||||
void handlePathEditChanged();
|
||||
|
||||
private:
|
||||
void setupUI(const QString &p_title,
|
||||
const QString &p_imageTitle,
|
||||
const QString &p_imagePath);
|
||||
|
||||
void fetchImageFromClipboard();
|
||||
|
||||
VLineEdit *m_imageTitleEdit;
|
||||
QLineEdit *m_pathEdit;
|
||||
QPushButton *browseBtn;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
QLabel *imagePreviewLabel;
|
||||
|
||||
QImage *m_image;
|
||||
|
||||
// Whether enable the browse action.
|
||||
bool m_browsable;
|
||||
|
||||
ImageType m_imageType;
|
||||
|
||||
// Timer for path edit change.
|
||||
QTimer *m_timer;
|
||||
};
|
||||
|
||||
inline VInsertImageDialog::ImageType VInsertImageDialog::getImageType() const
|
||||
{
|
||||
return m_imageType;
|
||||
}
|
||||
|
||||
#endif // VINSERTIMAGEDIALOG_H
|
@ -1,142 +0,0 @@
|
||||
#include "vinsertlinkdialog.h"
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
#include "vlineedit.h"
|
||||
|
||||
VInsertLinkDialog::VInsertLinkDialog(const QString &p_title,
|
||||
const QString &p_text,
|
||||
const QString &p_info,
|
||||
const QString &p_linkText,
|
||||
const QString &p_linkUrl,
|
||||
QWidget *p_parent)
|
||||
: QDialog(p_parent)
|
||||
{
|
||||
setupUI(p_title, p_text, p_info, p_linkText, p_linkUrl);
|
||||
|
||||
fetchLinkFromClipboard();
|
||||
|
||||
handleInputChanged();
|
||||
}
|
||||
|
||||
void VInsertLinkDialog::setupUI(const QString &p_title,
|
||||
const QString &p_text,
|
||||
const QString &p_info,
|
||||
const QString &p_linkText,
|
||||
const QString &p_linkUrl)
|
||||
{
|
||||
QLabel *textLabel = NULL;
|
||||
if (!p_text.isEmpty()) {
|
||||
textLabel = new QLabel(p_text);
|
||||
textLabel->setWordWrap(true);
|
||||
}
|
||||
|
||||
QLabel *infoLabel = NULL;
|
||||
if (!p_info.isEmpty()) {
|
||||
infoLabel = new QLabel(p_info);
|
||||
infoLabel->setWordWrap(true);
|
||||
}
|
||||
|
||||
m_linkTextEdit = new VLineEdit(p_linkText);
|
||||
m_linkTextEdit->selectAll();
|
||||
|
||||
m_linkUrlEdit = new QLineEdit(p_linkUrl);
|
||||
m_linkUrlEdit->setToolTip(tr("Absolute or relative path of the link"));
|
||||
|
||||
QFormLayout *inputLayout = new QFormLayout();
|
||||
inputLayout->addRow(tr("&Text:"), m_linkTextEdit);
|
||||
inputLayout->addRow(tr("&URL:"), m_linkUrlEdit);
|
||||
|
||||
// Ok is the default button.
|
||||
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setProperty("SpecialBtn", true);
|
||||
m_linkTextEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
if (textLabel) {
|
||||
mainLayout->addWidget(textLabel);
|
||||
}
|
||||
|
||||
if (infoLabel) {
|
||||
mainLayout->addWidget(infoLabel);
|
||||
}
|
||||
|
||||
mainLayout->addLayout(inputLayout);
|
||||
mainLayout->addWidget(m_btnBox);
|
||||
|
||||
setLayout(mainLayout);
|
||||
setWindowTitle(p_title);
|
||||
|
||||
connect(m_linkTextEdit, &QLineEdit::textChanged,
|
||||
this, &VInsertLinkDialog::handleInputChanged);
|
||||
connect(m_linkUrlEdit, &QLineEdit::textChanged,
|
||||
this, &VInsertLinkDialog::handleInputChanged);
|
||||
}
|
||||
|
||||
void VInsertLinkDialog::handleInputChanged()
|
||||
{
|
||||
bool textOk = true;
|
||||
if (m_linkTextEdit->getEvaluatedText().isEmpty()) {
|
||||
textOk = false;
|
||||
}
|
||||
|
||||
bool urlOk = true;
|
||||
if (m_linkUrlEdit->text().isEmpty()) {
|
||||
urlOk = false;
|
||||
}
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setEnabled(textOk && urlOk);
|
||||
}
|
||||
|
||||
void VInsertLinkDialog::fetchLinkFromClipboard()
|
||||
{
|
||||
if (!m_linkUrlEdit->text().isEmpty()
|
||||
|| !m_linkTextEdit->text().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QClipboard *clipboard = QApplication::clipboard();
|
||||
const QMimeData *mimeData = clipboard->mimeData();
|
||||
|
||||
if (!mimeData->hasText()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString text = mimeData->text();
|
||||
if (text.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QUrl url = QUrl::fromUserInput(text);
|
||||
if (url.isValid()) {
|
||||
if (m_linkUrlEdit->text().isEmpty()) {
|
||||
m_linkUrlEdit->setText(text);
|
||||
}
|
||||
} else if (m_linkTextEdit->text().isEmpty()) {
|
||||
m_linkTextEdit->setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
QString VInsertLinkDialog::getLinkText() const
|
||||
{
|
||||
return m_linkTextEdit->getEvaluatedText();
|
||||
}
|
||||
|
||||
QString VInsertLinkDialog::getLinkUrl() const
|
||||
{
|
||||
return m_linkUrlEdit->text();
|
||||
}
|
||||
|
||||
void VInsertLinkDialog::showEvent(QShowEvent *p_event)
|
||||
{
|
||||
QDialog::showEvent(p_event);
|
||||
|
||||
if (!m_linkTextEdit->text().isEmpty() && m_linkUrlEdit->text().isEmpty()) {
|
||||
m_linkUrlEdit->setFocus();
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
#ifndef VINSERTLINKDIALOG_H
|
||||
#define VINSERTLINKDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QString>
|
||||
|
||||
class VLineEdit;
|
||||
class QLineEdit;
|
||||
class QDialogButtonBox;
|
||||
class QShowEvent;
|
||||
|
||||
class VInsertLinkDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VInsertLinkDialog(const QString &p_title,
|
||||
const QString &p_text,
|
||||
const QString &p_info,
|
||||
const QString &p_linkText,
|
||||
const QString &p_linkUrl,
|
||||
QWidget *p_parent = nullptr);
|
||||
|
||||
QString getLinkText() const;
|
||||
|
||||
QString getLinkUrl() const;
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
private slots:
|
||||
void handleInputChanged();
|
||||
|
||||
private:
|
||||
void setupUI(const QString &p_title,
|
||||
const QString &p_text,
|
||||
const QString &p_info,
|
||||
const QString &p_linkText,
|
||||
const QString &p_linkUrl);
|
||||
|
||||
// Infer link text/url from clipboard only when text and url are both empty.
|
||||
void fetchLinkFromClipboard();
|
||||
|
||||
VLineEdit *m_linkTextEdit;
|
||||
|
||||
QLineEdit *m_linkUrlEdit;
|
||||
|
||||
QDialogButtonBox *m_btnBox;
|
||||
};
|
||||
|
||||
#endif // VINSERTLINKDIALOG_H
|
@ -1,113 +0,0 @@
|
||||
#include <QtWidgets>
|
||||
#include "vnewdirdialog.h"
|
||||
#include "vdirectory.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "vlineedit.h"
|
||||
#include "utils/vutils.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
VNewDirDialog::VNewDirDialog(const QString &title,
|
||||
const QString &info,
|
||||
const QString &defaultName,
|
||||
VDirectory *directory,
|
||||
QWidget *parent)
|
||||
: QDialog(parent), title(title), info(info), defaultName(defaultName),
|
||||
m_directory(directory)
|
||||
{
|
||||
setupUI();
|
||||
|
||||
connect(m_nameEdit, &QLineEdit::textChanged, this, &VNewDirDialog::handleInputChanged);
|
||||
|
||||
handleInputChanged();
|
||||
}
|
||||
|
||||
void VNewDirDialog::setupUI()
|
||||
{
|
||||
QLabel *infoLabel = NULL;
|
||||
if (!info.isEmpty()) {
|
||||
infoLabel = new QLabel(info);
|
||||
infoLabel->setWordWrap(true);
|
||||
}
|
||||
|
||||
QLabel *nameLabel = new QLabel(tr("Folder &name:"));
|
||||
m_nameEdit = new VLineEdit(defaultName);
|
||||
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
|
||||
m_nameEdit);
|
||||
m_nameEdit->setValidator(validator);
|
||||
m_nameEdit->selectAll();
|
||||
nameLabel->setBuddy(m_nameEdit);
|
||||
|
||||
m_warnLabel = new QLabel();
|
||||
m_warnLabel->setWordWrap(true);
|
||||
m_warnLabel->hide();
|
||||
|
||||
// Ok is the default button.
|
||||
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
m_btnBox->button(QDialogButtonBox::Ok)->setProperty("SpecialBtn", true);
|
||||
|
||||
QHBoxLayout *topLayout = new QHBoxLayout();
|
||||
topLayout->addWidget(nameLabel);
|
||||
topLayout->addWidget(m_nameEdit);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
m_nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
if (infoLabel) {
|
||||
mainLayout->addWidget(infoLabel);
|
||||
}
|
||||
mainLayout->addLayout(topLayout);
|
||||
mainLayout->addWidget(m_warnLabel);
|
||||
mainLayout->addWidget(m_btnBox);
|
||||
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
setLayout(mainLayout);
|
||||
setWindowTitle(title);
|
||||
}
|
||||
|
||||
void VNewDirDialog::handleInputChanged()
|
||||
{
|
||||
bool showWarnLabel = false;
|
||||
QString name = m_nameEdit->getEvaluatedText();
|
||||
bool nameOk = !name.isEmpty();
|
||||
if (nameOk) {
|
||||
// Check if the name conflicts with existing directory name.
|
||||
// Case-insensitive when creating folder.
|
||||
QString warnText;
|
||||
if (m_directory->findSubDirectory(name, false)) {
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
} else if (!VUtils::checkFileNameLegal(name)) {
|
||||
// Check if evaluated name contains illegal characters.
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name <span style=\"%2\">%3</span> contains illegal characters "
|
||||
"(after magic word evaluation).")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
}
|
||||
|
||||
if (!nameOk) {
|
||||
showWarnLabel = true;
|
||||
m_warnLabel->setText(warnText);
|
||||
}
|
||||
}
|
||||
|
||||
m_warnLabel->setVisible(showWarnLabel);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setEnabled(nameOk);
|
||||
}
|
||||
|
||||
QString VNewDirDialog::getNameInput() const
|
||||
{
|
||||
return m_nameEdit->getEvaluatedText();
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
#ifndef VNEWDIRDIALOG_H
|
||||
#define VNEWDIRDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class QLabel;
|
||||
class VLineEdit;
|
||||
class QDialogButtonBox;
|
||||
class QString;
|
||||
class VDirectory;
|
||||
|
||||
class VNewDirDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VNewDirDialog(const QString &title,
|
||||
const QString &info,
|
||||
const QString &defaultName,
|
||||
VDirectory *directory,
|
||||
QWidget *parent = 0);
|
||||
|
||||
QString getNameInput() const;
|
||||
|
||||
private slots:
|
||||
void handleInputChanged();
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
VLineEdit *m_nameEdit;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
|
||||
QLabel *m_warnLabel;
|
||||
|
||||
QString title;
|
||||
QString info;
|
||||
QString defaultName;
|
||||
|
||||
VDirectory *m_directory;
|
||||
};
|
||||
|
||||
#endif // VNEWDIRDIALOG_H
|
@ -1,285 +0,0 @@
|
||||
#include <QtWidgets>
|
||||
#include "vnewfiledialog.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "vdirectory.h"
|
||||
#include "vlineedit.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "utils/vmetawordmanager.h"
|
||||
#include "utils/viconutils.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
extern VMetaWordManager *g_mwMgr;
|
||||
|
||||
QString VNewFileDialog::s_lastTemplateFile;
|
||||
|
||||
|
||||
VNewFileDialog::VNewFileDialog(const QString &p_title,
|
||||
const QString &p_info,
|
||||
const QString &p_defaultName,
|
||||
VDirectory *p_directory,
|
||||
QWidget *p_parent)
|
||||
: QDialog(p_parent),
|
||||
m_currentTemplateType(DocType::Unknown),
|
||||
m_directory(p_directory)
|
||||
{
|
||||
setupUI(p_title, p_info, p_defaultName);
|
||||
|
||||
connect(m_nameEdit, &VLineEdit::textChanged, this, &VNewFileDialog::handleInputChanged);
|
||||
|
||||
connect(m_templateCB, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
this, &VNewFileDialog::handleCurrentTemplateChanged);
|
||||
|
||||
handleInputChanged();
|
||||
|
||||
tryToSelectLastTemplate();
|
||||
}
|
||||
|
||||
void VNewFileDialog::setupUI(const QString &p_title,
|
||||
const QString &p_info,
|
||||
const QString &p_defaultName)
|
||||
{
|
||||
QLabel *infoLabel = NULL;
|
||||
if (!p_info.isEmpty()) {
|
||||
infoLabel = new QLabel(p_info);
|
||||
}
|
||||
|
||||
// Name.
|
||||
m_nameEdit = new VLineEdit(p_defaultName);
|
||||
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
|
||||
m_nameEdit);
|
||||
m_nameEdit->setValidator(validator);
|
||||
int dotIndex = p_defaultName.lastIndexOf('.');
|
||||
m_nameEdit->setSelection(0, (dotIndex == -1) ? p_defaultName.size() : dotIndex);
|
||||
|
||||
// Template.
|
||||
m_templateCB = VUtils::getComboBox();
|
||||
m_templateCB->setToolTip(tr("Choose a template (magic word supported)"));
|
||||
m_templateCB->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
|
||||
QPushButton *templateBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/manage_template.svg"),
|
||||
"");
|
||||
templateBtn->setToolTip(tr("Manage Templates"));
|
||||
templateBtn->setProperty("FlatBtn", true);
|
||||
connect(templateBtn, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
QUrl url = QUrl::fromLocalFile(g_config->getTemplateConfigFolder());
|
||||
QDesktopServices::openUrl(url);
|
||||
});
|
||||
|
||||
QHBoxLayout *tempBtnLayout = new QHBoxLayout();
|
||||
tempBtnLayout->addWidget(m_templateCB);
|
||||
tempBtnLayout->addWidget(templateBtn);
|
||||
tempBtnLayout->addStretch();
|
||||
|
||||
m_templateEdit = new QTextEdit();
|
||||
m_templateEdit->setReadOnly(true);
|
||||
|
||||
QVBoxLayout *templateLayout = new QVBoxLayout();
|
||||
templateLayout->addLayout(tempBtnLayout);
|
||||
templateLayout->addWidget(m_templateEdit);
|
||||
|
||||
m_templateEdit->hide();
|
||||
|
||||
// InsertTitle.
|
||||
m_insertTitleCB = new QCheckBox(tr("Insert note name as title (for Markdown only)"));
|
||||
m_insertTitleCB->setToolTip(tr("Insert note name into the new note as a title"));
|
||||
m_insertTitleCB->setChecked(g_config->getInsertTitleFromNoteName());
|
||||
connect(m_insertTitleCB, &QCheckBox::stateChanged,
|
||||
this, [this](int p_state) {
|
||||
g_config->setInsertTitleFromNoteName(p_state == Qt::Checked);
|
||||
});
|
||||
|
||||
QFormLayout *topLayout = new QFormLayout();
|
||||
topLayout->addRow(tr("Note &name:"), m_nameEdit);
|
||||
topLayout->addWidget(m_insertTitleCB);
|
||||
topLayout->addRow(tr("Template:"), templateLayout);
|
||||
|
||||
m_nameEdit->setMinimumWidth(m_insertTitleCB->sizeHint().width());
|
||||
|
||||
m_warnLabel = new QLabel();
|
||||
m_warnLabel->setWordWrap(true);
|
||||
m_warnLabel->hide();
|
||||
|
||||
// Ok is the default button.
|
||||
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(m_btnBox, &QDialogButtonBox::accepted,
|
||||
this, [this]() {
|
||||
s_lastTemplateFile = m_templateCB->currentData().toString();
|
||||
QDialog::accept();
|
||||
});
|
||||
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setProperty("SpecialBtn", true);
|
||||
m_templateCB->setMaximumWidth(okBtn->sizeHint().width() * 4);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
if (infoLabel) {
|
||||
mainLayout->addWidget(infoLabel);
|
||||
}
|
||||
|
||||
mainLayout->addLayout(topLayout);
|
||||
mainLayout->addWidget(m_warnLabel);
|
||||
mainLayout->addWidget(m_btnBox);
|
||||
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
setLayout(mainLayout);
|
||||
|
||||
setWindowTitle(p_title);
|
||||
}
|
||||
|
||||
void VNewFileDialog::handleInputChanged()
|
||||
{
|
||||
bool showWarnLabel = false;
|
||||
const QString name = m_nameEdit->getEvaluatedText();
|
||||
bool nameOk = !name.isEmpty();
|
||||
if (nameOk) {
|
||||
// Check if the name conflicts with existing note name.
|
||||
// Case-insensitive when creating note.
|
||||
QString warnText;
|
||||
if (m_directory->findFile(name, false)) {
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
} else if (!VUtils::checkFileNameLegal(name)) {
|
||||
// Check if evaluated name contains illegal characters.
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name <span style=\"%2\">%3</span> contains illegal characters "
|
||||
"(after magic word evaluation).")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
}
|
||||
|
||||
if (!nameOk) {
|
||||
showWarnLabel = true;
|
||||
m_warnLabel->setText(warnText);
|
||||
}
|
||||
}
|
||||
|
||||
m_warnLabel->setVisible(showWarnLabel);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setEnabled(nameOk);
|
||||
|
||||
if (nameOk) {
|
||||
updateTemplates(VUtils::docTypeFromName(name));
|
||||
}
|
||||
}
|
||||
|
||||
QString VNewFileDialog::getNameInput() const
|
||||
{
|
||||
return m_nameEdit->getEvaluatedText();
|
||||
}
|
||||
|
||||
bool VNewFileDialog::getInsertTitleInput() const
|
||||
{
|
||||
return m_insertTitleCB->isEnabled() && m_insertTitleCB->isChecked();
|
||||
}
|
||||
|
||||
void VNewFileDialog::updateTemplates(DocType p_type)
|
||||
{
|
||||
if (m_currentTemplateType == p_type) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_currentTemplateType = p_type;
|
||||
|
||||
// Clear the combo.
|
||||
m_templateCB->clear();
|
||||
|
||||
// Add None item.
|
||||
m_templateCB->addItem(tr("None"), "None");
|
||||
|
||||
if (m_currentTemplateType == DocType::Unknown) {
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = 1;
|
||||
auto templates = g_config->getNoteTemplates(m_currentTemplateType);
|
||||
for (auto const & tp : templates) {
|
||||
m_templateCB->addItem(tp, tp);
|
||||
m_templateCB->setItemData(idx++, tp, Qt::ToolTipRole);
|
||||
}
|
||||
}
|
||||
|
||||
void VNewFileDialog::handleCurrentTemplateChanged(int p_idx)
|
||||
{
|
||||
if (p_idx == -1) {
|
||||
m_templateEdit->hide();
|
||||
enableInsertTitleCB(false);
|
||||
return;
|
||||
}
|
||||
|
||||
QString file = m_templateCB->itemData(p_idx).toString();
|
||||
if (file == "None") {
|
||||
m_templateEdit->hide();
|
||||
enableInsertTitleCB(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the template file.
|
||||
QString filePath = QDir(g_config->getTemplateConfigFolder()).filePath(file);
|
||||
m_template = VUtils::readFileFromDisk(filePath);
|
||||
DocType type = VUtils::docTypeFromName(file);
|
||||
switch (type) {
|
||||
case DocType::Html:
|
||||
m_templateEdit->setHtml(m_template);
|
||||
break;
|
||||
|
||||
case DocType::Markdown:
|
||||
m_templateEdit->setPlainText(m_template);
|
||||
break;
|
||||
|
||||
default:
|
||||
m_templateEdit->setPlainText(m_template);
|
||||
break;
|
||||
}
|
||||
|
||||
m_templateEdit->show();
|
||||
enableInsertTitleCB(true);
|
||||
}
|
||||
|
||||
void VNewFileDialog::enableInsertTitleCB(bool p_hasTemplate)
|
||||
{
|
||||
m_insertTitleCB->setEnabled(!p_hasTemplate
|
||||
&& VUtils::docTypeFromName(m_nameEdit->getEvaluatedText())
|
||||
== DocType::Markdown);
|
||||
}
|
||||
|
||||
bool VNewFileDialog::isTemplateUsed() const
|
||||
{
|
||||
QString file = m_templateCB->currentData().toString();
|
||||
return !(file.isEmpty() || file == "None");
|
||||
}
|
||||
|
||||
QString VNewFileDialog::getTemplate() const
|
||||
{
|
||||
QString name = m_nameEdit->getEvaluatedText();
|
||||
QHash<QString, QString> overriddenTable;
|
||||
overriddenTable.insert("note", name);
|
||||
overriddenTable.insert("no", QFileInfo(name).completeBaseName());
|
||||
return g_mwMgr->evaluate(m_template, overriddenTable);
|
||||
}
|
||||
|
||||
void VNewFileDialog::tryToSelectLastTemplate()
|
||||
{
|
||||
Q_ASSERT(m_templateCB->count() > 0
|
||||
&& m_templateCB->itemData(0).toString() == "None");
|
||||
if (s_lastTemplateFile.isEmpty() || s_lastTemplateFile == "None") {
|
||||
m_templateCB->setCurrentIndex(0);
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = m_templateCB->findData(s_lastTemplateFile);
|
||||
if (idx != -1) {
|
||||
m_templateCB->setCurrentIndex(idx);
|
||||
} else {
|
||||
s_lastTemplateFile.clear();
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
#ifndef VNEWFILEDIALOG_H
|
||||
#define VNEWFILEDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include "vconstants.h"
|
||||
|
||||
class QLabel;
|
||||
class VLineEdit;
|
||||
class QDialogButtonBox;
|
||||
class QCheckBox;
|
||||
class VDirectory;
|
||||
class QComboBox;
|
||||
class QTextEdit;
|
||||
|
||||
class VNewFileDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VNewFileDialog(const QString &p_title,
|
||||
const QString &p_info,
|
||||
const QString &p_defaultName,
|
||||
VDirectory *p_directory,
|
||||
QWidget *p_parent = 0);
|
||||
|
||||
QString getNameInput() const;
|
||||
|
||||
bool getInsertTitleInput() const;
|
||||
|
||||
// Whether user choose a note template.
|
||||
bool isTemplateUsed() const;
|
||||
|
||||
// Get the template content (after magic words evaluated) user chose.
|
||||
QString getTemplate() const;
|
||||
|
||||
private slots:
|
||||
void handleInputChanged();
|
||||
|
||||
void handleCurrentTemplateChanged(int p_idx);
|
||||
|
||||
private:
|
||||
void setupUI(const QString &p_title,
|
||||
const QString &p_info,
|
||||
const QString &p_defaultName);
|
||||
|
||||
// Update the templates according to @p_type.
|
||||
void updateTemplates(DocType p_type);
|
||||
|
||||
void enableInsertTitleCB(bool p_hasTemplate);
|
||||
|
||||
void tryToSelectLastTemplate();
|
||||
|
||||
VLineEdit *m_nameEdit;
|
||||
|
||||
QComboBox *m_templateCB;
|
||||
|
||||
// Used for template preview.
|
||||
QTextEdit *m_templateEdit;
|
||||
|
||||
QCheckBox *m_insertTitleCB;
|
||||
|
||||
QPushButton *okBtn;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
|
||||
QLabel *m_warnLabel;
|
||||
|
||||
// Template content.
|
||||
QString m_template;
|
||||
|
||||
// Doc type of current template.
|
||||
DocType m_currentTemplateType;
|
||||
|
||||
// Last chosen template file.
|
||||
static QString s_lastTemplateFile;
|
||||
|
||||
VDirectory *m_directory;
|
||||
};
|
||||
|
||||
#endif // VNEWFILEDIALOG_H
|
@ -1,353 +0,0 @@
|
||||
#include <QtWidgets>
|
||||
#include <QDir>
|
||||
#include "vnewnotebookdialog.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vnotebook.h"
|
||||
#include "vlineedit.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
VNewNotebookDialog::VNewNotebookDialog(const QString &title, const QString &info,
|
||||
const QString &defaultName, const QString &defaultPath,
|
||||
const QVector<VNotebook *> &p_notebooks,
|
||||
QWidget *parent)
|
||||
: QDialog(parent),
|
||||
defaultName(defaultName), defaultPath(defaultPath),
|
||||
m_importNotebook(false), m_manualPath(false), m_manualName(false),
|
||||
m_notebooks(p_notebooks)
|
||||
{
|
||||
setupUI(title, info);
|
||||
|
||||
connect(m_nameEdit, &QLineEdit::textChanged, this, &VNewNotebookDialog::handleInputChanged);
|
||||
connect(pathEdit, &QLineEdit::textChanged, this, &VNewNotebookDialog::handleInputChanged);
|
||||
connect(browseBtn, &QPushButton::clicked, this, &VNewNotebookDialog::handleBrowseBtnClicked);
|
||||
|
||||
handleInputChanged();
|
||||
}
|
||||
|
||||
void VNewNotebookDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
{
|
||||
QLabel *infoLabel = NULL;
|
||||
if (!p_info.isEmpty()) {
|
||||
infoLabel = new QLabel(p_info);
|
||||
infoLabel->setWordWrap(true);
|
||||
}
|
||||
|
||||
QLabel *nameLabel = new QLabel(tr("Notebook &name:"));
|
||||
m_nameEdit = new VLineEdit(defaultName);
|
||||
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
|
||||
m_nameEdit);
|
||||
m_nameEdit->setValidator(validator);
|
||||
nameLabel->setBuddy(m_nameEdit);
|
||||
|
||||
QLabel *pathLabel = new QLabel(tr("Notebook &root folder:"));
|
||||
pathEdit = new QLineEdit(defaultPath);
|
||||
pathLabel->setBuddy(pathEdit);
|
||||
browseBtn = new QPushButton(tr("&Browse"));
|
||||
|
||||
QLabel *imageFolderLabel = new QLabel(tr("&Image folder:"));
|
||||
m_imageFolderEdit = new QLineEdit();
|
||||
imageFolderLabel->setBuddy(m_imageFolderEdit);
|
||||
m_imageFolderEdit->setPlaceholderText(tr("Use global configuration (%1)")
|
||||
.arg(g_config->getImageFolder()));
|
||||
m_imageFolderEdit->setToolTip(tr("Set the name of the folder to hold images of all the notes in this notebook "
|
||||
"(empty to use global configuration)"));
|
||||
imageFolderLabel->setToolTip(m_imageFolderEdit->toolTip());
|
||||
validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_imageFolderEdit);
|
||||
m_imageFolderEdit->setValidator(validator);
|
||||
|
||||
QLabel *attachmentFolderLabel = new QLabel(tr("&Attachment folder:"));
|
||||
m_attachmentFolderEdit = new QLineEdit();
|
||||
attachmentFolderLabel->setBuddy(m_attachmentFolderEdit);
|
||||
m_attachmentFolderEdit->setPlaceholderText(tr("Use global configuration (%1)")
|
||||
.arg(g_config->getAttachmentFolder()));
|
||||
m_attachmentFolderEdit->setToolTip(tr("Set the name of the folder to hold attachments of all the notes in this notebook "
|
||||
"(empty to use global configuration, read-only once created)"));
|
||||
attachmentFolderLabel->setToolTip(m_attachmentFolderEdit->toolTip());
|
||||
validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_attachmentFolderEdit);
|
||||
m_attachmentFolderEdit->setValidator(validator);
|
||||
|
||||
QGridLayout *topLayout = new QGridLayout();
|
||||
topLayout->addWidget(nameLabel, 0, 0);
|
||||
topLayout->addWidget(m_nameEdit, 0, 1, 1, 2);
|
||||
topLayout->addWidget(pathLabel, 1, 0);
|
||||
topLayout->addWidget(pathEdit, 1, 1);
|
||||
topLayout->addWidget(browseBtn, 1, 2);
|
||||
topLayout->addWidget(imageFolderLabel, 2, 0);
|
||||
topLayout->addWidget(m_imageFolderEdit, 2, 1);
|
||||
topLayout->addWidget(attachmentFolderLabel, 3, 0);
|
||||
topLayout->addWidget(m_attachmentFolderEdit, 3, 1);
|
||||
|
||||
// Warning label.
|
||||
m_warnLabel = new QLabel();
|
||||
m_warnLabel->setWordWrap(true);
|
||||
m_warnLabel->hide();
|
||||
|
||||
// Ok is the default button.
|
||||
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setProperty("SpecialBtn", true);
|
||||
pathEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
if (infoLabel) {
|
||||
mainLayout->addWidget(infoLabel);
|
||||
}
|
||||
mainLayout->addLayout(topLayout);
|
||||
mainLayout->addWidget(m_warnLabel);
|
||||
mainLayout->addWidget(m_btnBox);
|
||||
|
||||
// Will set the parent of above widgets properly.
|
||||
setLayout(mainLayout);
|
||||
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
setWindowTitle(p_title);
|
||||
}
|
||||
|
||||
QString VNewNotebookDialog::getNameInput() const
|
||||
{
|
||||
return m_nameEdit->getEvaluatedText();
|
||||
}
|
||||
|
||||
QString VNewNotebookDialog::getPathInput() const
|
||||
{
|
||||
// absoluteFilePath() to convert the drive to upper case.
|
||||
// cleanPath() to remove duplicate separator, '.', and '..'.
|
||||
return QDir::cleanPath(QFileInfo(pathEdit->text()).absoluteFilePath());
|
||||
}
|
||||
|
||||
QString VNewNotebookDialog::getImageFolder() const
|
||||
{
|
||||
if (m_imageFolderEdit->isEnabled()) {
|
||||
return m_imageFolderEdit->text();
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
QString VNewNotebookDialog::getAttachmentFolder() const
|
||||
{
|
||||
if (m_attachmentFolderEdit->isEnabled()) {
|
||||
return m_attachmentFolderEdit->text();
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
void VNewNotebookDialog::handleBrowseBtnClicked()
|
||||
{
|
||||
static QString defaultPath;
|
||||
if (defaultPath.isEmpty()) {
|
||||
defaultPath = g_config->getVnoteNotebookFolderPath();
|
||||
if (!QFileInfo::exists(defaultPath)) {
|
||||
defaultPath = QDir::homePath();
|
||||
}
|
||||
}
|
||||
|
||||
QString dirPath = QFileDialog::getExistingDirectory(this,
|
||||
tr("Select Root Folder Of The Notebook"),
|
||||
defaultPath,
|
||||
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
|
||||
|
||||
if (!dirPath.isEmpty()) {
|
||||
m_manualPath = true;
|
||||
pathEdit->setText(dirPath);
|
||||
defaultPath = VUtils::basePathFromPath(dirPath);
|
||||
}
|
||||
}
|
||||
|
||||
bool VNewNotebookDialog::isImportExistingNotebook() const
|
||||
{
|
||||
return m_importNotebook;
|
||||
}
|
||||
|
||||
void VNewNotebookDialog::showEvent(QShowEvent *event)
|
||||
{
|
||||
m_nameEdit->setFocus();
|
||||
QDialog::showEvent(event);
|
||||
}
|
||||
|
||||
void VNewNotebookDialog::handleInputChanged()
|
||||
{
|
||||
QString warnText = tr("<span style=\"%1\">WARNING</span>: The folder chosen is NOT empty! "
|
||||
"It is highly recommended to use an EMPTY and EXCLUSIVE folder for a new notebook.")
|
||||
.arg(g_config->c_warningTextStyle);
|
||||
QString infoText = tr("<span style=\"%1\">INFO</span>: The folder chosen seems to be a root "
|
||||
"folder of a notebook created by VNote before. "
|
||||
"VNote will try to import it by reading the configuration file.")
|
||||
.arg("font-weight:bold;");
|
||||
bool pathOk = false;
|
||||
bool configExist = false;
|
||||
bool showWarnLabel = false;
|
||||
|
||||
// User has input some texts.
|
||||
if (pathEdit->isModified()) {
|
||||
m_manualPath = true;
|
||||
}
|
||||
|
||||
if (m_nameEdit->isModified()) {
|
||||
m_manualName = true;
|
||||
}
|
||||
|
||||
if (autoComplete()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString path = pathEdit->text();
|
||||
if (!path.isEmpty()) {
|
||||
if (QFileInfo::exists(path)) {
|
||||
QDir dir(path);
|
||||
QStringList files = dir.entryList(QDir::NoDotAndDotDot | QDir::AllEntries | QDir::Hidden);
|
||||
if (files.isEmpty()) {
|
||||
pathOk = true;
|
||||
} else {
|
||||
// Folder is not empty.
|
||||
configExist = VConfigManager::directoryConfigExist(path);
|
||||
showWarnLabel = true;
|
||||
}
|
||||
} else {
|
||||
pathOk = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (configExist) {
|
||||
pathOk = true;
|
||||
m_warnLabel->setText(infoText);
|
||||
} else {
|
||||
m_warnLabel->setText(warnText);
|
||||
}
|
||||
|
||||
// Try to validate if this is a legal path on the OS.
|
||||
if (pathOk) {
|
||||
pathOk = VUtils::checkPathLegal(path);
|
||||
if (!pathOk) {
|
||||
showWarnLabel = true;
|
||||
QString tmp = tr("<span style=\"%1\">WARNING</span>: The path seems to be illegal. "
|
||||
"Please choose another one.")
|
||||
.arg(g_config->c_warningTextStyle);
|
||||
m_warnLabel->setText(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
if (pathOk) {
|
||||
// Check if this path has been in VNote.
|
||||
int idx = -1;
|
||||
for (idx = 0; idx < m_notebooks.size(); ++idx) {
|
||||
if (VUtils::equalPath(m_notebooks[idx]->getPath(), path)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx < m_notebooks.size()) {
|
||||
pathOk = false;
|
||||
showWarnLabel = true;
|
||||
QString existText = tr("<span style=\"%1\">WARNING</span>: The folder chosen has already been a root folder "
|
||||
"of existing notebook <span style=\"%2\">%3</span> in VNote.")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(m_notebooks[idx]->getName());
|
||||
m_warnLabel->setText(existText);
|
||||
}
|
||||
}
|
||||
|
||||
QString name = m_nameEdit->getEvaluatedText();
|
||||
bool nameOk = !name.isEmpty();
|
||||
if (pathOk && nameOk) {
|
||||
// Check if the name conflicts with existing notebook name.
|
||||
// Case-insensitive.
|
||||
int idx = -1;
|
||||
for (idx = 0; idx < m_notebooks.size(); ++idx) {
|
||||
if (m_notebooks[idx]->getName().toLower() == name.toLower()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QString warnText;
|
||||
if (idx < m_notebooks.size()) {
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
} else if (!VUtils::checkFileNameLegal(name)) {
|
||||
// Check if evaluated name contains illegal characters.
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name <span style=\"%2\">%3</span> contains illegal characters "
|
||||
"(after magic word evaluation).")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
}
|
||||
|
||||
if (!nameOk) {
|
||||
showWarnLabel = true;
|
||||
m_warnLabel->setText(warnText);
|
||||
}
|
||||
}
|
||||
|
||||
m_warnLabel->setVisible(showWarnLabel);
|
||||
m_importNotebook = configExist;
|
||||
m_imageFolderEdit->setEnabled(!m_importNotebook);
|
||||
m_attachmentFolderEdit->setEnabled(!m_importNotebook);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setEnabled(nameOk && pathOk);
|
||||
}
|
||||
|
||||
bool VNewNotebookDialog::autoComplete()
|
||||
{
|
||||
QString nameText = m_nameEdit->getEvaluatedText();
|
||||
|
||||
if (m_manualPath) {
|
||||
if (m_manualName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the name according to user-chosen path.
|
||||
QString pathText = pathEdit->text();
|
||||
if (!pathText.isEmpty()) {
|
||||
QString autoName = VUtils::directoryNameFromPath(pathText);
|
||||
if (autoName != nameText) {
|
||||
m_nameEdit->setText(autoName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QString vnoteFolder = g_config->getVnoteNotebookFolderPath();
|
||||
QString pathText = pathEdit->text();
|
||||
if (!pathText.isEmpty()
|
||||
&& !VUtils::equalPath(vnoteFolder, VUtils::basePathFromPath(pathText))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
if (nameText.isEmpty()) {
|
||||
if (m_manualName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get a folder name under vnoteFolder and set it as the name of the notebook.
|
||||
QString name = "vnotebook";
|
||||
name = VUtils::getDirNameWithSequence(vnoteFolder, name);
|
||||
m_nameEdit->setText(name);
|
||||
ret = true;
|
||||
} else {
|
||||
// Use the name as the folder name under vnoteFolder.
|
||||
QString autoPath = QDir::cleanPath(QDir(vnoteFolder).filePath(nameText));
|
||||
if (autoPath != pathText) {
|
||||
pathEdit->setText(autoPath);
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
#ifndef VNEWNOTEBOOKDIALOG_H
|
||||
#define VNEWNOTEBOOKDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QVector>
|
||||
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class VLineEdit;
|
||||
class QPushButton;
|
||||
class QDialogButtonBox;
|
||||
class VNotebook;
|
||||
|
||||
class VNewNotebookDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VNewNotebookDialog(const QString &title, const QString &info, const QString &defaultName,
|
||||
const QString &defaultPath, const QVector<VNotebook *> &p_notebooks,
|
||||
QWidget *parent = 0);
|
||||
|
||||
QString getNameInput() const;
|
||||
|
||||
QString getPathInput() const;
|
||||
|
||||
// Whether import existing notebook by reading the config file.
|
||||
bool isImportExistingNotebook() const;
|
||||
|
||||
// Get the custom image folder for this notebook.
|
||||
// Empty string indicates using global config.
|
||||
QString getImageFolder() const;
|
||||
|
||||
// Get the custom attachment folder for this notebook.
|
||||
// Empty string indicates using global config.
|
||||
QString getAttachmentFolder() const;
|
||||
|
||||
private slots:
|
||||
void handleBrowseBtnClicked();
|
||||
|
||||
// Handle the change of the name and path input.
|
||||
void handleInputChanged();
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
void setupUI(const QString &p_title, const QString &p_info);
|
||||
|
||||
// Should be called before enableOkButton() when path changed.
|
||||
void checkRootFolder(const QString &p_path);
|
||||
|
||||
// Try to figure out name and path.
|
||||
// Returns true if name or path is modified.
|
||||
bool autoComplete();
|
||||
|
||||
VLineEdit *m_nameEdit;
|
||||
QLineEdit *pathEdit;
|
||||
QPushButton *browseBtn;
|
||||
QLabel *m_warnLabel;
|
||||
QLineEdit *m_imageFolderEdit;
|
||||
QLineEdit *m_attachmentFolderEdit;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
|
||||
QString defaultName;
|
||||
QString defaultPath;
|
||||
|
||||
// Whether import existing notebook config file.
|
||||
bool m_importNotebook;
|
||||
|
||||
// True if user has change the content of the path edit.
|
||||
bool m_manualPath;
|
||||
|
||||
// True if user has change the content of the name edit.
|
||||
bool m_manualName;
|
||||
|
||||
// All existing notebooks in VNote.
|
||||
const QVector<VNotebook *> &m_notebooks;
|
||||
};
|
||||
|
||||
#endif // VNEWNOTEBOOKDIALOG_H
|
@ -1,167 +0,0 @@
|
||||
#include <QtWidgets>
|
||||
#include "vnotebookinfodialog.h"
|
||||
#include "vnotebook.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "vlineedit.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
VNotebookInfoDialog::VNotebookInfoDialog(const QString &p_title,
|
||||
const QString &p_info,
|
||||
const VNotebook *p_notebook,
|
||||
const QVector<VNotebook *> &p_notebooks,
|
||||
QWidget *p_parent)
|
||||
: QDialog(p_parent), m_notebook(p_notebook),
|
||||
m_notebooks(p_notebooks)
|
||||
{
|
||||
setupUI(p_title, p_info);
|
||||
|
||||
connect(m_nameEdit, &QLineEdit::textChanged,
|
||||
this, &VNotebookInfoDialog::handleInputChanged);
|
||||
|
||||
handleInputChanged();
|
||||
}
|
||||
|
||||
void VNotebookInfoDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
{
|
||||
QLabel *infoLabel = NULL;
|
||||
if (!p_info.isEmpty()) {
|
||||
infoLabel = new QLabel(p_info);
|
||||
}
|
||||
|
||||
m_nameEdit = new VLineEdit(m_notebook->getName());
|
||||
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
|
||||
m_nameEdit);
|
||||
m_nameEdit->setValidator(validator);
|
||||
m_nameEdit->selectAll();
|
||||
|
||||
m_pathEdit = new QLineEdit(m_notebook->getPath());
|
||||
m_pathEdit->setReadOnly(true);
|
||||
|
||||
// Image folder.
|
||||
m_imageFolderEdit = new QLineEdit(m_notebook->getImageFolderConfig());
|
||||
m_imageFolderEdit->setPlaceholderText(tr("Use global configuration (%1)")
|
||||
.arg(g_config->getImageFolder()));
|
||||
m_imageFolderEdit->setToolTip(tr("Set the name of the folder to hold images of all the notes in this notebook "
|
||||
"(empty to use global configuration)"));
|
||||
validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_imageFolderEdit);
|
||||
m_imageFolderEdit->setValidator(validator);
|
||||
|
||||
// Attachment folder.
|
||||
Q_ASSERT(!m_notebook->getAttachmentFolder().isEmpty());
|
||||
m_attachmentFolderEdit = new QLineEdit(m_notebook->getAttachmentFolder());
|
||||
m_attachmentFolderEdit->setPlaceholderText(tr("Use global configuration (%1)")
|
||||
.arg(g_config->getAttachmentFolder()));
|
||||
m_attachmentFolderEdit->setToolTip(tr("The folder to hold attachments of all the notes in this notebook"));
|
||||
m_attachmentFolderEdit->setReadOnly(true);
|
||||
|
||||
// Recycle bin folder.
|
||||
QLineEdit *recycleBinFolderEdit = new QLineEdit(m_notebook->getRecycleBinFolder());
|
||||
recycleBinFolderEdit->setReadOnly(true);
|
||||
recycleBinFolderEdit->setToolTip(tr("The folder to hold deleted files from within VNote of all the notes in this notebook"));
|
||||
|
||||
// Created time.
|
||||
QString createdTimeStr = VUtils::displayDateTime(const_cast<VNotebook *>(m_notebook)->getCreatedTimeUtc().toLocalTime());
|
||||
QLabel *createdTimeLabel = new QLabel(createdTimeStr);
|
||||
|
||||
QFormLayout *topLayout = new QFormLayout();
|
||||
topLayout->addRow(tr("Notebook &name:"), m_nameEdit);
|
||||
topLayout->addRow(tr("Notebook &root folder:"), m_pathEdit);
|
||||
topLayout->addRow(tr("&Image folder:"), m_imageFolderEdit);
|
||||
topLayout->addRow(tr("Attachment folder:"), m_attachmentFolderEdit);
|
||||
topLayout->addRow(tr("Recycle bin folder:"), recycleBinFolderEdit);
|
||||
topLayout->addRow(tr("Created time:"), createdTimeLabel);
|
||||
|
||||
// Warning label.
|
||||
m_warnLabel = new QLabel();
|
||||
m_warnLabel->setWordWrap(true);
|
||||
m_warnLabel->hide();
|
||||
|
||||
// Ok is the default button.
|
||||
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setProperty("SpecialBtn", true);
|
||||
m_pathEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
if (infoLabel) {
|
||||
mainLayout->addWidget(infoLabel);
|
||||
}
|
||||
|
||||
mainLayout->addLayout(topLayout);
|
||||
mainLayout->addWidget(m_warnLabel);
|
||||
mainLayout->addWidget(m_btnBox);
|
||||
|
||||
setLayout(mainLayout);
|
||||
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
setWindowTitle(p_title);
|
||||
}
|
||||
|
||||
void VNotebookInfoDialog::handleInputChanged()
|
||||
{
|
||||
QString name = m_nameEdit->getEvaluatedText();
|
||||
bool nameOk = !name.isEmpty();
|
||||
bool showWarnLabel = false;
|
||||
|
||||
if (nameOk && name != m_notebook->getName()) {
|
||||
// Check if the name conflicts with existing notebook name.
|
||||
// Case-insensitive.
|
||||
int idx = -1;
|
||||
for (idx = 0; idx < m_notebooks.size(); ++idx) {
|
||||
if (m_notebooks[idx]->getName().toLower() == name.toLower()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QString warnText;
|
||||
if (idx < m_notebooks.size() && m_notebooks[idx] != m_notebook) {
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
} else if (!VUtils::checkFileNameLegal(name)) {
|
||||
// Check if evaluated name contains illegal characters.
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name <span style=\"%2\">%3</span> contains illegal characters "
|
||||
"(after magic word evaluation).")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
}
|
||||
|
||||
if (!nameOk) {
|
||||
showWarnLabel = true;
|
||||
m_warnLabel->setText(warnText);
|
||||
}
|
||||
}
|
||||
|
||||
m_warnLabel->setVisible(showWarnLabel);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setEnabled(nameOk);
|
||||
}
|
||||
|
||||
QString VNotebookInfoDialog::getName() const
|
||||
{
|
||||
return m_nameEdit->getEvaluatedText();
|
||||
}
|
||||
|
||||
QString VNotebookInfoDialog::getImageFolder() const
|
||||
{
|
||||
return m_imageFolderEdit->text();
|
||||
}
|
||||
|
||||
void VNotebookInfoDialog::showEvent(QShowEvent *p_event)
|
||||
{
|
||||
m_nameEdit->setFocus();
|
||||
QDialog::showEvent(p_event);
|
||||
}
|
||||
|
@ -1,52 +0,0 @@
|
||||
#ifndef VNOTEBOOKINFODIALOG_H
|
||||
#define VNOTEBOOKINFODIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QVector>
|
||||
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class VLineEdit;
|
||||
class QDialogButtonBox;
|
||||
class QString;
|
||||
class VNotebook;
|
||||
|
||||
class VNotebookInfoDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VNotebookInfoDialog(const QString &p_title,
|
||||
const QString &p_info,
|
||||
const VNotebook *p_notebook,
|
||||
const QVector<VNotebook *> &p_notebooks,
|
||||
QWidget *p_parent = 0);
|
||||
|
||||
QString getName() const;
|
||||
|
||||
// Get the custom image folder for this notebook.
|
||||
// Empty string indicates using global config.
|
||||
QString getImageFolder() const;
|
||||
|
||||
private slots:
|
||||
// Handle the change of the name and path input.
|
||||
void handleInputChanged();
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
void setupUI(const QString &p_title, const QString &p_info);
|
||||
|
||||
const VNotebook *m_notebook;
|
||||
|
||||
VLineEdit *m_nameEdit;
|
||||
QLineEdit *m_pathEdit;
|
||||
QLineEdit *m_imageFolderEdit;
|
||||
// Read-only.
|
||||
QLineEdit *m_attachmentFolderEdit;
|
||||
QLabel *m_warnLabel;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
const QVector<VNotebook *> &m_notebooks;
|
||||
};
|
||||
|
||||
#endif // VNOTEBOOKINFODIALOG_H
|
@ -1,71 +0,0 @@
|
||||
#include "vorphanfileinfodialog.h"
|
||||
|
||||
#include <QtWidgets>
|
||||
#include "vorphanfile.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
VOrphanFileInfoDialog::VOrphanFileInfoDialog(const VOrphanFile *p_file, QWidget *p_parent)
|
||||
: QDialog(p_parent), m_file(p_file)
|
||||
{
|
||||
setupUI();
|
||||
|
||||
connect(m_imageFolderEdit, &QLineEdit::textChanged,
|
||||
this, &VOrphanFileInfoDialog::handleInputChanged);
|
||||
|
||||
handleInputChanged();
|
||||
}
|
||||
|
||||
void VOrphanFileInfoDialog::setupUI()
|
||||
{
|
||||
QFormLayout *topLayout = new QFormLayout();
|
||||
|
||||
QLabel *fileLabel = new QLabel(m_file->fetchPath());
|
||||
topLayout->addRow(tr("File:"), fileLabel);
|
||||
|
||||
m_imageFolderEdit = new QLineEdit(m_file->getImageFolder());
|
||||
m_imageFolderEdit->setPlaceholderText(tr("Use global configuration (%1)")
|
||||
.arg(g_config->getImageFolderExt()));
|
||||
QString imgFolderTip = tr("Set the path of the image folder to store images "
|
||||
"of this file.\nIf absolute path is used, "
|
||||
"VNote will not manage those images."
|
||||
"(empty to use global configuration)");
|
||||
m_imageFolderEdit->setToolTip(imgFolderTip);
|
||||
topLayout->addRow(tr("&Image folder:"), m_imageFolderEdit);
|
||||
|
||||
// Ok is the default button.
|
||||
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setProperty("SpecialBtn", true);
|
||||
m_imageFolderEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
mainLayout->addLayout(topLayout);
|
||||
mainLayout->addWidget(m_btnBox);
|
||||
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
|
||||
setLayout(mainLayout);
|
||||
setWindowTitle(tr("External File Information"));
|
||||
}
|
||||
|
||||
QString VOrphanFileInfoDialog::getImageFolder() const
|
||||
{
|
||||
return m_imageFolderEdit->text();
|
||||
}
|
||||
|
||||
void VOrphanFileInfoDialog::handleInputChanged()
|
||||
{
|
||||
bool ok = false;
|
||||
QString imgFolder = m_imageFolderEdit->text();
|
||||
if (imgFolder.isEmpty() || VUtils::checkPathLegal(imgFolder)) {
|
||||
ok = true;
|
||||
}
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setEnabled(ok);
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
#ifndef VORPHANFILEINFODIALOG_H
|
||||
#define VORPHANFILEINFODIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class VOrphanFile;
|
||||
class QDialogButtonBox;
|
||||
class QLineEdit;
|
||||
|
||||
class VOrphanFileInfoDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
VOrphanFileInfoDialog(const VOrphanFile *p_file, QWidget *p_parent = 0);
|
||||
|
||||
// Get the custom image folder for this external file.
|
||||
// Empty string indicates using global config.
|
||||
QString getImageFolder() const;
|
||||
|
||||
private slots:
|
||||
// Handle the change of the image folder input.
|
||||
void handleInputChanged();
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
const VOrphanFile *m_file;
|
||||
|
||||
QDialogButtonBox *m_btnBox;
|
||||
QLineEdit *m_imageFolderEdit;
|
||||
};
|
||||
|
||||
#endif // VORPHANFILEINFODIALOG_H
|
@ -1,54 +0,0 @@
|
||||
#include <QtWidgets>
|
||||
#include "vselectdialog.h"
|
||||
|
||||
VSelectDialog::VSelectDialog(const QString &p_title, QWidget *p_parent)
|
||||
: QDialog(p_parent), m_choice(-1)
|
||||
{
|
||||
setupUI(p_title);
|
||||
}
|
||||
|
||||
void VSelectDialog::setupUI(const QString &p_title)
|
||||
{
|
||||
m_mainLayout = new QVBoxLayout();
|
||||
|
||||
QPushButton *cancelBtn = new QPushButton(tr("Cancel"));
|
||||
cancelBtn->setProperty("SelectionBtn", true);
|
||||
connect(cancelBtn, &QPushButton::clicked,
|
||||
this, &VSelectDialog::reject);
|
||||
m_mainLayout->addWidget(cancelBtn);
|
||||
|
||||
setLayout(m_mainLayout);
|
||||
m_mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
setWindowTitle(p_title);
|
||||
}
|
||||
|
||||
void VSelectDialog::addSelection(const QString &p_selectStr, int p_selectID)
|
||||
{
|
||||
Q_ASSERT(p_selectID >= 0);
|
||||
|
||||
QPushButton *btn = new QPushButton(p_selectStr);
|
||||
btn->setProperty("SelectionBtn", true);
|
||||
if (m_selections.isEmpty()) {
|
||||
btn->setDefault(true);
|
||||
btn->setAutoDefault(true);
|
||||
}
|
||||
connect(btn, &QPushButton::clicked,
|
||||
this, &VSelectDialog::selectionChosen);
|
||||
m_selections.insert(btn, p_selectID);
|
||||
m_mainLayout->insertWidget(m_selections.size() - 1, btn);
|
||||
}
|
||||
|
||||
void VSelectDialog::selectionChosen()
|
||||
{
|
||||
QPushButton *btn = dynamic_cast<QPushButton *>(sender());
|
||||
Q_ASSERT(btn);
|
||||
auto it = m_selections.find(btn);
|
||||
Q_ASSERT(it != m_selections.end());
|
||||
m_choice = *it;
|
||||
accept();
|
||||
}
|
||||
|
||||
int VSelectDialog::getSelection() const
|
||||
{
|
||||
return m_choice;
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
#ifndef VSELECTDIALOG_H
|
||||
#define VSELECTDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QMap>
|
||||
|
||||
class QPushButton;
|
||||
class QVBoxLayout;
|
||||
class QMouseEvent;
|
||||
|
||||
class VSelectDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VSelectDialog(const QString &p_title, QWidget *p_parent = 0);
|
||||
|
||||
void addSelection(const QString &p_selectStr, int p_selectID);
|
||||
int getSelection() const;
|
||||
|
||||
private slots:
|
||||
void selectionChosen();
|
||||
|
||||
private:
|
||||
void setupUI(const QString &p_title);
|
||||
|
||||
QVBoxLayout *m_mainLayout;
|
||||
int m_choice;
|
||||
QMap<QPushButton *, int> m_selections;
|
||||
};
|
||||
|
||||
#endif // VSELECTDIALOG_H
|
@ -1,810 +0,0 @@
|
||||
#include "vsettingsdialog.h"
|
||||
#include <QtWidgets>
|
||||
#include <QRegExp>
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vconstants.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
VSettingsDialog::VSettingsDialog(QWidget *p_parent)
|
||||
: QDialog(p_parent)
|
||||
{
|
||||
m_tabList = new QListWidget(this);
|
||||
m_tabList->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
|
||||
|
||||
m_tabs = new QStackedLayout();
|
||||
|
||||
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(m_btnBox, &QDialogButtonBox::accepted, this, &VSettingsDialog::saveConfiguration);
|
||||
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
m_btnBox->button(QDialogButtonBox::Ok)->setProperty("SpecialBtn", true);
|
||||
|
||||
QHBoxLayout *tabLayout = new QHBoxLayout();
|
||||
tabLayout->addWidget(m_tabList);
|
||||
tabLayout->addLayout(m_tabs);
|
||||
tabLayout->setContentsMargins(0, 0, 0, 0);
|
||||
tabLayout->setSpacing(0);
|
||||
tabLayout->setStretch(0, 0);
|
||||
tabLayout->setStretch(1, 5);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
mainLayout->addLayout(tabLayout);
|
||||
mainLayout->addWidget(m_btnBox);
|
||||
setLayout(mainLayout);
|
||||
|
||||
setWindowTitle(tr("Settings"));
|
||||
|
||||
// Add tabs.
|
||||
addTab(new VGeneralTab(), tr("General"));
|
||||
addTab(new VReadEditTab(), tr("Read/Edit"));
|
||||
addTab(new VNoteManagementTab(), tr("Note Management"));
|
||||
addTab(new VMarkdownTab(), tr("Markdown"));
|
||||
|
||||
m_tabList->setMaximumWidth(m_tabList->sizeHintForColumn(0) + 5);
|
||||
|
||||
connect(m_tabList, &QListWidget::currentItemChanged,
|
||||
this, [this](QListWidgetItem *p_cur, QListWidgetItem *p_pre) {
|
||||
Q_UNUSED(p_pre);
|
||||
Q_ASSERT(p_cur);
|
||||
int idx = p_cur->data(Qt::UserRole).toInt();
|
||||
Q_ASSERT(idx >= 0);
|
||||
m_tabs->setCurrentWidget(m_tabs->widget(idx));
|
||||
});
|
||||
|
||||
m_tabList->setCurrentRow(0);
|
||||
|
||||
loadConfiguration();
|
||||
}
|
||||
|
||||
void VSettingsDialog::addTab(QWidget *p_widget, const QString &p_label)
|
||||
{
|
||||
int idx = m_tabs->addWidget(p_widget);
|
||||
QListWidgetItem *item = new QListWidgetItem(p_label, m_tabList);
|
||||
item->setData(Qt::UserRole, idx);
|
||||
}
|
||||
|
||||
void VSettingsDialog::loadConfiguration()
|
||||
{
|
||||
// General Tab.
|
||||
{
|
||||
VGeneralTab *generalTab = dynamic_cast<VGeneralTab *>(m_tabs->widget(0));
|
||||
Q_ASSERT(generalTab);
|
||||
if (!generalTab->loadConfiguration()) {
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
// Read/Edit Tab.
|
||||
{
|
||||
VReadEditTab *readEditTab = dynamic_cast<VReadEditTab *>(m_tabs->widget(1));
|
||||
Q_ASSERT(readEditTab);
|
||||
if (!readEditTab->loadConfiguration()) {
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
// Note Management Tab.
|
||||
{
|
||||
VNoteManagementTab *noteManagementTab = dynamic_cast<VNoteManagementTab *>(m_tabs->widget(2));
|
||||
Q_ASSERT(noteManagementTab);
|
||||
if (!noteManagementTab->loadConfiguration()) {
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
// Markdown Tab.
|
||||
{
|
||||
VMarkdownTab *markdownTab = dynamic_cast<VMarkdownTab *>(m_tabs->widget(3));
|
||||
Q_ASSERT(markdownTab);
|
||||
if (!markdownTab->loadConfiguration()) {
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
err:
|
||||
VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
|
||||
tr("Fail to load configuration."), "",
|
||||
QMessageBox::Ok, QMessageBox::Ok, NULL);
|
||||
QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void VSettingsDialog::saveConfiguration()
|
||||
{
|
||||
// General Tab.
|
||||
{
|
||||
VGeneralTab *generalTab = dynamic_cast<VGeneralTab *>(m_tabs->widget(0));
|
||||
Q_ASSERT(generalTab);
|
||||
if (!generalTab->saveConfiguration()) {
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
// Read/Edit Tab.
|
||||
{
|
||||
VReadEditTab *readEditTab = dynamic_cast<VReadEditTab *>(m_tabs->widget(1));
|
||||
Q_ASSERT(readEditTab);
|
||||
if (!readEditTab->saveConfiguration()) {
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
// Note Management Tab.
|
||||
{
|
||||
VNoteManagementTab *noteManagementTab = dynamic_cast<VNoteManagementTab *>(m_tabs->widget(2));
|
||||
Q_ASSERT(noteManagementTab);
|
||||
if (!noteManagementTab->saveConfiguration()) {
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
// Markdown Tab.
|
||||
{
|
||||
VMarkdownTab *markdownTab = dynamic_cast<VMarkdownTab *>(m_tabs->widget(3));
|
||||
Q_ASSERT(markdownTab);
|
||||
if (!markdownTab->saveConfiguration()) {
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
accept();
|
||||
return;
|
||||
err:
|
||||
VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
|
||||
tr("Fail to save configuration. Please try it again."), "",
|
||||
QMessageBox::Ok, QMessageBox::Ok, NULL);
|
||||
}
|
||||
|
||||
const QVector<QString> VGeneralTab::c_availableLangs = { "System", "English", "Chinese" };
|
||||
|
||||
VGeneralTab::VGeneralTab(QWidget *p_parent)
|
||||
: QWidget(p_parent)
|
||||
{
|
||||
// Language combo.
|
||||
m_langCombo = VUtils::getComboBox(this);
|
||||
m_langCombo->setToolTip(tr("Choose the language of VNote interface"));
|
||||
m_langCombo->addItem(tr("System"), "System");
|
||||
auto langs = VUtils::getAvailableLanguages();
|
||||
for (auto const &lang : langs) {
|
||||
m_langCombo->addItem(lang.second, lang.first);
|
||||
}
|
||||
|
||||
// System tray checkbox.
|
||||
m_systemTray = new QCheckBox(tr("System tray"), this);
|
||||
m_systemTray->setToolTip(tr("Minimized to the system tray after closing VNote"
|
||||
" (not supported in macOS)"));
|
||||
#if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
|
||||
// Do not support minimized to tray on macOS.
|
||||
m_systemTray->setEnabled(false);
|
||||
#endif
|
||||
|
||||
// Startup pages.
|
||||
QLayout *startupLayout = setupStartupPagesLayout();
|
||||
|
||||
QFormLayout *optionLayout = new QFormLayout();
|
||||
optionLayout->addRow(tr("Language:"), m_langCombo);
|
||||
optionLayout->addRow(m_systemTray);
|
||||
optionLayout->addRow(tr("Startup pages:"), startupLayout);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
mainLayout->addLayout(optionLayout);
|
||||
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
QLayout *VGeneralTab::setupStartupPagesLayout()
|
||||
{
|
||||
m_startupPageTypeCombo = VUtils::getComboBox(this);
|
||||
m_startupPageTypeCombo->setToolTip(tr("Restore tabs or open specific notes on startup"));
|
||||
m_startupPageTypeCombo->addItem(tr("None"), (int)StartupPageType::None);
|
||||
m_startupPageTypeCombo->addItem(tr("Continue where you left off"), (int)StartupPageType::ContinueLeftOff);
|
||||
m_startupPageTypeCombo->addItem(tr("Open specific pages"), (int)StartupPageType::SpecificPages);
|
||||
connect(m_startupPageTypeCombo, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated),
|
||||
this, [this](int p_index) {
|
||||
int type = m_startupPageTypeCombo->itemData(p_index).toInt();
|
||||
bool pagesEditVisible = type == (int)StartupPageType::SpecificPages;
|
||||
m_startupPagesEdit->setVisible(pagesEditVisible);
|
||||
m_startupPagesAddBtn->setVisible(pagesEditVisible);
|
||||
});
|
||||
|
||||
m_startupPagesEdit = new QPlainTextEdit(this);
|
||||
m_startupPagesEdit->setProperty("LineEdit", true);
|
||||
m_startupPagesEdit->setToolTip(tr("Absolute path of the notes to open on startup (one note per line)"));
|
||||
|
||||
m_startupPagesAddBtn = new QPushButton(tr("Browse"), this);
|
||||
m_startupPagesAddBtn->setToolTip(tr("Select files to add as startup pages"));
|
||||
connect(m_startupPagesAddBtn, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
static QString lastPath = QDir::homePath();
|
||||
QStringList files = QFileDialog::getOpenFileNames(this,
|
||||
tr("Select Files As Startup Pages"),
|
||||
lastPath);
|
||||
if (files.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update lastPath
|
||||
lastPath = QFileInfo(files[0]).path();
|
||||
|
||||
m_startupPagesEdit->appendPlainText(files.join("\n"));
|
||||
});
|
||||
|
||||
QHBoxLayout *startupPagesBtnLayout = new QHBoxLayout();
|
||||
startupPagesBtnLayout->addStretch();
|
||||
startupPagesBtnLayout->addWidget(m_startupPagesAddBtn);
|
||||
|
||||
QVBoxLayout *startupPagesLayout = new QVBoxLayout();
|
||||
startupPagesLayout->addWidget(m_startupPagesEdit);
|
||||
startupPagesLayout->addLayout(startupPagesBtnLayout);
|
||||
|
||||
QVBoxLayout *startupLayout = new QVBoxLayout();
|
||||
startupLayout->addWidget(m_startupPageTypeCombo);
|
||||
startupLayout->addLayout(startupPagesLayout);
|
||||
|
||||
m_startupPagesEdit->hide();
|
||||
m_startupPagesAddBtn->hide();
|
||||
|
||||
return startupLayout;
|
||||
}
|
||||
|
||||
bool VGeneralTab::loadConfiguration()
|
||||
{
|
||||
if (!loadLanguage()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadSystemTray()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadStartupPageType()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VGeneralTab::saveConfiguration()
|
||||
{
|
||||
if (!saveLanguage()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!saveSystemTray()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!saveStartupPageType()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VGeneralTab::loadLanguage()
|
||||
{
|
||||
QString lang = g_config->getLanguage();
|
||||
if (lang.isNull()) {
|
||||
return false;
|
||||
} else if (lang == "System") {
|
||||
m_langCombo->setCurrentIndex(0);
|
||||
return true;
|
||||
}
|
||||
bool found = false;
|
||||
// lang is the value, not name.
|
||||
for (int i = 0; i < m_langCombo->count(); ++i) {
|
||||
if (m_langCombo->itemData(i).toString() == lang) {
|
||||
found = true;
|
||||
m_langCombo->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
qWarning() << "invalid language configuration (using default value)";
|
||||
m_langCombo->setCurrentIndex(0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VGeneralTab::saveLanguage()
|
||||
{
|
||||
QString curLang = m_langCombo->currentData().toString();
|
||||
g_config->setLanguage(curLang);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VGeneralTab::loadSystemTray()
|
||||
{
|
||||
m_systemTray->setChecked(g_config->getMinimizeToStystemTray() != 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VGeneralTab::saveSystemTray()
|
||||
{
|
||||
if (m_systemTray->isEnabled()) {
|
||||
g_config->setMinimizeToSystemTray(m_systemTray->isChecked() ? 1 : 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VGeneralTab::loadStartupPageType()
|
||||
{
|
||||
StartupPageType type = g_config->getStartupPageType();
|
||||
bool found = false;
|
||||
for (int i = 0; i < m_startupPageTypeCombo->count(); ++i) {
|
||||
if (m_startupPageTypeCombo->itemData(i).toInt() == (int)type) {
|
||||
found = true;
|
||||
m_startupPageTypeCombo->setCurrentIndex(i);
|
||||
}
|
||||
}
|
||||
|
||||
Q_ASSERT(found);
|
||||
|
||||
const QStringList &pages = g_config->getStartupPages();
|
||||
m_startupPagesEdit->setPlainText(pages.join("\n"));
|
||||
|
||||
bool pagesEditVisible = type == StartupPageType::SpecificPages;
|
||||
m_startupPagesEdit->setVisible(pagesEditVisible);
|
||||
m_startupPagesAddBtn->setVisible(pagesEditVisible);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VGeneralTab::saveStartupPageType()
|
||||
{
|
||||
StartupPageType type = (StartupPageType)m_startupPageTypeCombo->currentData().toInt();
|
||||
g_config->setStartupPageType(type);
|
||||
|
||||
if (type == StartupPageType::SpecificPages) {
|
||||
QStringList pages = m_startupPagesEdit->toPlainText().split("\n");
|
||||
g_config->setStartupPages(pages);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
VReadEditTab::VReadEditTab(QWidget *p_parent)
|
||||
: QWidget(p_parent)
|
||||
{
|
||||
m_readBox = new QGroupBox(tr("Read Mode (For Markdown Only)"));
|
||||
m_editBox = new QGroupBox(tr("Edit Mode"));
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
mainLayout->addWidget(m_readBox);
|
||||
mainLayout->addWidget(m_editBox);
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
bool VReadEditTab::loadConfiguration()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VReadEditTab::saveConfiguration()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
VNoteManagementTab::VNoteManagementTab(QWidget *p_parent)
|
||||
: QWidget(p_parent)
|
||||
{
|
||||
m_noteBox = new QGroupBox(tr("Notes"));
|
||||
m_externalBox = new QGroupBox(tr("External Files"));
|
||||
|
||||
// Note.
|
||||
// Image folder.
|
||||
m_customImageFolder = new QCheckBox(tr("Custom image folder"), this);
|
||||
m_customImageFolder->setToolTip(tr("Set the global name of the image folder to hold images "
|
||||
"of notes (restart VNote to make it work)"));
|
||||
connect(m_customImageFolder, &QCheckBox::stateChanged,
|
||||
this, &VNoteManagementTab::customImageFolderChanged);
|
||||
|
||||
m_imageFolderEdit = new QLineEdit(this);
|
||||
m_imageFolderEdit->setPlaceholderText(tr("Name of the image folder"));
|
||||
m_imageFolderEdit->setToolTip(m_customImageFolder->toolTip());
|
||||
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), this);
|
||||
m_imageFolderEdit->setValidator(validator);
|
||||
|
||||
QHBoxLayout *imageFolderLayout = new QHBoxLayout();
|
||||
imageFolderLayout->addWidget(m_customImageFolder);
|
||||
imageFolderLayout->addWidget(m_imageFolderEdit);
|
||||
|
||||
// Attachment folder.
|
||||
m_customAttachmentFolder = new QCheckBox(tr("Custom attachment folder"), this);
|
||||
m_customAttachmentFolder->setToolTip(tr("Set the global name of the attachment folder to hold attachments "
|
||||
"of notes (restart VNote to make it work)"));
|
||||
connect(m_customAttachmentFolder, &QCheckBox::stateChanged,
|
||||
this, &VNoteManagementTab::customAttachmentFolderChanged);
|
||||
|
||||
m_attachmentFolderEdit = new QLineEdit(this);
|
||||
m_attachmentFolderEdit->setPlaceholderText(tr("Name of the attachment folder"));
|
||||
m_attachmentFolderEdit->setToolTip(m_customAttachmentFolder->toolTip());
|
||||
validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), this);
|
||||
m_attachmentFolderEdit->setValidator(validator);
|
||||
|
||||
QHBoxLayout *attachmentFolderLayout = new QHBoxLayout();
|
||||
attachmentFolderLayout->addWidget(m_customAttachmentFolder);
|
||||
attachmentFolderLayout->addWidget(m_attachmentFolderEdit);
|
||||
|
||||
QFormLayout *noteLayout = new QFormLayout();
|
||||
noteLayout->addRow(imageFolderLayout);
|
||||
noteLayout->addRow(attachmentFolderLayout);
|
||||
m_noteBox->setLayout(noteLayout);
|
||||
|
||||
// External File.
|
||||
// Image folder.
|
||||
m_customImageFolderExt = new QCheckBox(tr("Custom image folder"), this);
|
||||
m_customImageFolderExt->setToolTip(tr("Set the path of the global image folder to hold images "
|
||||
"of external files (restart VNote to make it work).\nYou "
|
||||
"could use both absolute or relative path here. If "
|
||||
"absolute path is used, VNote will not manage\nthose images, "
|
||||
"so you need to clean up unused images manually."));
|
||||
connect(m_customImageFolderExt, &QCheckBox::stateChanged,
|
||||
this, &VNoteManagementTab::customImageFolderExtChanged);
|
||||
|
||||
m_imageFolderEditExt = new QLineEdit(this);
|
||||
m_imageFolderEditExt->setToolTip(m_customImageFolderExt->toolTip());
|
||||
m_imageFolderEditExt->setPlaceholderText(tr("Name of the image folder"));
|
||||
|
||||
QHBoxLayout *imageFolderExtLayout = new QHBoxLayout();
|
||||
imageFolderExtLayout->addWidget(m_customImageFolderExt);
|
||||
imageFolderExtLayout->addWidget(m_imageFolderEditExt);
|
||||
|
||||
QFormLayout *externalLayout = new QFormLayout();
|
||||
externalLayout->addRow(imageFolderExtLayout);
|
||||
m_externalBox->setLayout(externalLayout);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
mainLayout->addWidget(m_noteBox);
|
||||
mainLayout->addWidget(m_externalBox);
|
||||
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
bool VNoteManagementTab::loadConfiguration()
|
||||
{
|
||||
if (!loadImageFolder()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadAttachmentFolder()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadImageFolderExt()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VNoteManagementTab::saveConfiguration()
|
||||
{
|
||||
if (!saveImageFolder()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!saveAttachmentFolder()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!saveImageFolderExt()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VNoteManagementTab::loadImageFolder()
|
||||
{
|
||||
bool isCustom = g_config->isCustomImageFolder();
|
||||
|
||||
m_customImageFolder->setChecked(isCustom);
|
||||
m_imageFolderEdit->setText(g_config->getImageFolder());
|
||||
m_imageFolderEdit->setEnabled(isCustom);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VNoteManagementTab::saveImageFolder()
|
||||
{
|
||||
if (m_customImageFolder->isChecked()) {
|
||||
g_config->setImageFolder(m_imageFolderEdit->text());
|
||||
} else {
|
||||
g_config->setImageFolder("");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VNoteManagementTab::customImageFolderChanged(int p_state)
|
||||
{
|
||||
if (p_state == Qt::Checked) {
|
||||
m_imageFolderEdit->setEnabled(true);
|
||||
m_imageFolderEdit->selectAll();
|
||||
m_imageFolderEdit->setFocus();
|
||||
} else {
|
||||
m_imageFolderEdit->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool VNoteManagementTab::loadAttachmentFolder()
|
||||
{
|
||||
bool isCustom = g_config->isCustomAttachmentFolder();
|
||||
|
||||
m_customAttachmentFolder->setChecked(isCustom);
|
||||
m_attachmentFolderEdit->setText(g_config->getAttachmentFolder());
|
||||
m_attachmentFolderEdit->setEnabled(isCustom);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VNoteManagementTab::saveAttachmentFolder()
|
||||
{
|
||||
if (m_customAttachmentFolder->isChecked()) {
|
||||
g_config->setAttachmentFolder(m_attachmentFolderEdit->text());
|
||||
} else {
|
||||
g_config->setAttachmentFolder("");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VNoteManagementTab::customAttachmentFolderChanged(int p_state)
|
||||
{
|
||||
if (p_state == Qt::Checked) {
|
||||
m_attachmentFolderEdit->setEnabled(true);
|
||||
m_attachmentFolderEdit->selectAll();
|
||||
m_attachmentFolderEdit->setFocus();
|
||||
} else {
|
||||
m_attachmentFolderEdit->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool VNoteManagementTab::loadImageFolderExt()
|
||||
{
|
||||
bool isCustom = g_config->isCustomImageFolderExt();
|
||||
|
||||
m_customImageFolderExt->setChecked(isCustom);
|
||||
m_imageFolderEditExt->setText(g_config->getImageFolderExt());
|
||||
m_imageFolderEditExt->setEnabled(isCustom);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VNoteManagementTab::saveImageFolderExt()
|
||||
{
|
||||
if (m_customImageFolderExt->isChecked()) {
|
||||
g_config->setImageFolderExt(m_imageFolderEditExt->text());
|
||||
} else {
|
||||
g_config->setImageFolderExt("");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VNoteManagementTab::customImageFolderExtChanged(int p_state)
|
||||
{
|
||||
if (p_state == Qt::Checked) {
|
||||
m_imageFolderEditExt->setEnabled(true);
|
||||
m_imageFolderEditExt->selectAll();
|
||||
m_imageFolderEditExt->setFocus();
|
||||
} else {
|
||||
m_imageFolderEditExt->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
VMarkdownTab::VMarkdownTab(QWidget *p_parent)
|
||||
: QWidget(p_parent)
|
||||
{
|
||||
// Default note open mode.
|
||||
m_openModeCombo = VUtils::getComboBox();
|
||||
m_openModeCombo->setToolTip(tr("Default mode to open a note"));
|
||||
m_openModeCombo->addItem(tr("Read Mode"), (int)OpenFileMode::Read);
|
||||
m_openModeCombo->addItem(tr("Edit Mode"), (int)OpenFileMode::Edit);
|
||||
|
||||
// Heading sequence.
|
||||
m_headingSequenceTypeCombo = VUtils::getComboBox();
|
||||
m_headingSequenceTypeCombo->setToolTip(tr("Enable auto sequence for all headings (in the form like 1.2.3.4.)"));
|
||||
m_headingSequenceTypeCombo->addItem(tr("Disabled"), (int)HeadingSequenceType::Disabled);
|
||||
m_headingSequenceTypeCombo->addItem(tr("Enabled"), (int)HeadingSequenceType::Enabled);
|
||||
m_headingSequenceTypeCombo->addItem(tr("Enabled for notes only"), (int)HeadingSequenceType::EnabledNoteOnly);
|
||||
|
||||
m_headingSequenceLevelCombo = VUtils::getComboBox();
|
||||
m_headingSequenceLevelCombo->setToolTip(tr("Base level to start heading sequence"));
|
||||
m_headingSequenceLevelCombo->addItem(tr("1"), 1);
|
||||
m_headingSequenceLevelCombo->addItem(tr("2"), 2);
|
||||
m_headingSequenceLevelCombo->addItem(tr("3"), 3);
|
||||
m_headingSequenceLevelCombo->addItem(tr("4"), 4);
|
||||
m_headingSequenceLevelCombo->addItem(tr("5"), 5);
|
||||
m_headingSequenceLevelCombo->addItem(tr("6"), 6);
|
||||
m_headingSequenceLevelCombo->setEnabled(false);
|
||||
|
||||
connect(m_headingSequenceTypeCombo, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated),
|
||||
this, [this](int p_index){
|
||||
if (p_index > -1) {
|
||||
HeadingSequenceType type = (HeadingSequenceType)m_headingSequenceTypeCombo->itemData(p_index).toInt();
|
||||
m_headingSequenceLevelCombo->setEnabled(type != HeadingSequenceType::Disabled);
|
||||
}
|
||||
});
|
||||
|
||||
QHBoxLayout *headingSequenceLayout = new QHBoxLayout();
|
||||
headingSequenceLayout->addWidget(m_headingSequenceTypeCombo);
|
||||
headingSequenceLayout->addWidget(m_headingSequenceLevelCombo);
|
||||
|
||||
// Web Zoom Factor.
|
||||
m_customWebZoom = new QCheckBox(tr("Custom Web zoom factor"), this);
|
||||
m_customWebZoom->setToolTip(tr("Set the zoom factor of the Web page when reading"));
|
||||
m_webZoomFactorSpin = new QDoubleSpinBox(this);
|
||||
m_webZoomFactorSpin->setMaximum(c_webZoomFactorMax);
|
||||
m_webZoomFactorSpin->setMinimum(c_webZoomFactorMin);
|
||||
m_webZoomFactorSpin->setSingleStep(0.25);
|
||||
connect(m_customWebZoom, &QCheckBox::stateChanged,
|
||||
this, [this](int p_state){
|
||||
this->m_webZoomFactorSpin->setEnabled(p_state == Qt::Checked);
|
||||
});
|
||||
QHBoxLayout *zoomFactorLayout = new QHBoxLayout();
|
||||
zoomFactorLayout->addWidget(m_customWebZoom);
|
||||
zoomFactorLayout->addWidget(m_webZoomFactorSpin);
|
||||
|
||||
// Color column.
|
||||
m_colorColumnEdit = new QLineEdit();
|
||||
m_colorColumnEdit->setToolTip(tr("Specify the screen column in fenced code block "
|
||||
"which will be highlighted"));
|
||||
QValidator *validator = new QRegExpValidator(QRegExp("\\d+"), this);
|
||||
m_colorColumnEdit->setValidator(validator);
|
||||
|
||||
QLabel *colorColumnLabel = new QLabel(tr("Color column:"));
|
||||
colorColumnLabel->setToolTip(m_colorColumnEdit->toolTip());
|
||||
|
||||
QFormLayout *mainLayout = new QFormLayout();
|
||||
mainLayout->addRow(tr("Note open mode:"), m_openModeCombo);
|
||||
mainLayout->addRow(tr("Heading sequence:"), headingSequenceLayout);
|
||||
mainLayout->addRow(zoomFactorLayout);
|
||||
mainLayout->addRow(colorColumnLabel, m_colorColumnEdit);
|
||||
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
bool VMarkdownTab::loadConfiguration()
|
||||
{
|
||||
if (!loadOpenMode()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadHeadingSequence()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadWebZoomFactor()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadColorColumn()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VMarkdownTab::saveConfiguration()
|
||||
{
|
||||
if (!saveOpenMode()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!saveHeadingSequence()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!saveWebZoomFactor()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!saveColorColumn()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VMarkdownTab::loadOpenMode()
|
||||
{
|
||||
int mode = (int)g_config->getNoteOpenMode();
|
||||
bool found = false;
|
||||
for (int i = 0; i < m_openModeCombo->count(); ++i) {
|
||||
if (m_openModeCombo->itemData(i).toInt() == mode) {
|
||||
m_openModeCombo->setCurrentIndex(i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Q_ASSERT(found);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VMarkdownTab::saveOpenMode()
|
||||
{
|
||||
int mode = m_openModeCombo->currentData().toInt();
|
||||
g_config->setNoteOpenMode((OpenFileMode)mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VMarkdownTab::loadHeadingSequence()
|
||||
{
|
||||
HeadingSequenceType type = g_config->getHeadingSequenceType();
|
||||
int level = g_config->getHeadingSequenceBaseLevel();
|
||||
if (level < 1 || level > 6) {
|
||||
level = 1;
|
||||
}
|
||||
|
||||
int idx = m_headingSequenceTypeCombo->findData((int)type);
|
||||
Q_ASSERT(idx > -1);
|
||||
m_headingSequenceTypeCombo->setCurrentIndex(idx);
|
||||
m_headingSequenceLevelCombo->setCurrentIndex(level - 1);
|
||||
m_headingSequenceLevelCombo->setEnabled(type != HeadingSequenceType::Disabled);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VMarkdownTab::saveHeadingSequence()
|
||||
{
|
||||
QVariant typeData = m_headingSequenceTypeCombo->currentData();
|
||||
Q_ASSERT(typeData.isValid());
|
||||
g_config->setHeadingSequenceType((HeadingSequenceType)typeData.toInt());
|
||||
g_config->setHeadingSequenceBaseLevel(m_headingSequenceLevelCombo->currentData().toInt());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VMarkdownTab::loadWebZoomFactor()
|
||||
{
|
||||
qreal factor = g_config->getWebZoomFactor();
|
||||
bool customFactor = g_config->isCustomWebZoomFactor();
|
||||
if (customFactor) {
|
||||
if (factor < c_webZoomFactorMin || factor > c_webZoomFactorMax) {
|
||||
factor = 1;
|
||||
}
|
||||
m_customWebZoom->setChecked(true);
|
||||
m_webZoomFactorSpin->setValue(factor);
|
||||
} else {
|
||||
m_customWebZoom->setChecked(false);
|
||||
m_webZoomFactorSpin->setValue(factor);
|
||||
m_webZoomFactorSpin->setEnabled(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VMarkdownTab::saveWebZoomFactor()
|
||||
{
|
||||
if (m_customWebZoom->isChecked()) {
|
||||
g_config->setWebZoomFactor(m_webZoomFactorSpin->value());
|
||||
} else {
|
||||
g_config->setWebZoomFactor(-1);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VMarkdownTab::loadColorColumn()
|
||||
{
|
||||
int colorColumn = g_config->getColorColumn();
|
||||
m_colorColumnEdit->setText(QString::number(colorColumn <= 0 ? 0 : colorColumn));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VMarkdownTab::saveColorColumn()
|
||||
{
|
||||
bool ok = false;
|
||||
int colorColumn = m_colorColumnEdit->text().toInt(&ok);
|
||||
if (ok && colorColumn >= 0) {
|
||||
g_config->setColorColumn(colorColumn);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1,163 +0,0 @@
|
||||
#ifndef VSETTINGSDIALOG_H
|
||||
#define VSETTINGSDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QVector>
|
||||
#include <QString>
|
||||
|
||||
class QDialogButtonBox;
|
||||
class QComboBox;
|
||||
class QGroupBox;
|
||||
class QDoubleSpinBox;
|
||||
class QCheckBox;
|
||||
class QLineEdit;
|
||||
class QStackedLayout;
|
||||
class QListWidget;
|
||||
class QPlainTextEdit;
|
||||
class QVBoxLayout;
|
||||
|
||||
class VGeneralTab : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VGeneralTab(QWidget *p_parent = 0);
|
||||
bool loadConfiguration();
|
||||
bool saveConfiguration();
|
||||
|
||||
private:
|
||||
QLayout *setupStartupPagesLayout();
|
||||
|
||||
bool loadLanguage();
|
||||
bool saveLanguage();
|
||||
|
||||
bool loadSystemTray();
|
||||
bool saveSystemTray();
|
||||
|
||||
bool loadStartupPageType();
|
||||
bool saveStartupPageType();
|
||||
|
||||
// Language
|
||||
QComboBox *m_langCombo;
|
||||
|
||||
// System tray
|
||||
QCheckBox *m_systemTray;
|
||||
|
||||
// Startup page type.
|
||||
QComboBox *m_startupPageTypeCombo;
|
||||
|
||||
// Startup pages.
|
||||
QPlainTextEdit *m_startupPagesEdit;
|
||||
|
||||
// Startup pages add files button.
|
||||
QPushButton *m_startupPagesAddBtn;
|
||||
|
||||
static const QVector<QString> c_availableLangs;
|
||||
};
|
||||
|
||||
class VReadEditTab : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VReadEditTab(QWidget *p_parent = 0);
|
||||
bool loadConfiguration();
|
||||
bool saveConfiguration();
|
||||
|
||||
QGroupBox *m_readBox;
|
||||
QGroupBox *m_editBox;
|
||||
};
|
||||
|
||||
class VNoteManagementTab : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VNoteManagementTab(QWidget *p_parent = 0);
|
||||
bool loadConfiguration();
|
||||
bool saveConfiguration();
|
||||
|
||||
QGroupBox *m_noteBox;
|
||||
QGroupBox *m_externalBox;
|
||||
|
||||
// Image folder.
|
||||
QCheckBox *m_customImageFolder;
|
||||
QLineEdit *m_imageFolderEdit;
|
||||
|
||||
// Image folder of External File.
|
||||
QCheckBox *m_customImageFolderExt;
|
||||
QLineEdit *m_imageFolderEditExt;
|
||||
|
||||
// Attachment folder.
|
||||
QCheckBox *m_customAttachmentFolder;
|
||||
QLineEdit *m_attachmentFolderEdit;
|
||||
|
||||
private slots:
|
||||
void customImageFolderChanged(int p_state);
|
||||
void customImageFolderExtChanged(int p_state);
|
||||
void customAttachmentFolderChanged(int p_state);
|
||||
|
||||
private:
|
||||
bool loadImageFolder();
|
||||
bool saveImageFolder();
|
||||
|
||||
bool loadImageFolderExt();
|
||||
bool saveImageFolderExt();
|
||||
|
||||
bool loadAttachmentFolder();
|
||||
bool saveAttachmentFolder();
|
||||
};
|
||||
|
||||
class VMarkdownTab : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VMarkdownTab(QWidget *p_parent = 0);
|
||||
bool loadConfiguration();
|
||||
bool saveConfiguration();
|
||||
|
||||
// Default note open mode for markdown.
|
||||
QComboBox *m_openModeCombo;
|
||||
|
||||
// Whether enable heading sequence.
|
||||
QComboBox *m_headingSequenceTypeCombo;
|
||||
QComboBox *m_headingSequenceLevelCombo;
|
||||
|
||||
// Web zoom factor.
|
||||
QCheckBox *m_customWebZoom;
|
||||
QDoubleSpinBox *m_webZoomFactorSpin;
|
||||
|
||||
// Color column in code block.
|
||||
QLineEdit *m_colorColumnEdit;
|
||||
|
||||
private:
|
||||
bool loadOpenMode();
|
||||
bool saveOpenMode();
|
||||
|
||||
bool loadHeadingSequence();
|
||||
bool saveHeadingSequence();
|
||||
|
||||
bool loadWebZoomFactor();
|
||||
bool saveWebZoomFactor();
|
||||
|
||||
bool loadColorColumn();
|
||||
bool saveColorColumn();
|
||||
};
|
||||
|
||||
class VSettingsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VSettingsDialog(QWidget *p_parent = 0);
|
||||
|
||||
private slots:
|
||||
void saveConfiguration();
|
||||
|
||||
private:
|
||||
void loadConfiguration();
|
||||
|
||||
void addTab(QWidget *p_widget, const QString &p_label);
|
||||
|
||||
QStackedLayout *m_tabs;
|
||||
QListWidget *m_tabList;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
};
|
||||
|
||||
#endif // VSETTINGSDIALOG_H
|
@ -1,275 +0,0 @@
|
||||
#include "vsortdialog.h"
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
void VTreeWidget::dropEvent(QDropEvent *p_event)
|
||||
{
|
||||
QList<QTreeWidgetItem *> dragItems = selectedItems();
|
||||
|
||||
int first = -1, last = -1;
|
||||
QTreeWidgetItem *firstItem = NULL;
|
||||
for (int i = 0; i < dragItems.size(); ++i) {
|
||||
int row = indexFromItem(dragItems[i]).row();
|
||||
if (row > last) {
|
||||
last = row;
|
||||
}
|
||||
|
||||
if (first == -1 || row < first) {
|
||||
first = row;
|
||||
firstItem = dragItems[i];
|
||||
}
|
||||
}
|
||||
|
||||
Q_ASSERT(firstItem);
|
||||
|
||||
QTreeWidget::dropEvent(p_event);
|
||||
|
||||
int target = indexFromItem(firstItem).row();
|
||||
emit rowsMoved(first, last, target);
|
||||
}
|
||||
|
||||
|
||||
VSortDialog::VSortDialog(const QString &p_title,
|
||||
const QString &p_info,
|
||||
QWidget *p_parent)
|
||||
: QDialog(p_parent)
|
||||
{
|
||||
setupUI(p_title, p_info);
|
||||
}
|
||||
|
||||
void VSortDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
{
|
||||
QLabel *infoLabel = NULL;
|
||||
if (!p_info.isEmpty()) {
|
||||
infoLabel = new QLabel(p_info);
|
||||
infoLabel->setWordWrap(true);
|
||||
}
|
||||
|
||||
m_treeWidget = new VTreeWidget();
|
||||
m_treeWidget->setRootIsDecorated(false);
|
||||
m_treeWidget->setSelectionMode(QAbstractItemView::ContiguousSelection);
|
||||
m_treeWidget->setDragDropMode(QAbstractItemView::InternalMove);
|
||||
connect(m_treeWidget, &VTreeWidget::rowsMoved,
|
||||
this, [this](int p_first, int p_last, int p_row) {
|
||||
Q_UNUSED(p_first);
|
||||
Q_UNUSED(p_last);
|
||||
QTreeWidgetItem *item = m_treeWidget->topLevelItem(p_row);
|
||||
if (item) {
|
||||
m_treeWidget->setCurrentItem(item);
|
||||
|
||||
// Select all items back.
|
||||
int cnt = p_last - p_first + 1;
|
||||
for (int i = 0; i < cnt; ++i) {
|
||||
QTreeWidgetItem *it = m_treeWidget->topLevelItem(p_row + i);
|
||||
if (it) {
|
||||
it->setSelected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Buttons for top/up/down/bottom.
|
||||
m_topBtn = new QPushButton(tr("&Top"));
|
||||
m_topBtn->setToolTip(tr("Move selected items to top"));
|
||||
connect(m_topBtn, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
this->handleMoveOperation(MoveOperation::Top);
|
||||
});
|
||||
|
||||
m_upBtn = new QPushButton(tr("&Up"));
|
||||
m_upBtn->setToolTip(tr("Move selected items up"));
|
||||
connect(m_upBtn, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
this->handleMoveOperation(MoveOperation::Up);
|
||||
});
|
||||
|
||||
m_downBtn = new QPushButton(tr("&Down"));
|
||||
m_downBtn->setToolTip(tr("Move selected items down"));
|
||||
connect(m_downBtn, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
this->handleMoveOperation(MoveOperation::Down);
|
||||
});
|
||||
|
||||
m_bottomBtn = new QPushButton(tr("&Bottom"));
|
||||
m_bottomBtn->setToolTip(tr("Move selected items to bottom"));
|
||||
connect(m_bottomBtn, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
this->handleMoveOperation(MoveOperation::Bottom);
|
||||
});
|
||||
|
||||
QVBoxLayout *btnLayout = new QVBoxLayout;
|
||||
btnLayout->addWidget(m_topBtn);
|
||||
btnLayout->addWidget(m_upBtn);
|
||||
btnLayout->addWidget(m_downBtn);
|
||||
btnLayout->addWidget(m_bottomBtn);
|
||||
btnLayout->addStretch();
|
||||
|
||||
QHBoxLayout *midLayout = new QHBoxLayout;
|
||||
midLayout->addWidget(m_treeWidget);
|
||||
midLayout->addLayout(btnLayout);
|
||||
|
||||
// Ok is the default button.
|
||||
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
m_btnBox->button(QDialogButtonBox::Ok)->setProperty("SpecialBtn", true);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
if (infoLabel) {
|
||||
mainLayout->addWidget(infoLabel);
|
||||
}
|
||||
|
||||
mainLayout->addLayout(midLayout);
|
||||
mainLayout->addWidget(m_btnBox);
|
||||
|
||||
setLayout(mainLayout);
|
||||
setWindowTitle(p_title);
|
||||
}
|
||||
|
||||
void VSortDialog::treeUpdated()
|
||||
{
|
||||
int cols = m_treeWidget->columnCount();
|
||||
for (int i = 0; i < cols; ++i) {
|
||||
m_treeWidget->resizeColumnToContents(i);
|
||||
}
|
||||
|
||||
QHeaderView *header = m_treeWidget->header();
|
||||
if (header) {
|
||||
header->setStretchLastSection(true);
|
||||
}
|
||||
|
||||
// We just need single level.
|
||||
int cnt = m_treeWidget->topLevelItemCount();
|
||||
for (int i = 0; i < cnt; ++i) {
|
||||
QTreeWidgetItem *item = m_treeWidget->topLevelItem(i);
|
||||
item->setFlags(item->flags() & ~Qt::ItemIsDropEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
void VSortDialog::handleMoveOperation(MoveOperation p_op)
|
||||
{
|
||||
const QList<QTreeWidgetItem *> selectedItems = m_treeWidget->selectedItems();
|
||||
if (selectedItems.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int first = m_treeWidget->topLevelItemCount();
|
||||
int last = -1;
|
||||
for (auto const & it : selectedItems) {
|
||||
int idx = m_treeWidget->indexOfTopLevelItem(it);
|
||||
Q_ASSERT(idx > -1);
|
||||
if (idx < first) {
|
||||
first = idx;
|
||||
}
|
||||
|
||||
if (idx > last) {
|
||||
last = idx;
|
||||
}
|
||||
}
|
||||
|
||||
Q_ASSERT(first <= last && (last - first + 1) == selectedItems.size());
|
||||
QTreeWidgetItem *firstItem = NULL;
|
||||
|
||||
switch (p_op) {
|
||||
case MoveOperation::Top:
|
||||
if (first == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
m_treeWidget->clearSelection();
|
||||
|
||||
// Insert item[last] to index 0 repeatedly.
|
||||
for (int i = last - first; i >= 0; --i) {
|
||||
QTreeWidgetItem *item = m_treeWidget->takeTopLevelItem(last);
|
||||
Q_ASSERT(item);
|
||||
m_treeWidget->insertTopLevelItem(0, item);
|
||||
item->setSelected(true);
|
||||
}
|
||||
|
||||
firstItem = m_treeWidget->topLevelItem(0);
|
||||
|
||||
break;
|
||||
|
||||
case MoveOperation::Up:
|
||||
if (first == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
m_treeWidget->clearSelection();
|
||||
|
||||
// Insert item[last] to index (first -1) repeatedly.
|
||||
for (int i = last - first; i >= 0; --i) {
|
||||
QTreeWidgetItem *item = m_treeWidget->takeTopLevelItem(last);
|
||||
Q_ASSERT(item);
|
||||
m_treeWidget->insertTopLevelItem(first - 1, item);
|
||||
item->setSelected(true);
|
||||
}
|
||||
|
||||
firstItem = m_treeWidget->topLevelItem(first - 1);
|
||||
|
||||
break;
|
||||
|
||||
case MoveOperation::Down:
|
||||
if (last == m_treeWidget->topLevelItemCount() - 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
m_treeWidget->clearSelection();
|
||||
|
||||
// Insert item[first] to index (last) repeatedly.
|
||||
for (int i = last - first; i >= 0; --i) {
|
||||
QTreeWidgetItem *item = m_treeWidget->takeTopLevelItem(first);
|
||||
Q_ASSERT(item);
|
||||
m_treeWidget->insertTopLevelItem(last + 1, item);
|
||||
item->setSelected(true);
|
||||
|
||||
if (!firstItem) {
|
||||
firstItem = item;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MoveOperation::Bottom:
|
||||
if (last == m_treeWidget->topLevelItemCount() - 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
m_treeWidget->clearSelection();
|
||||
|
||||
// Insert item[first] to the last of the tree repeatedly.
|
||||
for (int i = last - first; i >= 0; --i) {
|
||||
QTreeWidgetItem *item = m_treeWidget->takeTopLevelItem(first);
|
||||
Q_ASSERT(item);
|
||||
m_treeWidget->addTopLevelItem(item);
|
||||
item->setSelected(true);
|
||||
|
||||
if (!firstItem) {
|
||||
firstItem = item;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstItem) {
|
||||
m_treeWidget->setCurrentItem(firstItem);
|
||||
m_treeWidget->scrollToItem(firstItem);
|
||||
}
|
||||
}
|
||||
|
||||
QVector<QVariant> VSortDialog::getSortedData() const
|
||||
{
|
||||
int cnt = m_treeWidget->topLevelItemCount();
|
||||
QVector<QVariant> data(cnt);
|
||||
for (int i = 0; i < cnt; ++i) {
|
||||
QTreeWidgetItem *item = m_treeWidget->topLevelItem(i);
|
||||
Q_ASSERT(item);
|
||||
data[i] = item->data(0, Qt::UserRole);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
#ifndef VSORTDIALOG_H
|
||||
#define VSORTDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QVector>
|
||||
#include <QTreeWidget>
|
||||
|
||||
class QPushButton;
|
||||
class QDialogButtonBox;
|
||||
class QTreeWidget;
|
||||
class QDropEvent;
|
||||
|
||||
// QTreeWidget won't emit the rowsMoved() signal after drag-and-drop.
|
||||
// VTreeWidget will emit rowsMoved() signal.
|
||||
class VTreeWidget : public QTreeWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VTreeWidget(QWidget *p_parent = 0)
|
||||
: QTreeWidget(p_parent)
|
||||
{
|
||||
setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
}
|
||||
|
||||
protected:
|
||||
void dropEvent(QDropEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
signals:
|
||||
// Rows [@p_first, @p_last] were moved to @p_row.
|
||||
void rowsMoved(int p_first, int p_last, int p_row);
|
||||
|
||||
};
|
||||
|
||||
class VSortDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VSortDialog(const QString &p_title,
|
||||
const QString &p_info,
|
||||
QWidget *p_parent = 0);
|
||||
|
||||
QTreeWidget *getTreeWidget() const;
|
||||
|
||||
// Called after updating the m_treeWidget.
|
||||
void treeUpdated();
|
||||
|
||||
// Get user data of column 0 from sorted items.
|
||||
QVector<QVariant> getSortedData() const;
|
||||
|
||||
private:
|
||||
enum MoveOperation { Top, Up, Down, Bottom };
|
||||
|
||||
private slots:
|
||||
void handleMoveOperation(MoveOperation p_op);
|
||||
|
||||
private:
|
||||
void setupUI(const QString &p_title, const QString &p_info);
|
||||
|
||||
VTreeWidget *m_treeWidget;
|
||||
QPushButton *m_topBtn;
|
||||
QPushButton *m_upBtn;
|
||||
QPushButton *m_downBtn;
|
||||
QPushButton *m_bottomBtn;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
};
|
||||
|
||||
inline QTreeWidget *VSortDialog::getTreeWidget() const
|
||||
{
|
||||
return m_treeWidget;
|
||||
}
|
||||
|
||||
#endif // VSORTDIALOG_H
|
@ -1,172 +0,0 @@
|
||||
#include "vupdater.h"
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include "vconfigmanager.h"
|
||||
#include "vdownloader.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
VUpdater::VUpdater(QWidget *p_parent)
|
||||
: QDialog(p_parent)
|
||||
{
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void VUpdater::setupUI()
|
||||
{
|
||||
QImage img(":/resources/icons/vnote_update.svg");
|
||||
QSize imgSize(128, 128);
|
||||
QLabel *imgLabel = new QLabel();
|
||||
imgLabel->setPixmap(QPixmap::fromImage(img.scaled(imgSize)));
|
||||
|
||||
m_versionLabel = new QLabel(tr("Current Version: v%1")
|
||||
.arg(g_config->c_version));
|
||||
|
||||
m_proLabel = new QLabel(tr("Checking for updates..."));
|
||||
m_proLabel->setOpenExternalLinks(true);
|
||||
m_proBar = new QProgressBar();
|
||||
m_proBar->setTextVisible(false);
|
||||
|
||||
m_descriptionTb = new QTextBrowser();
|
||||
|
||||
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok);
|
||||
m_btnBox->button(QDialogButtonBox::Ok)->setProperty("SpecialBtn", true);
|
||||
connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
|
||||
QVBoxLayout *verLayout = new QVBoxLayout();
|
||||
verLayout->addStretch();
|
||||
verLayout->addWidget(m_versionLabel);
|
||||
verLayout->addStretch();
|
||||
verLayout->addWidget(m_proLabel);
|
||||
verLayout->addWidget(m_proBar);
|
||||
|
||||
QHBoxLayout *topLayout = new QHBoxLayout();
|
||||
topLayout->addWidget(imgLabel);
|
||||
topLayout->addLayout(verLayout);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->addLayout(topLayout);
|
||||
mainLayout->addWidget(m_descriptionTb, 1);
|
||||
mainLayout->addWidget(m_btnBox);
|
||||
|
||||
m_proLabel->hide();
|
||||
m_proBar->hide();
|
||||
m_descriptionTb->hide();
|
||||
|
||||
setLayout(mainLayout);
|
||||
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
setWindowTitle(tr("VNote Update"));
|
||||
}
|
||||
|
||||
void VUpdater::showEvent(QShowEvent *p_event)
|
||||
{
|
||||
Q_UNUSED(p_event);
|
||||
QDialog::showEvent(p_event);
|
||||
|
||||
QTimer *timer = new QTimer(this);
|
||||
timer->setSingleShot(true);
|
||||
timer->setInterval(1000);
|
||||
connect(timer, &QTimer::timeout,
|
||||
this, [this]() {
|
||||
this->checkUpdates();
|
||||
});
|
||||
|
||||
timer->start();
|
||||
}
|
||||
|
||||
void VUpdater::checkUpdates()
|
||||
{
|
||||
// Change UI.
|
||||
m_proLabel->setText(tr("Checking for updates..."));
|
||||
m_proLabel->show();
|
||||
|
||||
m_proBar->setEnabled(true);
|
||||
m_proBar->setMinimum(0);
|
||||
m_proBar->setMaximum(100);
|
||||
m_proBar->reset();
|
||||
m_proBar->show();
|
||||
|
||||
QString url("https://api.github.com/repos/tamlok/vnote/releases/latest");
|
||||
VDownloader *downloader = new VDownloader(this);
|
||||
connect(downloader, &VDownloader::downloadFinished,
|
||||
this, &VUpdater::parseResult);
|
||||
downloader->download(url);
|
||||
|
||||
m_proBar->setValue(20);
|
||||
}
|
||||
|
||||
// Return if @p_latestVersion is newer than p_curVersion.
|
||||
// They are both in format xx.xx.xx.xx
|
||||
bool isNewerVersion(const QString &p_curVersion, const QString &p_latestVersion)
|
||||
{
|
||||
QStringList curList = p_curVersion.split('.', QString::SkipEmptyParts);
|
||||
QStringList latestList = p_latestVersion.split('.', QString::SkipEmptyParts);
|
||||
|
||||
int i = 0;
|
||||
for (; i < curList.size() && i < latestList.size(); ++i) {
|
||||
int a = curList[i].toInt();
|
||||
int b = latestList[i].toInt();
|
||||
|
||||
if (a > b) {
|
||||
return false;
|
||||
} else if (a < b) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (i < curList.size()) {
|
||||
// 1.2.1 vs 1.2
|
||||
return false;
|
||||
} else if (i < latestList.size()) {
|
||||
// 1.2 vs 1.2.1
|
||||
return true;
|
||||
} else {
|
||||
// 1.2 vs 1.2
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void VUpdater::parseResult(const QByteArray &p_data)
|
||||
{
|
||||
m_proBar->setValue(40);
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(p_data);
|
||||
QJsonObject json = jsonDoc.object();
|
||||
|
||||
if (jsonDoc.isNull() || json.empty()) {
|
||||
m_proBar->setEnabled(false);
|
||||
m_proLabel->setText(tr(":( Fail to check for updates.\n"
|
||||
"Please try it later."));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
m_proBar->setValue(100);
|
||||
|
||||
QString tag = json["tag_name"].toString();
|
||||
if (tag.startsWith('v') && tag.size() > 3) {
|
||||
tag = tag.right(tag.size() - 1);
|
||||
}
|
||||
|
||||
QString releaseName = json["name"].toString();
|
||||
QString releaseUrl = json["html_url"].toString();
|
||||
QString body = json["body"].toString();
|
||||
|
||||
m_versionLabel->setText(tr("Current Version: v%1\nLatest Version: v%2")
|
||||
.arg(g_config->c_version).arg(tag));
|
||||
if (isNewerVersion(g_config->c_version, tag)) {
|
||||
m_proLabel->setText(tr("<span style=\"font-weight: bold;\">Updates Available!</span><br/>"
|
||||
"Please visit <a href=\"%1\">Github Releases</a> to download the latest version.")
|
||||
.arg(releaseUrl));
|
||||
} else {
|
||||
m_proLabel->setText(tr("VNote is already the latest version."));
|
||||
}
|
||||
|
||||
m_descriptionTb->setText(releaseName + "\n==========\n" + body);
|
||||
m_descriptionTb->show();
|
||||
m_proBar->hide();
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
#ifndef VUPDATER_H
|
||||
#define VUPDATER_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QByteArray>
|
||||
|
||||
class QLabel;
|
||||
class QDialogButtonBox;
|
||||
class QTextBrowser;
|
||||
class QProgressBar;
|
||||
class QShowEvent;
|
||||
|
||||
class VUpdater : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VUpdater(QWidget *p_parent = 0);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
private slots:
|
||||
// Calling to Github api got responses.
|
||||
void parseResult(const QByteArray &p_data);
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
// Fetch the latest release info from Github.
|
||||
void checkUpdates();
|
||||
|
||||
QLabel *m_versionLabel;
|
||||
QTextBrowser *m_descriptionTb;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
|
||||
// Progress label and bar.
|
||||
QLabel *m_proLabel;
|
||||
QProgressBar *m_proBar;
|
||||
};
|
||||
|
||||
#endif // VUPDATER_H
|
@ -1,741 +0,0 @@
|
||||
#include <QtGui>
|
||||
#include <QtDebug>
|
||||
#include <QTextCursor>
|
||||
#include <algorithm>
|
||||
#include "hgmarkdownhighlighter.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vtextblockdata.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
const int HGMarkdownHighlighter::initCapacity = 1024;
|
||||
|
||||
void HGMarkdownHighlighter::resizeBuffer(int newCap)
|
||||
{
|
||||
if (newCap == capacity) {
|
||||
return;
|
||||
}
|
||||
if (capacity > 0) {
|
||||
Q_ASSERT(content);
|
||||
delete [] content;
|
||||
}
|
||||
capacity = newCap;
|
||||
content = new char [capacity];
|
||||
}
|
||||
|
||||
// Will be freeed by parent automatically
|
||||
HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector<HighlightingStyle> &styles,
|
||||
const QHash<QString, QTextCharFormat> &codeBlockStyles,
|
||||
int waitInterval,
|
||||
QTextDocument *parent)
|
||||
: QSyntaxHighlighter(parent),
|
||||
highlightingStyles(styles),
|
||||
m_codeBlockStyles(codeBlockStyles),
|
||||
m_numOfCodeBlockHighlightsToRecv(0),
|
||||
parsing(0),
|
||||
m_blockHLResultReady(false),
|
||||
waitInterval(waitInterval),
|
||||
content(NULL),
|
||||
capacity(0),
|
||||
result(NULL)
|
||||
{
|
||||
codeBlockStartExp = QRegExp(VUtils::c_fencedCodeBlockStartRegExp);
|
||||
codeBlockEndExp = QRegExp(VUtils::c_fencedCodeBlockEndRegExp);
|
||||
|
||||
codeBlockFormat.setForeground(QBrush(Qt::darkYellow));
|
||||
for (int index = 0; index < styles.size(); ++index) {
|
||||
const pmh_element_type &eleType = styles[index].type;
|
||||
if (eleType == pmh_VERBATIM) {
|
||||
codeBlockFormat = styles[index].format;
|
||||
} else if (eleType == pmh_LINK) {
|
||||
m_linkFormat = styles[index].format;
|
||||
} else if (eleType == pmh_IMAGE) {
|
||||
m_imageFormat = styles[index].format;
|
||||
}
|
||||
}
|
||||
|
||||
m_colorColumnFormat = codeBlockFormat;
|
||||
m_colorColumnFormat.setForeground(QColor(g_config->getEditorColorColumnFg()));
|
||||
m_colorColumnFormat.setBackground(QColor(g_config->getEditorColorColumnBg()));
|
||||
|
||||
resizeBuffer(initCapacity);
|
||||
document = parent;
|
||||
|
||||
timer = new QTimer(this);
|
||||
timer->setSingleShot(true);
|
||||
timer->setInterval(this->waitInterval);
|
||||
connect(timer, &QTimer::timeout,
|
||||
this, [this]() {
|
||||
startParseAndHighlight(false);
|
||||
});
|
||||
|
||||
static const int completeWaitTime = 500;
|
||||
m_completeTimer = new QTimer(this);
|
||||
m_completeTimer->setSingleShot(true);
|
||||
m_completeTimer->setInterval(completeWaitTime);
|
||||
connect(m_completeTimer, &QTimer::timeout,
|
||||
this, &HGMarkdownHighlighter::highlightCompleted);
|
||||
|
||||
connect(document, &QTextDocument::contentsChange,
|
||||
this, &HGMarkdownHighlighter::handleContentChange);
|
||||
}
|
||||
|
||||
HGMarkdownHighlighter::~HGMarkdownHighlighter()
|
||||
{
|
||||
if (result) {
|
||||
pmh_free_elements(result);
|
||||
result = NULL;
|
||||
}
|
||||
if (content) {
|
||||
delete [] content;
|
||||
capacity = 0;
|
||||
content = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void HGMarkdownHighlighter::updateBlockUserData(int p_blockNum, const QString &p_text)
|
||||
{
|
||||
Q_UNUSED(p_text);
|
||||
|
||||
VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(currentBlockUserData());
|
||||
if (!blockData) {
|
||||
blockData = new VTextBlockData();
|
||||
setCurrentBlockUserData(blockData);
|
||||
}
|
||||
|
||||
if (blockData->getPreviews().isEmpty()) {
|
||||
m_possiblePreviewBlocks.remove(p_blockNum);
|
||||
} else {
|
||||
m_possiblePreviewBlocks.insert(p_blockNum);
|
||||
}
|
||||
}
|
||||
|
||||
void HGMarkdownHighlighter::highlightBlock(const QString &text)
|
||||
{
|
||||
int blockNum = currentBlock().blockNumber();
|
||||
if (m_blockHLResultReady && blockHighlights.size() > blockNum) {
|
||||
const QVector<HLUnit> &units = blockHighlights[blockNum];
|
||||
for (int i = 0; i < units.size(); ++i) {
|
||||
// TODO: merge two format within the same range
|
||||
const HLUnit &unit = units[i];
|
||||
setFormat(unit.start, unit.length, highlightingStyles[unit.styleIndex].format);
|
||||
}
|
||||
}
|
||||
|
||||
// We use PEG Markdown Highlight as the main highlighter.
|
||||
// We can use other highlighting methods to complement it.
|
||||
|
||||
// Set current block's user data.
|
||||
updateBlockUserData(blockNum, text);
|
||||
|
||||
// If it is a block inside HTML comment, just skip it.
|
||||
if (isBlockInsideCommentRegion(currentBlock())) {
|
||||
setCurrentBlockState(HighlightBlockState::Comment);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// PEG Markdown Highlight does not handle the ``` code block correctly.
|
||||
setCurrentBlockState(HighlightBlockState::Normal);
|
||||
highlightCodeBlock(text);
|
||||
|
||||
// PEG Markdown Highlight does not handle links with spaces in the URL.
|
||||
// Links in the URL should be encoded to %20. We just let it be here and won't
|
||||
// fix this.
|
||||
// highlightLinkWithSpacesInURL(text);
|
||||
|
||||
// Highlight CodeBlock using VCodeBlockHighlightHelper.
|
||||
if (m_codeBlockHighlights.size() > blockNum) {
|
||||
const QVector<HLUnitStyle> &units = m_codeBlockHighlights[blockNum];
|
||||
// Manually simply merge the format of all the units within the same block.
|
||||
// Using QTextCursor to get the char format after setFormat() seems
|
||||
// not to work.
|
||||
QVector<QTextCharFormat> formats;
|
||||
formats.reserve(units.size());
|
||||
// formatIndex[i] is the index in @formats which is the format of the
|
||||
// ith character.
|
||||
QVector<int> formatIndex(currentBlock().length(), -1);
|
||||
for (int i = 0; i < units.size(); ++i) {
|
||||
const HLUnitStyle &unit = units[i];
|
||||
auto it = m_codeBlockStyles.find(unit.style);
|
||||
if (it != m_codeBlockStyles.end()) {
|
||||
QTextCharFormat newFormat;
|
||||
if (unit.start < (unsigned int)formatIndex.size() && formatIndex[unit.start] != -1) {
|
||||
newFormat = formats[formatIndex[unit.start]];
|
||||
newFormat.merge(*it);
|
||||
} else {
|
||||
newFormat = *it;
|
||||
}
|
||||
setFormat(unit.start, unit.length, newFormat);
|
||||
|
||||
formats.append(newFormat);
|
||||
int idx = formats.size() - 1;
|
||||
unsigned int endIdx = unit.length + unit.start;
|
||||
for (unsigned int i = unit.start; i < endIdx && i < (unsigned int)formatIndex.size(); ++i) {
|
||||
formatIndex[i] = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
highlightCodeBlockColorColumn(text);
|
||||
|
||||
exit:
|
||||
highlightChanged();
|
||||
}
|
||||
|
||||
void HGMarkdownHighlighter::initBlockHighlightFromResult(int nrBlocks)
|
||||
{
|
||||
blockHighlights.resize(nrBlocks);
|
||||
for (int i = 0; i < blockHighlights.size(); ++i) {
|
||||
blockHighlights[i].clear();
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < highlightingStyles.size(); i++)
|
||||
{
|
||||
const HighlightingStyle &style = highlightingStyles[i];
|
||||
pmh_element *elem_cursor = result[style.type];
|
||||
|
||||
// pmh_H1 to pmh_H6 is continuous.
|
||||
bool isHeader = style.type >= pmh_H1 && style.type <= pmh_H6;
|
||||
|
||||
while (elem_cursor != NULL)
|
||||
{
|
||||
// elem_cursor->pos and elem_cursor->end is the start
|
||||
// and end position of the element in document.
|
||||
if (elem_cursor->end <= elem_cursor->pos) {
|
||||
elem_cursor = elem_cursor->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check header. Skip those headers with no spaces after #s.
|
||||
if (isHeader
|
||||
&& !isValidHeader(elem_cursor->pos, elem_cursor->end)) {
|
||||
elem_cursor = elem_cursor->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
initBlockHighlihgtOne(elem_cursor->pos, elem_cursor->end, i);
|
||||
elem_cursor = elem_cursor->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HGMarkdownHighlighter::initHtmlCommentRegionsFromResult()
|
||||
{
|
||||
// From Qt5.7, the capacity is preserved.
|
||||
m_commentRegions.clear();
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
pmh_element *elem = result[pmh_COMMENT];
|
||||
while (elem != NULL) {
|
||||
if (elem->end <= elem->pos) {
|
||||
elem = elem->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
m_commentRegions.push_back(VElementRegion(elem->pos, elem->end));
|
||||
|
||||
elem = elem->next;
|
||||
}
|
||||
|
||||
qDebug() << "highlighter: parse" << m_commentRegions.size() << "HTML comment regions";
|
||||
}
|
||||
|
||||
void HGMarkdownHighlighter::initImageRegionsFromResult()
|
||||
{
|
||||
if (!result) {
|
||||
// From Qt5.7, the capacity is preserved.
|
||||
m_imageRegions.clear();
|
||||
emit imageLinksUpdated(m_imageRegions);
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = 0;
|
||||
int oriSize = m_imageRegions.size();
|
||||
pmh_element *elem = result[pmh_IMAGE];
|
||||
while (elem != NULL) {
|
||||
if (elem->end <= elem->pos) {
|
||||
elem = elem->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (idx < oriSize) {
|
||||
// Try to reuse the original element.
|
||||
VElementRegion ® = m_imageRegions[idx];
|
||||
if ((int)elem->pos != reg.m_startPos || (int)elem->end != reg.m_endPos) {
|
||||
reg.m_startPos = (int)elem->pos;
|
||||
reg.m_endPos = (int)elem->end;
|
||||
}
|
||||
} else {
|
||||
m_imageRegions.push_back(VElementRegion(elem->pos, elem->end));
|
||||
}
|
||||
|
||||
++idx;
|
||||
elem = elem->next;
|
||||
}
|
||||
|
||||
if (idx < oriSize) {
|
||||
m_imageRegions.resize(idx);
|
||||
}
|
||||
|
||||
qDebug() << "highlighter: parse" << m_imageRegions.size() << "image regions";
|
||||
|
||||
emit imageLinksUpdated(m_imageRegions);
|
||||
}
|
||||
|
||||
void HGMarkdownHighlighter::initHeaderRegionsFromResult()
|
||||
{
|
||||
if (!result) {
|
||||
// From Qt5.7, the capacity is preserved.
|
||||
m_headerRegions.clear();
|
||||
emit headersUpdated(m_headerRegions);
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = 0;
|
||||
int oriSize = m_headerRegions.size();
|
||||
pmh_element_type hx[6] = {pmh_H1, pmh_H2, pmh_H3, pmh_H4, pmh_H5, pmh_H6};
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
pmh_element *elem = result[hx[i]];
|
||||
while (elem != NULL) {
|
||||
if (elem->end <= elem->pos
|
||||
|| !isValidHeader(elem->pos, elem->end)) {
|
||||
elem = elem->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (idx < oriSize) {
|
||||
// Try to reuse the original element.
|
||||
VElementRegion ® = m_headerRegions[idx];
|
||||
if ((int)elem->pos != reg.m_startPos || (int)elem->end != reg.m_endPos) {
|
||||
reg.m_startPos = (int)elem->pos;
|
||||
reg.m_endPos = (int)elem->end;
|
||||
}
|
||||
} else {
|
||||
m_headerRegions.push_back(VElementRegion(elem->pos, elem->end));
|
||||
}
|
||||
|
||||
++idx;
|
||||
elem = elem->next;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx < oriSize) {
|
||||
m_headerRegions.resize(idx);
|
||||
}
|
||||
|
||||
std::sort(m_headerRegions.begin(), m_headerRegions.end());
|
||||
|
||||
qDebug() << "highlighter: parse" << m_headerRegions.size() << "header regions";
|
||||
|
||||
emit headersUpdated(m_headerRegions);
|
||||
}
|
||||
|
||||
void HGMarkdownHighlighter::initBlockHighlihgtOne(unsigned long pos,
|
||||
unsigned long end,
|
||||
int styleIndex)
|
||||
{
|
||||
// When the the highlight element is at the end of document, @end will equals
|
||||
// to the characterCount.
|
||||
unsigned int nrChar = (unsigned int)document->characterCount();
|
||||
if (end >= nrChar && nrChar > 0) {
|
||||
end = nrChar - 1;
|
||||
}
|
||||
|
||||
int startBlockNum = document->findBlock(pos).blockNumber();
|
||||
int endBlockNum = document->findBlock(end).blockNumber();
|
||||
|
||||
for (int i = startBlockNum; i <= endBlockNum; ++i)
|
||||
{
|
||||
QTextBlock block = document->findBlockByNumber(i);
|
||||
int blockStartPos = block.position();
|
||||
HLUnit unit;
|
||||
if (i == startBlockNum) {
|
||||
unit.start = pos - blockStartPos;
|
||||
unit.length = (startBlockNum == endBlockNum) ?
|
||||
(end - pos) : (block.length() - unit.start);
|
||||
} else if (i == endBlockNum) {
|
||||
unit.start = 0;
|
||||
unit.length = end - blockStartPos;
|
||||
} else {
|
||||
unit.start = 0;
|
||||
unit.length = block.length();
|
||||
}
|
||||
unit.styleIndex = styleIndex;
|
||||
|
||||
blockHighlights[i].append(unit);
|
||||
}
|
||||
}
|
||||
|
||||
void HGMarkdownHighlighter::highlightCodeBlock(const QString &text)
|
||||
{
|
||||
static int startLeadingSpaces = -1;
|
||||
int length = 0;
|
||||
int index = -1;
|
||||
int preState = previousBlockState();
|
||||
int state = HighlightBlockState::Normal;
|
||||
|
||||
if (preState != HighlightBlockState::CodeBlock
|
||||
&& preState != HighlightBlockState::CodeBlockStart) {
|
||||
// Need to find a new code block start.
|
||||
index = codeBlockStartExp.indexIn(text);
|
||||
if (index >= 0) {
|
||||
// Start a new code block.
|
||||
length = text.length();
|
||||
state = HighlightBlockState::CodeBlockStart;
|
||||
|
||||
// The leading spaces of code block start and end must be identical.
|
||||
startLeadingSpaces = codeBlockStartExp.capturedTexts()[1].size();
|
||||
} else {
|
||||
// A normal block.
|
||||
startLeadingSpaces = -1;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Need to find a code block end.
|
||||
index = codeBlockEndExp.indexIn(text);
|
||||
|
||||
// The closing ``` should have the same indentation as the open ```.
|
||||
if (index >= 0
|
||||
&& startLeadingSpaces == codeBlockEndExp.capturedTexts()[1].size()) {
|
||||
// End of code block.
|
||||
length = text.length();
|
||||
state = HighlightBlockState::CodeBlockEnd;
|
||||
} else {
|
||||
// Within code block.
|
||||
index = 0;
|
||||
length = text.length();
|
||||
state = HighlightBlockState::CodeBlock;
|
||||
}
|
||||
}
|
||||
|
||||
setCurrentBlockState(state);
|
||||
setFormat(index, length, codeBlockFormat);
|
||||
}
|
||||
|
||||
void HGMarkdownHighlighter::highlightCodeBlockColorColumn(const QString &p_text)
|
||||
{
|
||||
int cc = g_config->getColorColumn();
|
||||
if (cc <= 0 || currentBlockState() != HighlightBlockState::CodeBlock) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_text.size() < cc) {
|
||||
return;
|
||||
}
|
||||
|
||||
setFormat(cc - 1, 1, m_colorColumnFormat);
|
||||
}
|
||||
|
||||
void HGMarkdownHighlighter::highlightLinkWithSpacesInURL(const QString &p_text)
|
||||
{
|
||||
if (currentBlockState() == HighlightBlockState::CodeBlock) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: should select links with spaces in URL.
|
||||
QRegExp regExp("[\\!]?\\[[^\\]]*\\]\\(([^\\n\\)]+)\\)");
|
||||
int index = regExp.indexIn(p_text);
|
||||
while (index >= 0) {
|
||||
Q_ASSERT(regExp.captureCount() == 1);
|
||||
int length = regExp.matchedLength();
|
||||
QString capturedText = regExp.capturedTexts()[1];
|
||||
if (capturedText.contains(' ')) {
|
||||
if (p_text[index] == '!' && m_imageFormat.isValid()) {
|
||||
setFormat(index, length, m_imageFormat);
|
||||
} else if (m_linkFormat.isValid()) {
|
||||
setFormat(index, length, m_linkFormat);
|
||||
}
|
||||
}
|
||||
index = regExp.indexIn(p_text, index + length);
|
||||
}
|
||||
}
|
||||
|
||||
void HGMarkdownHighlighter::parse(bool p_fast)
|
||||
{
|
||||
if (!parsing.testAndSetRelaxed(0, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (highlightingStyles.isEmpty()) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
{
|
||||
m_blockHLResultReady = false;
|
||||
|
||||
int nrBlocks = document->blockCount();
|
||||
parseInternal();
|
||||
|
||||
initBlockHighlightFromResult(nrBlocks);
|
||||
|
||||
m_blockHLResultReady = true;
|
||||
|
||||
if (!p_fast) {
|
||||
initHtmlCommentRegionsFromResult();
|
||||
|
||||
initImageRegionsFromResult();
|
||||
|
||||
initHeaderRegionsFromResult();
|
||||
}
|
||||
|
||||
if (result) {
|
||||
pmh_free_elements(result);
|
||||
result = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
parsing.store(0);
|
||||
}
|
||||
|
||||
void HGMarkdownHighlighter::parseInternal()
|
||||
{
|
||||
QString text = document->toPlainText();
|
||||
QByteArray ba = text.toUtf8();
|
||||
const char *data = (const char *)ba.data();
|
||||
int len = ba.size();
|
||||
|
||||
if (result) {
|
||||
pmh_free_elements(result);
|
||||
result = NULL;
|
||||
}
|
||||
|
||||
if (len == 0) {
|
||||
return;
|
||||
} else if (len >= capacity) {
|
||||
resizeBuffer(qMax(2 * capacity, len * 2));
|
||||
} else if (len < (capacity >> 2)) {
|
||||
resizeBuffer(qMax(capacity >> 1, len * 2));
|
||||
}
|
||||
|
||||
memcpy(content, data, len);
|
||||
content[len] = '\0';
|
||||
|
||||
pmh_markdown_to_elements(content, pmh_EXT_NONE, &result);
|
||||
}
|
||||
|
||||
void HGMarkdownHighlighter::handleContentChange(int /* position */, int charsRemoved, int charsAdded)
|
||||
{
|
||||
if (charsRemoved == 0 && charsAdded == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
timer->stop();
|
||||
timer->start();
|
||||
}
|
||||
|
||||
void HGMarkdownHighlighter::startParseAndHighlight(bool p_fast)
|
||||
{
|
||||
qDebug() << "HGMarkdownHighlighter start a new parse (fast" << p_fast << ")";
|
||||
parse(p_fast);
|
||||
|
||||
if (p_fast) {
|
||||
rehighlight();
|
||||
} else {
|
||||
if (!updateCodeBlocks()) {
|
||||
rehighlight();
|
||||
}
|
||||
|
||||
highlightChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void HGMarkdownHighlighter::updateHighlight()
|
||||
{
|
||||
timer->stop();
|
||||
startParseAndHighlight(false);
|
||||
}
|
||||
|
||||
void HGMarkdownHighlighter::updateHighlightFast()
|
||||
{
|
||||
timer->stop();
|
||||
startParseAndHighlight(true);
|
||||
}
|
||||
|
||||
bool HGMarkdownHighlighter::updateCodeBlocks()
|
||||
{
|
||||
if (!g_config->getEnableCodeBlockHighlight()) {
|
||||
m_codeBlockHighlights.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_codeBlockHighlights.resize(document->blockCount());
|
||||
for (int i = 0; i < m_codeBlockHighlights.size(); ++i) {
|
||||
m_codeBlockHighlights[i].clear();
|
||||
}
|
||||
|
||||
QVector<VCodeBlock> codeBlocks;
|
||||
|
||||
VCodeBlock item;
|
||||
bool inBlock = false;
|
||||
int startLeadingSpaces = -1;
|
||||
|
||||
// Only handle complete codeblocks.
|
||||
QTextBlock block = document->firstBlock();
|
||||
while (block.isValid()) {
|
||||
QString text = block.text();
|
||||
if (inBlock) {
|
||||
item.m_text = item.m_text + "\n" + text;
|
||||
int idx = codeBlockEndExp.indexIn(text);
|
||||
if (idx >= 0 && codeBlockEndExp.capturedTexts()[1].size() == startLeadingSpaces) {
|
||||
// End block.
|
||||
inBlock = false;
|
||||
item.m_endBlock = block.blockNumber();
|
||||
|
||||
// See if it is a code block inside HTML comment.
|
||||
if (!isBlockInsideCommentRegion(block)) {
|
||||
qDebug() << "add one code block in lang" << item.m_lang;
|
||||
codeBlocks.append(item);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int idx = codeBlockStartExp.indexIn(text);
|
||||
if (idx >= 0) {
|
||||
// Start block.
|
||||
inBlock = true;
|
||||
item.m_startBlock = block.blockNumber();
|
||||
item.m_startPos = block.position();
|
||||
item.m_text = text;
|
||||
if (codeBlockStartExp.captureCount() == 2) {
|
||||
item.m_lang = codeBlockStartExp.capturedTexts()[2];
|
||||
}
|
||||
|
||||
startLeadingSpaces = codeBlockStartExp.capturedTexts()[1].size();
|
||||
}
|
||||
}
|
||||
|
||||
block = block.next();
|
||||
}
|
||||
|
||||
m_numOfCodeBlockHighlightsToRecv = codeBlocks.size();
|
||||
if (m_numOfCodeBlockHighlightsToRecv > 0) {
|
||||
emit codeBlocksUpdated(codeBlocks);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool HLUnitStyleComp(const HLUnitStyle &a, const HLUnitStyle &b)
|
||||
{
|
||||
if (a.start < b.start) {
|
||||
return true;
|
||||
} else if (a.start == b.start) {
|
||||
return a.length > b.length;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void HGMarkdownHighlighter::setCodeBlockHighlights(const QVector<HLUnitPos> &p_units)
|
||||
{
|
||||
if (p_units.isEmpty()) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
{
|
||||
QVector<QVector<HLUnitStyle>> highlights(m_codeBlockHighlights.size());
|
||||
|
||||
for (auto const &unit : p_units) {
|
||||
int pos = unit.m_position;
|
||||
int end = unit.m_position + unit.m_length;
|
||||
int startBlockNum = document->findBlock(pos).blockNumber();
|
||||
int endBlockNum = document->findBlock(end).blockNumber();
|
||||
|
||||
// Text has been changed. Abandon the obsolete parsed result.
|
||||
if (startBlockNum == -1 || endBlockNum >= highlights.size()) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
for (int i = startBlockNum; i <= endBlockNum; ++i)
|
||||
{
|
||||
QTextBlock block = document->findBlockByNumber(i);
|
||||
int blockStartPos = block.position();
|
||||
HLUnitStyle hl;
|
||||
hl.style = unit.m_style;
|
||||
if (i == startBlockNum) {
|
||||
hl.start = pos - blockStartPos;
|
||||
hl.length = (startBlockNum == endBlockNum) ?
|
||||
(end - pos) : (block.length() - hl.start);
|
||||
} else if (i == endBlockNum) {
|
||||
hl.start = 0;
|
||||
hl.length = end - blockStartPos;
|
||||
} else {
|
||||
hl.start = 0;
|
||||
hl.length = block.length();
|
||||
}
|
||||
|
||||
highlights[i].append(hl);
|
||||
}
|
||||
}
|
||||
|
||||
// Need to highlight in order.
|
||||
for (int i = 0; i < highlights.size(); ++i) {
|
||||
QVector<HLUnitStyle> &units = highlights[i];
|
||||
if (!units.isEmpty()) {
|
||||
std::sort(units.begin(), units.end(), HLUnitStyleComp);
|
||||
m_codeBlockHighlights[i].append(units);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
--m_numOfCodeBlockHighlightsToRecv;
|
||||
if (m_numOfCodeBlockHighlightsToRecv <= 0) {
|
||||
rehighlight();
|
||||
}
|
||||
}
|
||||
|
||||
bool HGMarkdownHighlighter::isBlockInsideCommentRegion(const QTextBlock &p_block) const
|
||||
{
|
||||
if (!p_block.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int start = p_block.position();
|
||||
int end = start + p_block.length();
|
||||
|
||||
for (auto const & reg : m_commentRegions) {
|
||||
if (reg.contains(start) && reg.contains(end)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HGMarkdownHighlighter::highlightChanged()
|
||||
{
|
||||
m_completeTimer->stop();
|
||||
m_completeTimer->start();
|
||||
}
|
||||
|
||||
bool HGMarkdownHighlighter::isValidHeader(unsigned long p_pos, unsigned long p_end)
|
||||
{
|
||||
// There must exist spaces after #s.
|
||||
// No more than 6 #s.
|
||||
int nrNumberSign = 0;
|
||||
for (unsigned long i = p_pos; i < p_end; ++i) {
|
||||
QChar ch = document->characterAt(i);
|
||||
if (ch.isSpace()) {
|
||||
return true;
|
||||
} else if (ch == QChar('#')) {
|
||||
if (++nrNumberSign > 6) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
@ -1,276 +0,0 @@
|
||||
#ifndef HGMARKDOWNHIGHLIGHTER_H
|
||||
#define HGMARKDOWNHIGHLIGHTER_H
|
||||
|
||||
#include <QTextCharFormat>
|
||||
#include <QSyntaxHighlighter>
|
||||
#include <QAtomicInt>
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
|
||||
extern "C" {
|
||||
#include <pmh_parser.h>
|
||||
}
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QTextDocument;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
struct HighlightingStyle
|
||||
{
|
||||
pmh_element_type type;
|
||||
QTextCharFormat format;
|
||||
};
|
||||
|
||||
// One continuous region for a certain markdown highlight style
|
||||
// within a QTextBlock.
|
||||
// Pay attention to the change of HighlightingStyles[]
|
||||
struct HLUnit
|
||||
{
|
||||
// Highlight offset @start and @length with style HighlightingStyles[styleIndex]
|
||||
// within a QTextBlock
|
||||
unsigned long start;
|
||||
unsigned long length;
|
||||
unsigned int styleIndex;
|
||||
};
|
||||
|
||||
struct HLUnitStyle
|
||||
{
|
||||
unsigned long start;
|
||||
unsigned long length;
|
||||
QString style;
|
||||
};
|
||||
|
||||
// Fenced code block only.
|
||||
struct VCodeBlock
|
||||
{
|
||||
int m_startPos;
|
||||
int m_startBlock;
|
||||
int m_endBlock;
|
||||
QString m_lang;
|
||||
|
||||
QString m_text;
|
||||
};
|
||||
|
||||
// Highlight unit with global position and string style name.
|
||||
struct HLUnitPos
|
||||
{
|
||||
HLUnitPos() : m_position(-1), m_length(-1)
|
||||
{
|
||||
}
|
||||
|
||||
HLUnitPos(int p_position, int p_length, const QString &p_style)
|
||||
: m_position(p_position), m_length(p_length), m_style(p_style)
|
||||
{
|
||||
}
|
||||
|
||||
int m_position;
|
||||
int m_length;
|
||||
QString m_style;
|
||||
};
|
||||
|
||||
// Denote the region of a certain Markdown element.
|
||||
struct VElementRegion
|
||||
{
|
||||
VElementRegion() : m_startPos(0), m_endPos(0) {}
|
||||
|
||||
VElementRegion(int p_start, int p_end) : m_startPos(p_start), m_endPos(p_end) {}
|
||||
|
||||
// The start position of the region in document.
|
||||
int m_startPos;
|
||||
|
||||
// The end position of the region in document.
|
||||
int m_endPos;
|
||||
|
||||
// Whether this region contains @p_pos.
|
||||
bool contains(int p_pos) const
|
||||
{
|
||||
return m_startPos <= p_pos && m_endPos >= p_pos;
|
||||
}
|
||||
|
||||
bool operator==(const VElementRegion &p_other) const
|
||||
{
|
||||
return (m_startPos == p_other.m_startPos
|
||||
&& m_endPos == p_other.m_endPos);
|
||||
}
|
||||
|
||||
bool operator<(const VElementRegion &p_other) const
|
||||
{
|
||||
if (m_startPos < p_other.m_startPos) {
|
||||
return true;
|
||||
} else if (m_startPos == p_other.m_startPos) {
|
||||
return m_endPos <= p_other.m_endPos;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class HGMarkdownHighlighter : public QSyntaxHighlighter
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
HGMarkdownHighlighter(const QVector<HighlightingStyle> &styles,
|
||||
const QHash<QString, QTextCharFormat> &codeBlockStyles,
|
||||
int waitInterval,
|
||||
QTextDocument *parent = 0);
|
||||
~HGMarkdownHighlighter();
|
||||
|
||||
// Request to update highlihgt (re-parse and re-highlight)
|
||||
void setCodeBlockHighlights(const QVector<HLUnitPos> &p_units);
|
||||
|
||||
const QMap<int, bool> &getPotentialPreviewBlocks() const;
|
||||
|
||||
const QVector<VElementRegion> &getHeaderRegions() const;
|
||||
|
||||
const QSet<int> &getPossiblePreviewBlocks() const;
|
||||
|
||||
void clearPossiblePreviewBlocks(const QVector<int> &p_blocksToClear);
|
||||
|
||||
// Parse and only update the highlight results for rehighlight().
|
||||
void updateHighlightFast();
|
||||
|
||||
signals:
|
||||
void highlightCompleted();
|
||||
|
||||
// QVector is implicitly shared.
|
||||
void codeBlocksUpdated(const QVector<VCodeBlock> &p_codeBlocks);
|
||||
|
||||
// Emitted when image regions have been fetched from a new parsing result.
|
||||
void imageLinksUpdated(const QVector<VElementRegion> &p_imageRegions);
|
||||
|
||||
// Emitted when header regions have been fetched from a new parsing result.
|
||||
void headersUpdated(const QVector<VElementRegion> &p_headerRegions);
|
||||
|
||||
protected:
|
||||
void highlightBlock(const QString &text) Q_DECL_OVERRIDE;
|
||||
|
||||
public slots:
|
||||
// Parse and rehighlight immediately.
|
||||
void updateHighlight();
|
||||
|
||||
private slots:
|
||||
void handleContentChange(int position, int charsRemoved, int charsAdded);
|
||||
|
||||
// @p_fast: if true, just parse and update styles.
|
||||
void startParseAndHighlight(bool p_fast = false);
|
||||
|
||||
private:
|
||||
QRegExp codeBlockStartExp;
|
||||
QRegExp codeBlockEndExp;
|
||||
QTextCharFormat codeBlockFormat;
|
||||
QTextCharFormat m_linkFormat;
|
||||
QTextCharFormat m_imageFormat;
|
||||
QTextCharFormat m_colorColumnFormat;
|
||||
|
||||
QTextDocument *document;
|
||||
QVector<HighlightingStyle> highlightingStyles;
|
||||
QHash<QString, QTextCharFormat> m_codeBlockStyles;
|
||||
QVector<QVector<HLUnit> > blockHighlights;
|
||||
|
||||
// Use another member to store the codeblocks highlights, because the highlight
|
||||
// sequence is blockHighlights, regular-expression-based highlihgts, and then
|
||||
// codeBlockHighlights.
|
||||
// Support fenced code block only.
|
||||
QVector<QVector<HLUnitStyle> > m_codeBlockHighlights;
|
||||
|
||||
int m_numOfCodeBlockHighlightsToRecv;
|
||||
|
||||
// All HTML comment regions.
|
||||
QVector<VElementRegion> m_commentRegions;
|
||||
|
||||
// All image link regions.
|
||||
QVector<VElementRegion> m_imageRegions;
|
||||
|
||||
// All header regions.
|
||||
// May contains illegal elements.
|
||||
// Sorted by start position.
|
||||
QVector<VElementRegion> m_headerRegions;
|
||||
|
||||
// Timer to signal highlightCompleted().
|
||||
QTimer *m_completeTimer;
|
||||
|
||||
QAtomicInt parsing;
|
||||
|
||||
// Whether highlight results for blocks are ready.
|
||||
bool m_blockHLResultReady;
|
||||
|
||||
QTimer *timer;
|
||||
int waitInterval;
|
||||
|
||||
// Block number of those blocks which possible contains previewed image.
|
||||
QSet<int> m_possiblePreviewBlocks;
|
||||
|
||||
char *content;
|
||||
int capacity;
|
||||
pmh_element **result;
|
||||
|
||||
static const int initCapacity;
|
||||
|
||||
void resizeBuffer(int newCap);
|
||||
void highlightCodeBlock(const QString &text);
|
||||
|
||||
// Highlight links using regular expression.
|
||||
// PEG Markdown Highlight treat URLs with spaces illegal. This function is
|
||||
// intended to complement this.
|
||||
void highlightLinkWithSpacesInURL(const QString &p_text);
|
||||
|
||||
void parse(bool p_fast = false);
|
||||
|
||||
void parseInternal();
|
||||
|
||||
// Init highlight elements for all the blocks from parse results.
|
||||
void initBlockHighlightFromResult(int nrBlocks);
|
||||
|
||||
// Init highlight elements for blocks from one parse result.
|
||||
void initBlockHighlihgtOne(unsigned long pos,
|
||||
unsigned long end,
|
||||
int styleIndex);
|
||||
|
||||
// Return true if there are fenced code blocks and it will call rehighlight() later.
|
||||
// Return false if there is none.
|
||||
bool updateCodeBlocks();
|
||||
|
||||
// Fetch all the HTML comment regions from parsing result.
|
||||
void initHtmlCommentRegionsFromResult();
|
||||
|
||||
// Fetch all the image link regions from parsing result.
|
||||
void initImageRegionsFromResult();
|
||||
|
||||
// Fetch all the header regions from parsing result.
|
||||
void initHeaderRegionsFromResult();
|
||||
|
||||
// Whether @p_block is totally inside a HTML comment.
|
||||
bool isBlockInsideCommentRegion(const QTextBlock &p_block) const;
|
||||
|
||||
// Highlights have been changed. Try to signal highlightCompleted().
|
||||
void highlightChanged();
|
||||
|
||||
// Set the user data of currentBlock().
|
||||
void updateBlockUserData(int p_blockNum, const QString &p_text);
|
||||
|
||||
// Highlight color column in code block.
|
||||
void highlightCodeBlockColorColumn(const QString &p_text);
|
||||
|
||||
// Check if [p_pos, p_end) is a valid header.
|
||||
bool isValidHeader(unsigned long p_pos, unsigned long p_end);
|
||||
};
|
||||
|
||||
inline const QVector<VElementRegion> &HGMarkdownHighlighter::getHeaderRegions() const
|
||||
{
|
||||
return m_headerRegions;
|
||||
}
|
||||
|
||||
inline const QSet<int> &HGMarkdownHighlighter::getPossiblePreviewBlocks() const
|
||||
{
|
||||
return m_possiblePreviewBlocks;
|
||||
}
|
||||
|
||||
inline void HGMarkdownHighlighter::clearPossiblePreviewBlocks(const QVector<int> &p_blocksToClear)
|
||||
{
|
||||
for (auto i : p_blocksToClear) {
|
||||
m_possiblePreviewBlocks.remove(i);
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,45 +0,0 @@
|
||||
#include "lineeditdelegate.h"
|
||||
|
||||
#include <QLineEdit>
|
||||
|
||||
|
||||
LineEditDelegate::LineEditDelegate(QObject *p_parent)
|
||||
: QStyledItemDelegate(p_parent)
|
||||
{
|
||||
}
|
||||
|
||||
QWidget *LineEditDelegate::createEditor(QWidget *p_parent,
|
||||
const QStyleOptionViewItem &p_option,
|
||||
const QModelIndex &p_index) const
|
||||
{
|
||||
Q_UNUSED(p_option);
|
||||
Q_UNUSED(p_index);
|
||||
|
||||
QLineEdit *edit = new QLineEdit(p_parent);
|
||||
return edit;
|
||||
}
|
||||
|
||||
void LineEditDelegate::setEditorData(QWidget *p_editor, const QModelIndex &p_index) const
|
||||
{
|
||||
QString text = p_index.model()->data(p_index, Qt::EditRole).toString();
|
||||
|
||||
QLineEdit *edit = static_cast<QLineEdit *>(p_editor);
|
||||
edit->setText(text);
|
||||
}
|
||||
|
||||
void LineEditDelegate::setModelData(QWidget *p_editor,
|
||||
QAbstractItemModel *p_model,
|
||||
const QModelIndex &p_index) const
|
||||
{
|
||||
QLineEdit *edit = static_cast<QLineEdit *>(p_editor);
|
||||
|
||||
p_model->setData(p_index, edit->text(), Qt::EditRole);
|
||||
}
|
||||
|
||||
void LineEditDelegate::updateEditorGeometry(QWidget *p_editor,
|
||||
const QStyleOptionViewItem &p_option,
|
||||
const QModelIndex &p_index) const
|
||||
{
|
||||
Q_UNUSED(p_index);
|
||||
p_editor->setGeometry(p_option.rect);
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
#ifndef LINEEDITDELEGATE_H
|
||||
#define LINEEDITDELEGATE_H
|
||||
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
|
||||
class LineEditDelegate : public QStyledItemDelegate
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
LineEditDelegate(QObject *p_parent = nullptr);
|
||||
|
||||
QWidget *createEditor(QWidget *p_parent,
|
||||
const QStyleOptionViewItem &p_option,
|
||||
const QModelIndex &p_index) const Q_DECL_OVERRIDE;
|
||||
|
||||
void setEditorData(QWidget *p_editor, const QModelIndex &p_index) const Q_DECL_OVERRIDE;
|
||||
|
||||
void setModelData(QWidget *p_editor,
|
||||
QAbstractItemModel *p_model,
|
||||
const QModelIndex &p_index) const Q_DECL_OVERRIDE;
|
||||
|
||||
void updateEditorGeometry(QWidget *p_editor,
|
||||
const QStyleOptionViewItem &p_option,
|
||||
const QModelIndex &p_index) const Q_DECL_OVERRIDE;
|
||||
};
|
||||
|
||||
#endif // LINEEDITDELEGATE_H
|
188
src/main.cpp
@ -1,188 +0,0 @@
|
||||
#include "vmainwindow.h"
|
||||
#include <QApplication>
|
||||
#include <QTranslator>
|
||||
#include <QDebug>
|
||||
#include <QLibraryInfo>
|
||||
#include <QFile>
|
||||
#include <QTextCodec>
|
||||
#include <QFileInfo>
|
||||
#include <QStringList>
|
||||
#include <QDir>
|
||||
#include <QSslSocket>
|
||||
#include "utils/vutils.h"
|
||||
#include "vsingleinstanceguard.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "vpalette.h"
|
||||
|
||||
VConfigManager *g_config;
|
||||
|
||||
VPalette *g_palette;
|
||||
|
||||
#if defined(QT_NO_DEBUG)
|
||||
// 5MB log size.
|
||||
#define MAX_LOG_SIZE 5 * 1024 * 1024
|
||||
|
||||
QFile g_logFile;
|
||||
|
||||
static void initLogFile(const QString &p_file)
|
||||
{
|
||||
g_logFile.setFileName(p_file);
|
||||
if (g_logFile.size() >= MAX_LOG_SIZE) {
|
||||
g_logFile.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||
} else {
|
||||
g_logFile.open(QIODevice::Append | QIODevice::Text);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void VLogger(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||
{
|
||||
QByteArray localMsg = msg.toUtf8();
|
||||
QString header;
|
||||
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
header = "Debug:";
|
||||
break;
|
||||
|
||||
case QtInfoMsg:
|
||||
header = "Info:";
|
||||
break;
|
||||
|
||||
case QtWarningMsg:
|
||||
header = "Warning:";
|
||||
break;
|
||||
|
||||
case QtCriticalMsg:
|
||||
header = "Critical:";
|
||||
break;
|
||||
|
||||
case QtFatalMsg:
|
||||
header = "Fatal:";
|
||||
}
|
||||
|
||||
#if defined(QT_NO_DEBUG)
|
||||
Q_UNUSED(context);
|
||||
|
||||
QTextStream stream(&g_logFile);
|
||||
|
||||
stream << header << localMsg << "\n";
|
||||
|
||||
if (type == QtFatalMsg) {
|
||||
g_logFile.close();
|
||||
abort();
|
||||
}
|
||||
|
||||
#else
|
||||
std::string fileStr = QFileInfo(context.file).fileName().toStdString();
|
||||
const char *file = fileStr.c_str();
|
||||
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
fprintf(stderr, "%s(%s:%u) %s\n",
|
||||
header.toStdString().c_str(), file, context.line, localMsg.constData());
|
||||
break;
|
||||
case QtInfoMsg:
|
||||
fprintf(stderr, "%s(%s:%u) %s\n",
|
||||
header.toStdString().c_str(), file, context.line, localMsg.constData());
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
fprintf(stderr, "%s(%s:%u) %s\n",
|
||||
header.toStdString().c_str(), file, context.line, localMsg.constData());
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
fprintf(stderr, "%s(%s:%u) %s\n",
|
||||
header.toStdString().c_str(), file, context.line, localMsg.constData());
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
fprintf(stderr, "%s(%s:%u) %s\n",
|
||||
header.toStdString().c_str(), file, context.line, localMsg.constData());
|
||||
abort();
|
||||
}
|
||||
|
||||
fflush(stderr);
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
VSingleInstanceGuard guard;
|
||||
bool canRun = guard.tryRun();
|
||||
|
||||
QTextCodec *codec = QTextCodec::codecForName("UTF8");
|
||||
if (codec) {
|
||||
QTextCodec::setCodecForLocale(codec);
|
||||
}
|
||||
|
||||
QApplication app(argc, argv);
|
||||
|
||||
// The file path passed via command line arguments.
|
||||
QStringList filePaths = VUtils::filterFilePathsToOpen(app.arguments().mid(1));
|
||||
|
||||
if (!canRun) {
|
||||
// Ask another instance to open files passed in.
|
||||
if (!filePaths.isEmpty()) {
|
||||
guard.openExternalFiles(filePaths);
|
||||
} else {
|
||||
guard.showInstance();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
VConfigManager vconfig;
|
||||
vconfig.initialize();
|
||||
g_config = &vconfig;
|
||||
|
||||
#if defined(QT_NO_DEBUG)
|
||||
initLogFile(vconfig.getLogFilePath());
|
||||
#endif
|
||||
|
||||
qInstallMessageHandler(VLogger);
|
||||
|
||||
QString locale = VUtils::getLocale();
|
||||
// Set default locale.
|
||||
if (locale == "zh_CN") {
|
||||
QLocale::setDefault(QLocale(QLocale::Chinese, QLocale::China));
|
||||
}
|
||||
|
||||
qDebug() << "command line arguments" << app.arguments();
|
||||
qDebug() << "files to open from arguments" << filePaths;
|
||||
|
||||
// Check the openSSL.
|
||||
qDebug() << "openSSL" << QSslSocket::sslLibraryBuildVersionString()
|
||||
<< QSslSocket::sslLibraryVersionNumber();
|
||||
|
||||
// load translation for Qt
|
||||
QTranslator qtTranslator;
|
||||
if (!qtTranslator.load("qt_" + locale, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
|
||||
qtTranslator.load("qt_" + locale, "translations");
|
||||
}
|
||||
|
||||
app.installTranslator(&qtTranslator);
|
||||
|
||||
// load translation for vnote
|
||||
QTranslator translator;
|
||||
if (translator.load("vnote_" + locale, ":/translations")) {
|
||||
app.installTranslator(&translator);
|
||||
}
|
||||
|
||||
VPalette palette(g_config->getThemeFile());
|
||||
g_palette = &palette;
|
||||
|
||||
VMainWindow w(&guard);
|
||||
QString style = palette.fetchQtStyleSheet();
|
||||
if (!style.isEmpty()) {
|
||||
app.setStyleSheet(style);
|
||||
}
|
||||
|
||||
w.show();
|
||||
|
||||
w.openStartupPages();
|
||||
|
||||
w.openFiles(filePaths);
|
||||
|
||||
w.promptNewNotebookIfEmpty();
|
||||
|
||||
return app.exec();
|
||||
}
|
@ -1,143 +0,0 @@
|
||||
# Markdown Guide
|
||||
This is a quick guide [^1] for Markdown, a lightweight and easy-to-use syntax for writing.
|
||||
|
||||
## What is Markdown?
|
||||
Markdown is a way to style text via a few simple marker characters. You could write the document in plain text and then read it with a beautiful typesetting.
|
||||
|
||||
There is no a standard Markdown syntax and many editor will add its own additional syntax. VNote supports only the widely-used basic syntax.
|
||||
|
||||
## How to Use Markdown?
|
||||
If you are new to Markdown, it is better to learn the syntax elements step by step. Knowing headers and emphasis is enough to make you survive. You could learn another new syntax and practise it every one or two days.
|
||||
|
||||
## Syntax Guide
|
||||
Here is an overview of Markdown syntax supported by VNote.
|
||||
|
||||
### Headers
|
||||
```md
|
||||
# This is an <h1> tag
|
||||
## This is an <h2> tag
|
||||
###### This is an <h6> tag
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
|
||||
- At least one space is needed after the `#`;
|
||||
- A header should occupy one entire line;
|
||||
|
||||
### Emphasis
|
||||
```md
|
||||
*This text will be italic*
|
||||
_This text will be italic_
|
||||
|
||||
**This text will be bold**
|
||||
__This text will be bold__
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
|
||||
- `*` is recommended in VNote;
|
||||
- If the render failed, try to add an additional space before the first `*` and after the last `*`. The space is necessary if the surrounded text begins or ends with full width punctuation;
|
||||
- VNote provides shortcuts `Ctrl+I` and `Ctrl+B` to insert italic and bold text;
|
||||
|
||||
### Lists
|
||||
#### Unordered
|
||||
```md
|
||||
* Item 1
|
||||
This is a text under Item 1. Notice that there are two spaces at the end above.
|
||||
* Item 2
|
||||
* Item 2a
|
||||
* Item 2b
|
||||
* Item 3
|
||||
|
||||
To end a list, there should be one empty line above.
|
||||
```
|
||||
|
||||
#### Ordered
|
||||
```md
|
||||
1. Item 1
|
||||
1. Item 2
|
||||
Notice that the sequence number is irrelevant. Markdown will change the sequence automatically when renderring.
|
||||
3. Item 3
|
||||
1. Item 3a
|
||||
2. Item 3b
|
||||
4. Item 4
|
||||
```
|
||||
|
||||
### Images and Links
|
||||
```md
|
||||

|
||||
|
||||
[Link Text](/url/of/the/link)
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
|
||||
- It is not recommended to use image links in reference format. VNote will not preview those images.
|
||||
|
||||
### Blockquotes
|
||||
```md
|
||||
As VNote suggests:
|
||||
|
||||
> VNote is the best Markdown note-taking application
|
||||
> ever.
|
||||
>
|
||||
> THere is two spaces after `ever.` above to insert a
|
||||
> new line.
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
|
||||
- Space is needed after the marker `>`;
|
||||
- You could just add only one `>` at the first line;
|
||||
|
||||
### Fenced Code Block
|
||||
```lang
|
||||
This is a fenced code block.
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
|
||||
- `lang` is optional to specify the language of the code;
|
||||
|
||||
### Inline Code
|
||||
```md
|
||||
Here is a `inline code`.
|
||||
```
|
||||
|
||||
### Strikethrough
|
||||
```md
|
||||
Here is a ~~text~~ with strikethrough.
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
|
||||
- VNote provides shortcuts `Ctrl+D` to insert text with strikethrough;
|
||||
|
||||
### Task Lists
|
||||
```md
|
||||
- [x] this is a complete item.
|
||||
- [ ] this is an incomplete item.
|
||||
```
|
||||
|
||||
### Subscript and Superscript
|
||||
```md
|
||||
This is a text with subscript H~2~o.
|
||||
|
||||
This is a text with superscript 29^th^.
|
||||
```
|
||||
|
||||
### Footnote
|
||||
```md
|
||||
This is a footnote [^1].
|
||||
|
||||
[^1]: Here is the detail of the footnote.
|
||||
```
|
||||
|
||||
### New Line and Paragraph
|
||||
If you want to enter a new line, you should add two spaces after current line and then continue your input. VNote provides `Shift+Enter` to help.
|
||||
|
||||
If you want to enter a new paragraph, you should add an empty line and then continue entering the new paragraph.
|
||||
|
||||
Generally, you need to add an empty line after a block element (such as code block, lists, blockquote) to explicitly end it.
|
||||
|
||||
[^1]: This guide references [Mastering Markdown](https://guides.github.com/features/mastering-markdown/).
|