├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── README.md ├── build_on_mac.sh ├── debian ├── changelog ├── compat ├── control ├── copyright ├── docs ├── rules └── source │ └── format ├── icons.h ├── marknoto ├── CMakeLists.txt ├── Home.md ├── data │ ├── MacOSXBundleInfo.plist.in │ ├── index.html │ ├── marknoto.qrc │ ├── marknoto.rc │ ├── qwebchannel.js │ └── style.css ├── icon.rc ├── iconfilter.cpp ├── iconfilter.h ├── icons │ ├── 128-apps-marknoto.png │ ├── 16-apps-marknoto.png │ ├── 32-apps-marknoto.png │ ├── 48-apps-marknoto.png │ ├── 64-apps-marknoto.png │ ├── CMakeLists.txt │ └── marknoto128.ico ├── listitemdelegate.cpp ├── listitemdelegate.h ├── listpanel.cpp ├── listpanel.h ├── main.cpp ├── mainview.cpp ├── mainview.h ├── marknoto.cpp ├── marknoto.desktop ├── marknoto.h ├── metadata.cpp ├── metadata.h ├── navpanel.cpp ├── navpanel.h ├── noteview.cpp ├── noteview.h ├── panel.cpp ├── panel.h ├── po │ ├── CMakeLists.txt │ ├── Messages.sh │ ├── marknoto.pot │ └── zh_CN.po ├── taglist.cpp ├── taglist.h ├── terminalpanel.cpp └── terminalpanel.h ├── markpado ├── CMakeLists.txt ├── data │ ├── MacOSXBundleInfo.plist.in │ ├── index.html │ ├── markpado.qrc │ ├── markpado.rc │ ├── qwebchannel.js │ └── style.css ├── document.cpp ├── document.h ├── highlighterbykate.cpp ├── highlighterbykate.h ├── htmlgenerator.cpp ├── htmlgenerator.h ├── icon.rc ├── icons │ ├── 128-apps-markpado.png │ ├── 16-apps-markpado.png │ ├── 32-apps-markpado.png │ ├── 48-apps-markpado.png │ ├── 64-apps-markpado.png │ ├── CMakeLists.txt │ └── markpado128.ico ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── markpado.cpp ├── markpado.desktop ├── markpado.h ├── po │ ├── CMakeLists.txt │ ├── Messages.sh │ ├── markpado.pot │ └── zh_CN.po ├── webpage.cpp └── webpage.h └── screenshot └── render_code.png /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .kde4/ 3 | kmarknote.kdev4 4 | .directory 5 | *~ 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "markpado/kate-commonmark"] 2 | path = markpado/kate-commonmark 3 | url = https://github.com/sadhen/kate-commonmark.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.8.12 FATAL_ERROR) 2 | 3 | project (marketo) 4 | 5 | if (WIN32) 6 | elseif (APPLE) 7 | else (WIN32) 8 | add_definitions("-DUNIX") 9 | set (CMAKE_CXX_FLAGS "-std=c++11") 10 | endif (WIN32) 11 | 12 | find_package (ECM 1.7.0 REQUIRED NO_MODULE) 13 | set (CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) 14 | include (FeatureSummary) 15 | include (ECMAddAppIcon) 16 | include (KDEInstallDirs) 17 | include (KDECMakeSettings) 18 | include (KDECompilerSettings) 19 | include (ECMInstallIcons) 20 | 21 | find_package(Qt5 REQUIRED 22 | Core 23 | WebEngineWidgets 24 | WebChannel 25 | ) 26 | 27 | if (WIN32) 28 | find_package (KF5 REQUIRED COMPONENTS 29 | I18n 30 | CoreAddons 31 | XmlGui 32 | TextEditor 33 | ) 34 | elseif (APPLE) 35 | find_package (KF5 REQUIRED COMPONENTS 36 | I18n 37 | CoreAddons 38 | XmlGui 39 | TextEditor 40 | ) 41 | else (WIN32) 42 | find_package (KF5 REQUIRED COMPONENTS 43 | I18n 44 | CoreAddons 45 | XmlGui 46 | TextEditor 47 | FileMetaData 48 | ) 49 | endif (WIN32) 50 | 51 | #include_directories( ${QT_INCLUDES} 52 | # /usr/include/KF5/KCoreAddons 53 | # /usr/include/KF5/XmlGui 54 | # /usr/include/KF5/TextEditor 55 | # /usr/include/KF5/KI18n 56 | #) 57 | 58 | add_subdirectory (markpado) 59 | add_subdirectory (marknoto) 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Marketo 2 | 3 | A KDE application for note taking with the power of lightweight markup language. 4 | 5 | It is bundled with a separated Markdown editor. 6 | 7 | ## Features 8 | + Use **katepart** as the default editor (cool **vim mode**) 9 | + Partially support **CommonMark** 10 | + Live Preview(auto sync between two views) 11 | + Real-time fenced-code highlight 12 | + MathJax support 13 | 14 | ![Rendering when editing](screenshot/render_code.png) 15 | 16 | 17 | ## Planning 18 | + Integrated with baloo for search 19 | + Git support 20 | + Support blog post generating 21 | 22 | ## For User 23 | 24 | See [Installation Guide](https://github.com/sadhen/marketo/wiki/Install-Guide) to install Marketo. 25 | 26 | Starting Marknoto, you will see the guide - [The Home Note](marknoto/Home.md). 27 | 28 | ## For Developer 29 | 30 | ### Dependencies 31 | 32 | You need to install [libmdcpp](https://github.com/sadhen/libmdcpp) first. 33 | 34 | Also you need to install many other dependencies for compiling. Please see the CMakeLists.txt file for more info. 35 | 36 | ### Compile 37 | 38 | Then follow these instructions: 39 | ``` bash 40 | cd /tmp 41 | git clone https://github.com/sadhen/marketo.git 42 | cd marketo 43 | git submodule init 44 | git submodule update 45 | mkdir build 46 | cd build 47 | cmake -DCMAKE_INSTALL_PREFIX=$HOME/.local .. 48 | make 49 | make install 50 | ``` 51 | 52 | **NOTE**: `$HOME` is your home directory. The above instructions will install Marketo in `$HOME/.local`. 53 | 54 | ### Run it 55 | ``` bash 56 | cd ~/.local/bin/ 57 | ./marknoto # this will launch the note-taking application 58 | ./markpado # this will launch the Markdown editor 59 | ``` 60 | 61 | ## License 62 | ![GPL v3](http://www.gnu.org/graphics/gplv3-127x51.png) 63 | 64 | Copyright © 2014,2015 Darcy Shen 65 | -------------------------------------------------------------------------------- /build_on_mac.sh: -------------------------------------------------------------------------------- 1 | cmake .. -DCMAKE_PREFIX_PATH=/usr/local/opt/qt5 \ 2 | -DCMAKE_INSTALL_PREFIX=~/software 3 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | marketo (0.1-1) unstable; urgency=low 2 | 3 | * Initial release (Closes: #nnnn) 4 | 5 | -- Darcy Shen Thu, 26 Nov 2015 21:26:45 +0800 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: marketo 2 | Section: kde 3 | Priority: optional 4 | Maintainer: Darcy Shen 5 | Build-Depends: debhelper (>= 9), cmake, qt5-default, libqt5webkit5-dev, extra-cmake-modules, libkf5i18n-dev, libkf5coreaddons-dev, libkf5xmlgui-dev, libkf5texteditor-dev, libkf5webkit-dev, libkf5filemetadata-dev, libjs-mathjax, libmdcpp-dev 6 | Standards-Version: 3.9.6 7 | Homepage: https://github.com/sadhen/marketo 8 | #Vcs-Git: git://anonscm.debian.org/collab-maint/marketo.git 9 | #Vcs-Browser: https://anonscm.debian.org/gitweb/?p=collab-maint/marketo.git;a=summary 10 | 11 | Package: marketo 12 | Architecture: any 13 | Depends: breeze-icon-theme, ${shlibs:Depends}, ${misc:Depends} 14 | Description: A note-taking KDE application with the power of lightweight markup language 15 | Marketo is a note-taking app using Markdown with the features: 16 | * Use katepart as the default editor(cool vim mode) 17 | * Support CommonMark 18 | * Live highlighting of fenced code 19 | * Live Preview(auto sync between two views) 20 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: marketo 3 | Source: https://github.com/sadhen/marketo 4 | 5 | Files: * 6 | Copyright: 2015 Darcy Shen 7 | License: GPL-3 8 | 9 | Files: debian/* 10 | Copyright: 2015 Darcy Shen 11 | License: GPL-2+ 12 | This package is free software; you can redistribute it and/or modify 13 | it under the terms of the GNU General Public License as published by 14 | the Free Software Foundation; either version 2 of the License, or 15 | (at your option) any later version. 16 | . 17 | This package is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | . 22 | You should have received a copy of the GNU General Public License 23 | along with this program. If not, see 24 | . 25 | On Debian systems, the complete text of the GNU General 26 | Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". 27 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # See debhelper(7) (uncomment to enable) 3 | # output every command that modifies files on the build system. 4 | #export DH_VERBOSE = 1 5 | 6 | # see EXAMPLES in dpkg-buildflags(1) and read /usr/share/dpkg/* 7 | DPKG_EXPORT_BUILDFLAGS = 1 8 | include /usr/share/dpkg/default.mk 9 | 10 | # see FEATURE AREAS in dpkg-buildflags(1) 11 | #export DEB_BUILD_MAINT_OPTIONS = hardening=+all 12 | 13 | # see ENVIRONMENT in dpkg-buildflags(1) 14 | # package maintainers to append CFLAGS 15 | #export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic 16 | # package maintainers to append LDFLAGS 17 | #export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed 18 | 19 | 20 | # main packaging script based on dh7 syntax 21 | %: 22 | dh $@ 23 | 24 | # dh_make generated override targets 25 | # This is example for Cmake (See https://bugs.debian.org/641051 ) 26 | #override_dh_auto_configure: 27 | # dh_auto_configure -- \ 28 | # -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH) 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /icons.h: -------------------------------------------------------------------------------- 1 | /* This file is part of the KDE project 2 | Copyright (C) 2015 Christoph Cullmann 3 | 4 | This library is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU Library General Public 6 | License as published by the Free Software Foundation; either 7 | version 2 of the License, or (at your option) any later version. 8 | 9 | This library is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Library General Public License for more details. 13 | 14 | You should have received a copy of the GNU Library General Public License 15 | along with this library; see the file COPYING.LIB. If not, write to 16 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 17 | Boston, MA 02110-1301, USA. 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | /** 31 | * If we have some local breeze icon resource, prefer it 32 | */ 33 | static void setupIconTheme() 34 | { 35 | /** 36 | * let QStandardPaths handle this, it will look for app local stuff 37 | * this means e.g. for mac: "/../Resources" and for win: "/data" 38 | */ 39 | const QString breezeIcons = QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral("breeze.rcc")); 40 | if (!breezeIcons.isEmpty() && QFile::exists(breezeIcons) && QResource::registerResource(breezeIcons)) { 41 | // tell qt about the theme 42 | QIcon::setThemeSearchPaths(QStringList() << QStringLiteral(":/icons")); 43 | QIcon::setThemeName(QStringLiteral("breeze")); 44 | 45 | // tell KIconLoader an co. about the theme 46 | KConfigGroup cg(KSharedConfig::openConfig(), "Icons"); 47 | cg.writeEntry("Theme", "breeze"); 48 | cg.sync(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /marknoto/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set (CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) 2 | 3 | if (WIN32) 4 | set(marknoto_ICON icon.rc) 5 | endif (WIN32) 6 | 7 | set (marknoto_SRCS 8 | main.cpp 9 | marknoto.cpp 10 | mainview.cpp 11 | noteview.cpp 12 | panel.cpp 13 | iconfilter.cpp 14 | taglist.cpp 15 | metadata.cpp 16 | navpanel.cpp 17 | listpanel.cpp 18 | listitemdelegate.cpp 19 | ../markpado/markpado.cpp 20 | ../markpado/highlighterbykate.cpp 21 | ../markpado/htmlgenerator.cpp 22 | ../markpado/document.cpp 23 | ../markpado/webpage.cpp 24 | icon.rc 25 | ) 26 | qt5_add_resources(marknoto_SRCS data/marknoto.qrc) 27 | 28 | add_executable (marknoto ${marknoto_SRCS}) 29 | add_subdirectory (icons) 30 | add_subdirectory (po) 31 | 32 | include_directories (../markpado/) 33 | include_directories( ${QT_INCLUDES} 34 | /usr/include/KF5/KDEWebKit 35 | /usr/include/KF5/KFileMetaData 36 | /usr/include/KF5/BalooWidgets) 37 | 38 | 39 | if (WIN32) 40 | include_directories (${CMAKE_INSTALL_PREFIX}/include/) 41 | target_link_libraries (marknoto 42 | ${CMAKE_INSTALL_PREFIX}/lib/mdcpp.lib 43 | C:/local/boost_1_60_0/lib64-msvc-14.0/libboost_regex-vc140-mt-1_60.lib 44 | Qt5::WebEngineWidgets 45 | Qt5::WebChannel 46 | KF5::TextEditor 47 | KF5::I18n 48 | KF5::XmlGui 49 | ) 50 | elseif (APPLE) 51 | target_link_libraries (marknoto 52 | mdcpp 53 | Qt5::WebEngineWidgets 54 | Qt5::WebChannel 55 | KF5::TextEditor 56 | KF5::I18n 57 | KF5::XmlGui 58 | ) 59 | else (WIN32) 60 | target_link_libraries (marknoto 61 | mdcpp 62 | Qt5::WebEngineWidgets 63 | Qt5::WebChannel 64 | KF5::TextEditor 65 | KF5::I18n 66 | KF5::XmlGui 67 | KF5::FileMetaData 68 | ) 69 | endif (WIN32) 70 | 71 | if(APPLE) 72 | # own plist template 73 | set_target_properties (marknoto PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/data/MacOSXBundleInfo.plist.in) 74 | 75 | # the MacOSX bundle display name property (CFBundleDisplayName) is not currently supported by cmake, 76 | # so has to be set for all targets in this cmake file 77 | set(MACOSX_BUNDLE_DISPLAY_NAME Marknoto) 78 | set_target_properties(marknoto PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "com.sadhen.kde.Marknoto") 79 | set_target_properties(marknoto PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Marknoto") 80 | set_target_properties(marknoto PROPERTIES MACOSX_BUNDLE_DISPLAY_NAME "Marknoto") 81 | set_target_properties(marknoto PROPERTIES MACOSX_BUNDLE_INFO_STRING "Marknoto - Markdown Note-taking Application") 82 | set_target_properties(marknoto PROPERTIES MACOSX_BUNDLE_LONG_VERSION_STRING "Marknoto ${KDE_APPLICATIONS_VERSION}") 83 | set_target_properties(marknoto PROPERTIES MACOSX_BUNDLE_SHORT_VERSION_STRING "${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}") 84 | set_target_properties(marknoto PROPERTIES MACOSX_BUNDLE_BUNDLE_VERSION "${KDE_APPLICATIONS_VERSION}") 85 | set_target_properties(marknoto PROPERTIES MACOSX_BUNDLE_COPYRIGHT "2000-2017 Darcy Shen") 86 | endif() 87 | 88 | install(TARGETS marknoto ${INSTALL_TARGETS_DEFAULT_ARGS}) 89 | install(PROGRAMS marknoto.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) 90 | install(FILES Home.md DESTINATION ${DATA_INSTALL_DIR}/marknoto) 91 | -------------------------------------------------------------------------------- /marknoto/Home.md: -------------------------------------------------------------------------------- 1 |  4 | 5 | # Home of Marketo 6 | Hi, this is the home of Marketo. When you first start Marketo or click the home button on the toolbar, you will go home! 7 | 8 | ## Customize 9 | ### Starred Notes 10 | [Home.md](/Home.md) 11 | 12 | ### TODO List 13 | 1. Make a new folder in the Navigator 14 | 2. Make a sub folder in the created catogary. 15 | 3. See what happened in the file system 16 | 4. Creat a new note in `$NOTEDIR`/`$CREATED`/`$SUB_CREATED` 17 | 5. Add tags for the newly created note 18 | 6. Find these tags in the Navigator and in the Dolphin file explorer 19 | ... 20 | 21 | ### DONE List 22 | 23 | ## Document 24 | ### Metadata 25 | The metadata part starts with ``. You must put your metadata(tags, date, licence...) at the starting of the document for the parser to parse. 26 | 27 | ### MathJax 28 | $$J_\alpha(x) = \sum_{m=0}^\infty \frac{(-1)^m}{m! \Gamma (m + \alpha + 1)} {\left({ \frac{x}{2} }\right)}^{2m + \alpha}$$ 29 | 30 | ### Real-time Highlighting 31 | Below is a list of languages supporting real-time highlighting: 32 | ``` cpp 33 | map mimeMap = { 34 | {"dot", "dot"}, 35 | {"mysql", "SQL (MySQL)"}, 36 | {"ocaml", "Objective Caml"}, 37 | // by name 38 | {"cmake", "CMake"}, 39 | {"diff", "Diff"}, 40 | {"dockerfile", "Dockerfile"}, 41 | {"PHP", "PHP/PHP"}, 42 | {"R", "R Script"}, 43 | // by suffix 44 | {"awk", "AWK"}, 45 | {"c", "C"}, 46 | {"clj", "Clojure"}, 47 | {"cpp", "C++"}, 48 | {"css", "CSS"}, 49 | {"erl", "Erlang"}, 50 | {"for", "Fortran"}, 51 | {"fpp", "Fortran"}, 52 | {"go", "Go"}, 53 | {"hs", "Haskell"}, 54 | {"ini", "INI Files"}, 55 | {"java", "Java"}, 56 | {"jl", "Julia"}, 57 | {"js", "JavaScript"}, 58 | {"json", "JSON"}, 59 | {"lua", "Lua"}, 60 | {"mk", "Makefile"}, 61 | {"php", "PHP/PHP"}, 62 | {"py", "Python"}, 63 | {"qml", "QML"}, 64 | {"r", "R Script"}, 65 | {"rb", "Ruby"}, 66 | {"rs", "Rust"}, 67 | {"scala", "Scala"}, 68 | {"scm", "Scheme"}, 69 | {"sql", "SQL"}, 70 | {"sh", "Bash"}, 71 | {"zsh", "Zsh"} 72 | }; 73 | ``` 74 | ## Contribute to Marketo 75 | See [https://github.com/sadhen/marketo](https://github.com/sadhen/marketo) for more info. 76 | -------------------------------------------------------------------------------- /marknoto/data/MacOSXBundleInfo.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrincipalClass 6 | NSApplication 7 | NSHighResolutionCapable 8 | True 9 | CFBundleDevelopmentRegion 10 | English 11 | CFBundleExecutable 12 | ${MACOSX_BUNDLE_EXECUTABLE_NAME} 13 | CFBundleGetInfoString 14 | ${MACOSX_BUNDLE_INFO_STRING} 15 | CFBundleIconFile 16 | ${MACOSX_BUNDLE_ICON_FILE} 17 | CFBundleIdentifier 18 | ${MACOSX_BUNDLE_GUI_IDENTIFIER} 19 | CFBundleInfoDictionaryVersion 20 | 6.0 21 | CFBundleLongVersionString 22 | ${MACOSX_BUNDLE_LONG_VERSION_STRING} 23 | CFBundleName 24 | ${MACOSX_BUNDLE_BUNDLE_NAME} 25 | CFBundlePackageType 26 | APPL 27 | CFBundleShortVersionString 28 | ${MACOSX_BUNDLE_SHORT_VERSION_STRING} 29 | CFBundleSignature 30 | ???? 31 | CFBundleVersion 32 | ${MACOSX_BUNDLE_BUNDLE_VERSION} 33 | CSResourcesFileMapped 34 | 35 | LSRequiresCarbon 36 | 37 | NSHumanReadableCopyright 38 | ${MACOSX_BUNDLE_COPYRIGHT} 39 | CFBundleDocumentTypes 40 | 41 | 42 | CFBundleTypeExtensions 43 | 44 | * 45 | 46 | CFBundleTypeName 47 | NSStringPboardType 48 | CFBundleTypeRole 49 | Editor 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /marknoto/data/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | 16 |
17 | 34 | 35 | -------------------------------------------------------------------------------- /marknoto/data/marknoto.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | marknoto.rc 5 | 6 | 7 | style.css 8 | 9 | 10 | qwebchannel.js 11 | 12 | 13 | index.html 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /marknoto/data/marknoto.rc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | &Go 9 | 10 | 11 | 12 | 13 | 14 | &Window 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /marknoto/data/qwebchannel.js: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: http://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** You may use this file under the terms of the BSD license as follows: 10 | ** 11 | ** "Redistribution and use in source and binary forms, with or without 12 | ** modification, are permitted provided that the following conditions are 13 | ** met: 14 | ** * Redistributions of source code must retain the above copyright 15 | ** notice, this list of conditions and the following disclaimer. 16 | ** * Redistributions in binary form must reproduce the above copyright 17 | ** notice, this list of conditions and the following disclaimer in 18 | ** the documentation and/or other materials provided with the 19 | ** distribution. 20 | ** * Neither the name of The Qt Company Ltd nor the names of its 21 | ** contributors may be used to endorse or promote products derived 22 | ** from this software without specific prior written permission. 23 | ** 24 | ** 25 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 27 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 28 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 29 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 30 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 31 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 32 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 33 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 36 | ** 37 | ** $QT_END_LICENSE$ 38 | ** 39 | ****************************************************************************/ 40 | 41 | "use strict"; 42 | 43 | var QWebChannelMessageTypes = { 44 | signal: 1, 45 | propertyUpdate: 2, 46 | init: 3, 47 | idle: 4, 48 | debug: 5, 49 | invokeMethod: 6, 50 | connectToSignal: 7, 51 | disconnectFromSignal: 8, 52 | setProperty: 9, 53 | response: 10, 54 | }; 55 | 56 | var QWebChannel = function(transport, initCallback) 57 | { 58 | if (typeof transport !== "object" || typeof transport.send !== "function") { 59 | console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." + 60 | " Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send)); 61 | return; 62 | } 63 | 64 | var channel = this; 65 | this.transport = transport; 66 | 67 | this.send = function(data) 68 | { 69 | if (typeof(data) !== "string") { 70 | data = JSON.stringify(data); 71 | } 72 | channel.transport.send(data); 73 | } 74 | 75 | this.transport.onmessage = function(message) 76 | { 77 | var data = message.data; 78 | if (typeof data === "string") { 79 | data = JSON.parse(data); 80 | } 81 | switch (data.type) { 82 | case QWebChannelMessageTypes.signal: 83 | channel.handleSignal(data); 84 | break; 85 | case QWebChannelMessageTypes.response: 86 | channel.handleResponse(data); 87 | break; 88 | case QWebChannelMessageTypes.propertyUpdate: 89 | channel.handlePropertyUpdate(data); 90 | break; 91 | default: 92 | console.error("invalid message received:", message.data); 93 | break; 94 | } 95 | } 96 | 97 | this.execCallbacks = {}; 98 | this.execId = 0; 99 | this.exec = function(data, callback) 100 | { 101 | if (!callback) { 102 | // if no callback is given, send directly 103 | channel.send(data); 104 | return; 105 | } 106 | if (channel.execId === Number.MAX_VALUE) { 107 | // wrap 108 | channel.execId = Number.MIN_VALUE; 109 | } 110 | if (data.hasOwnProperty("id")) { 111 | console.error("Cannot exec message with property id: " + JSON.stringify(data)); 112 | return; 113 | } 114 | data.id = channel.execId++; 115 | channel.execCallbacks[data.id] = callback; 116 | channel.send(data); 117 | }; 118 | 119 | this.objects = {}; 120 | 121 | this.handleSignal = function(message) 122 | { 123 | var object = channel.objects[message.object]; 124 | if (object) { 125 | object.signalEmitted(message.signal, message.args); 126 | } else { 127 | console.warn("Unhandled signal: " + message.object + "::" + message.signal); 128 | } 129 | } 130 | 131 | this.handleResponse = function(message) 132 | { 133 | if (!message.hasOwnProperty("id")) { 134 | console.error("Invalid response message received: ", JSON.stringify(message)); 135 | return; 136 | } 137 | channel.execCallbacks[message.id](message.data); 138 | delete channel.execCallbacks[message.id]; 139 | } 140 | 141 | this.handlePropertyUpdate = function(message) 142 | { 143 | for (var i in message.data) { 144 | var data = message.data[i]; 145 | var object = channel.objects[data.object]; 146 | if (object) { 147 | object.propertyUpdate(data.signals, data.properties); 148 | } else { 149 | console.warn("Unhandled property update: " + data.object + "::" + data.signal); 150 | } 151 | } 152 | channel.exec({type: QWebChannelMessageTypes.idle}); 153 | } 154 | 155 | this.debug = function(message) 156 | { 157 | channel.send({type: QWebChannelMessageTypes.debug, data: message}); 158 | }; 159 | 160 | channel.exec({type: QWebChannelMessageTypes.init}, function(data) { 161 | for (var objectName in data) { 162 | var object = new QObject(objectName, data[objectName], channel); 163 | } 164 | // now unwrap properties, which might reference other registered objects 165 | for (var objectName in channel.objects) { 166 | channel.objects[objectName].unwrapProperties(); 167 | } 168 | if (initCallback) { 169 | initCallback(channel); 170 | } 171 | channel.exec({type: QWebChannelMessageTypes.idle}); 172 | }); 173 | }; 174 | 175 | function QObject(name, data, webChannel) 176 | { 177 | this.__id__ = name; 178 | webChannel.objects[name] = this; 179 | 180 | // List of callbacks that get invoked upon signal emission 181 | this.__objectSignals__ = {}; 182 | 183 | // Cache of all properties, updated when a notify signal is emitted 184 | this.__propertyCache__ = {}; 185 | 186 | var object = this; 187 | 188 | // ---------------------------------------------------------------------- 189 | 190 | this.unwrapQObject = function(response) 191 | { 192 | if (response instanceof Array) { 193 | // support list of objects 194 | var ret = new Array(response.length); 195 | for (var i = 0; i < response.length; ++i) { 196 | ret[i] = object.unwrapQObject(response[i]); 197 | } 198 | return ret; 199 | } 200 | if (!response 201 | || !response["__QObject*__"] 202 | || response.id === undefined) { 203 | return response; 204 | } 205 | 206 | var objectId = response.id; 207 | if (webChannel.objects[objectId]) 208 | return webChannel.objects[objectId]; 209 | 210 | if (!response.data) { 211 | console.error("Cannot unwrap unknown QObject " + objectId + " without data."); 212 | return; 213 | } 214 | 215 | var qObject = new QObject( objectId, response.data, webChannel ); 216 | qObject.destroyed.connect(function() { 217 | if (webChannel.objects[objectId] === qObject) { 218 | delete webChannel.objects[objectId]; 219 | // reset the now deleted QObject to an empty {} object 220 | // just assigning {} though would not have the desired effect, but the 221 | // below also ensures all external references will see the empty map 222 | // NOTE: this detour is necessary to workaround QTBUG-40021 223 | var propertyNames = []; 224 | for (var propertyName in qObject) { 225 | propertyNames.push(propertyName); 226 | } 227 | for (var idx in propertyNames) { 228 | delete qObject[propertyNames[idx]]; 229 | } 230 | } 231 | }); 232 | // here we are already initialized, and thus must directly unwrap the properties 233 | qObject.unwrapProperties(); 234 | return qObject; 235 | } 236 | 237 | this.unwrapProperties = function() 238 | { 239 | for (var propertyIdx in object.__propertyCache__) { 240 | object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]); 241 | } 242 | } 243 | 244 | function addSignal(signalData, isPropertyNotifySignal) 245 | { 246 | var signalName = signalData[0]; 247 | var signalIndex = signalData[1]; 248 | object[signalName] = { 249 | connect: function(callback) { 250 | if (typeof(callback) !== "function") { 251 | console.error("Bad callback given to connect to signal " + signalName); 252 | return; 253 | } 254 | 255 | object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; 256 | object.__objectSignals__[signalIndex].push(callback); 257 | 258 | if (!isPropertyNotifySignal && signalName !== "destroyed") { 259 | // only required for "pure" signals, handled separately for properties in propertyUpdate 260 | // also note that we always get notified about the destroyed signal 261 | webChannel.exec({ 262 | type: QWebChannelMessageTypes.connectToSignal, 263 | object: object.__id__, 264 | signal: signalIndex 265 | }); 266 | } 267 | }, 268 | disconnect: function(callback) { 269 | if (typeof(callback) !== "function") { 270 | console.error("Bad callback given to disconnect from signal " + signalName); 271 | return; 272 | } 273 | object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; 274 | var idx = object.__objectSignals__[signalIndex].indexOf(callback); 275 | if (idx === -1) { 276 | console.error("Cannot find connection of signal " + signalName + " to " + callback.name); 277 | return; 278 | } 279 | object.__objectSignals__[signalIndex].splice(idx, 1); 280 | if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) { 281 | // only required for "pure" signals, handled separately for properties in propertyUpdate 282 | webChannel.exec({ 283 | type: QWebChannelMessageTypes.disconnectFromSignal, 284 | object: object.__id__, 285 | signal: signalIndex 286 | }); 287 | } 288 | } 289 | }; 290 | } 291 | 292 | /** 293 | * Invokes all callbacks for the given signalname. Also works for property notify callbacks. 294 | */ 295 | function invokeSignalCallbacks(signalName, signalArgs) 296 | { 297 | var connections = object.__objectSignals__[signalName]; 298 | if (connections) { 299 | connections.forEach(function(callback) { 300 | callback.apply(callback, signalArgs); 301 | }); 302 | } 303 | } 304 | 305 | this.propertyUpdate = function(signals, propertyMap) 306 | { 307 | // update property cache 308 | for (var propertyIndex in propertyMap) { 309 | var propertyValue = propertyMap[propertyIndex]; 310 | object.__propertyCache__[propertyIndex] = propertyValue; 311 | } 312 | 313 | for (var signalName in signals) { 314 | // Invoke all callbacks, as signalEmitted() does not. This ensures the 315 | // property cache is updated before the callbacks are invoked. 316 | invokeSignalCallbacks(signalName, signals[signalName]); 317 | } 318 | } 319 | 320 | this.signalEmitted = function(signalName, signalArgs) 321 | { 322 | invokeSignalCallbacks(signalName, signalArgs); 323 | } 324 | 325 | function addMethod(methodData) 326 | { 327 | var methodName = methodData[0]; 328 | var methodIdx = methodData[1]; 329 | object[methodName] = function() { 330 | var args = []; 331 | var callback; 332 | for (var i = 0; i < arguments.length; ++i) { 333 | if (typeof arguments[i] === "function") 334 | callback = arguments[i]; 335 | else 336 | args.push(arguments[i]); 337 | } 338 | 339 | webChannel.exec({ 340 | "type": QWebChannelMessageTypes.invokeMethod, 341 | "object": object.__id__, 342 | "method": methodIdx, 343 | "args": args 344 | }, function(response) { 345 | if (response !== undefined) { 346 | var result = object.unwrapQObject(response); 347 | if (callback) { 348 | (callback)(result); 349 | } 350 | } 351 | }); 352 | }; 353 | } 354 | 355 | function bindGetterSetter(propertyInfo) 356 | { 357 | var propertyIndex = propertyInfo[0]; 358 | var propertyName = propertyInfo[1]; 359 | var notifySignalData = propertyInfo[2]; 360 | // initialize property cache with current value 361 | // NOTE: if this is an object, it is not directly unwrapped as it might 362 | // reference other QObject that we do not know yet 363 | object.__propertyCache__[propertyIndex] = propertyInfo[3]; 364 | 365 | if (notifySignalData) { 366 | if (notifySignalData[0] === 1) { 367 | // signal name is optimized away, reconstruct the actual name 368 | notifySignalData[0] = propertyName + "Changed"; 369 | } 370 | addSignal(notifySignalData, true); 371 | } 372 | 373 | Object.defineProperty(object, propertyName, { 374 | configurable: true, 375 | get: function () { 376 | var propertyValue = object.__propertyCache__[propertyIndex]; 377 | if (propertyValue === undefined) { 378 | // This shouldn't happen 379 | console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__); 380 | } 381 | 382 | return propertyValue; 383 | }, 384 | set: function(value) { 385 | if (value === undefined) { 386 | console.warn("Property setter for " + propertyName + " called with undefined value!"); 387 | return; 388 | } 389 | object.__propertyCache__[propertyIndex] = value; 390 | webChannel.exec({ 391 | "type": QWebChannelMessageTypes.setProperty, 392 | "object": object.__id__, 393 | "property": propertyIndex, 394 | "value": value 395 | }); 396 | } 397 | }); 398 | 399 | } 400 | 401 | // ---------------------------------------------------------------------- 402 | 403 | data.methods.forEach(addMethod); 404 | 405 | data.properties.forEach(bindGetterSetter); 406 | 407 | data.signals.forEach(function(signal) { addSignal(signal, false); }); 408 | 409 | for (var name in data.enums) { 410 | object[name] = data.enums[name]; 411 | } 412 | } 413 | 414 | //required for use with nodejs 415 | if (typeof module === 'object') { 416 | module.exports = { 417 | QWebChannel: QWebChannel 418 | }; 419 | } 420 | -------------------------------------------------------------------------------- /marknoto/data/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | font-size: 24px; 3 | } 4 | h2 { 5 | font-size: 22px; 6 | } 7 | h3 { 8 | font-size: 20px; 9 | } 10 | h4 { 11 | font-size: 18px; 12 | } 13 | h5 { 14 | font-size: 16px; 15 | } 16 | h6 { 17 | font-size: 16px; 18 | } 19 | blockquote { 20 | border-left: 3px solid #D0E5F2; 21 | padding: 0 0 0 10px; 22 | margin: 15px 0 15px 15px; 23 | } 24 | code { 25 | display: inline-block; 26 | padding: 0 4px; 27 | margin: 0 5px; 28 | background: #eeeeee; 29 | border-radius: 3px; 30 | font-size: 13px; 31 | } 32 | pre { 33 | padding: 10px 5px 10px 10px; 34 | margin: 15px 0; 35 | display: block; 36 | line-height: 18px; 37 | background: #F0F0F0; 38 | border-radius: 3px; 39 | font-size: 13px; 40 | white-space: pre; 41 | word-wrap: normal; 42 | overflow-x: auto; 43 | } 44 | pre code { 45 | display: block; 46 | padding: 0; 47 | margin: 0; 48 | background: none; 49 | border-radius: 0; 50 | } 51 | hr { 52 | display: block; 53 | height: 0px; 54 | border: 0; 55 | border-top: 1px solid #ccc; 56 | margin: 15px 0; 57 | padding: 0; 58 | } 59 | table { 60 | width: 100%; 61 | table-layout: fixed; 62 | border-collapse: collapse; 63 | border-spacing: 0; 64 | margin: 15px 0; 65 | } 66 | table thead { 67 | background-color: #f9f9f9; 68 | } 69 | table td, table th, table td, table th { 70 | min-width: 40px; 71 | height: 30px; 72 | border: 1px solid #ccc; 73 | vertical-align: top; 74 | padding: 2px 4px; 75 | text-align: left; 76 | box-sizing: border-box; 77 | } 78 | 79 | p{ 80 | margin-bottom: 0; 81 | } 82 | p img{ 83 | margin-bottom: 7px; 84 | } 85 | -------------------------------------------------------------------------------- /marknoto/icon.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "icons/marknoto128.ico" -------------------------------------------------------------------------------- /marknoto/iconfilter.cpp: -------------------------------------------------------------------------------- 1 | #include "iconfilter.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | QIcon IconFilter::icon(IconType type) const 8 | { 9 | switch (type) { 10 | case QFileIconProvider::Folder: 11 | return QIcon::fromTheme(QLatin1String("folder-blue")); 12 | case QFileIconProvider::File: 13 | return QIcon::fromTheme(QLatin1String("text-markdown")); 14 | default: 15 | return QIcon::fromTheme(QLatin1String("text-markdown")); 16 | } 17 | } 18 | 19 | QIcon IconFilter::icon(const QFileInfo &info) const 20 | { 21 | return QIcon::fromTheme(type(info).toLatin1()); 22 | } 23 | 24 | QString IconFilter::type(const QFileInfo &info) const 25 | { 26 | if (info.isDir()) 27 | return QString("folder-blue"); 28 | if (info.suffix().isEmpty()) 29 | return QString("text-plain"); 30 | // TODO: using a global mime dict 31 | return QString("text-markdown"); 32 | } 33 | 34 | IconFilter::~IconFilter() 35 | { 36 | } 37 | -------------------------------------------------------------------------------- /marknoto/iconfilter.h: -------------------------------------------------------------------------------- 1 | #ifndef ICONFILTER_H 2 | #define ICONFILTER_H 3 | 4 | #include 5 | 6 | class QFileInfo; 7 | 8 | class IconFilter : public QFileIconProvider 9 | { 10 | public: 11 | IconFilter() = default; 12 | ~IconFilter(); 13 | 14 | QIcon icon(IconType type) const override; 15 | QIcon icon(const QFileInfo &info) const override; 16 | QString type(const QFileInfo &info) const override; 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /marknoto/icons/128-apps-marknoto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/da-liii/marketo/0ec2d643c4a6a3aec7ecfdcda72236a78de1635e/marknoto/icons/128-apps-marknoto.png -------------------------------------------------------------------------------- /marknoto/icons/16-apps-marknoto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/da-liii/marketo/0ec2d643c4a6a3aec7ecfdcda72236a78de1635e/marknoto/icons/16-apps-marknoto.png -------------------------------------------------------------------------------- /marknoto/icons/32-apps-marknoto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/da-liii/marketo/0ec2d643c4a6a3aec7ecfdcda72236a78de1635e/marknoto/icons/32-apps-marknoto.png -------------------------------------------------------------------------------- /marknoto/icons/48-apps-marknoto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/da-liii/marketo/0ec2d643c4a6a3aec7ecfdcda72236a78de1635e/marknoto/icons/48-apps-marknoto.png -------------------------------------------------------------------------------- /marknoto/icons/64-apps-marknoto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/da-liii/marketo/0ec2d643c4a6a3aec7ecfdcda72236a78de1635e/marknoto/icons/64-apps-marknoto.png -------------------------------------------------------------------------------- /marknoto/icons/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ecm_install_icons (ICONS 2 | 16-apps-marknoto.png 3 | 32-apps-marknoto.png 4 | 48-apps-marknoto.png 5 | 64-apps-marknoto.png 6 | 128-apps-marknoto.png 7 | DESTINATION ${ICON_INSTALL_DIR} 8 | THEME hicolor 9 | ) 10 | -------------------------------------------------------------------------------- /marknoto/icons/marknoto128.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/da-liii/marketo/0ec2d643c4a6a3aec7ecfdcda72236a78de1635e/marknoto/icons/marknoto128.ico -------------------------------------------------------------------------------- /marknoto/listitemdelegate.cpp: -------------------------------------------------------------------------------- 1 | #include "listpanel.h" 2 | #include "listitemdelegate.h" 3 | #include 4 | #include 5 | 6 | ListItemDelegate::ListItemDelegate(ListPanel* parent) 7 | { 8 | m_parent = parent; 9 | } 10 | 11 | void ListItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const 12 | { 13 | QString text(m_parent->getTitleByIndex(index)); 14 | 15 | QRect decorationRect = QRect(option.rect.topLeft()+QPoint(2, 0), QSize(24, 24)); 16 | QFont font; 17 | font.setFamily(font.defaultFamily()); 18 | QFontMetrics fm(font); 19 | QRect textRect = QRect(decorationRect.topRight()+QPoint(5, 2), QSize(fm.width(text)+5, fm.height()+4)); 20 | 21 | painter->setBackgroundMode(Qt::TransparentMode); 22 | painter->drawPixmap(decorationRect, QIcon::fromTheme(QLatin1String("text-markdown")).pixmap(QSize(24, 24))); 23 | painter->drawText(textRect, text); 24 | 25 | painter->setBackgroundMode(Qt::OpaqueMode); 26 | if (option.state & QStyle::State_Selected) 27 | painter->fillRect(option.rect, QColor(0, 128, 255, 60)); 28 | else if (option.state & QStyle::State_MouseOver) 29 | painter->fillRect(option.rect, QColor(0, 128, 255, 10)); 30 | else 31 | painter->fillRect(option.rect, QColor(255, 255, 255, 0)); 32 | } 33 | 34 | #include "listitemdelegate.moc" -------------------------------------------------------------------------------- /marknoto/listitemdelegate.h: -------------------------------------------------------------------------------- 1 | #ifndef LISTITEMDELEGATE_H 2 | #define LISTITEMDELEGATE_H 3 | 4 | #include 5 | 6 | class ListPanel; 7 | class QRect; 8 | 9 | class ListItemDelegate : public QStyledItemDelegate 10 | { 11 | Q_OBJECT 12 | public: 13 | explicit ListItemDelegate(ListPanel *parent); 14 | virtual void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const override; 15 | 16 | private: 17 | ListPanel *m_parent; 18 | }; 19 | 20 | #endif -------------------------------------------------------------------------------- /marknoto/listpanel.cpp: -------------------------------------------------------------------------------- 1 | #include "listpanel.h" 2 | #include "iconfilter.h" 3 | #include "listitemdelegate.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | ListPanel::ListPanel(QWidget* parent) 26 | : Panel(parent), 27 | m_parent(parent), 28 | displayByTag(false) 29 | { 30 | KConfigGroup cfg(KSharedConfig::openConfig(), "General Options"); 31 | 32 | m_pos = QPoint(0, 0); 33 | lmodel = new QFileSystemModel; 34 | lmodel->setRootPath(cfg.readEntry("NoteDir")); 35 | lmodel->setFilter(QDir::Files); 36 | 37 | m_filters << "*.md" << "*.cm"; 38 | lmodel->setNameFilters(m_filters); 39 | lmodel->setNameFilterDisables(false); 40 | 41 | smodel = new QStringListModel(QStringList(), this); 42 | 43 | listView = new QListView(this); 44 | listView->setModel(lmodel); 45 | m_delegate = new ListItemDelegate(this); 46 | listView->setItemDelegate(m_delegate); 47 | listView->setRootIndex(lmodel->index(cfg.readEntry("NoteDir"))); 48 | listView->setGridSize(QSize(listView->sizeHint().width(), 26)); 49 | listView->setAlternatingRowColors(true); 50 | listView->setContextMenuPolicy(Qt::CustomContextMenu); 51 | connect(listView, SIGNAL(clicked(QModelIndex)), 52 | this, SLOT(setUrlFromIndex(QModelIndex))); 53 | connect(listView, SIGNAL(customContextMenuRequested(const QPoint&)), 54 | this, SLOT(showContextMenu(const QPoint&))); 55 | 56 | vl = new QVBoxLayout(this); 57 | vl->addWidget(listView); 58 | } 59 | 60 | void ListPanel::setDisplayMode(int index) 61 | { 62 | displayByTag = (index!=0); 63 | } 64 | 65 | void ListPanel::setTaggedList(const QStringList& list) 66 | { 67 | smodel->setStringList(list); 68 | listView->setModel(smodel); 69 | displayByTag = true; 70 | } 71 | 72 | void ListPanel::goHome() 73 | { 74 | displayByTag = false; 75 | listView->setModel(lmodel); 76 | lmodel->setRootPath(removeLeadingSlash(url().path())); 77 | lmodel->setNameFilters(m_filters); 78 | listView->setRootIndex(lmodel->index(removeLeadingSlash(url().path()))); 79 | } 80 | 81 | void ListPanel::setUrlForLModel(const QUrl& url) 82 | { 83 | displayByTag = false; 84 | setUrl(url.adjusted(QUrl::RemoveFilename)); 85 | listView->scrollTo(lmodel->index(removeLeadingSlash(url.path()))); 86 | listView->setCurrentIndex(lmodel->index(removeLeadingSlash(url.path()))); 87 | } 88 | 89 | void ListPanel::setUrlFromIndex(const QModelIndex& index) 90 | { 91 | if (displayByTag) { 92 | listView->setModel(smodel); 93 | KConfigGroup cfg(KSharedConfig::openConfig(), "General Options"); 94 | QString halfPath = smodel->data(index, Qt::DisplayRole).toString(); 95 | halfPath = cfg.readEntry("NoteDir") + halfPath; 96 | setUrl(QUrl::fromLocalFile(halfPath)); 97 | } else { 98 | listView->setModel(lmodel); 99 | setUrl(QUrl::fromLocalFile(lmodel->filePath(index))); 100 | } 101 | } 102 | 103 | QString ListPanel::getTitleByIndex(const QModelIndex& index) 104 | { 105 | if (displayByTag) { 106 | QString halfPath = smodel->data(index, Qt::DisplayRole).toString(); 107 | return QUrl::fromLocalFile(halfPath).fileName(); 108 | } else { 109 | return lmodel->fileName(index); 110 | } 111 | } 112 | 113 | bool ListPanel::urlChanged() 114 | { 115 | if (displayByTag) 116 | listView->setModel(smodel); 117 | else 118 | listView->setModel(lmodel); 119 | if (QFileInfo(url().toLocalFile()).isDir() && !displayByTag) { 120 | lmodel->setRootPath(removeLeadingSlash(url().path())); 121 | lmodel->setNameFilters(m_filters); 122 | listView->setRootIndex(lmodel->index(removeLeadingSlash(url().path()))); 123 | } 124 | emit changeUrl(url()); 125 | return true; 126 | } 127 | 128 | void ListPanel::showContextMenu(const QPoint& pos) 129 | { 130 | if (displayByTag) 131 | return; 132 | m_pos = pos; 133 | QMenu *contextMenu = new QMenu(); 134 | QAction *newNoteAction = contextMenu->addAction(QString("New Note")); 135 | QAction *deleteNoteAction = contextMenu->addAction(QString("Delete Note")); 136 | QAction *copyNoteLinkAction = contextMenu->addAction(QString("Copy Note Link")); 137 | connect(newNoteAction, SIGNAL(triggered()), m_parent, SLOT(newNote())); 138 | connect(deleteNoteAction, SIGNAL(triggered()), this, SLOT(deleteNote())); 139 | connect(copyNoteLinkAction, SIGNAL(triggered()), this, SLOT(copyNoteLink())); 140 | 141 | if (contextMenu) { 142 | contextMenu->exec(QCursor::pos()); 143 | } 144 | delete contextMenu; 145 | } 146 | 147 | void ListPanel::deleteNote() 148 | { 149 | QModelIndex index = listView->indexAt(m_pos); 150 | QString filePath(lmodel->filePath(index)); 151 | 152 | if (filePath.isEmpty()) 153 | return ; 154 | 155 | QFile file(filePath); 156 | QFileInfo fileInfo(filePath); 157 | KConfigGroup cfg(KSharedConfig::openConfig(), "General Options"); 158 | QString trashFileName(cfg.readEntry("NoteDir")+"/Trash/"+ fileInfo.fileName()); 159 | if (filePath == trashFileName) 160 | QFile::remove(filePath); 161 | else 162 | file.rename(trashFileName); 163 | } 164 | 165 | void ListPanel::copyNoteLink() 166 | { 167 | QModelIndex index = listView->indexAt(m_pos); 168 | QString file(lmodel->filePath(index)); 169 | QUrl url(file); 170 | 171 | KConfigGroup cfg(KSharedConfig::openConfig(), "General Options"); 172 | QString rootPath(cfg.readEntry("NoteDir")); 173 | QClipboard *clipBoard = QApplication::clipboard(); 174 | clipBoard->clear(); 175 | clipBoard->setText("[" + url.fileName() + "](" 176 | + QString(url.toEncoded()).midRef(rootPath.length()).toString() 177 | + ")"); 178 | } 179 | 180 | ListPanel::~ListPanel() 181 | { 182 | 183 | } 184 | 185 | QString ListPanel::removeLeadingSlash(QString path) { 186 | #ifdef UNIX 187 | return path; 188 | #endif 189 | return path.right(path.length() - 1); 190 | } 191 | 192 | #include "listpanel.moc" 193 | -------------------------------------------------------------------------------- /marknoto/listpanel.h: -------------------------------------------------------------------------------- 1 | #ifndef LISTPANEL_H 2 | #define LISTPANEL_H 3 | 4 | #include "panel.h" 5 | 6 | class QListView; 7 | class QFileSystemModel; 8 | class QVBoxLayout; 9 | class QModelIndex; 10 | class QContextMenuEvent; 11 | class QPoint; 12 | class QStringListModel; 13 | class ListItemDelegate; 14 | 15 | class ListPanel : public Panel 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | ListPanel(QWidget *parent = 0); 21 | virtual ~ListPanel(); 22 | void setTaggedList(const QStringList& list); 23 | QString getTitleByIndex(const QModelIndex& index); 24 | void setUrlForLModel(const QUrl& url); 25 | void goHome(); 26 | QString removeLeadingSlash(QString path); 27 | 28 | private: 29 | QListView *listView; 30 | QFileSystemModel *lmodel; 31 | QStringListModel *smodel; 32 | QVBoxLayout *vl; 33 | QPoint m_pos; 34 | QStringList m_filters; 35 | QWidget *m_parent; 36 | ListItemDelegate *m_delegate; 37 | bool displayByTag; 38 | 39 | public slots: 40 | void setDisplayMode(int); 41 | 42 | private slots: 43 | void setUrlFromIndex(const QModelIndex& index); 44 | void showContextMenu(const QPoint& pos); 45 | void deleteNote(); 46 | void copyNoteLink(); 47 | 48 | protected: 49 | virtual bool urlChanged(); 50 | }; 51 | 52 | #endif -------------------------------------------------------------------------------- /marknoto/main.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2015 by Darcy Shen * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; if not, write to the * 16 | * Free Software Foundation, Inc., * 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * 18 | ***************************************************************************/ 19 | 20 | #include "marknoto.h" 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "../icons.h" 34 | 35 | #define DESCRIPTION "Marknoto - Note-taking Part of Marketo" 36 | #define VERSION "0.2.2" 37 | 38 | int main(int argc, char **argv) 39 | { 40 | QApplication app(argc, argv); 41 | app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); 42 | 43 | setupIconTheme(); 44 | QIcon::setThemeName(QStringLiteral("breeze")); 45 | 46 | KAboutData about(QStringLiteral("marknoto"), 47 | i18n("Marknoto"), 48 | QStringLiteral(VERSION), 49 | i18n(DESCRIPTION), 50 | KAboutLicense::GPL_V3, 51 | i18n("(C) 2015 Darcy Shen"), 52 | QString(), 53 | QStringLiteral("https://github.com/sadhen/marketo")); 54 | about.addAuthor( i18n("Darcy Shen"), i18n("Developer"), "sadhen@zoho.com" ); 55 | KLocalizedString::setApplicationDomain("marknoto"); 56 | 57 | KAboutData::setApplicationData(about); 58 | 59 | app.setApplicationName(about.componentName()); 60 | app.setApplicationDisplayName(about.displayName()); 61 | app.setOrganizationDomain(about.organizationDomain()); 62 | app.setApplicationVersion(about.version()); 63 | 64 | QCommandLineParser parser; 65 | about.setupCommandLine(&parser); 66 | parser.setApplicationDescription(about.shortDescription()); 67 | parser.addHelpOption(); 68 | parser.addVersionOption(); 69 | parser.process(app); 70 | about.processCommandLine(&parser); 71 | 72 | KSharedConfigPtr config = KSharedConfig::openConfig(); 73 | KConfigGroup cfg(config, "General Options"); 74 | if ( !cfg.hasKey("NoteDir") || !QFile::exists(cfg.readEntry("NoteDir")) ) 75 | { 76 | QUrl url = QFileDialog::getExistingDirectoryUrl(0, 77 | i18n("Choose where your notes save"), 78 | QDir::homePath(), 79 | QFileDialog::ShowDirsOnly 80 | | QFileDialog::DontResolveSymlinks); 81 | cfg.writeEntry("NoteDir", url.toLocalFile()); 82 | config->sync(); 83 | 84 | } 85 | 86 | QString noteDir(cfg.readEntry("NoteDir")); 87 | 88 | // create the Home note 89 | if (!QFile::exists(noteDir + "/Home.md")) { 90 | QFile::copy(QStandardPaths::locate(QStandardPaths::GenericDataLocation, 91 | QLatin1String("marknoto/Home.md")), 92 | QString(noteDir + "/Home.md")); 93 | } 94 | 95 | // create the Trash dir 96 | QDir qDir(noteDir + "/Trash"); 97 | if (!qDir.exists()) 98 | qDir.mkpath(qDir.path()); 99 | 100 | MarkNote *mainWindow = new MarkNote; 101 | mainWindow->show(); 102 | 103 | return app.exec(); 104 | } 105 | -------------------------------------------------------------------------------- /marknoto/mainview.cpp: -------------------------------------------------------------------------------- 1 | #include "mainview.h" 2 | // #include "terminalpanel.h" 3 | #include "navpanel.h" 4 | #include "listpanel.h" 5 | #include "metadata.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #ifdef UNIX 15 | #include 16 | #endif 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | MainView::MainView(QWidget *parent, KActionCollection *pActions) 32 | : Panel(parent), 33 | actions(pActions) 34 | 35 | { 36 | setupUI(); 37 | 38 | column = 3; 39 | switch (column) { 40 | case 1: 41 | oneColView(); 42 | break; 43 | case 2: 44 | twoColView(); 45 | break; 46 | case 3: 47 | threeColView(); 48 | break; 49 | default: 50 | threeColView(); 51 | break; 52 | } 53 | } 54 | 55 | void MainView::setupUI() 56 | { 57 | resize(629, 470); 58 | horizonLayout = new QHBoxLayout(this); 59 | hsplitter = new QSplitter(Qt::Horizontal, this); 60 | 61 | //terminal = new TerminalPanel(this); 62 | //terminal->hide(); 63 | //connect(terminal, SIGNAL(changeUrl(QUrl)), this, SLOT(setUrl(QUrl))); 64 | 65 | navigator = new Navigator(this); 66 | navigator->setContentsMargins(0, -5, -7, 0); 67 | connect(navigator, SIGNAL(changeUrl(QUrl)), this, SLOT(setUrl(QUrl))); 68 | connect(navigator->tagTree, SIGNAL(itemClicked(QTreeWidgetItem *, int )), 69 | this, SLOT(showTaggedFiles(QTreeWidgetItem *, int ))); 70 | 71 | listPanel = new ListPanel(this); 72 | listPanel->setContentsMargins(0, -5, -15, 0); 73 | connect(listPanel, SIGNAL(changeUrl(QUrl)), this, SLOT(setUrl(QUrl))); 74 | connect(navigator->tabWidget, SIGNAL(tabBarClicked(int)), 75 | listPanel, SLOT(setDisplayMode(int))); 76 | connect(navigator->tabWidget, SIGNAL(tabBarClicked(int)), 77 | this, SLOT(setDisplayMode(int))); 78 | 79 | noteView = new NoteView(this, actions); 80 | noteView->setContentsMargins(0, -5, 0, 0); 81 | note = noteView->note; 82 | markPad = noteView->markPad; 83 | connect(noteView, SIGNAL(tagsAdded(const QStringList &, const QUrl &)), 84 | navigator, SLOT(addNewTags(const QStringList &, const QUrl &))); 85 | connect(note, &KTextEditor::Document::documentSavedOrUploaded, 86 | this, &MainView::afterSave); 87 | 88 | hsplitter->addWidget(navigator); 89 | hsplitter->addWidget(listPanel); 90 | hsplitter->setHandleWidth(0); 91 | navigator->setMinimumWidth(180); 92 | hsplitter->setStretchFactor(0, 0); 93 | hsplitter->setStretchFactor(1, 1); 94 | 95 | horizonLayout->addWidget(hsplitter); 96 | horizonLayout->addWidget(noteView); 97 | horizonLayout->setSpacing(0); 98 | horizonLayout->setStretch(0, 0); 99 | horizonLayout->setStretch(1, 1); 100 | } 101 | 102 | bool MainView::urlChanged() 103 | { 104 | //terminal->setUrl(url()); 105 | listPanel->setUrl(url()); 106 | navigator->setUrl(url()); 107 | if (QFileInfo(url().toLocalFile()).isFile()) { 108 | if (noteView->note->isModified()) 109 | noteView->note->documentSave(); 110 | noteView->openUrl(url()); 111 | } 112 | return true; 113 | } 114 | 115 | KTextEditor::View* MainView::getEditor() 116 | { 117 | return markPad->m_editor; 118 | } 119 | 120 | bool MainView::preview() 121 | { 122 | noteView->hideMetaData(); 123 | markPad->preview(column == 2); 124 | markPad->setFocus(); 125 | actions->action("file_preview")->setChecked(true); 126 | return true; 127 | } 128 | 129 | bool MainView::unpreview() 130 | { 131 | if (column == 2) 132 | return true; 133 | else { 134 | if (column == 3) 135 | noteView->showMetaData(); 136 | markPad->unpreview(); 137 | markPad->setFocus(); 138 | actions->action("file_preview")->setChecked(false); 139 | return false; 140 | } 141 | } 142 | 143 | void MainView::oneColView() 144 | { 145 | navigator->setHidden(true); 146 | listPanel->setHidden(true); 147 | noteView->hideMetaData(); 148 | 149 | column = 1; 150 | unpreview(); 151 | } 152 | 153 | void MainView::twoColView() 154 | { 155 | QList sizeList; 156 | sizeList << 0 << 0 << 400; 157 | hsplitter->setSizes(sizeList); 158 | 159 | navigator->setHidden(true); 160 | listPanel->setHidden(true); 161 | noteView->hideMetaData(); 162 | 163 | column = 2; 164 | preview(); 165 | } 166 | 167 | void MainView::threeColView() 168 | { 169 | navigator->setHidden(false); 170 | listPanel->setHidden(false); 171 | noteView->showMetaData(); 172 | 173 | QList sizeList; 174 | sizeList << 50 << 50<< 300; 175 | hsplitter->setSizes(sizeList); 176 | 177 | column = 3; 178 | unpreview(); 179 | } 180 | /* 181 | * only called by Markpado, for separating pado and noto 182 | */ 183 | void MainView::slotOpen(const QUrl& url) 184 | { 185 | setUrl(url); 186 | } 187 | 188 | void MainView::newNote() 189 | { 190 | QUrl tmpUrl; 191 | 192 | tmpUrl = url(); 193 | 194 | // avoid empty tmpUrl 195 | if (tmpUrl.isEmpty()) { 196 | KConfigGroup cfg(KSharedConfig::openConfig(), "General Options"); 197 | tmpUrl = QUrl::fromLocalFile(cfg.readEntry("NoteDir")); 198 | } 199 | 200 | bool flag; 201 | #ifdef UNIX 202 | flag = QFileInfo(tmpUrl.path()).isDir(); 203 | #else 204 | QString path = tmpUrl.path(); 205 | flag = QFileInfo(path.right(path.length()-1)).isDir(); 206 | #endif 207 | if (flag) 208 | tmpUrl = QUrl::fromLocalFile(tmpUrl.toLocalFile() + QString("/Untitled.md")); 209 | else 210 | tmpUrl.setUrl(tmpUrl.url(QUrl::RemoveFilename).append("/Untitled.md")); 211 | 212 | noteView->setTitle(tmpUrl.fileName()); 213 | noteView->focusTitle(); 214 | noteView->openUrl(tmpUrl); 215 | unpreview(); 216 | } 217 | 218 | void MainView::afterSave() 219 | { 220 | MetaData metadata(note->url().toLocalFile()); 221 | navigator->addNewTags(metadata.tags(), note->url()); 222 | #ifdef UNIX 223 | KFileMetaData::UserMetaData kmetadata(note->url().toLocalFile()); 224 | kmetadata.setTags(metadata.tags()); 225 | #endif 226 | } 227 | 228 | 229 | void MainView::goHome() 230 | { 231 | KConfigGroup cfg(KSharedConfig::openConfig(), "General Options"); 232 | QString noteDir(cfg.readEntry("NoteDir")); 233 | 234 | QUrl tmpUrl = QUrl::fromLocalFile(noteDir + QString("/Home.md")); 235 | noteView->openUrl(tmpUrl); 236 | noteView->setTitle(tmpUrl.fileName()); 237 | 238 | navigator->tabWidget->setCurrentIndex(0); 239 | 240 | setUrl(QUrl::fromLocalFile(noteDir)); 241 | 242 | listPanel->goHome(); 243 | } 244 | 245 | void MainView::showTaggedFiles(const QString& tag) 246 | { 247 | QStringList halfPathList; 248 | QStringListIterator iter(navigator->getFilesByTag(tag)); 249 | KConfigGroup cfg(KSharedConfig::openConfig(), "General Options"); 250 | int len = cfg.readEntry("NoteDir").length(); 251 | 252 | while(iter.hasNext()) 253 | halfPathList.append(iter.next().midRef(len).toString()); 254 | listPanel->setTaggedList(halfPathList); 255 | } 256 | 257 | void MainView::showTaggedFiles(QTreeWidgetItem *item, int row) 258 | { 259 | QString tag(item->text(row)); 260 | showTaggedFiles(tag); 261 | } 262 | 263 | void MainView::setDisplayMode(int mode) { 264 | if (mode == 0) { 265 | navigator->tabWidget->setCurrentIndex(0); 266 | listPanel->setUrlForLModel(note->url()); 267 | } else { 268 | if (navigator->tagTree->currentItem()) 269 | showTaggedFiles(navigator->tagTree->currentItem()->text(0)); 270 | else showTaggedFiles(QString("@todo")); 271 | } 272 | } 273 | 274 | void MainView::toggleTerminal() 275 | { 276 | /* 277 | if (terminal->isVisible()) 278 | terminal->hide(); 279 | else 280 | terminal->show(); 281 | */ 282 | } 283 | 284 | MainView::~MainView() 285 | { 286 | } 287 | 288 | #include "mainview.moc" 289 | -------------------------------------------------------------------------------- /marknoto/mainview.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINVIEW_H 2 | #define MAINVIEW_H 3 | 4 | #include "noteview.h" 5 | #include "panel.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | class KActionCollection; 12 | 13 | class QUrl; 14 | class QVariant; 15 | class QApplication; 16 | class QListView; 17 | class QSplitter; 18 | class QModelIndex; 19 | class QTreeWidgetItem; 20 | class Navigator; 21 | class ListPanel; 22 | 23 | class MainView : public Panel 24 | { 25 | Q_OBJECT 26 | 27 | public: 28 | Markpado *markPad; 29 | KTextEditor::Document *note; 30 | NoteView *noteView; 31 | 32 | MainView(QWidget *parent = 0, KActionCollection *actions = 0); 33 | virtual ~MainView(); 34 | KTextEditor::View *getEditor(); 35 | bool preview(); 36 | bool unpreview(); 37 | void setupUI(); 38 | 39 | private: 40 | QHBoxLayout *horizonLayout; 41 | QSplitter *hsplitter; 42 | Panel *terminal; 43 | Navigator *navigator; 44 | ListPanel *listPanel; 45 | KActionCollection *actions; 46 | 47 | KTextEditor::View *editor; 48 | int column; 49 | 50 | public slots: 51 | void newNote(); 52 | void goHome(); 53 | void afterSave(); 54 | 55 | private slots: 56 | void oneColView(); 57 | void twoColView(); 58 | void threeColView(); 59 | void toggleTerminal(); 60 | void slotOpen(const QUrl &url); 61 | void showTaggedFiles(QTreeWidgetItem*, int); 62 | void setDisplayMode(int mode); 63 | 64 | private: 65 | void showTaggedFiles(const QString &); 66 | 67 | protected: 68 | virtual bool urlChanged(); 69 | }; 70 | 71 | #endif // MAINVIEW_H 72 | -------------------------------------------------------------------------------- /marknoto/marknoto.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2015 by Darcy Shen * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; if not, write to the * 16 | * Free Software Foundation, Inc., * 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * 18 | ***************************************************************************/ 19 | 20 | #include "marknoto.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | MarkNote::MarkNote(QWidget* parent) 33 | : KXmlGuiWindow(parent) 34 | , m_firstTextChange(false) 35 | , actions(actionCollection()) 36 | , m_recentFiles(0) 37 | { 38 | QAction* previewAction = actions->addAction("file_preview", this, SLOT(togglePreview())); 39 | previewAction->setIcon(QIcon::fromTheme(QLatin1String("document-preview"))); 40 | previewAction->setText(i18n("Preview")); 41 | previewAction->setCheckable(true); 42 | actions->setDefaultShortcut(previewAction, QKeySequence("F8")); 43 | 44 | m_view = new MainView(parent, actions); 45 | m_note = m_view->note; 46 | setupAction(); 47 | setupUI(); 48 | setupConnect(); 49 | 50 | m_view->goHome(); 51 | actions->action("go_backward")->setChecked(true); 52 | 53 | m_view->preview(); 54 | } 55 | 56 | void MarkNote::setupAction() 57 | { 58 | //KStandardAction::openNew(this, SLOT(newNote()), actionCollection()); 59 | //KStandardAction::close(this, SLOT(close()), actionCollection()); 60 | 61 | QAction* oneColAction = actions->addAction("win_onecol", m_view, SLOT(oneColView())); 62 | QAction* twoColAction = actions->addAction("win_twocol", m_view, SLOT(twoColView())); 63 | QAction* threeColAction = actions->addAction("win_threecol", m_view, SLOT(threeColView())); 64 | QAction* addNoteAction = actions->addAction("add_note", m_view, SLOT(newNote())); 65 | QAction* goHomeAction = actions->addAction("go_home", m_view, SLOT(goHome())); 66 | QAction* backwardAction = actions->addAction("go_backward", this, SLOT(backward())); 67 | QAction* forwardAction = actions->addAction("go_forward", this, SLOT(forward())); 68 | 69 | forwardAction->setCheckable(true); 70 | forwardAction->setChecked(true); 71 | backwardAction->setCheckable(true); 72 | 73 | oneColAction->setText(i18n("Focus")); 74 | twoColAction->setText(i18n("Split")); 75 | threeColAction->setText(i18n("Browse")); 76 | addNoteAction->setText(i18n("New Note")); 77 | forwardAction->setText(i18n("Forward")); 78 | backwardAction->setText(i18n("Backward")); 79 | goHomeAction->setText(i18n("Home")); 80 | 81 | actions->setDefaultShortcut(addNoteAction, QKeySequence("Ctrl+N")); 82 | actions->setDefaultShortcut(forwardAction, QKeySequence("Alt+Right")); 83 | actions->setDefaultShortcut(backwardAction, QKeySequence("Alt+Left")); 84 | 85 | oneColAction->setIcon(QIcon::fromTheme(QLatin1String("view-split-top-bottom"))); 86 | twoColAction->setIcon(QIcon::fromTheme(QLatin1String("view-split-left-right"))); 87 | threeColAction->setIcon(QIcon::fromTheme(QLatin1String("view-file-columns"))); 88 | addNoteAction->setIcon(QIcon::fromTheme(QLatin1String("list-add"))); 89 | goHomeAction->setIcon(QIcon::fromTheme(QLatin1String("go-home"))); 90 | forwardAction->setIcon(QIcon::fromTheme(QLatin1String("arrow-right"))); 91 | backwardAction->setIcon(QIcon::fromTheme(QLatin1String("arrow-left"))); 92 | 93 | 94 | m_recentFiles = KStandardAction::openRecent(m_view, SLOT(setUrl(QUrl)), this); 95 | actions->addAction(m_recentFiles->objectName(), m_recentFiles); 96 | m_recentFiles->setWhatsThis(i18n("This lists files which you have opened recently, and allows you to easily open them again.")); 97 | 98 | //QAction* terminalAction = actionCollection()->addAction("toggle_terminal", m_view, SLOT(toggleTerminal())); 99 | //terminalAction->setShortcut(QKeySequence("F4")); 100 | } 101 | 102 | void MarkNote::setupUI() 103 | { 104 | setCentralWidget(m_view); 105 | setupGUI(QSize(500,600), Default, "marknoto.rc"); 106 | guiFactory()->addClient(m_view->getEditor()); 107 | setStandardToolBarMenuEnabled(true); 108 | setAutoSaveSettings(); 109 | readConfig(); 110 | } 111 | 112 | void MarkNote::setupConnect() 113 | { 114 | connect(m_note, &KTextEditor::Document::modifiedChanged, 115 | this, &MarkNote::updateCaption); 116 | connect(m_note, &KTextEditor::Document::documentUrlChanged, 117 | this, &MarkNote::slotDocumentUrlChanged); 118 | connect(m_note, &KTextEditor::Document::textChanged, 119 | this, &MarkNote::updateCaptionModified); 120 | } 121 | 122 | //common config 123 | void MarkNote::readConfig(KSharedConfigPtr config) 124 | { 125 | KConfigGroup cfg(config, "General Options"); 126 | 127 | m_recentFiles->loadEntries(config->group("Recent Files")); 128 | } 129 | 130 | void MarkNote::writeConfig(KSharedConfigPtr config) 131 | { 132 | KConfigGroup generalOptions(config, "General Options"); 133 | 134 | m_recentFiles->saveEntries(KConfigGroup(config, "Recent Files")); 135 | 136 | config->sync(); 137 | } 138 | 139 | //config file 140 | void MarkNote::readConfig() 141 | { 142 | readConfig(KSharedConfig::openConfig()); 143 | } 144 | 145 | void MarkNote::writeConfig() 146 | { 147 | writeConfig(KSharedConfig::openConfig()); 148 | } 149 | 150 | void MarkNote::readProperties(const KConfigGroup &config) 151 | { 152 | Q_UNUSED(config); 153 | readConfig(); 154 | } 155 | 156 | void MarkNote::saveProperties(KConfigGroup &config) 157 | { 158 | Q_UNUSED(config); 159 | writeConfig(); 160 | } 161 | 162 | void MarkNote::newNote() 163 | { 164 | m_view->newNote(); 165 | } 166 | 167 | void MarkNote::updateCaptionModified() 168 | { 169 | // The first textChanged signal is caused by document loading 170 | // Thus, we should ignore it 171 | if (m_firstTextChange) { 172 | m_firstTextChange = false; 173 | return ; 174 | } 175 | setCaption(m_note->url().fileName() + " [modified]- Marketo"); 176 | } 177 | 178 | void MarkNote::updateCaption() 179 | { 180 | setCaption(m_note->url().fileName() + " - Marketo"); 181 | } 182 | 183 | void MarkNote::slotDocumentUrlChanged() 184 | { 185 | // TODO: figure out how Qt signal slot works and make sure 186 | // slotDocumentUrlChanged is done before updateCaptionModified 187 | m_firstTextChange = true; 188 | updateCaption(); 189 | if (!m_note->url().isEmpty()) 190 | m_recentFiles->addUrl(m_note->url()); 191 | } 192 | 193 | void MarkNote::togglePreview() 194 | { 195 | if (!actions->action("file_preview")->isChecked()) 196 | m_view->unpreview(); 197 | else 198 | m_view->preview(); 199 | } 200 | 201 | void MarkNote::forward() 202 | { 203 | if (m_view->noteView->canForward()) { 204 | m_view->noteView->forward(); 205 | actions->action("go_backward")->setChecked(false); 206 | } 207 | if (m_view->noteView->canForward()) 208 | actions->action("go_forward")->setChecked(false); 209 | else 210 | actions->action("go_forward")->setChecked(true); 211 | } 212 | 213 | void MarkNote::backward() 214 | { 215 | if (m_view->noteView->canBackward()) { 216 | m_view->noteView->backward(); 217 | actions->action("go_forward")->setChecked(false); 218 | } 219 | if (m_view->noteView->canBackward()) 220 | actions->action("go_backward")->setChecked(false); 221 | else 222 | actions->action("go_backward")->setChecked(true); 223 | } 224 | 225 | MarkNote::~MarkNote() 226 | { 227 | writeConfig(); 228 | } 229 | 230 | #include "marknoto.moc" 231 | -------------------------------------------------------------------------------- /marknoto/marknoto.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Marknoto 3 | Name[zh_CN]=Marknoto 4 | Name[zh_TW]=Marknoto 5 | Exec=marknoto 6 | Icon=marknoto 7 | Type=Application 8 | MimeType=text/markdown; 9 | Categories=KDE;Utility;TextEditor; 10 | X-DocPath=Marknoto/index.html 11 | GenericName=Note-taking with Markdown 12 | GenericName[zh_CN]=Markdown 做笔记 13 | GenericName[zh_TW]=Markdown 做筆記 14 | Comment=A KDE Note-taking Application 15 | Comment[en_GB]=A KDE Note-taking Application 16 | Comment[zh_CN]=KDE 笔记软件 17 | Comment[zh_TW]=KDE 笔记程式 18 | 19 | -------------------------------------------------------------------------------- /marknoto/marknoto.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) %{CURRENT_YEAR} by %{AUTHOR} <%{EMAIL}> * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; if not, write to the * 16 | * Free Software Foundation, Inc., * 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * 18 | ***************************************************************************/ 19 | 20 | #ifndef MARKNOTE_H 21 | #define MARKNOTE_H 22 | 23 | #include "mainview.h" 24 | #include 25 | #include 26 | #include 27 | 28 | class KRecentFilesAction; 29 | class KActionCollection; 30 | 31 | class MarkNote : public KXmlGuiWindow 32 | { 33 | Q_OBJECT 34 | public: 35 | MarkNote(QWidget *parent = 0); 36 | virtual ~MarkNote(); 37 | 38 | void unpreview(); 39 | void setupAction(); 40 | void setupUI(); 41 | void setupConnect(); 42 | 43 | private slots: 44 | void newNote(); 45 | void updateCaptionModified(); 46 | void updateCaption(); 47 | void togglePreview(); 48 | void slotDocumentUrlChanged(); 49 | void forward(); 50 | void backward(); 51 | 52 | private: 53 | KTextEditor::Document *m_note; 54 | MainView *m_view; 55 | bool m_firstTextChange; 56 | KActionCollection *actions; 57 | 58 | // session management 59 | private: 60 | void readConfig(); 61 | void writeConfig(); 62 | void writeConfig(KSharedConfigPtr config); 63 | void readConfig(KSharedConfigPtr config); 64 | void readProperties(const KConfigGroup &config) override; 65 | void saveProperties(KConfigGroup &config) override; 66 | KRecentFilesAction *m_recentFiles; 67 | }; 68 | 69 | #endif // _MARKNOTE_H_ -------------------------------------------------------------------------------- /marknoto/metadata.cpp: -------------------------------------------------------------------------------- 1 | #include "metadata.h" 2 | 3 | #include 4 | #include 5 | 6 | MetaData::MetaData(const QString& filePath) 7 | { 8 | this->filePath = filePath; 9 | parse(); 10 | } 11 | 12 | void MetaData::parse() 13 | { 14 | QFile file(filePath); 15 | if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) 16 | return; 17 | QTextStream in(&file); 18 | QString line = in.readLine(); 19 | if (line!="") { 22 | line = in.readLine(); 23 | QStringList lineSplited = line.split(":"); 24 | if (lineSplited.size() > 1) 25 | data[lineSplited.at(0)] = lineSplited.at(1); 26 | } 27 | file.close(); 28 | } 29 | 30 | QStringList MetaData::tags() 31 | { 32 | if (data.contains("tags")) 33 | return data["tags"].split(","); 34 | else 35 | return QStringList(); 36 | } 37 | 38 | MetaData::~MetaData() 39 | { 40 | 41 | } -------------------------------------------------------------------------------- /marknoto/metadata.h: -------------------------------------------------------------------------------- 1 | #ifndef METADATA_H 2 | #define METADATA_H 3 | 4 | #include 5 | #include 6 | 7 | class MetaData { 8 | public: 9 | MetaData(const QString& filePath); 10 | virtual ~MetaData(); 11 | 12 | QStringList tags(); 13 | void parse(); 14 | 15 | private: 16 | QString filePath; 17 | QMap data; 18 | }; 19 | 20 | #endif -------------------------------------------------------------------------------- /marknoto/navpanel.cpp: -------------------------------------------------------------------------------- 1 | #include "navpanel.h" 2 | #include "iconfilter.h" 3 | #include "metadata.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | Navigator::Navigator(Panel* parent) 29 | : Panel(parent) 30 | { 31 | KConfigGroup cfg(KSharedConfig::openConfig(), "General Options"); 32 | m_pos = QPoint(0, 0); 33 | 34 | tmodel = new QFileSystemModel; 35 | tmodel->setRootPath(cfg.readEntry("NoteDir")); 36 | tmodel->setFilter(QDir::Dirs | QDir::NoDotAndDotDot); 37 | 38 | QFileIconProvider *iconProvider = new IconFilter(); 39 | tmodel->setIconProvider(iconProvider); 40 | 41 | m_selectionModel = new QItemSelectionModel(tmodel); 42 | 43 | treeView = new QTreeView(this); 44 | treeView->setModel(tmodel); 45 | treeView->setRootIndex(tmodel->index(cfg.readEntry("NoteDir"))); 46 | treeView->resizeColumnToContents(0); 47 | treeView->setColumnHidden(1, true); 48 | treeView->setColumnHidden(2, true); 49 | treeView->setColumnHidden(3, true); 50 | treeView->setUniformRowHeights(true); 51 | treeView->setIconSize(QSize(16, 16)); 52 | treeView->setHeaderHidden(true); 53 | treeView->setAnimated(true); 54 | treeView->setContentsMargins(-5, -5, 0, 0); 55 | treeView->setContextMenuPolicy(Qt::CustomContextMenu); 56 | 57 | connect(treeView, SIGNAL(clicked(QModelIndex)), 58 | this, SLOT(setUrlFromIndex(QModelIndex))); 59 | connect(treeView, SIGNAL(customContextMenuRequested(const QPoint&)), 60 | this, SLOT(showContextMenu(const QPoint&))); 61 | 62 | buildTagStaffs(); 63 | 64 | tabWidget = new QTabWidget(this); 65 | tabWidget->addTab(treeView, i18n("Folder")); 66 | tabWidget->addTab(tagTree, i18n("Tag")); 67 | tabWidget->setContentsMargins(0, 0, 0, 0); 68 | vl = new QVBoxLayout(this); 69 | vl->addWidget(tabWidget); 70 | } 71 | 72 | void Navigator::buildTagStaffs() 73 | { 74 | KConfigGroup cfg(KSharedConfig::openConfig(), "General Options"); 75 | QString noteDir(cfg.readEntry("NoteDir")); 76 | 77 | tagPaths = new QStringList(); 78 | tagRoots = new QStringList(); 79 | 80 | QDirIterator it(noteDir, QStringList() << "*.cm" << "*.md", QDir::Files, QDirIterator::Subdirectories); 81 | while (it.hasNext()) { 82 | QString file(it.next()); 83 | MetaData metaData(file); 84 | QStringList tags = metaData.tags(); 85 | if (!tags.isEmpty()) { 86 | tagPaths->append(file); 87 | *tagRoots += tags; 88 | } 89 | } 90 | tagRoots->removeDuplicates(); 91 | tagRoots->sort(); 92 | 93 | tagTree = new QTreeWidget(this); 94 | tagTree->header()->close(); 95 | QStringListIterator iter(*tagRoots); 96 | while (iter.hasNext()) { 97 | QTreeWidgetItem *item = new QTreeWidgetItem; 98 | item->setText(0, iter.next()); 99 | item->setIcon(0, QIcon::fromTheme(QLatin1String("tag"))); 100 | tagTree->addTopLevelItem(item); 101 | } 102 | } 103 | 104 | QStringList Navigator::getFilesByTag(const QString& tag) 105 | { 106 | QStringList list; 107 | QStringListIterator iter(*tagPaths); 108 | while (iter.hasNext()) { 109 | QString curPath(iter.next()); 110 | MetaData metaData(curPath); 111 | if (metaData.tags().contains(tag)) { 112 | list.append(curPath); 113 | } 114 | } 115 | return list; 116 | } 117 | 118 | void Navigator::addNewTags(const QStringList &list, const QUrl & url) 119 | { 120 | if (!tagPaths->contains(url.toLocalFile())) 121 | tagPaths->append(url.toLocalFile()); 122 | QStringListIterator iter(list); 123 | while(iter.hasNext()) { 124 | QString tag(iter.next()); 125 | if (!tagRoots->contains(tag)) { 126 | tagRoots->append(tag); 127 | QTreeWidgetItem *item = new QTreeWidgetItem; 128 | item->setText(0, tag); 129 | item->setIcon(0, QIcon::fromTheme(QLatin1String("tag"))); 130 | tagTree->addTopLevelItem(item); 131 | tagTree->sortItems(0, Qt::AscendingOrder); 132 | } 133 | } 134 | } 135 | 136 | void Navigator::setUrlFromIndex(const QModelIndex& index) 137 | { 138 | setUrl(QUrl::fromLocalFile(tmodel->filePath(index))); 139 | } 140 | 141 | bool Navigator::urlChanged() 142 | { 143 | emit changeUrl(url()); 144 | 145 | if (QFileInfo(url().path()).isDir()) { 146 | treeView->scrollTo(tmodel->index(url().toLocalFile())); 147 | QItemSelection selection( 148 | tmodel->index(url().toLocalFile()), 149 | tmodel->index(url().toLocalFile()) 150 | ); 151 | m_selectionModel->select(selection, QItemSelectionModel::ClearAndSelect); 152 | treeView->setSelectionModel(m_selectionModel); 153 | } 154 | 155 | return true; 156 | } 157 | 158 | void Navigator::showContextMenu(const QPoint& pos) 159 | { 160 | m_pos = pos; 161 | QMenu *contextMenu = new QMenu(); 162 | QAction *newDirAction = contextMenu->addAction(QString("New Directory")); 163 | QAction *deleteDirAction = contextMenu->addAction(QString("Delete Directory")); 164 | connect(newDirAction, SIGNAL(triggered()), this, SLOT(slotNewDir())); 165 | connect(deleteDirAction, SIGNAL(triggered()), this, SLOT(slotDeleteDir())); 166 | 167 | if (contextMenu) { 168 | contextMenu->exec(QCursor::pos()); 169 | } 170 | delete contextMenu; 171 | } 172 | 173 | void Navigator::slotNewDir() 174 | { 175 | QModelIndex index = treeView->indexAt(m_pos); 176 | QString indexDir(tmodel->filePath(index)); 177 | QDir dir(indexDir); 178 | 179 | KConfigGroup cfg(KSharedConfig::openConfig(), "General Options"); 180 | if (indexDir.isEmpty()) 181 | dir.setPath(cfg.readEntry("NoteDir")); 182 | 183 | QString folderName = QInputDialog::getText(this, 184 | QString("Create Folder in ") + tmodel->filePath(index), 185 | QString("Folder Name")); 186 | dir.mkdir(folderName); 187 | } 188 | 189 | /* 190 | * move the Notes to Trash, than delete the Directory 191 | */ 192 | void Navigator::slotDeleteDir() 193 | { 194 | KConfigGroup cfg(KSharedConfig::openConfig(), "General Options"); 195 | 196 | QModelIndex index = treeView->indexAt(m_pos); 197 | qDebug() << tmodel->filePath(index); 198 | QDir dir(tmodel->filePath(index)); 199 | QDirIterator it(tmodel->filePath(index), QDirIterator::Subdirectories); 200 | while (it.hasNext()) { 201 | QFileInfo fileInfo(it.next()); 202 | if (fileInfo.isFile()) { 203 | QFile file(fileInfo.filePath()); 204 | // TODO: what if the target file already exists, currently overwrite 205 | file.rename(cfg.readEntry("NoteDir")+"/Trash/"+fileInfo.fileName()); 206 | } 207 | } 208 | dir.removeRecursively(); 209 | } 210 | 211 | Navigator::~Navigator() 212 | { 213 | 214 | } 215 | 216 | #include "navpanel.moc" 217 | -------------------------------------------------------------------------------- /marknoto/navpanel.h: -------------------------------------------------------------------------------- 1 | #ifndef NAVPANEL_H 2 | #define NAVPANEL_H 3 | 4 | #include "panel.h" 5 | #include 6 | 7 | class QFileSystemModel; 8 | class QTreeView; 9 | class QVBoxLayout; 10 | class QModelIndex; 11 | class QItemSelection; 12 | class QItemSelectionModel; 13 | class QContextMenuEvent; 14 | class QTabWidget; 15 | class QTreeWidget; 16 | class QUrl; 17 | class QStringList; 18 | 19 | class Navigator : public Panel 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | Navigator(Panel *parent=0); 25 | virtual ~Navigator(); 26 | QTreeWidget *tagTree; 27 | void buildTagStaffs(); 28 | QStringList getFilesByTag(const QString &); 29 | QTabWidget *tabWidget; 30 | 31 | private: 32 | Panel *m_parent; 33 | QTreeView *treeView; 34 | QFileSystemModel *tmodel; 35 | QStringList *tagRoots; 36 | QStringList *tagPaths; 37 | QVBoxLayout *vl; 38 | QItemSelection *m_selection; 39 | QItemSelectionModel *m_selectionModel; 40 | QPoint m_pos; 41 | 42 | public slots: 43 | void addNewTags(const QStringList &, const QUrl &); 44 | 45 | private slots: 46 | void setUrlFromIndex(const QModelIndex& index); 47 | void showContextMenu(const QPoint& pos); 48 | void slotNewDir(); 49 | void slotDeleteDir(); 50 | 51 | protected: 52 | virtual bool urlChanged(); 53 | }; 54 | #endif -------------------------------------------------------------------------------- /marknoto/noteview.cpp: -------------------------------------------------------------------------------- 1 | #include "noteview.h" 2 | #include "taglist.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | NoteView::NoteView(QWidget* parent, KActionCollection *pActions) 21 | : QWidget(parent), 22 | actions(pActions) 23 | { 24 | done = new QStack(); 25 | todo = new QStack(); 26 | setupUI(); 27 | setupConnect(); 28 | } 29 | 30 | void NoteView::pureOpenUrl(const QUrl& url) 31 | { 32 | // TODO:if the url is not in the watching dir and is in three column view 33 | // switch to one column view 34 | title->setText(url.fileName()); 35 | note->openUrl(url); 36 | note->setHighlightingMode("CommonMark"); 37 | if (actions->action("file_preview")->isChecked()) 38 | markPad->preview(); 39 | else 40 | markPad->unpreview(); 41 | } 42 | 43 | void NoteView::setupConnect() 44 | { 45 | connect(title, &QLineEdit::returnPressed, 46 | this, &NoteView::saveNote); 47 | } 48 | 49 | void NoteView::setupUI() 50 | { 51 | vl = new QVBoxLayout(this); 52 | title = new QLineEdit(this); 53 | markPad = new Markpado(this); 54 | note = markPad->m_note; 55 | 56 | vl->addWidget(title); 57 | vl->addWidget(markPad); 58 | 59 | title->setFixedHeight(24); 60 | title->setContentsMargins(0, 0, 0, 0); 61 | title->setAlignment(Qt::AlignHCenter); 62 | title->setStyleSheet("QLineEdit { border: 1px solid lightskyblue; border-radius: 2px; }"); 63 | 64 | vl->setSpacing(0); 65 | } 66 | 67 | void NoteView::saveNote() 68 | { 69 | QString name(title->text()); 70 | QUrl url = note->url(); 71 | 72 | note->documentSave(); 73 | if (QFileInfo(url.path()).isDir()) { 74 | url = QUrl::fromLocalFile(url.path().append("/").append(name)); 75 | } else 76 | url.setUrl(url.url(QUrl::RemoveFilename).append(name)); 77 | 78 | QDir dir(url.url(QUrl::RemoveFilename | QUrl::PreferLocalFile)); 79 | dir.rename(note->url().fileName(), url.fileName()); 80 | note->openUrl(url); 81 | markPad->view()->setFocus(); 82 | } 83 | 84 | void NoteView::hideMetaData() 85 | { 86 | title->setHidden(true); 87 | } 88 | 89 | void NoteView::showMetaData() 90 | { 91 | title->setHidden(false); 92 | } 93 | 94 | void NoteView::setTitle(const QString& titleOfNote) 95 | { 96 | title->setText(titleOfNote); 97 | } 98 | 99 | void NoteView::forward() 100 | { 101 | QUrl url(todo->pop()); 102 | done->push(url); 103 | pureOpenUrl(url); 104 | } 105 | 106 | void NoteView::backward() 107 | { 108 | QUrl url(done->pop()); 109 | todo->push(url); 110 | pureOpenUrl(done->top()); 111 | } 112 | 113 | bool NoteView::canBackward() 114 | { 115 | if (done->count() > 1) 116 | return true; 117 | else 118 | return false; 119 | } 120 | 121 | bool NoteView::canForward() 122 | { 123 | if (todo->isEmpty()) 124 | return false; 125 | else 126 | return true; 127 | } 128 | 129 | void NoteView::openUrl(const QUrl& url) 130 | { 131 | pureOpenUrl(url); 132 | if (done->isEmpty() || url != done->top()) 133 | done->push(url); 134 | if (canBackward()) 135 | actions->action("go_backward")->setChecked(false); 136 | delete todo; 137 | todo = new QStack(); 138 | actions->action("go_forward")->setChecked(true); 139 | 140 | /* 141 | QVectorIterator i(*done); 142 | while (i.hasNext()) 143 | qDebug() << i.next(); 144 | */ 145 | } 146 | 147 | // Open url that from the note(links) 148 | void NoteView::slotOpen(const QUrl& url) 149 | { 150 | if (url.toString().startsWith("qrc:/")) { 151 | // case1: fake root link 152 | KConfigGroup cfg(KSharedConfig::openConfig(), "General Options"); 153 | QString rootPath(cfg.readEntry("NoteDir")); 154 | QString notePath(url.toString().replace("qrc:/", "/")); 155 | QString path = rootPath + notePath; 156 | qDebug() << path; 157 | if (QFileInfo(path).exists()) { 158 | openUrl(QUrl::fromLocalFile(path)); 159 | return ; 160 | } 161 | 162 | // case2: relative path link(automatically handled by API) 163 | openUrl(url); 164 | } else { 165 | QDesktopServices::openUrl(url); 166 | } 167 | } 168 | 169 | void NoteView::focusTitle() 170 | { 171 | title->setFocus(); 172 | } 173 | 174 | NoteView::~NoteView() 175 | { 176 | delete done; 177 | delete todo; 178 | } 179 | 180 | #include "noteview.moc" 181 | -------------------------------------------------------------------------------- /marknoto/noteview.h: -------------------------------------------------------------------------------- 1 | #ifndef NOTEVIEW_H 2 | #define NOTEVIEW_H 3 | 4 | #include "markpado.h" 5 | 6 | #include 7 | 8 | class QVBoxLayout; 9 | class QLineEdit; 10 | class KActionCollection; 11 | class QListWidget; 12 | class QLabel; 13 | class TagList; 14 | 15 | class QUrl; 16 | 17 | class NoteView : public QWidget 18 | { 19 | Q_OBJECT 20 | public: 21 | Markpado *markPad; 22 | KTextEditor::Document *note; 23 | 24 | NoteView(QWidget *parent, KActionCollection *pActions); 25 | virtual ~NoteView(); 26 | void hideMetaData(); 27 | void showMetaData(); 28 | void setTitle(const QString& titleOfNote); 29 | void focusTitle(); 30 | void openUrl(const QUrl& url); 31 | void forward(); 32 | void backward(); 33 | bool canForward(); 34 | bool canBackward(); 35 | 36 | signals: 37 | void tagsAdded(const QStringList &, const QUrl &); 38 | 39 | public slots: 40 | void slotOpen(const QUrl& url); 41 | 42 | private: 43 | KActionCollection *actions; 44 | QVBoxLayout *vl; 45 | QLineEdit *title; 46 | QStack *done; 47 | QStack *todo; 48 | 49 | void setupUI(); 50 | void setupConnect(); 51 | void pureOpenUrl(const QUrl& url); 52 | 53 | private slots: 54 | void saveNote(); 55 | }; 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /marknoto/panel.cpp: -------------------------------------------------------------------------------- 1 | #include "panel.h" 2 | #include 3 | 4 | Panel::Panel(QWidget* parent) 5 | : QWidget(parent) 6 | , m_url() 7 | { 8 | } 9 | 10 | Panel::~Panel() 11 | { 12 | } 13 | 14 | QUrl Panel::url() const 15 | { 16 | return m_url; 17 | } 18 | 19 | QSize Panel::sizeHint() const 20 | { 21 | // The size hint will be requested already when starting Dolphin even 22 | // if the panel is invisible. For performance reasons most panels delay 23 | // the creation and initialization of widgets until a showEvent() is called. 24 | // Because of this the size-hint of the embedded widgets cannot be used 25 | // and a default size is provided: 26 | return QSize(180, 180); 27 | } 28 | 29 | void Panel::setUrl(const QUrl& url) 30 | { 31 | // TODO:url and m_url should be compared without 32 | // the trailing slash 33 | if (url == m_url) { 34 | return; 35 | } 36 | 37 | const QUrl oldUrl = m_url; 38 | m_url = url; 39 | const bool accepted = urlChanged(); 40 | if (!accepted) { 41 | m_url = oldUrl; 42 | } 43 | } 44 | 45 | void Panel::readSettings() 46 | { 47 | 48 | } 49 | 50 | #include "panel.moc" 51 | -------------------------------------------------------------------------------- /marknoto/panel.h: -------------------------------------------------------------------------------- 1 | #ifndef PANEL_H 2 | #define PANEL_H 3 | 4 | #include 5 | #include 6 | 7 | class Panel : public QWidget 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | explicit Panel(QWidget* parent = 0); 13 | virtual ~Panel(); 14 | 15 | /** Returns the current set URL of the active Dolphin view. */ 16 | QUrl url() const; 17 | 18 | /** @see QWidget::sizeHint() */ 19 | virtual QSize sizeHint() const; 20 | 21 | signals: 22 | void changeUrl(const QUrl &url); 23 | 24 | public slots: 25 | /** 26 | * This is invoked every time the folder being displayed in the 27 | * active Dolphin view changes. 28 | */ 29 | void setUrl(const QUrl& url); 30 | 31 | /** 32 | * Refreshes the view to get synchronized with the settings. 33 | */ 34 | virtual void readSettings(); 35 | 36 | protected: 37 | /** 38 | * Must be implemented by derived classes and is invoked when 39 | * the URL has been changed (see Panel::setUrl()). 40 | * @return True, if the new URL will get accepted by the derived 41 | * class. If false is returned, 42 | * the URL will be reset to the previous URL. 43 | */ 44 | virtual bool urlChanged() = 0; 45 | 46 | private: 47 | QUrl m_url; 48 | }; 49 | 50 | #endif // PANEL_H 51 | -------------------------------------------------------------------------------- /marknoto/po/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB PO_FILES *.po) 2 | 3 | gettext_create_translations(marknoto.pot ALL ${PO_FILES}) 4 | 5 | add_custom_target(tr_noto SOURCES marknoto.pot ${PO_FILES}) -------------------------------------------------------------------------------- /marknoto/po/Messages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | BASEDIR=".." # root of translatable sources 3 | PROJECT="marknoto" # project name 4 | BUGADDR="https://github.com/sadhen/marketo/issues" # MSGID-Bugs 5 | WDIR=`pwd` # working dir 6 | 7 | check_error() 8 | { 9 | if [ "$?" -ne "0" ]; then 10 | echo "ERROR: ${1}" 11 | exit 1 12 | fi 13 | } 14 | 15 | echo "Preparing rc files" 16 | cd ${BASEDIR} 17 | # we use simple sorting to make sure the lines don't jump around too much from system to system 18 | find . -name '*.rc' -o -name '*.ui' -o -name '*.kcfg' | sort > ${WDIR}/rcfiles.list 19 | xargs --arg-file=${WDIR}/rcfiles.list extractrc > ${WDIR}/rc.cpp 20 | check_error "Failed to extract messages from rc files. Do you have extractrc installed?" 21 | # additional string for KAboutData 22 | echo 'i18nc("NAME OF TRANSLATORS","Your names");' >> ${WDIR}/rc.cpp 23 | echo 'i18nc("EMAIL OF TRANSLATORS","Your emails");' >> ${WDIR}/rc.cpp 24 | cd ${WDIR} 25 | echo "Done preparing rc files" 26 | 27 | 28 | echo "Extracting messages" 29 | cd ${BASEDIR} 30 | # see above on sorting 31 | find . -name '*.cpp' -o -name '*.h' -o -name '*.c' | sort > ${WDIR}/infiles.list 32 | echo "rc.cpp" >> ${WDIR}/infiles.list 33 | cd ${WDIR} 34 | xgettext --from-code=UTF-8 -C -kde -ci18n -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -ktr2i18n:1 \ 35 | -kI18N_NOOP:1 -kI18N_NOOP2:1c,2 -kaliasLocale -kki18n:1 -kki18nc:1c,2 -kki18np:1,2 -kki18ncp:1c,2,3 \ 36 | --package-name="${PROJECT}" \ 37 | --msgid-bugs-address="${BUGADDR}" \ 38 | --files-from=infiles.list -D ${BASEDIR} -D ${WDIR} -o ${PROJECT}.pot 39 | check_error "Failed to extract messages from source files. Do you have xgettext installed?" 40 | echo "Done extracting messages" 41 | 42 | 43 | echo "Merging translations" 44 | catalogs=`find . -name '*.po'` 45 | for cat in $catalogs; do 46 | echo $cat 47 | msgmerge -o $cat.new $cat ${PROJECT}.pot 48 | check_error "Failed to merge messages. Do you have msgmerge installed?" 49 | mv $cat.new $cat 50 | done 51 | echo "Done merging translations" 52 | 53 | 54 | echo "Cleaning up" 55 | cd ${WDIR} 56 | rm rcfiles.list 57 | rm infiles.list 58 | rm rc.cpp 59 | echo "Done" 60 | -------------------------------------------------------------------------------- /marknoto/po/marknoto.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: marknoto\n" 10 | "Report-Msgid-Bugs-To: https://github.com/sadhen/marketo/issues\n" 11 | "POT-Creation-Date: 2016-02-07 01:15+0800\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: main.cpp:42 21 | msgid "Marknoto" 22 | msgstr "" 23 | 24 | #: main.cpp:46 25 | msgid "(C) 2015 Darcy Shen" 26 | msgstr "" 27 | 28 | #: main.cpp:49 29 | msgid "Darcy Shen" 30 | msgstr "" 31 | 32 | #: main.cpp:49 33 | msgid "Developer" 34 | msgstr "" 35 | 36 | #: main.cpp:72 37 | msgid "Choose where your notes save" 38 | msgstr "" 39 | 40 | #: marknoto.cpp:40 41 | msgid "Preview" 42 | msgstr "" 43 | 44 | #: marknoto.cpp:73 45 | msgid "Focus" 46 | msgstr "" 47 | 48 | #: marknoto.cpp:74 49 | msgid "Split" 50 | msgstr "" 51 | 52 | #: marknoto.cpp:75 53 | msgid "Browse" 54 | msgstr "" 55 | 56 | #: marknoto.cpp:76 57 | msgid "New Note" 58 | msgstr "" 59 | 60 | #: marknoto.cpp:77 61 | msgid "Forward" 62 | msgstr "" 63 | 64 | #: marknoto.cpp:78 65 | msgid "Backward" 66 | msgstr "" 67 | 68 | #: marknoto.cpp:79 69 | msgid "Home" 70 | msgstr "" 71 | 72 | #: marknoto.cpp:96 73 | msgid "" 74 | "This lists files which you have opened recently, and allows you to easily " 75 | "open them again." 76 | msgstr "" 77 | 78 | #: navpanel.cpp:64 79 | msgid "Folder" 80 | msgstr "" 81 | 82 | #: navpanel.cpp:65 83 | msgid "Tag" 84 | msgstr "" 85 | 86 | #. i18n: file: marknoto.rc:8 87 | #. i18n: ectx: Menu (go) 88 | #: po/rc.cpp:3 89 | msgid "&Go" 90 | msgstr "" 91 | 92 | #. i18n: file: marknoto.rc:14 93 | #. i18n: ectx: Menu (window) 94 | #: po/rc.cpp:6 95 | msgid "&Window" 96 | msgstr "" 97 | 98 | #: po/rc.cpp:7 99 | msgctxt "NAME OF TRANSLATORS" 100 | msgid "Your names" 101 | msgstr "" 102 | 103 | #: po/rc.cpp:8 104 | msgctxt "EMAIL OF TRANSLATORS" 105 | msgid "Your emails" 106 | msgstr "" 107 | -------------------------------------------------------------------------------- /marknoto/po/zh_CN.po: -------------------------------------------------------------------------------- 1 | # Copyright (C) YEAR This_file_is_part_of_KDE 2 | # This file is distributed under the same license as the PACKAGE package. 3 | # 4 | # shenda , 2016. 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: \n" 8 | "Report-Msgid-Bugs-To: https://github.com/sadhen/marketo/issues\n" 9 | "POT-Creation-Date: 2016-02-07 01:15+0800\n" 10 | "PO-Revision-Date: 2016-01-03 19:04+0800\n" 11 | "Last-Translator: shenda \n" 12 | "Language-Team: English \n" 13 | "Language: zh_CN\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 18 | "X-Generator: Lokalize 2.0\n" 19 | 20 | #: main.cpp:42 21 | msgid "Marknoto" 22 | msgstr "Marknoto" 23 | 24 | #: main.cpp:46 25 | msgid "(C) 2015 Darcy Shen" 26 | msgstr "(C) 2015 Darcy Shen" 27 | 28 | #: main.cpp:49 29 | msgid "Darcy Shen" 30 | msgstr "Darcy Shen" 31 | 32 | #: main.cpp:49 33 | msgid "Developer" 34 | msgstr "开发者" 35 | 36 | #: main.cpp:72 37 | msgid "Choose where your notes save" 38 | msgstr "" 39 | 40 | #: marknoto.cpp:40 41 | msgid "Preview" 42 | msgstr "预览" 43 | 44 | #: marknoto.cpp:73 45 | msgid "Focus" 46 | msgstr "集中" 47 | 48 | #: marknoto.cpp:74 49 | msgid "Split" 50 | msgstr "分栏" 51 | 52 | #: marknoto.cpp:75 53 | msgid "Browse" 54 | msgstr "三栏" 55 | 56 | #: marknoto.cpp:76 57 | msgid "New Note" 58 | msgstr "新建笔记" 59 | 60 | #: marknoto.cpp:77 61 | msgid "Forward" 62 | msgstr "前进" 63 | 64 | #: marknoto.cpp:78 65 | msgid "Backward" 66 | msgstr "后退" 67 | 68 | #: marknoto.cpp:79 69 | msgid "Home" 70 | msgstr "首页" 71 | 72 | #: marknoto.cpp:96 73 | msgid "" 74 | "This lists files which you have opened recently, and allows you to easily " 75 | "open them again." 76 | msgstr "" 77 | 78 | #: navpanel.cpp:64 79 | msgid "Folder" 80 | msgstr "目录" 81 | 82 | #: navpanel.cpp:65 83 | msgid "Tag" 84 | msgstr "标签" 85 | 86 | #. i18n: file: marknoto.rc:8 87 | #. i18n: ectx: Menu (go) 88 | #: po/rc.cpp:3 89 | msgid "&Go" 90 | msgstr "转到" 91 | 92 | #. i18n: file: marknoto.rc:14 93 | #. i18n: ectx: Menu (window) 94 | #: po/rc.cpp:6 95 | msgid "&Window" 96 | msgstr "窗口" 97 | 98 | #: po/rc.cpp:7 99 | msgctxt "NAME OF TRANSLATORS" 100 | msgid "Your names" 101 | msgstr "你的名字" 102 | 103 | #: po/rc.cpp:8 104 | msgctxt "EMAIL OF TRANSLATORS" 105 | msgid "Your emails" 106 | msgstr "你的邮箱" 107 | -------------------------------------------------------------------------------- /marknoto/taglist.cpp: -------------------------------------------------------------------------------- 1 | #include "taglist.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | TagList::TagList(QWidget* parent) 8 | : QListWidget(parent) 9 | { 10 | setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 11 | setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 12 | setFrameShape(QFrame::NoFrame); 13 | setFlow(QListView::LeftToRight); 14 | setContentsMargins(0, 0, 0, 0); 15 | setFixedHeight(24); 16 | sizePolicy().setHorizontalPolicy(QSizePolicy::MinimumExpanding); 17 | } 18 | 19 | void TagList::stretchWidth() 20 | { 21 | QFont font; 22 | font.setFamily(font.defaultFamily()); 23 | QFontMetrics fm(font); 24 | int size = 0; 25 | for (auto item : findItems("*", Qt::MatchWildcard)) 26 | size += fm.width(tagText(row(item))) + 5; 27 | 28 | setFixedWidth(size); 29 | } 30 | 31 | QString TagList::tagText(int i) const { 32 | return (dynamic_cast (itemWidget(item(i))))->text(); 33 | } 34 | 35 | QStringList TagList::addTags(const QString &tags) 36 | { 37 | QStringList listOfTags; 38 | QStringListIterator tagIter(tags.split(";", QString::SkipEmptyParts)); 39 | while (tagIter.hasNext()) { 40 | QString tag(tagIter.next()); 41 | QListWidgetItem *item = nullptr; 42 | 43 | // the full item list is a set 44 | for (auto iter : findItems("*", Qt::MatchWildcard)) 45 | if (tag == tagText(row(iter))) { 46 | item = iter; 47 | break; 48 | } 49 | 50 | if (item == nullptr) { 51 | listOfTags << tag; 52 | QListWidgetItem *newItem = new QListWidgetItem(this); 53 | QLabel *newLabel = new QLabel(tag); 54 | newLabel->setAlignment(Qt::AlignHCenter); 55 | setItemWidget(newItem, newLabel); 56 | newItem->setText(""); 57 | newItem->setSizeHint(itemWidget(newItem)->sizeHint() + QSize(5, 5)); 58 | addItem(newItem); 59 | } else { 60 | auto itemToDelete = takeItem(row(item)); 61 | delete itemWidget(itemToDelete); 62 | delete itemToDelete; 63 | } 64 | } // end of outer while 65 | return listOfTags; 66 | } 67 | 68 | TagList::~TagList() 69 | { 70 | for (auto item : findItems("*", Qt::MatchWildcard)) 71 | delete itemWidget(item); 72 | } 73 | 74 | #include "taglist.moc" -------------------------------------------------------------------------------- /marknoto/taglist.h: -------------------------------------------------------------------------------- 1 | #ifndef TAGLIST_H 2 | #define TAGLIST_H 3 | 4 | #include 5 | 6 | class TagList : public QListWidget 7 | { 8 | Q_OBJECT 9 | public: 10 | TagList(QWidget *parent); 11 | ~TagList(); 12 | QString tagText(int i) const; 13 | void stretchWidth(); 14 | QStringList addTags(const QString &); 15 | }; 16 | 17 | #endif -------------------------------------------------------------------------------- /marknoto/terminalpanel.cpp: -------------------------------------------------------------------------------- 1 | #include "terminalpanel.h" 2 | #include "kmarknote_generalsettings.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | TerminalPanel::TerminalPanel(QWidget* parent) : 18 | Panel(parent), 19 | m_layout(0), 20 | m_terminal(0), 21 | m_terminalWidget(0), 22 | m_konsolePart(0) 23 | { 24 | m_layout = new QVBoxLayout(this); 25 | m_layout->setMargin(0); 26 | } 27 | 28 | void TerminalPanel::showEvent(QShowEvent* event) 29 | { 30 | if (!m_terminal) { 31 | KPluginFactory* factory = 0; 32 | KService::Ptr service = KService::serviceByDesktopName("konsolepart"); 33 | if (service) 34 | factory = KPluginLoader(service->library()).factory(); 35 | m_konsolePart = factory ? (factory->create(this)) : 0; 36 | if (m_konsolePart) { 37 | connect(m_konsolePart, SIGNAL(destroyed(QObject*)), this, SLOT(terminalExited())); 38 | m_terminalWidget = m_konsolePart->widget(); 39 | m_layout->addWidget(m_terminalWidget); 40 | m_terminal = qobject_cast(m_konsolePart); 41 | } 42 | } 43 | if (m_terminal) { 44 | m_terminalWidget->setFocus(); 45 | urlChanged(); 46 | connect(m_konsolePart, SIGNAL(currentDirectoryChanged(QString)), 47 | this, SLOT(slotKonsolePartCurrentDirectoryChanged(QString))); 48 | } 49 | } 50 | 51 | void TerminalPanel::sendCdToTerminal(const QString& dir) 52 | { 53 | if (dir == m_konsolePartCurrentDirectory) { 54 | m_clearTerminal = false; 55 | return; 56 | } 57 | 58 | if (!m_clearTerminal) { 59 | // The TerminalV2 interface does not provide a way to delete the 60 | // current line before sending a new input. This is mandatory, 61 | // otherwise sending a 'cd x' to a existing 'rm -rf *' might 62 | // result in data loss. As workaround SIGINT is send. 63 | const int processId = m_terminal->terminalProcessId(); 64 | if (processId > 0) { 65 | kill(processId, SIGINT); 66 | } 67 | } 68 | 69 | m_terminal->sendInput(" cd " + KShell::quoteArg(dir) + '\n'); 70 | m_konsolePartCurrentDirectory = dir; 71 | 72 | if (m_clearTerminal) { 73 | m_terminal->sendInput(" clear\n"); 74 | m_clearTerminal = false; 75 | } 76 | } 77 | 78 | bool TerminalPanel::urlChanged() 79 | { 80 | if (!url().isValid()) { 81 | return false; 82 | } 83 | 84 | const bool sendInput = m_terminal && (m_terminal->foregroundProcessId() == -1) && isVisible(); 85 | if (sendInput) { 86 | if (QFileInfo(url().path()).isDir()) 87 | sendCdToTerminal(url().toLocalFile()); 88 | else 89 | sendCdToTerminal(url().directory()); 90 | } 91 | 92 | return true; 93 | } 94 | 95 | void TerminalPanel::terminalExited() 96 | { 97 | m_terminal = 0; 98 | hide(); 99 | } 100 | 101 | void TerminalPanel::slotKonsolePartCurrentDirectoryChanged(const QString& dir) 102 | { 103 | m_konsolePartCurrentDirectory = dir; 104 | 105 | // Only change the view URL if 'dir' is different from the current view URL. 106 | // Note that the current view URL could also be a symbolic link to 'dir' 107 | // -> use QDir::canonicalPath() to check that. 108 | const KUrl oldUrl(url()); 109 | const KUrl newUrl(dir); 110 | if (newUrl != oldUrl && dir != QDir(oldUrl.path()).canonicalPath()) { 111 | emit changeUrl(newUrl); 112 | } 113 | } 114 | 115 | TerminalPanel::~TerminalPanel() 116 | { 117 | 118 | } 119 | 120 | #include "terminalpanel.moc" 121 | -------------------------------------------------------------------------------- /marknoto/terminalpanel.h: -------------------------------------------------------------------------------- 1 | #ifndef TERMINALVIEW_H 2 | #define TERMINALVIEW_H 3 | 4 | #include "panel.h" 5 | 6 | #include 7 | 8 | class TerminalInterfaceV2; 9 | class QVBoxLayout; 10 | class QShowEvent; 11 | 12 | namespace KParts { 13 | class ReadOnlyPart; 14 | } 15 | 16 | class TerminalPanel : public Panel 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | TerminalPanel(QWidget *parent = 0); 22 | virtual ~TerminalPanel(); 23 | void sendCdToTerminal(const QString& dir); 24 | 25 | signals: 26 | void hideTerminalPanel(); 27 | 28 | public slots: 29 | void terminalExited(); 30 | void slotKonsolePartCurrentDirectoryChanged(const QString& dir); 31 | 32 | protected: 33 | /** @see QWidget::showEvent() */ 34 | virtual void showEvent(QShowEvent* event); 35 | /** @see Panel::urlChanged() */ 36 | virtual bool urlChanged(); 37 | 38 | private: 39 | QVBoxLayout* m_layout; 40 | TerminalInterfaceV2* m_terminal; 41 | QWidget* m_terminalWidget; 42 | KParts::ReadOnlyPart* m_konsolePart; 43 | QString m_konsolePartCurrentDirectory; 44 | bool m_clearTerminal; 45 | }; 46 | #endif -------------------------------------------------------------------------------- /markpado/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(markpado) 2 | 3 | set (CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) 4 | 5 | if (WIN32) 6 | set (markpado_ICON icon.rc) 7 | endif (WIN32) 8 | 9 | set (markpado_SRCS 10 | main.cpp 11 | mainwindow.cpp 12 | markpado.cpp 13 | highlighterbykate.cpp 14 | htmlgenerator.cpp 15 | document.cpp 16 | webpage.cpp 17 | ${markpado_ICON} 18 | ) 19 | 20 | 21 | qt5_add_resources(markpado_SRCS data/markpado.qrc) 22 | 23 | add_executable (markpado ${markpado_SRCS}) 24 | 25 | include_directories(../marknote/) 26 | 27 | if (WIN32) 28 | include_directories(${CMAKE_INSTALL_PREFIX}/include/) 29 | target_link_libraries (markpado 30 | Qt5::WebEngineWidgets 31 | Qt5::WebChannel 32 | KF5::TextEditor 33 | KF5::I18n 34 | KF5::XmlGui 35 | C:/local/boost_1_60_0/lib64-msvc-14.0/libboost_regex-vc140-mt-1_60.lib 36 | ${CMAKE_INSTALL_PREFIX}/lib/mdcpp.lib 37 | ) 38 | else (WIN32) 39 | target_link_libraries (markpado 40 | Qt5::WebEngineWidgets 41 | Qt5::WebChannel 42 | KF5::TextEditor 43 | KF5::I18n 44 | KF5::XmlGui 45 | KF5KIOFileWidgets 46 | mdcpp 47 | ) 48 | endif (WIN32) 49 | 50 | if(APPLE) 51 | # own plist template 52 | set_target_properties (markpado PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/data/MacOSXBundleInfo.plist.in) 53 | 54 | # the MacOSX bundle display name property (CFBundleDisplayName) is not currently supported by cmake, 55 | # so has to be set for all targets in this cmake file 56 | set(MACOSX_BUNDLE_DISPLAY_NAME Markpado) 57 | set_target_properties(markpado PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "com.sadhen.kde.Markpado") 58 | set_target_properties(markpado PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Markpado") 59 | set_target_properties(markpado PROPERTIES MACOSX_BUNDLE_DISPLAY_NAME "Markpado") 60 | set_target_properties(markpado PROPERTIES MACOSX_BUNDLE_INFO_STRING "Markpado - Markdown Editor") 61 | set_target_properties(markpado PROPERTIES MACOSX_BUNDLE_LONG_VERSION_STRING "Markpado ${KDE_APPLICATIONS_VERSION}") 62 | set_target_properties(markpado PROPERTIES MACOSX_BUNDLE_SHORT_VERSION_STRING "${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}") 63 | set_target_properties(markpado PROPERTIES MACOSX_BUNDLE_BUNDLE_VERSION "${KDE_APPLICATIONS_VERSION}") 64 | set_target_properties(markpado PROPERTIES MACOSX_BUNDLE_COPYRIGHT "2000-2017 Darcy Shen") 65 | endif() 66 | 67 | install(TARGETS markpado ${INSTALL_TARGETS_DEFAULT_ARGS}) 68 | install(PROGRAMS markpado.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) 69 | 70 | add_subdirectory (icons) 71 | add_subdirectory (kate-commonmark) 72 | add_subdirectory (po) 73 | -------------------------------------------------------------------------------- /markpado/data/MacOSXBundleInfo.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrincipalClass 6 | NSApplication 7 | NSHighResolutionCapable 8 | True 9 | CFBundleDevelopmentRegion 10 | English 11 | CFBundleExecutable 12 | ${MACOSX_BUNDLE_EXECUTABLE_NAME} 13 | CFBundleGetInfoString 14 | ${MACOSX_BUNDLE_INFO_STRING} 15 | CFBundleIconFile 16 | ${MACOSX_BUNDLE_ICON_FILE} 17 | CFBundleIdentifier 18 | ${MACOSX_BUNDLE_GUI_IDENTIFIER} 19 | CFBundleInfoDictionaryVersion 20 | 6.0 21 | CFBundleLongVersionString 22 | ${MACOSX_BUNDLE_LONG_VERSION_STRING} 23 | CFBundleName 24 | ${MACOSX_BUNDLE_BUNDLE_NAME} 25 | CFBundlePackageType 26 | APPL 27 | CFBundleShortVersionString 28 | ${MACOSX_BUNDLE_SHORT_VERSION_STRING} 29 | CFBundleSignature 30 | ???? 31 | CFBundleVersion 32 | ${MACOSX_BUNDLE_BUNDLE_VERSION} 33 | CSResourcesFileMapped 34 | 35 | LSRequiresCarbon 36 | 37 | NSHumanReadableCopyright 38 | ${MACOSX_BUNDLE_COPYRIGHT} 39 | CFBundleDocumentTypes 40 | 41 | 42 | CFBundleTypeExtensions 43 | 44 | * 45 | 46 | CFBundleTypeName 47 | NSStringPboardType 48 | CFBundleTypeRole 49 | Editor 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /markpado/data/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | 16 |
17 | 34 | 35 | -------------------------------------------------------------------------------- /markpado/data/markpado.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | markpado.rc 5 | 6 | 7 | style.css 8 | 9 | 10 | index.html 11 | 12 | 13 | qwebchannel.js 14 | 15 | 16 | -------------------------------------------------------------------------------- /markpado/data/markpado.rc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | &Edit 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | &View 25 | 26 | 27 | 28 | &Tools 29 | 30 | 31 | 32 | 33 | 34 | Main Toolbar 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /markpado/data/qwebchannel.js: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: http://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** You may use this file under the terms of the BSD license as follows: 10 | ** 11 | ** "Redistribution and use in source and binary forms, with or without 12 | ** modification, are permitted provided that the following conditions are 13 | ** met: 14 | ** * Redistributions of source code must retain the above copyright 15 | ** notice, this list of conditions and the following disclaimer. 16 | ** * Redistributions in binary form must reproduce the above copyright 17 | ** notice, this list of conditions and the following disclaimer in 18 | ** the documentation and/or other materials provided with the 19 | ** distribution. 20 | ** * Neither the name of The Qt Company Ltd nor the names of its 21 | ** contributors may be used to endorse or promote products derived 22 | ** from this software without specific prior written permission. 23 | ** 24 | ** 25 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 27 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 28 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 29 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 30 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 31 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 32 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 33 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 36 | ** 37 | ** $QT_END_LICENSE$ 38 | ** 39 | ****************************************************************************/ 40 | 41 | "use strict"; 42 | 43 | var QWebChannelMessageTypes = { 44 | signal: 1, 45 | propertyUpdate: 2, 46 | init: 3, 47 | idle: 4, 48 | debug: 5, 49 | invokeMethod: 6, 50 | connectToSignal: 7, 51 | disconnectFromSignal: 8, 52 | setProperty: 9, 53 | response: 10, 54 | }; 55 | 56 | var QWebChannel = function(transport, initCallback) 57 | { 58 | if (typeof transport !== "object" || typeof transport.send !== "function") { 59 | console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." + 60 | " Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send)); 61 | return; 62 | } 63 | 64 | var channel = this; 65 | this.transport = transport; 66 | 67 | this.send = function(data) 68 | { 69 | if (typeof(data) !== "string") { 70 | data = JSON.stringify(data); 71 | } 72 | channel.transport.send(data); 73 | } 74 | 75 | this.transport.onmessage = function(message) 76 | { 77 | var data = message.data; 78 | if (typeof data === "string") { 79 | data = JSON.parse(data); 80 | } 81 | switch (data.type) { 82 | case QWebChannelMessageTypes.signal: 83 | channel.handleSignal(data); 84 | break; 85 | case QWebChannelMessageTypes.response: 86 | channel.handleResponse(data); 87 | break; 88 | case QWebChannelMessageTypes.propertyUpdate: 89 | channel.handlePropertyUpdate(data); 90 | break; 91 | default: 92 | console.error("invalid message received:", message.data); 93 | break; 94 | } 95 | } 96 | 97 | this.execCallbacks = {}; 98 | this.execId = 0; 99 | this.exec = function(data, callback) 100 | { 101 | if (!callback) { 102 | // if no callback is given, send directly 103 | channel.send(data); 104 | return; 105 | } 106 | if (channel.execId === Number.MAX_VALUE) { 107 | // wrap 108 | channel.execId = Number.MIN_VALUE; 109 | } 110 | if (data.hasOwnProperty("id")) { 111 | console.error("Cannot exec message with property id: " + JSON.stringify(data)); 112 | return; 113 | } 114 | data.id = channel.execId++; 115 | channel.execCallbacks[data.id] = callback; 116 | channel.send(data); 117 | }; 118 | 119 | this.objects = {}; 120 | 121 | this.handleSignal = function(message) 122 | { 123 | var object = channel.objects[message.object]; 124 | if (object) { 125 | object.signalEmitted(message.signal, message.args); 126 | } else { 127 | console.warn("Unhandled signal: " + message.object + "::" + message.signal); 128 | } 129 | } 130 | 131 | this.handleResponse = function(message) 132 | { 133 | if (!message.hasOwnProperty("id")) { 134 | console.error("Invalid response message received: ", JSON.stringify(message)); 135 | return; 136 | } 137 | channel.execCallbacks[message.id](message.data); 138 | delete channel.execCallbacks[message.id]; 139 | } 140 | 141 | this.handlePropertyUpdate = function(message) 142 | { 143 | for (var i in message.data) { 144 | var data = message.data[i]; 145 | var object = channel.objects[data.object]; 146 | if (object) { 147 | object.propertyUpdate(data.signals, data.properties); 148 | } else { 149 | console.warn("Unhandled property update: " + data.object + "::" + data.signal); 150 | } 151 | } 152 | channel.exec({type: QWebChannelMessageTypes.idle}); 153 | } 154 | 155 | this.debug = function(message) 156 | { 157 | channel.send({type: QWebChannelMessageTypes.debug, data: message}); 158 | }; 159 | 160 | channel.exec({type: QWebChannelMessageTypes.init}, function(data) { 161 | for (var objectName in data) { 162 | var object = new QObject(objectName, data[objectName], channel); 163 | } 164 | // now unwrap properties, which might reference other registered objects 165 | for (var objectName in channel.objects) { 166 | channel.objects[objectName].unwrapProperties(); 167 | } 168 | if (initCallback) { 169 | initCallback(channel); 170 | } 171 | channel.exec({type: QWebChannelMessageTypes.idle}); 172 | }); 173 | }; 174 | 175 | function QObject(name, data, webChannel) 176 | { 177 | this.__id__ = name; 178 | webChannel.objects[name] = this; 179 | 180 | // List of callbacks that get invoked upon signal emission 181 | this.__objectSignals__ = {}; 182 | 183 | // Cache of all properties, updated when a notify signal is emitted 184 | this.__propertyCache__ = {}; 185 | 186 | var object = this; 187 | 188 | // ---------------------------------------------------------------------- 189 | 190 | this.unwrapQObject = function(response) 191 | { 192 | if (response instanceof Array) { 193 | // support list of objects 194 | var ret = new Array(response.length); 195 | for (var i = 0; i < response.length; ++i) { 196 | ret[i] = object.unwrapQObject(response[i]); 197 | } 198 | return ret; 199 | } 200 | if (!response 201 | || !response["__QObject*__"] 202 | || response.id === undefined) { 203 | return response; 204 | } 205 | 206 | var objectId = response.id; 207 | if (webChannel.objects[objectId]) 208 | return webChannel.objects[objectId]; 209 | 210 | if (!response.data) { 211 | console.error("Cannot unwrap unknown QObject " + objectId + " without data."); 212 | return; 213 | } 214 | 215 | var qObject = new QObject( objectId, response.data, webChannel ); 216 | qObject.destroyed.connect(function() { 217 | if (webChannel.objects[objectId] === qObject) { 218 | delete webChannel.objects[objectId]; 219 | // reset the now deleted QObject to an empty {} object 220 | // just assigning {} though would not have the desired effect, but the 221 | // below also ensures all external references will see the empty map 222 | // NOTE: this detour is necessary to workaround QTBUG-40021 223 | var propertyNames = []; 224 | for (var propertyName in qObject) { 225 | propertyNames.push(propertyName); 226 | } 227 | for (var idx in propertyNames) { 228 | delete qObject[propertyNames[idx]]; 229 | } 230 | } 231 | }); 232 | // here we are already initialized, and thus must directly unwrap the properties 233 | qObject.unwrapProperties(); 234 | return qObject; 235 | } 236 | 237 | this.unwrapProperties = function() 238 | { 239 | for (var propertyIdx in object.__propertyCache__) { 240 | object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]); 241 | } 242 | } 243 | 244 | function addSignal(signalData, isPropertyNotifySignal) 245 | { 246 | var signalName = signalData[0]; 247 | var signalIndex = signalData[1]; 248 | object[signalName] = { 249 | connect: function(callback) { 250 | if (typeof(callback) !== "function") { 251 | console.error("Bad callback given to connect to signal " + signalName); 252 | return; 253 | } 254 | 255 | object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; 256 | object.__objectSignals__[signalIndex].push(callback); 257 | 258 | if (!isPropertyNotifySignal && signalName !== "destroyed") { 259 | // only required for "pure" signals, handled separately for properties in propertyUpdate 260 | // also note that we always get notified about the destroyed signal 261 | webChannel.exec({ 262 | type: QWebChannelMessageTypes.connectToSignal, 263 | object: object.__id__, 264 | signal: signalIndex 265 | }); 266 | } 267 | }, 268 | disconnect: function(callback) { 269 | if (typeof(callback) !== "function") { 270 | console.error("Bad callback given to disconnect from signal " + signalName); 271 | return; 272 | } 273 | object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; 274 | var idx = object.__objectSignals__[signalIndex].indexOf(callback); 275 | if (idx === -1) { 276 | console.error("Cannot find connection of signal " + signalName + " to " + callback.name); 277 | return; 278 | } 279 | object.__objectSignals__[signalIndex].splice(idx, 1); 280 | if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) { 281 | // only required for "pure" signals, handled separately for properties in propertyUpdate 282 | webChannel.exec({ 283 | type: QWebChannelMessageTypes.disconnectFromSignal, 284 | object: object.__id__, 285 | signal: signalIndex 286 | }); 287 | } 288 | } 289 | }; 290 | } 291 | 292 | /** 293 | * Invokes all callbacks for the given signalname. Also works for property notify callbacks. 294 | */ 295 | function invokeSignalCallbacks(signalName, signalArgs) 296 | { 297 | var connections = object.__objectSignals__[signalName]; 298 | if (connections) { 299 | connections.forEach(function(callback) { 300 | callback.apply(callback, signalArgs); 301 | }); 302 | } 303 | } 304 | 305 | this.propertyUpdate = function(signals, propertyMap) 306 | { 307 | // update property cache 308 | for (var propertyIndex in propertyMap) { 309 | var propertyValue = propertyMap[propertyIndex]; 310 | object.__propertyCache__[propertyIndex] = propertyValue; 311 | } 312 | 313 | for (var signalName in signals) { 314 | // Invoke all callbacks, as signalEmitted() does not. This ensures the 315 | // property cache is updated before the callbacks are invoked. 316 | invokeSignalCallbacks(signalName, signals[signalName]); 317 | } 318 | } 319 | 320 | this.signalEmitted = function(signalName, signalArgs) 321 | { 322 | invokeSignalCallbacks(signalName, signalArgs); 323 | } 324 | 325 | function addMethod(methodData) 326 | { 327 | var methodName = methodData[0]; 328 | var methodIdx = methodData[1]; 329 | object[methodName] = function() { 330 | var args = []; 331 | var callback; 332 | for (var i = 0; i < arguments.length; ++i) { 333 | if (typeof arguments[i] === "function") 334 | callback = arguments[i]; 335 | else 336 | args.push(arguments[i]); 337 | } 338 | 339 | webChannel.exec({ 340 | "type": QWebChannelMessageTypes.invokeMethod, 341 | "object": object.__id__, 342 | "method": methodIdx, 343 | "args": args 344 | }, function(response) { 345 | if (response !== undefined) { 346 | var result = object.unwrapQObject(response); 347 | if (callback) { 348 | (callback)(result); 349 | } 350 | } 351 | }); 352 | }; 353 | } 354 | 355 | function bindGetterSetter(propertyInfo) 356 | { 357 | var propertyIndex = propertyInfo[0]; 358 | var propertyName = propertyInfo[1]; 359 | var notifySignalData = propertyInfo[2]; 360 | // initialize property cache with current value 361 | // NOTE: if this is an object, it is not directly unwrapped as it might 362 | // reference other QObject that we do not know yet 363 | object.__propertyCache__[propertyIndex] = propertyInfo[3]; 364 | 365 | if (notifySignalData) { 366 | if (notifySignalData[0] === 1) { 367 | // signal name is optimized away, reconstruct the actual name 368 | notifySignalData[0] = propertyName + "Changed"; 369 | } 370 | addSignal(notifySignalData, true); 371 | } 372 | 373 | Object.defineProperty(object, propertyName, { 374 | configurable: true, 375 | get: function () { 376 | var propertyValue = object.__propertyCache__[propertyIndex]; 377 | if (propertyValue === undefined) { 378 | // This shouldn't happen 379 | console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__); 380 | } 381 | 382 | return propertyValue; 383 | }, 384 | set: function(value) { 385 | if (value === undefined) { 386 | console.warn("Property setter for " + propertyName + " called with undefined value!"); 387 | return; 388 | } 389 | object.__propertyCache__[propertyIndex] = value; 390 | webChannel.exec({ 391 | "type": QWebChannelMessageTypes.setProperty, 392 | "object": object.__id__, 393 | "property": propertyIndex, 394 | "value": value 395 | }); 396 | } 397 | }); 398 | 399 | } 400 | 401 | // ---------------------------------------------------------------------- 402 | 403 | data.methods.forEach(addMethod); 404 | 405 | data.properties.forEach(bindGetterSetter); 406 | 407 | data.signals.forEach(function(signal) { addSignal(signal, false); }); 408 | 409 | for (var name in data.enums) { 410 | object[name] = data.enums[name]; 411 | } 412 | } 413 | 414 | //required for use with nodejs 415 | if (typeof module === 'object') { 416 | module.exports = { 417 | QWebChannel: QWebChannel 418 | }; 419 | } 420 | -------------------------------------------------------------------------------- /markpado/data/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | font-size: 24px; 3 | } 4 | h2 { 5 | font-size: 22px; 6 | } 7 | h3 { 8 | font-size: 20px; 9 | } 10 | h4 { 11 | font-size: 18px; 12 | } 13 | h5 { 14 | font-size: 16px; 15 | } 16 | h6 { 17 | font-size: 16px; 18 | } 19 | blockquote { 20 | border-left: 3px solid #D0E5F2; 21 | padding: 0 0 0 10px; 22 | margin: 15px 0 15px 15px; 23 | } 24 | code { 25 | display: inline-block; 26 | padding: 0 4px; 27 | margin: 0 5px; 28 | background: #eeeeee; 29 | border-radius: 3px; 30 | font-size: 13px; 31 | } 32 | pre { 33 | padding: 10px 5px 10px 10px; 34 | margin: 15px 0; 35 | display: block; 36 | line-height: 18px; 37 | background: #F0F0F0; 38 | border-radius: 3px; 39 | font-size: 13px; 40 | white-space: pre; 41 | word-wrap: normal; 42 | overflow-x: auto; 43 | } 44 | pre code { 45 | display: block; 46 | padding: 0; 47 | margin: 0; 48 | background: none; 49 | border-radius: 0; 50 | } 51 | hr { 52 | display: block; 53 | height: 0px; 54 | border: 0; 55 | border-top: 1px solid #ccc; 56 | margin: 15px 0; 57 | padding: 0; 58 | } 59 | table { 60 | width: 100%; 61 | table-layout: fixed; 62 | border-collapse: collapse; 63 | border-spacing: 0; 64 | margin: 15px 0; 65 | } 66 | table thead { 67 | background-color: #f9f9f9; 68 | } 69 | table td, table th, table td, table th { 70 | min-width: 40px; 71 | height: 30px; 72 | border: 1px solid #ccc; 73 | vertical-align: top; 74 | padding: 2px 4px; 75 | text-align: left; 76 | box-sizing: border-box; 77 | } 78 | 79 | p{ 80 | margin-bottom: 0; 81 | } 82 | p img{ 83 | margin-bottom: 7px; 84 | } 85 | -------------------------------------------------------------------------------- /markpado/document.cpp: -------------------------------------------------------------------------------- 1 | #include "document.h" 2 | 3 | void Document::setText(const QString &text) { 4 | if (text == m_text) 5 | return; 6 | m_text = text; 7 | emit textChanged(m_text); 8 | } -------------------------------------------------------------------------------- /markpado/document.h: -------------------------------------------------------------------------------- 1 | #ifndef DOCUMENT_H 2 | #define DOCUMENT_H 3 | 4 | #include 5 | #include 6 | 7 | class Document : public QObject 8 | { 9 | Q_OBJECT 10 | Q_PROPERTY(QString text MEMBER m_text NOTIFY textChanged) 11 | public: 12 | explicit Document(QObject *parent = nullptr) : QObject(parent) {} 13 | 14 | void setText(const QString &text); 15 | 16 | signals: 17 | void textChanged(const QString &text); 18 | 19 | private: 20 | QString m_text; 21 | }; 22 | 23 | #endif -------------------------------------------------------------------------------- /markpado/highlighterbykate.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | HighlighterByKate::HighlighterByKate() 12 | { 13 | m_note = KTextEditor::Editor::instance()->createDocument(0); 14 | 15 | // NOTE: currently it must be created for acccess of KateDocument::lineAttributes 16 | // TODO: should be removed when upstream fix it 17 | m_editor = qobject_cast(m_note->createView(0)); 18 | } 19 | 20 | void HighlighterByKate::highlight(const string& plain, const string type, std::ostream& out) 21 | { 22 | QString resultType("None"); 23 | for (auto it = mimeMap.cbegin(); it != mimeMap.cend(); it++) { 24 | if (it->first == type 25 | || QString::fromStdString(it->second).compare(QString::fromStdString(type), Qt::CaseInsensitive) == 0) { 26 | resultType = QString::fromStdString(it->second); 27 | break; 28 | } 29 | } 30 | 31 | m_note->setText(QString::fromUtf8(plain.c_str())); 32 | m_note->setHighlightingMode(resultType); 33 | out << exportDocument(m_note).toUtf8().constData(); 34 | } 35 | 36 | void exportText(QString& ret, 37 | const QString& text, 38 | const KTextEditor::Attribute::Ptr& attrib) 39 | { 40 | if ( !attrib || !attrib->hasAnyProperty()) { 41 | ret.append(text.toHtmlEscaped()); 42 | return; 43 | } 44 | 45 | if ( attrib->fontBold() ) { 46 | ret.append(""); 47 | } 48 | if ( attrib->fontItalic() ) { 49 | ret.append(""); 50 | } 51 | 52 | bool writeForeground = attrib->hasProperty(QTextCharFormat::ForegroundBrush); 53 | bool writeBackground = attrib->hasProperty(QTextCharFormat::BackgroundBrush); 54 | 55 | if ( writeForeground || writeBackground ) { 56 | ret.append(QString("") 57 | .arg(writeForeground ? QString(QLatin1String("color:") 58 | + attrib->foreground().color().name() 59 | + QLatin1Char(';')) 60 | : QString()) 61 | .arg(writeBackground ? QString(QLatin1String("background:") 62 | + attrib->background().color().name() 63 | + QLatin1Char(';')) 64 | : QString())); 65 | } 66 | ret.append(text.toHtmlEscaped()); 67 | 68 | if ( writeBackground || writeForeground ) { 69 | ret.append(""); 70 | } 71 | if ( attrib->fontItalic() ) { 72 | ret.append(""); 73 | } 74 | if ( attrib->fontBold() ) { 75 | ret.append(""); 76 | } 77 | } 78 | 79 | /* 80 | * Export documents with kate highlight style 81 | */ 82 | QString HighlighterByKate::exportDocument(KTextEditor::Document* note) 83 | { 84 | QString ret(""); 85 | KTextEditor::Range range; 86 | 87 | range = note->documentRange(); 88 | 89 | const KTextEditor::Attribute::Ptr noAttrib(0); 90 | 91 | for (int i = 0; i < note->lines(); ++i) 92 | { 93 | QString content(""); 94 | const QString &line = note->line(i); 95 | 96 | QList attribs = m_editor->lineAttributes(i); 97 | 98 | int lineStart = 0; 99 | int remainingChars = line.length(); 100 | if ( range.onSingleLine() ) { 101 | lineStart = range.start().column(); 102 | remainingChars = range.columnWidth(); 103 | } else if ( i == range.start().line() ) { 104 | lineStart = range.start().column(); 105 | } else if ( i == range.end().line() ) { 106 | remainingChars = range.end().column(); 107 | } 108 | 109 | int handledUntil = lineStart; 110 | 111 | foreach ( const KTextEditor::AttributeBlock& block, attribs ) { 112 | // honor (block-) selections 113 | if ( block.start + block.length <= lineStart ) { 114 | continue; 115 | } else if ( block.start >= lineStart + remainingChars ) { 116 | break; 117 | } 118 | int start = qMax(block.start, lineStart); 119 | if ( start > handledUntil ) { 120 | exportText(content, line.mid( handledUntil, start - handledUntil ), noAttrib ); 121 | } 122 | int length = qMin(block.length, remainingChars); 123 | exportText(content, line.mid( start, length ), block.attribute); 124 | handledUntil = start + length; 125 | } 126 | 127 | if ( handledUntil < lineStart + remainingChars ) { 128 | exportText(content, line.mid( handledUntil, remainingChars ), noAttrib ); 129 | } 130 | 131 | if (i != range.end().line()) { 132 | ret.append(content.isEmpty() ? "\n" : content); 133 | ret.append("\n"); 134 | } 135 | } 136 | return ret; 137 | } 138 | 139 | HighlighterByKate::~HighlighterByKate() 140 | { 141 | 142 | } 143 | -------------------------------------------------------------------------------- /markpado/highlighterbykate.h: -------------------------------------------------------------------------------- 1 | #ifndef HIGHLIGHERBYKATE_H 2 | #define HIGHLIGHERBYKATE_H 3 | 4 | #include 5 | #include 6 | using std::string; 7 | using std::map; 8 | 9 | #include "libmdcpp.h" 10 | 11 | class QString; 12 | 13 | 14 | namespace KTextEditor { 15 | class Document; 16 | class Editor; 17 | class View; 18 | }; 19 | 20 | class HighlighterByKate : SyntaxHighlighter 21 | { 22 | public: 23 | HighlighterByKate(); 24 | virtual ~HighlighterByKate(); 25 | 26 | virtual void highlight(const string& code, const string lang, std::ostream& out); 27 | QString exportDocument(KTextEditor::Document* note); 28 | private: 29 | KTextEditor::Editor* m_new_editor; 30 | KTextEditor::Document* m_note; 31 | KTextEditor::View* m_editor; 32 | 33 | // see /usr/share/katepart5/syntax/ for more info 34 | map mimeMap = { 35 | {"dot", "dot"}, 36 | {"mysql", "SQL (MySQL)"}, 37 | {"ocaml", "Objective Caml"}, 38 | // by name 39 | {"cmake", "CMake"}, 40 | {"diff", "Diff"}, 41 | {"dockerfile", "Dockerfile"}, 42 | {"PHP", "PHP/PHP"}, 43 | {"R", "R Script"}, 44 | // by suffix 45 | {"awk", "AWK"}, 46 | {"c", "C"}, 47 | {"clj", "Clojure"}, 48 | {"cpp", "C++"}, 49 | {"css", "CSS"}, 50 | {"erl", "Erlang"}, 51 | {"for", "Fortran"}, 52 | {"fpp", "Fortran"}, 53 | {"go", "Go"}, 54 | {"hs", "Haskell"}, 55 | {"ini", "INI Files"}, 56 | {"java", "Java"}, 57 | {"jl", "Julia"}, 58 | {"js", "JavaScript"}, 59 | {"json", "JSON"}, 60 | {"lua", "Lua"}, 61 | {"mk", "Makefile"}, 62 | {"php", "PHP/PHP"}, 63 | {"py", "Python"}, 64 | {"qml", "QML"}, 65 | {"r", "R Script"}, 66 | {"rb", "Ruby"}, 67 | {"rs", "Rust"}, 68 | {"scala", "Scala"}, 69 | {"scm", "Scheme"}, 70 | {"sql", "SQL"}, 71 | {"sh", "Bash"}, 72 | {"zsh", "Zsh"} 73 | }; 74 | }; 75 | #endif 76 | -------------------------------------------------------------------------------- /markpado/htmlgenerator.cpp: -------------------------------------------------------------------------------- 1 | #include "htmlgenerator.h" 2 | #include "highlighterbykate.h" 3 | #include "libmdcpp.h" 4 | 5 | #include 6 | #include 7 | using std::istringstream; 8 | using std::ostringstream; 9 | 10 | HTMLGenerator::HTMLGenerator() 11 | { 12 | m_highlighter = new HighlighterByKate; 13 | } 14 | 15 | std::string HTMLGenerator::generated(string plain) 16 | { 17 | ostringstream sout; 18 | 19 | Procesoro processor((SyntaxHighlighter *) m_highlighter, "markdown"); 20 | processor.read(plain); 21 | processor.write(sout); 22 | 23 | return string(sout.str()); 24 | } 25 | 26 | HTMLGenerator::~HTMLGenerator() 27 | { 28 | 29 | } 30 | -------------------------------------------------------------------------------- /markpado/htmlgenerator.h: -------------------------------------------------------------------------------- 1 | #ifndef HTMLGENERATOR_H 2 | #define HTMLGENERATOR_H 3 | 4 | #include "highlighterbykate.h" 5 | 6 | #include 7 | using std::string; 8 | 9 | class HTMLGenerator { 10 | public: 11 | HTMLGenerator(); 12 | ~HTMLGenerator(); 13 | string generated(string plain); 14 | private: 15 | string result; 16 | HighlighterByKate *m_highlighter; 17 | }; 18 | 19 | #endif -------------------------------------------------------------------------------- /markpado/icon.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "icons/markpado128.ico" -------------------------------------------------------------------------------- /markpado/icons/128-apps-markpado.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/da-liii/marketo/0ec2d643c4a6a3aec7ecfdcda72236a78de1635e/markpado/icons/128-apps-markpado.png -------------------------------------------------------------------------------- /markpado/icons/16-apps-markpado.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/da-liii/marketo/0ec2d643c4a6a3aec7ecfdcda72236a78de1635e/markpado/icons/16-apps-markpado.png -------------------------------------------------------------------------------- /markpado/icons/32-apps-markpado.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/da-liii/marketo/0ec2d643c4a6a3aec7ecfdcda72236a78de1635e/markpado/icons/32-apps-markpado.png -------------------------------------------------------------------------------- /markpado/icons/48-apps-markpado.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/da-liii/marketo/0ec2d643c4a6a3aec7ecfdcda72236a78de1635e/markpado/icons/48-apps-markpado.png -------------------------------------------------------------------------------- /markpado/icons/64-apps-markpado.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/da-liii/marketo/0ec2d643c4a6a3aec7ecfdcda72236a78de1635e/markpado/icons/64-apps-markpado.png -------------------------------------------------------------------------------- /markpado/icons/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ecm_install_icons (ICONS 2 | 16-apps-markpado.png 3 | 32-apps-markpado.png 4 | 48-apps-markpado.png 5 | 64-apps-markpado.png 6 | 128-apps-markpado.png 7 | DESTINATION ${ICON_INSTALL_DIR} 8 | THEME hicolor 9 | ) 10 | -------------------------------------------------------------------------------- /markpado/icons/markpado128.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/da-liii/marketo/0ec2d643c4a6a3aec7ecfdcda72236a78de1635e/markpado/icons/markpado128.ico -------------------------------------------------------------------------------- /markpado/main.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2015 by Darcy Shen * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; if not, write to the * 16 | * Free Software Foundation, Inc., * 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * 18 | ***************************************************************************/ 19 | 20 | #include "mainwindow.h" 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "../icons.h" 33 | 34 | #define DESCRIPTION "Markpado - Editor Part of Marketo" 35 | 36 | #define VERSION "0.2.2" 37 | 38 | int main(int argc, char **argv) 39 | { 40 | QApplication app(argc, argv); 41 | 42 | setupIconTheme(); 43 | QIcon::setThemeName(QStringLiteral("breeze")); 44 | 45 | /* 46 | * enable high dpi support 47 | */ 48 | app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); 49 | KLocalizedString::setApplicationDomain("markpado"); 50 | 51 | KAboutData about(QStringLiteral("markpado"), 52 | i18n("Markpado"), 53 | QStringLiteral(VERSION), 54 | i18n(DESCRIPTION), 55 | KAboutLicense::GPL_V3, 56 | i18n("(C) 2015 Darcy Shen"), 57 | QString(), 58 | QStringLiteral("https://github.com/sadhen/marketo")); 59 | about.addAuthor(i18n("Darcy Shen"), i18n("Developer"), "sadhen@zoho.com" ); 60 | 61 | KAboutData::setApplicationData(about); 62 | 63 | app.setApplicationName(about.componentName()); 64 | app.setApplicationDisplayName(about.displayName()); 65 | app.setOrganizationDomain(about.organizationDomain()); 66 | app.setApplicationVersion(about.version()); 67 | 68 | QCommandLineParser parser; 69 | about.setupCommandLine(&parser); 70 | parser.setApplicationDescription(about.shortDescription()); 71 | parser.addHelpOption(); 72 | parser.addVersionOption(); 73 | 74 | // urls to open 75 | parser.addPositionalArgument(QStringLiteral("urls"), i18n("Documents to open."), QStringLiteral("[urls...]")); 76 | 77 | // do the commandline parsing 78 | parser.process(app); 79 | about.processCommandLine(&parser); 80 | 81 | 82 | if (parser.positionalArguments().count() > 0) { 83 | Q_FOREACH(const QString positionalArgument, parser.positionalArguments()) { 84 | QUrl url = QUrl::fromLocalFile(positionalArgument); 85 | if (url.isLocalFile()) 86 | new MainWindow(url); 87 | } 88 | } else { 89 | new MainWindow(); 90 | } 91 | 92 | return app.exec(); 93 | } 94 | -------------------------------------------------------------------------------- /markpado/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "markpado.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | MainWindow::MainWindow() 19 | : m_markpad(0) 20 | , m_recentFiles(0) 21 | , m_firstTextChange(false) 22 | { 23 | m_markpad = new Markpado(this); 24 | 25 | setupAction(); 26 | setupConnect(); 27 | 28 | setCentralWidget(m_markpad); 29 | setupGUI(QSize(500,600), Default, "markpado.rc"); 30 | createShellGUI(true); 31 | guiFactory()->addClient(m_markpad->m_editor); 32 | setStandardToolBarMenuEnabled(true); 33 | 34 | setAutoSaveSettings(); 35 | readConfig(); 36 | 37 | show(); 38 | } 39 | 40 | MainWindow::MainWindow(const QUrl& url) 41 | : m_markpad(0) 42 | , m_recentFiles(0) 43 | , m_firstTextChange(false) 44 | { 45 | m_markpad = new Markpado(this); 46 | 47 | setupAction(); 48 | setupConnect(); 49 | 50 | 51 | setCentralWidget(m_markpad); 52 | setupGUI(QSize(500,600), Default, "markpado.rc"); 53 | guiFactory()->addClient(m_markpad->m_editor); 54 | setStandardToolBarMenuEnabled(true); 55 | 56 | // FIXME: make sure the config dir exists, any idea how to do it more cleanly? 57 | QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation)).mkpath(QStringLiteral(".")); 58 | 59 | setAutoSaveSettings(); 60 | readConfig(); 61 | 62 | slotOpen(url); 63 | show(); 64 | } 65 | 66 | void MainWindow::setupAction() 67 | { 68 | actionCollection()->addAction( KStandardAction::Close, "file_close", this, SLOT(slotClose()) ); 69 | actionCollection()->addAction( KStandardAction::New, "file_new", this, SLOT(slotNew()) ); 70 | actionCollection()->addAction( KStandardAction::Open, "file_open", this, SLOT(slotOpen()) ); 71 | 72 | QAction *previewAction = actionCollection()->addAction("file_preview", this, SLOT(slotPreview())); 73 | previewAction->setIcon(QIcon::fromTheme(QLatin1String("document-preview"))); 74 | previewAction->setText(i18n("Preview")); 75 | previewAction->setCheckable(true); 76 | actionCollection()->setDefaultShortcut(previewAction, QKeySequence("F8")); 77 | 78 | QAction *splitAction = actionCollection()->addAction("window_split", this, SLOT(slotSplit())); 79 | splitAction->setIcon(QIcon::fromTheme(QLatin1String("view-split-left-right"))); 80 | splitAction->setText(i18n("Split")); 81 | splitAction->setCheckable(true); 82 | 83 | m_recentFiles = KStandardAction::openRecent(this, SLOT(slotOpen(QUrl)), this); 84 | actionCollection()->addAction(m_recentFiles->objectName(), m_recentFiles); 85 | m_recentFiles->setWhatsThis(i18n("This lists files which you have opened recently, and allows you to easily open them again.")); 86 | } 87 | 88 | void MainWindow::setupConnect() 89 | { 90 | connect(m_markpad->m_note, &KTextEditor::Document::modifiedChanged, 91 | this, &MainWindow::updateCaption); 92 | connect(m_markpad->m_note, &KTextEditor::Document::documentUrlChanged, 93 | this, &MainWindow::updateCaption); 94 | connect(m_markpad->m_note, &KTextEditor::Document::textChanged, 95 | this, &MainWindow::updateCaptionModified); 96 | } 97 | 98 | //common config 99 | void MainWindow::readConfig(KSharedConfigPtr config) 100 | { 101 | KConfigGroup cfg(config, "General Options"); 102 | 103 | m_recentFiles->loadEntries(config->group("Recent Files")); 104 | } 105 | 106 | void MainWindow::writeConfig(KSharedConfigPtr config) 107 | { 108 | KConfigGroup generalOptions(config, "General Options"); 109 | 110 | m_recentFiles->saveEntries(KConfigGroup(config, "Recent Files")); 111 | 112 | config->sync(); 113 | } 114 | 115 | //config file 116 | void MainWindow::readConfig() 117 | { 118 | readConfig(KSharedConfig::openConfig()); 119 | } 120 | 121 | void MainWindow::writeConfig() 122 | { 123 | writeConfig(KSharedConfig::openConfig()); 124 | } 125 | 126 | void MainWindow::readProperties(const KConfigGroup &config) 127 | { 128 | Q_UNUSED(config); 129 | readConfig(); 130 | } 131 | 132 | void MainWindow::saveProperties(KConfigGroup &config) 133 | { 134 | Q_UNUSED(config); 135 | writeConfig(); 136 | } 137 | 138 | void MainWindow::updateCaptionModified() 139 | { 140 | // The first textChanged signal is caused by document loading 141 | // Thus, we should ignore it 142 | if (m_firstTextChange) { 143 | m_firstTextChange = false; 144 | return ; 145 | } 146 | setCaption(m_markpad->m_note->url().fileName() + " [modified]- markpado"); 147 | } 148 | 149 | void MainWindow::updateCaption() 150 | { 151 | setCaption(m_markpad->m_note->url().fileName() + " - markpado"); 152 | } 153 | 154 | void MainWindow::slotNew() 155 | { 156 | new MainWindow(); 157 | } 158 | 159 | void MainWindow::slotClose() 160 | { 161 | m_markpad->m_note->closeUrl(); 162 | } 163 | 164 | void MainWindow::slotOpen() 165 | { 166 | QUrl url = QFileDialog::getOpenFileUrl(); 167 | 168 | slotOpen(url); 169 | } 170 | 171 | void MainWindow::slotOpen(const QUrl &url) 172 | { 173 | if (url.toString().startsWith("http")) { 174 | QDesktopServices::openUrl(url); 175 | return ; 176 | } 177 | // NOTICE: the order of assigning m_firstTextChange and markpad matters 178 | m_firstTextChange = true; 179 | m_markpad->m_note->openUrl(url); 180 | m_markpad->m_note->setHighlightingMode("CommonMark"); 181 | m_recentFiles->addUrl(url); 182 | } 183 | 184 | void MainWindow::slotPreview() 185 | { 186 | bool isPreview = actionCollection()->action("file_preview")->isChecked(); 187 | m_markpad->setPreview(isPreview); 188 | if (!isPreview) { 189 | m_markpad->setSplit(false); 190 | actionCollection()->action("window_split")->setChecked(false); 191 | } 192 | } 193 | 194 | void MainWindow::slotSplit() 195 | { 196 | bool isSplit = actionCollection()->action("window_split")->isChecked(); 197 | m_markpad->setSplit(isSplit); 198 | actionCollection()->action("file_preview")->setChecked(isSplit); 199 | } 200 | 201 | MainWindow::~MainWindow() 202 | { 203 | writeConfig(); 204 | } 205 | 206 | 207 | #include "mainwindow.moc" 208 | -------------------------------------------------------------------------------- /markpado/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | class Markpado; 8 | class KRecentFilesAction; 9 | 10 | class MainWindow : public KParts::MainWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | MainWindow(); 16 | MainWindow(const QUrl &); 17 | virtual ~MainWindow(); 18 | 19 | private: 20 | void setupAction(); 21 | void setupConnect(); 22 | 23 | private slots: 24 | void slotNew(); 25 | void slotOpen(); 26 | void slotOpen(const QUrl&); 27 | void slotClose(); 28 | void slotPreview(); 29 | void slotSplit(); 30 | void updateCaption(); 31 | void updateCaptionModified(); 32 | 33 | private: 34 | Markpado *m_markpad; 35 | 36 | // session management 37 | private: 38 | void readConfig(); 39 | void writeConfig(); 40 | void writeConfig(KSharedConfigPtr config); 41 | void readConfig(KSharedConfigPtr config); 42 | void readProperties(const KConfigGroup &config) override; 43 | void saveProperties(KConfigGroup &config) override; 44 | KRecentFilesAction *m_recentFiles; 45 | bool m_firstTextChange; 46 | }; 47 | 48 | #endif -------------------------------------------------------------------------------- /markpado/markpado.cpp: -------------------------------------------------------------------------------- 1 | #include "markpado.h" 2 | #include "htmlgenerator.h" 3 | #include "webpage.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | using std::string; 26 | 27 | Markpado::Markpado(QWidget *parent) 28 | : QWidget(parent) 29 | , m_generator(new HTMLGenerator) 30 | { 31 | hl = new QHBoxLayout(this); 32 | hl->setMargin(0); 33 | 34 | hs = new QSplitter(this); 35 | 36 | m_page = new WebPage(this); 37 | m_previewer = new QWebEngineView(this); 38 | m_previewer->setPage(m_page); 39 | m_livePreview = false; 40 | 41 | QWebChannel *channel = new QWebChannel(this); 42 | channel->registerObject(QStringLiteral("content"), &m_content); 43 | m_page->setWebChannel(channel); 44 | m_page->setUrl(QUrl("qrc:/index.html")); 45 | connect(m_page, SIGNAL(linkClicked(const QUrl&)), 46 | parent, SLOT(slotOpen(const QUrl&))); 47 | 48 | m_note = KTextEditor::Editor::instance()->createDocument(0); 49 | m_note->setHighlightingMode("CommonMark"); 50 | m_editor = qobject_cast(m_note->createView(this)); 51 | m_editor->setStatusBarEnabled(false); 52 | m_editor->setFocus(Qt::OtherFocusReason); 53 | 54 | hs->addWidget(m_editor); 55 | hs->addWidget(m_previewer); 56 | hl->addWidget(hs); 57 | 58 | hs->setStretchFactor(0, 16); 59 | hs->setStretchFactor(1, 10); 60 | 61 | setPreview(false); 62 | setSplit(false); 63 | 64 | connect(m_note, &KTextEditor::Document::textChanged, 65 | this, &Markpado::updatePreviewer); 66 | connect(m_editor, &KTextEditor::View::cursorPositionChanged, 67 | this, &Markpado::updatePreviewerByCursor); 68 | connect(m_note, &KTextEditor::Document::documentSavedOrUploaded, 69 | this, &Markpado::onSavedOrUploaded); 70 | } 71 | 72 | void Markpado::onSavedOrUploaded(KTextEditor::Document *document, bool saveAs) 73 | { 74 | Q_UNUSED(document); 75 | Q_UNUSED(saveAs); 76 | m_note->setHighlightingMode("CommonMark"); 77 | generateHtml(); 78 | } 79 | 80 | void Markpado::preview(bool livePreview) 81 | { 82 | m_livePreview = livePreview; 83 | preview(); 84 | } 85 | 86 | 87 | void Markpado::preview() 88 | { 89 | if (m_livePreview) { 90 | m_editor->setHidden(false); 91 | m_previewer->setHidden(false); 92 | } else { 93 | m_editor->setHidden(true); 94 | m_previewer->setHidden(false); 95 | } 96 | 97 | generateHtml(); 98 | // scroll to the correct position 99 | updatePreviewerByCursor(0, m_editor->cursorPosition()); 100 | 101 | setFocusProxy(m_previewer); 102 | } 103 | 104 | void Markpado::unpreview() 105 | { 106 | m_editor->setHidden(false); 107 | m_previewer->setHidden(true); 108 | m_livePreview = false; 109 | 110 | setFocusProxy(m_editor); 111 | } 112 | 113 | void Markpado::updatePreviewer() 114 | { 115 | generateHtml(); 116 | } 117 | 118 | void Markpado::generateHtml() { 119 | string html = m_generator->generated(string(m_note->text().toUtf8().constData())); 120 | m_content.setText(QString::fromUtf8(html.c_str())); 121 | } 122 | 123 | void Markpado::updatePreviewerByCursor(KTextEditor::View *editor, const KTextEditor::Cursor& cursor) 124 | { 125 | Q_UNUSED(editor); 126 | 127 | int sourceTotal = m_note->lines(); 128 | int sourceCur = cursor.line(); 129 | int targetCur = sourceCur / sourceTotal; 130 | int offset = (sourceCur - sourceTotal/2) * 400 / sourceTotal; 131 | 132 | const QString ScrollJavaScript("window.scrollTo(0, document.body.scrollHeight * %1 / %2 + %3);"); 133 | m_previewer->page()->runJavaScript(ScrollJavaScript.arg(sourceCur).arg(sourceTotal).arg(offset)); 134 | } 135 | 136 | void Markpado::setPreview(bool checked) 137 | { 138 | if (checked) { 139 | m_editor->setHidden(true); 140 | m_previewer->setHidden(false); 141 | preview(); 142 | } else { 143 | m_editor->setHidden(false); 144 | m_previewer->setHidden(!m_livePreview); 145 | } 146 | } 147 | 148 | void Markpado::setSplit(bool checked) 149 | { 150 | m_livePreview = checked; 151 | m_previewer->setHidden(!checked); 152 | m_editor->setHidden(false); 153 | } 154 | 155 | KTextEditor::View* Markpado::view() 156 | { 157 | return m_editor; 158 | } 159 | 160 | Markpado::~Markpado() 161 | { 162 | } 163 | 164 | #include "markpado.moc" 165 | -------------------------------------------------------------------------------- /markpado/markpado.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Markpado 3 | Name[zh_CN]=Markpado 4 | Name[zh_TW]=Markpado 5 | Exec=markpado 6 | Icon=markpado 7 | Type=Application 8 | Mimetype=text/markdown; 9 | Categories=KDE;Utility;TextEditor; 10 | GenericName=Markdown Editor 11 | GenericName[zh_CN]=Markdown 编辑器 12 | GenericName[zh_TW]=Markdown 編輯器 13 | Comment=Just Another Markdown Editor 14 | Comment[zh_TW]=另一個 Markdown 編輯器 15 | Comment[zh_CN]=另一个 Markdown 编辑器 16 | -------------------------------------------------------------------------------- /markpado/markpado.h: -------------------------------------------------------------------------------- 1 | #ifndef KMARKVIEW_H 2 | #define KMARKVIEW_H 3 | 4 | #include "htmlgenerator.h" 5 | #include "document.h" 6 | 7 | #include 8 | #include 9 | #include 10 | class QWebEngineView; 11 | 12 | class QHBoxLayout; 13 | class QSplitter; 14 | class QUrl; 15 | class WebPage; 16 | namespace KTextEditor { 17 | class View; 18 | class Document; 19 | class Cursor; 20 | class Editor; 21 | }; 22 | 23 | class Markpado : public QWidget 24 | { 25 | Q_OBJECT 26 | 27 | public: 28 | Markpado(QWidget *parent = 0); 29 | virtual ~Markpado(); 30 | void unpreview(); 31 | KTextEditor::View* view(); 32 | KTextEditor::Document *m_note; 33 | KTextEditor::View *m_editor; 34 | void setPreview(bool checked); 35 | void setSplit(bool checked); 36 | 37 | signals: 38 | void stop(); 39 | 40 | public slots: 41 | void preview(bool livePreview); 42 | void preview(); 43 | 44 | private: 45 | QSplitter *hs; 46 | QHBoxLayout *hl; 47 | QWebEngineView *m_previewer; 48 | WebPage *m_page; 49 | bool m_livePreview; 50 | KTextEditor::Editor *m_new_editor; 51 | HTMLGenerator *m_generator; 52 | Document m_content; 53 | 54 | private slots: 55 | void updatePreviewer(); 56 | void updatePreviewerByCursor(KTextEditor::View *a_editor, const KTextEditor::Cursor& a_cursor); 57 | void onSavedOrUploaded(KTextEditor::Document *document, bool saveAs); 58 | void generateHtml(); 59 | }; 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /markpado/po/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB PO_FILES *.po) 2 | gettext_create_translations(markpado.pot ALL ${PO_FILES}) 3 | 4 | add_custom_target(tr_pado SOURCES markpado.pot ${PO_FILES}) 5 | -------------------------------------------------------------------------------- /markpado/po/Messages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | BASEDIR=".." # root of translatable sources 3 | PROJECT="markpado" # project name 4 | BUGADDR="https://github.com/sadhen/marketo/issues" # MSGID-Bugs 5 | WDIR=`pwd` # working dir 6 | 7 | check_error() 8 | { 9 | if [ "$?" -ne "0" ]; then 10 | echo "ERROR: ${1}" 11 | exit 1 12 | fi 13 | } 14 | 15 | echo "Preparing rc files" 16 | cd ${BASEDIR} 17 | # we use simple sorting to make sure the lines don't jump around too much from system to system 18 | find . -name '*.rc' -o -name '*.ui' -o -name '*.kcfg' | sort > ${WDIR}/rcfiles.list 19 | xargs --arg-file=${WDIR}/rcfiles.list extractrc > ${WDIR}/rc.cpp 20 | check_error "Failed to extract messages from rc files. Do you have extractrc installed?" 21 | # additional string for KAboutData 22 | echo 'i18nc("NAME OF TRANSLATORS","Your names");' >> ${WDIR}/rc.cpp 23 | echo 'i18nc("EMAIL OF TRANSLATORS","Your emails");' >> ${WDIR}/rc.cpp 24 | cd ${WDIR} 25 | echo "Done preparing rc files" 26 | 27 | 28 | echo "Extracting messages" 29 | cd ${BASEDIR} 30 | # see above on sorting 31 | find . -name '*.cpp' -o -name '*.h' -o -name '*.c' | sort > ${WDIR}/infiles.list 32 | echo "rc.cpp" >> ${WDIR}/infiles.list 33 | cd ${WDIR} 34 | xgettext --from-code=UTF-8 -C -kde -ci18n -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -ktr2i18n:1 \ 35 | -kI18N_NOOP:1 -kI18N_NOOP2:1c,2 -kaliasLocale -kki18n:1 -kki18nc:1c,2 -kki18np:1,2 -kki18ncp:1c,2,3 \ 36 | --package-name="${PROJECT}" \ 37 | --msgid-bugs-address="${BUGADDR}" \ 38 | --files-from=infiles.list -D ${BASEDIR} -D ${WDIR} -o ${PROJECT}.pot 39 | check_error "Failed to extract messages from source files. Do you have xgettext installed?" 40 | echo "Done extracting messages" 41 | 42 | 43 | echo "Merging translations" 44 | catalogs=`find . -name '*.po'` 45 | for cat in $catalogs; do 46 | echo $cat 47 | msgmerge -o $cat.new $cat ${PROJECT}.pot 48 | check_error "Failed to merge messages. Do you have msgmerge installed?" 49 | mv $cat.new $cat 50 | done 51 | echo "Done merging translations" 52 | 53 | 54 | echo "Cleaning up" 55 | cd ${WDIR} 56 | rm rcfiles.list 57 | rm infiles.list 58 | rm rc.cpp 59 | echo "Done" 60 | -------------------------------------------------------------------------------- /markpado/po/markpado.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: markpado\n" 10 | "Report-Msgid-Bugs-To: https://github.com/sadhen/marketo/issues\n" 11 | "POT-Creation-Date: 2016-01-03 19:06+0800\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: main.cpp:46 21 | msgid "Markpado" 22 | msgstr "" 23 | 24 | #: main.cpp:50 25 | msgid "(C) 2015 Darcy Shen" 26 | msgstr "" 27 | 28 | #: main.cpp:53 29 | msgid "Darcy Shen" 30 | msgstr "" 31 | 32 | #: main.cpp:53 33 | msgid "Developer" 34 | msgstr "" 35 | 36 | #: main.cpp:69 37 | msgid "Documents to open." 38 | msgstr "" 39 | 40 | #: mainwindow.cpp:72 41 | msgid "Preview" 42 | msgstr "" 43 | 44 | #: mainwindow.cpp:78 45 | msgid "Split" 46 | msgstr "" 47 | 48 | #: mainwindow.cpp:83 49 | msgid "" 50 | "This lists files which you have opened recently, and allows you to easily " 51 | "open them again." 52 | msgstr "" 53 | 54 | #. i18n: file: markpado.rc:12 55 | #. i18n: ectx: Menu (edit) 56 | #: po/rc.cpp:3 rc.cpp:3 57 | msgid "&Edit" 58 | msgstr "" 59 | 60 | #. i18n: file: markpado.rc:24 61 | #. i18n: ectx: Menu (view) 62 | #: po/rc.cpp:6 rc.cpp:6 63 | msgid "&View" 64 | msgstr "" 65 | 66 | #. i18n: file: markpado.rc:28 67 | #. i18n: ectx: Menu (tools) 68 | #: po/rc.cpp:9 rc.cpp:9 69 | msgid "&Tools" 70 | msgstr "" 71 | 72 | #. i18n: file: markpado.rc:34 73 | #. i18n: ectx: ToolBar (mainToolBar) 74 | #: po/rc.cpp:12 rc.cpp:12 75 | msgid "Main Toolbar" 76 | msgstr "" 77 | 78 | #: po/rc.cpp:13 rc.cpp:13 79 | msgctxt "NAME OF TRANSLATORS" 80 | msgid "Your names" 81 | msgstr "" 82 | 83 | #: po/rc.cpp:14 rc.cpp:14 84 | msgctxt "EMAIL OF TRANSLATORS" 85 | msgid "Your emails" 86 | msgstr "" 87 | -------------------------------------------------------------------------------- /markpado/po/zh_CN.po: -------------------------------------------------------------------------------- 1 | # Copyright (C) YEAR This_file_is_part_of_KDE 2 | # This file is distributed under the same license as the PACKAGE package. 3 | # 4 | # shenda , 2016. 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: \n" 8 | "Report-Msgid-Bugs-To: https://github.com/sadhen/marketo/issues\n" 9 | "POT-Creation-Date: 2016-01-03 19:06+0800\n" 10 | "PO-Revision-Date: 2016-01-03 19:04+0800\n" 11 | "Last-Translator: shenda \n" 12 | "Language-Team: English \n" 13 | "Language: zh_CN\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 18 | "X-Generator: Lokalize 2.0\n" 19 | 20 | #. i18n: file: markpado.rc:12 21 | #. i18n: ectx: Menu (edit) 22 | #: po/rc.cpp:3 rc.cpp:3 23 | msgid "&Edit" 24 | msgstr "编辑" 25 | 26 | #. i18n: file: markpado.rc:28 27 | #. i18n: ectx: Menu (tools) 28 | #: po/rc.cpp:9 rc.cpp:9 29 | msgid "&Tools" 30 | msgstr "工具" 31 | 32 | #. i18n: file: markpado.rc:24 33 | #. i18n: ectx: Menu (view) 34 | #: po/rc.cpp:6 rc.cpp:6 35 | msgid "&View" 36 | msgstr "视图" 37 | 38 | #: main.cpp:50 39 | msgid "(C) 2015 Darcy Shen" 40 | msgstr "(C) 2015 Darcy Shen" 41 | 42 | #: main.cpp:53 43 | msgid "Darcy Shen" 44 | msgstr "Darcy Shen" 45 | 46 | #: main.cpp:53 47 | msgid "Developer" 48 | msgstr "开发者" 49 | 50 | #: main.cpp:69 51 | msgid "Documents to open." 52 | msgstr "" 53 | 54 | #. i18n: file: markpado.rc:34 55 | #. i18n: ectx: ToolBar (mainToolBar) 56 | #: po/rc.cpp:12 rc.cpp:12 57 | msgid "Main Toolbar" 58 | msgstr "主要工具栏" 59 | 60 | #: main.cpp:46 61 | msgid "Markpado" 62 | msgstr "Markpado" 63 | 64 | #: mainwindow.cpp:72 65 | msgid "Preview" 66 | msgstr "预览" 67 | 68 | #: mainwindow.cpp:78 69 | msgid "Split" 70 | msgstr "分栏" 71 | 72 | #: mainwindow.cpp:83 73 | msgid "" 74 | "This lists files which you have opened recently, and allows you to easily " 75 | "open them again." 76 | msgstr "" 77 | 78 | #: po/rc.cpp:14 rc.cpp:14 79 | msgctxt "EMAIL OF TRANSLATORS" 80 | msgid "Your emails" 81 | msgstr "你的邮箱" 82 | 83 | #: po/rc.cpp:13 rc.cpp:13 84 | msgctxt "NAME OF TRANSLATORS" 85 | msgid "Your names" 86 | msgstr "你的名字" 87 | -------------------------------------------------------------------------------- /markpado/webpage.cpp: -------------------------------------------------------------------------------- 1 | #include "webpage.h" 2 | 3 | #include 4 | 5 | bool WebPage::acceptNavigationRequest(const QUrl & url, QWebEnginePage::NavigationType type, bool) 6 | { 7 | if (type == QWebEnginePage::NavigationTypeLinkClicked) 8 | { 9 | emit linkClicked(url); 10 | return false; 11 | } 12 | return true; 13 | } -------------------------------------------------------------------------------- /markpado/webpage.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBPAGE_H 2 | #define WEBPAGE_H 3 | #include 4 | 5 | class WebPage : public QWebEnginePage 6 | { 7 | Q_OBJECT 8 | public: 9 | WebPage(QObject* parent = 0) : QWebEnginePage(parent){} 10 | 11 | bool acceptNavigationRequest(const QUrl & url, QWebEnginePage::NavigationType type, bool); 12 | signals: 13 | void linkClicked(const QUrl&); 14 | 15 | }; 16 | #endif -------------------------------------------------------------------------------- /screenshot/render_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/da-liii/marketo/0ec2d643c4a6a3aec7ecfdcda72236a78de1635e/screenshot/render_code.png --------------------------------------------------------------------------------