├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── LICENSE ├── README.md └── src ├── cache.cpp ├── cache.h ├── eversticky.pro ├── helpers ├── misc_helpers.cpp ├── misc_helpers.h ├── xml_helpers.cpp └── xml_helpers.h ├── icon ├── appicon.ico ├── appicon.svg ├── sizegripicon.svg ├── trayicon.svg ├── trayicon_black.ico └── trayicon_white.ico ├── main.cpp ├── note.cpp ├── note.h ├── note_controller.cpp ├── note_controller.h ├── note_formatter.cpp ├── note_formatter.h ├── note_sync_controller.cpp ├── note_sync_controller.h ├── qevercloud ├── AsyncResult.cpp ├── AsyncResult.h ├── EventLoopFinisher.cpp ├── EventLoopFinisher.h ├── EverCloudException.cpp ├── EverCloudException.h ├── InkNoteImageDownloader.cpp ├── InkNoteImageDownloader.h ├── Optional.h ├── QEverCloud.h ├── QEverCloudOAuth.h ├── exceptions.cpp ├── exceptions.h ├── export.h ├── generated │ ├── EDAMErrorCode.h │ ├── constants.cpp │ ├── constants.h │ ├── services.cpp │ ├── services.h │ ├── types.cpp │ ├── types.h │ └── types_impl.h ├── globals.cpp ├── globals.h ├── http.cpp ├── http.h ├── impl.h ├── oauth.cpp ├── oauth.h ├── qt4helpers.h ├── services_nongenerated.cpp ├── thrift.h ├── thumbnail.cpp └── thumbnail.h ├── resources.qrc ├── settings.cpp ├── settings.h ├── style ├── about_stylesheet.qss └── note_stylesheet.qss └── ui ├── about_widget.cpp ├── about_widget.h ├── note_header.cpp ├── note_header.h ├── note_header_text_edit.cpp ├── note_header_text_edit.h ├── note_scroll_area.cpp ├── note_scroll_area.h ├── note_title_bar.cpp ├── note_title_bar.h ├── note_widget.cpp ├── note_widget.h ├── settings_widget.cpp ├── settings_widget.h ├── tray_icon.cpp ├── tray_icon.h └── webview ├── js_interface.cpp ├── js_interface.h ├── note_webview.cpp ├── note_webview.css ├── note_webview.h ├── note_webview.js ├── note_webview_helpers.js └── note_webview_keyhandler.js /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. Ubuntu 20.04, Debian 11] 28 | - Version [e.g. v0.95.2] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EverSticky 2 | 3 | 4 | 5 | A Linux open-source sticky note client that syncs to Evernote. Displays rich text formatting. 6 | 7 | ## Installation and running 8 | 9 | Supports Ubuntu 20.04 and greater. Unlikely to work on older Ubuntu versions. 10 | 11 | [Find the Linux installation guide here.](https://eversticky.joeeey.com/install) 12 | 13 | ### Compiling from source 14 | 15 | > IMPORTANT NOTE: You will need a production Evernote API key to compile the application and be able to see and alter notes you currently store on Evernote. This secret (along with the desired domain and key) needs to be provided at the top of `./src/eversticky.pro`. 16 | 17 | Install build dependencies: 18 | ``` 19 | sudo apt install build-essential qt5-default qtwebengine5-dev libqt5x11extras5-dev libxpm-dev 20 | ``` 21 | 22 | From the root of the repo run the following commands: 23 | ``` 24 | mkdir src/build 25 | cd src/build 26 | qmake ../eversticky.pro 27 | make 28 | ``` 29 | This produces the executable binary `./eversticky` in the current directory. 30 | 31 | ## Bugs 32 | This project is still in its early days and bugs are expected. Open an issue if you encounter some unexpected behaviour (after making sure one hasn't already been raised). 33 | 34 | ## License 35 | `src/qevercloud` licensed under MIT terms. Consists of `v4.0.0` release from @d1vanov's fork (https://github.com/d1vanov/QEverCloud), plus two patches ([66671bf](https://github.com/itsmejoeeey/eversticky/commit/66671bf4ffc03c4d7ed64227249971be2b35a492), [74b9b98](https://github.com/itsmejoeeey/eversticky/commit/74b9b98d67a9370a34e9b0d0a073482c0657bb3f)). 36 | 37 | For all other code see `LICENSE` 38 | -------------------------------------------------------------------------------- /src/cache.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the eversticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #ifndef CACHE_H 19 | #define CACHE_H 20 | 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #include "note.h" 29 | 30 | 31 | struct queueItem { 32 | int id; 33 | QString type; 34 | Note note; 35 | }; 36 | 37 | struct noteItem { 38 | int pos_x; 39 | int pos_y; 40 | int size_x; 41 | int size_y; 42 | int scroll_x; 43 | int scroll_y; 44 | }; 45 | 46 | class Cache 47 | { 48 | public: 49 | static QSqlDatabase openDatabase(); 50 | static void closeDatabase(); 51 | static void deleteDatabase(); 52 | static QString getDatabasePath(); 53 | 54 | static void emptySyncTable(); 55 | static void insertSyncTable(Note note); 56 | static std::optional retrieveFromSyncTable(qevercloud::Guid guid); 57 | static std::vector retrieveAllFromSyncTable(); 58 | 59 | static int countQueueTableRows(); 60 | static void deleteQueueTable(Note note); 61 | static void deleteFromQueueTable(int id); 62 | static void emptyQueueTable(); 63 | static void insertQueueTable(Note note); 64 | static void removeGuidFromQueueTable(qevercloud::Guid guid); 65 | static std::optional retrieveFromQueueTable(qevercloud::Guid guid); 66 | static std::vector retrieveAllFromQueueTable(); 67 | static std::vector retrieveNewFromQueueTable(); 68 | 69 | static void deleteFromNotesTable(qevercloud::Guid guid); 70 | static void insertNotesTable(QString guid, int screens, int res_x, int res_y, int pos_x, int pos_y, int size_x, int size_y); 71 | static noteItem retrieveFromNotesTable(qevercloud::Guid guid, int screens, int res_x, int res_y); 72 | static void updateGuidInNotesTable(qevercloud::Guid oldGuid, qevercloud::Guid newGuid); 73 | }; 74 | 75 | #endif // CACHE_H 76 | -------------------------------------------------------------------------------- /src/eversticky.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | TARGET = eversticky 3 | 4 | DEFINES += \ 5 | APP_VERSION=\\\"0.96.0\\\" \ 6 | \ 7 | ## API_DOMAIN 8 | # Can be either 'www.evernote.com' or 'sandbox.evernote.com' for testing 9 | # --> If error 'Host requires authentication' you must go to 'https://dev.evernote.com/support/' 10 | # to request activation of your API key on our production service. 11 | API_HOST=\\\"XXXXXXXXXXXXXXXX\\\" \ 12 | # 13 | ## API_KEY 14 | # Typically The username of the user than owns the API key 15 | API_KEY=\\\"XXXXXXXXXXXXXXXX\\\" \ 16 | # 17 | ## API_SECRET 18 | # The 16-digit API key 19 | API_SECRET=\\\"XXXXXXXXXXXXXXXX\\\" 20 | 21 | CONFIG += c++17 22 | 23 | # Ensure Qt WebKit is not required as a dependency 24 | DEFINES += QEVERCLOUD_USE_QT_WEB_ENGINE 25 | 26 | greaterThan(QT_MAJOR_VERSION, 4): QT += core gui network sql webenginewidgets widgets x11extras xml 27 | 28 | SOURCES += \ 29 | main.cpp \ 30 | helpers/misc_helpers.cpp \ 31 | helpers/xml_helpers.cpp \ 32 | note_formatter.cpp \ 33 | cache.cpp \ 34 | note_sync_controller.cpp \ 35 | settings.cpp \ 36 | ui/about_widget.cpp \ 37 | ui/note_header.cpp \ 38 | ui/note_header_text_edit.cpp \ 39 | ui/note_scroll_area.cpp \ 40 | ui/note_title_bar.cpp \ 41 | note.cpp \ 42 | note_controller.cpp \ 43 | ui/note_widget.cpp \ 44 | ui/settings_widget.cpp \ 45 | ui/tray_icon.cpp \ 46 | ui/webview/js_interface.cpp \ 47 | ui/webview/note_webview.cpp \ 48 | # 49 | qevercloud/AsyncResult.cpp \ 50 | qevercloud/EventLoopFinisher.cpp \ 51 | qevercloud/EverCloudException.cpp \ 52 | qevercloud/exceptions.cpp \ 53 | qevercloud/globals.cpp \ 54 | qevercloud/http.cpp \ 55 | qevercloud/InkNoteImageDownloader.cpp \ 56 | qevercloud/oauth.cpp \ 57 | qevercloud/services_nongenerated.cpp \ 58 | qevercloud/thumbnail.cpp \ 59 | qevercloud/generated/constants.cpp \ 60 | qevercloud/generated/services.cpp \ 61 | qevercloud/generated/types.cpp 62 | 63 | HEADERS += \ 64 | cache.h \ 65 | helpers/misc_helpers.h \ 66 | helpers/xml_helpers.h \ 67 | note.h \ 68 | note_controller.h \ 69 | note_formatter.h \ 70 | note_sync_controller.h \ 71 | settings.h \ 72 | ui/about_widget.h \ 73 | ui/note_header.h \ 74 | ui/note_header_text_edit.h \ 75 | ui/note_scroll_area.h \ 76 | ui/note_title_bar.h \ 77 | ui/note_widget.h \ 78 | ui/settings_widget.h \ 79 | ui/tray_icon.h \ 80 | ui/webview/js_interface.h \ 81 | # 82 | qevercloud/AsyncResult.h \ 83 | qevercloud/EventLoopFinisher.h \ 84 | qevercloud/EverCloudException.h \ 85 | qevercloud/exceptions.h \ 86 | qevercloud/export.h \ 87 | qevercloud/globals.h \ 88 | qevercloud/http.h \ 89 | qevercloud/impl.h \ 90 | qevercloud/InkNoteImageDownloader.h \ 91 | qevercloud/oauth.h \ 92 | qevercloud/Optional.h \ 93 | qevercloud/QEverCloud.h \ 94 | qevercloud/QEverCloudOAuth.h \ 95 | qevercloud/qt4helpers.h \ 96 | qevercloud/thrift.h \ 97 | qevercloud/thumbnail.h \ 98 | qevercloud/generated/constants.h \ 99 | qevercloud/generated/EDAMErrorCode.h \ 100 | qevercloud/generated/services.h \ 101 | qevercloud/generated/types.h \ 102 | qevercloud/generated/types_impl.h \ 103 | ui/webview/note_webview.h 104 | 105 | DISTFILES += \ 106 | style/note_stylesheet.qss 107 | 108 | RESOURCES += \ 109 | resources.qrc \ 110 | ui/webview/note_webview.css \ 111 | ui/webview/note_webview.js \ 112 | ui/webview/note_webview_helpers.js \ 113 | ui/webview/note_webview_keyhandler.js 114 | 115 | LIBS += \ 116 | -lX11 117 | 118 | INCLUDEPATH += \ 119 | qevercloud 120 | -------------------------------------------------------------------------------- /src/helpers/misc_helpers.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #include "misc_helpers.h" 19 | 20 | #include 21 | 22 | 23 | namespace helpers 24 | { 25 | namespace misc 26 | { 27 | QString random_hex_string(unsigned int length) 28 | { 29 | const char charset[] = "0123456789" 30 | "abcdef"; 31 | const unsigned char maxIndex = (sizeof(charset) - 1); 32 | 33 | std::string str(length,0); 34 | std::generate_n(str.begin(), length, [&]() -> char { 35 | return charset[rand() % maxIndex]; 36 | }); 37 | 38 | return QString::fromStdString(str); 39 | } 40 | } // namespace misc 41 | } // namespace helpers 42 | -------------------------------------------------------------------------------- /src/helpers/misc_helpers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #ifndef MISC_HELPERS_H 19 | #define MISC_HELPERS_H 20 | 21 | #include 22 | 23 | #include 24 | 25 | 26 | namespace helpers 27 | { 28 | namespace misc 29 | { 30 | QString random_hex_string(unsigned int length); 31 | 32 | } // namespace misc 33 | } // namespace helpers 34 | 35 | #endif // MISC_HELPERS_H 36 | -------------------------------------------------------------------------------- /src/helpers/xml_helpers.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #include "xml_helpers.h" 19 | 20 | #include 21 | 22 | 23 | namespace helpers 24 | { 25 | namespace xml 26 | { 27 | QDomElement findFirstElementOfTagMatchingAttribute(const QString& tag_name, const QString& attribute_name, const QString& attribute_value, QList elems) 28 | { 29 | QList next_elems = QList(); 30 | 31 | for(int i = 0; i < elems.size(); i++) { 32 | QDomElement elem = elems.at(i); 33 | if( elem.tagName() == tag_name && elem.attribute(attribute_name) == attribute_value) 34 | return elem; 35 | 36 | QDomNodeList append_list = elem.childNodes(); 37 | for(int n = 0; n < append_list.size(); n++) { 38 | next_elems.append(append_list.at(n).toElement()); 39 | } 40 | } 41 | 42 | return findFirstElementOfTagMatchingAttribute(tag_name, attribute_name, attribute_value, next_elems); 43 | } 44 | 45 | void findAllElements(const QDomElement elem, QList& foundElements) 46 | { 47 | foundElements.append(elem); 48 | 49 | QDomElement child = elem.firstChildElement(); 50 | while(!child.isNull()) { 51 | findAllElements(child, foundElements); 52 | child = child.nextSiblingElement(); 53 | } 54 | } 55 | 56 | void deleteAllAttributes(QDomElement& element) 57 | { 58 | while(element.attributes().size() > 0) { 59 | QDomAttr attribute = element.attributes().item(0).toAttr(); 60 | element.removeAttribute(attribute.name()); 61 | } 62 | } 63 | 64 | void deleteAllChildren(QDomNode& node) { 65 | QDomNodeList childNodes = node.childNodes(); 66 | while(!childNodes.isEmpty()) { 67 | QDomNode childNode = childNodes.at(0); 68 | deleteNode(childNode); 69 | } 70 | } 71 | 72 | void deleteNode(QDomNode& node) 73 | { 74 | node.parentNode().removeChild(node); 75 | } 76 | 77 | QStringList getAllAttributeNames(QDomElement& element) 78 | { 79 | QStringList output; 80 | QDomNamedNodeMap attributes = element.attributes(); 81 | for(int i = 0; i < attributes.size(); i++) { 82 | output.append(attributes.item(i).toAttr().name()); 83 | } 84 | 85 | return output; 86 | } 87 | } // namespace xml 88 | } // namespace helpers 89 | -------------------------------------------------------------------------------- /src/helpers/xml_helpers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #ifndef XML_HELPERS_H 19 | #define XML_HELPERS_H 20 | 21 | #include 22 | #include 23 | 24 | 25 | namespace helpers 26 | { 27 | namespace xml 28 | { 29 | QDomElement findFirstElementOfTagMatchingAttribute(const QString& tag_name, const QString& attribute_name, const QString& attribute_value, QList elems); 30 | void findAllElements(const QDomElement elem, QList& foundElements); 31 | 32 | void deleteAllAttributes(QDomElement& element); 33 | void deleteAllChildren(QDomNode& node); 34 | void deleteNode(QDomNode& node); 35 | 36 | QStringList getAllAttributeNames(QDomElement& element); 37 | 38 | } // namespace xml 39 | } // namespace misc 40 | 41 | #endif // XML_HELPERS_H 42 | -------------------------------------------------------------------------------- /src/icon/appicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsmejoeeey/eversticky/ca64d34fd955615c6e305d9eda38cf2db7336af8/src/icon/appicon.ico -------------------------------------------------------------------------------- /src/icon/appicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 30 | 32 | 38 | 41 | 45 | 46 | 47 | 51 | 55 | 59 | 64 | 68 | 72 | 77 | 78 | 82 | 86 | 90 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/icon/sizegripicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 40 | 42 | 46 | 51 | 56 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/icon/trayicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 40 | 42 | 47 | 51 | 55 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/icon/trayicon_black.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsmejoeeey/eversticky/ca64d34fd955615c6e305d9eda38cf2db7336af8/src/icon/trayicon_black.ico -------------------------------------------------------------------------------- /src/icon/trayicon_white.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsmejoeeey/eversticky/ca64d34fd955615c6e305d9eda38cf2db7336af8/src/icon/trayicon_white.ico -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "note_controller.h" 25 | 26 | 27 | int main(int argc, char **argv) 28 | { 29 | QApplication app (argc, argv); 30 | app.setApplicationName("eversticky"); 31 | app.setQuitOnLastWindowClosed(false); 32 | 33 | // Show timestamp in logging output 34 | qSetMessagePattern("[%{time}] %{message}"); 35 | 36 | const int numScreens = app.screens().length(); 37 | const QRect screenSize = app.primaryScreen()->virtualGeometry(); 38 | // *.* Where the magic happens *.* 39 | new NoteController(numScreens, screenSize.width(), screenSize.height()); 40 | 41 | return app.exec(); 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/note.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #include "note.h" 19 | 20 | #include 21 | 22 | 23 | Note::Note(qevercloud::Guid guid, int usn, QString title, QString content) : 24 | guid(guid), usn(usn), title(title), content(content) 25 | { 26 | changed = false; 27 | new_note = false; 28 | } 29 | 30 | Note::Note() { 31 | changed = false; 32 | new_note = true; 33 | } 34 | -------------------------------------------------------------------------------- /src/note.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #ifndef NOTE_H 19 | #define NOTE_H 20 | 21 | #include 22 | 23 | class Note 24 | { 25 | public: 26 | Note(qevercloud::Guid guid, int usn, QString title, QString content); 27 | Note(); 28 | 29 | qevercloud::Guid guid; 30 | int usn; 31 | QString title; 32 | QString content; 33 | bool changed; 34 | bool new_note; 35 | }; 36 | 37 | #endif // NOTE_H 38 | -------------------------------------------------------------------------------- /src/note_controller.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #ifndef NOTECONTROLLER_H 19 | #define NOTECONTROLLER_H 20 | 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "cache.h" 30 | #include "note.h" 31 | #include "note_sync_controller.h" 32 | #include "ui/tray_icon.h" 33 | 34 | 35 | class NoteWidget; 36 | 37 | class NoteController : public QObject 38 | { 39 | Q_OBJECT 40 | 41 | public: 42 | NoteController(int screens, int screenWidth, int screenHeight); 43 | ~NoteController(); 44 | 45 | NoteWidget* createNote(); 46 | NoteWidget* createNote(Note *note); 47 | NoteWidget* createNote(Note* note_model, noteItem size); 48 | 49 | bool isAuthorised(); 50 | 51 | void syncAllNoteModels(); 52 | 53 | void bringAllToFront(); 54 | void closeAllNotes(); 55 | void updateNoteDimensions(qevercloud::Guid guid, int x, int y, int width, int height); 56 | 57 | void logoutPrepare(); 58 | void showLogoutDialog(); 59 | 60 | public slots: 61 | void login(); 62 | void showNotes(); 63 | void periodicUpdate(); 64 | 65 | private: 66 | tAuthState state; 67 | 68 | NoteSyncController* noteSyncController; 69 | TrayIcon *trayIcon; 70 | 71 | std::vector notes; 72 | int noteCount; 73 | 74 | int screens; 75 | int screenWidth; 76 | int screenHeight; 77 | 78 | void hardLogout(); 79 | void softLogout(); 80 | 81 | bool isNoteCreated(qevercloud::Guid noteGuid); 82 | 83 | void noteDestroyed(NoteWidget *note); 84 | }; 85 | 86 | #endif // NOTECONTROLLER_H 87 | -------------------------------------------------------------------------------- /src/note_formatter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #include "note_formatter.h" 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "helpers/xml_helpers.h" 25 | 26 | 27 | NoteFormatter::NoteFormatter(QString input_text) : inputText(input_text) { 28 | 29 | }; 30 | 31 | QString NoteFormatter::standardiseInput() 32 | { 33 | QDomDocument doc; 34 | doc.setContent(inputText); 35 | 36 | QDomElement body = doc.elementsByTagName("en-note").at(0).toElement(); 37 | 38 | // If note contains nothing, return empty ENML note. 39 | // --> This ensures has a child div so that initial user text 40 | // is inserted in the correct place (not in the ) 41 | if(body.childNodes().size() <= 0) { 42 | return createEmptyENML(); 43 | } 44 | 45 | QString output_str; 46 | QTextStream stream(&output_str); 47 | body.save(stream, 0); 48 | 49 | return enmlXmlHeader + output_str; 50 | } 51 | 52 | QString NoteFormatter::createEmptyENML() 53 | { 54 | return enmlXmlHeader + "\n
\n
\n
\n
"; 55 | } 56 | 57 | /* 58 | * Convert ENML (XML) note content to valid HTML 59 | */ 60 | QString NoteFormatter::convertFromEML() 61 | { 62 | QDomDocument doc; 63 | doc.setContent(inputText); 64 | 65 | // Convert ENML "en-note" top-level element to a div. 66 | QDomElement body = doc.elementsByTagName("en-note").at(0).toElement(); 67 | body.setTagName("div"); 68 | body.setAttribute("id", "en-note"); 69 | 70 | // Convert ENML "en-todo" elements to html checkboxes. 71 | QDomNodeList todoElements = doc.elementsByTagName("en-todo"); 72 | while(todoElements.size() > 0) { 73 | QDomElement todoElement = todoElements.at(0).toElement(); 74 | 75 | // This is a hacky solution to allow the checkbox to be selected by the user. 76 | todoElement.setTagName("input"); 77 | todoElement.setAttribute("class", "en-todo"); 78 | // Set the image to an empty pixel (see: https://stackoverflow.com/a/14115340/3213602). 79 | // This prevents the 'missing-image' icon from appearing. 80 | todoElement.setAttribute("src", "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="); 81 | todoElement.setAttribute("type", "image"); 82 | 83 | // Only keep 'checked' attribute if true, inferred false otherwise. 84 | QString checked = todoElement.attribute("checked"); 85 | if(checked != "true") { 86 | todoElement.removeAttribute("checked"); 87 | } 88 | } 89 | 90 | // Convert ENML "en-media" elements to html placeholders 91 | QDomNodeList mediaElements = doc.elementsByTagName("en-media"); 92 | while(mediaElements.size() > 0) { 93 | QDomElement mediaElement = mediaElements.at(0).toElement(); 94 | 95 | mediaElement.setTagName("img"); 96 | mediaElement.setAttribute("class", "en-media unsupported"); 97 | 98 | for(int i = 0; i < validMediaAttributes.size(); i++) { 99 | QString attribute_name = validMediaAttributes.at(i); 100 | QString attribute_value = mediaElement.attribute(validMediaAttributes.at(i)); 101 | if(!attribute_value.isNull()) { 102 | mediaElement.setAttribute("data-" + attribute_name, attribute_value); 103 | mediaElement.removeAttribute(attribute_name); 104 | } 105 | } 106 | } 107 | 108 | // Convert ENML "en-crypt" elements to html placeholders 109 | QDomNodeList cryptElements = doc.elementsByTagName("en-crypt"); 110 | while(cryptElements.size() > 0) { 111 | QDomElement cryptElement = cryptElements.at(0).toElement(); 112 | 113 | cryptElement.setTagName("img"); 114 | cryptElement.setAttribute("class", "en-crypt unsupported"); 115 | 116 | for(int i = 0; i < validCryptAttributes.size(); i++) { 117 | QString attribute_name = validCryptAttributes.at(i); 118 | QString attribute_value = cryptElement.attribute(validCryptAttributes.at(i)); 119 | if(!attribute_value.isNull()) { 120 | cryptElement.setAttribute("data-" + attribute_name, attribute_value); 121 | cryptElement.removeAttribute(attribute_name); 122 | } 123 | } 124 | } 125 | 126 | // Save to string 127 | QString output_str; 128 | QTextStream stream(&output_str); 129 | body.save(stream, 0); 130 | 131 | return output_str; 132 | } 133 | 134 | /* 135 | * Convert HTML note content to ENML (XML) 136 | */ 137 | QString NoteFormatter::convertToEML() 138 | { 139 | QDomDocument doc; 140 | doc.setContent(inputText); 141 | 142 | // Get parent element 143 | QDomElement parent = helpers::xml::findFirstElementOfTagMatchingAttribute("div", "id", "en-note", QList() << doc.documentElement()); 144 | parent.setTagName("en-note"); 145 | helpers::xml::deleteAllAttributes(parent); 146 | 147 | QList elements; 148 | helpers::xml::findAllElements(parent, elements); 149 | // Loop through and check each element 150 | for(int i = 0; i < elements.size(); i++) { 151 | QDomElement element = elements.at(i); 152 | 153 | // Convert ENML "en-todo" elements to html checkboxes. 154 | if(element.tagName() == "input" && element.attribute("class") == "en-todo") { 155 | element.setTagName("en-todo"); 156 | element.removeAttribute("class"); 157 | element.removeAttribute("src"); 158 | element.removeAttribute("type"); 159 | } 160 | 161 | // Convert ENML "en-media" elements to html placeholders 162 | if(element.tagName() == "img" && element.attribute("class").contains("en-media")) { 163 | element.setTagName("en-media"); 164 | for(int i = 0; i < validMediaAttributes.size(); i++) { 165 | QString attribute_name = validMediaAttributes.at(i); 166 | QString attribute_value = element.attribute("data-" + attribute_name); 167 | if(!attribute_value.isNull()) { 168 | element.setAttribute(attribute_name, attribute_value); 169 | element.removeAttribute("data-" + attribute_name); 170 | } 171 | } 172 | helpers::xml::deleteAllChildren(element); 173 | } 174 | 175 | // Convert ENML "en-crypt" elements to html placeholders 176 | if(element.tagName() == "img" && element.attribute("class").contains("en-crypt")) { 177 | element.setTagName("en-crypt"); 178 | for(int i = 0; i < validCryptAttributes.size(); i++) { 179 | QString attribute_name = validCryptAttributes.at(i); 180 | QString attribute_value = element.attribute("data-" + validCryptAttributes.at(i)); 181 | if(!attribute_value.isNull()) { 182 | element.setAttribute(attribute_name, attribute_value); 183 | element.removeAttribute("data-" + attribute_name); 184 | } 185 | } 186 | helpers::xml::deleteAllChildren(element); 187 | } 188 | 189 | // Remove invalid attributes 190 | QStringList attributeNames = helpers::xml::getAllAttributeNames(element); 191 | QSet deleteAttributes = attributeNames.toSet().intersect(invalidAttributes.toSet()); 192 | for(QSet::iterator i = deleteAttributes.begin(); i != deleteAttributes.end(); ++i) { 193 | element.removeAttribute(*i); 194 | } 195 | 196 | // Remove invalid tags 197 | if(!validTags.contains(element.tagName())) { 198 | helpers::xml::deleteNode(element); 199 | } 200 | } 201 | 202 | // Save to string 203 | QString output_str; 204 | QTextStream stream(&output_str); 205 | parent.save(stream, 0); 206 | 207 | output_str = enmlXmlHeader + output_str; 208 | 209 | return output_str; 210 | } 211 | -------------------------------------------------------------------------------- /src/note_formatter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #ifndef NOTEFORMATTER_H 19 | #define NOTEFORMATTER_H 20 | 21 | #include 22 | #include 23 | 24 | 25 | static const QString enmlXmlHeader = "\n" \ 26 | "\n"; 27 | 28 | class NoteFormatter 29 | { 30 | public: 31 | NoteFormatter(QString input_text); 32 | 33 | QString standardiseInput(); 34 | 35 | QString convertFromEML(); 36 | QString convertToEML(); 37 | 38 | static QString createEmptyENML(); 39 | 40 | private: 41 | QString inputText; 42 | 43 | QStringList invalidAttributes = { "id", "class", "onclick", "ondblclick", //"on*", 44 | "accesskey", "data", "dynsrc", "tabindex" }; 45 | QStringList validCryptAttributes = { "cipher", "hint", "length" }; 46 | QStringList validMediaAttributes = { "align", "hash", "style", "type" }; 47 | 48 | QStringList validTags = { "en-note", "en-todo", "en-media", "en-crypt", 49 | // 50 | "a", "abbr", "acronym", "address", "area", 51 | "b", "bdo", "big", "blockquote", "br", "caption", 52 | "center", "cite", "code", "col", "colgroup", "dd", 53 | "del", "dfn", "div", "dl", "dt", "em", "font", "h1", 54 | "h2", "h3", "h4", "h5", "h6", "hr", "i", "img", "ins", 55 | "kbd", "li", "map", "ol", "p", "pre", "q", "s", "samp", 56 | "small", "span", "strike", "strong", "sub", "sup", "table", 57 | "tbody", "td", "tfoot", "th", "thead", "title", "tr", "tt", 58 | "u", "ul", "var", "xmp" }; 59 | }; 60 | 61 | #endif // NOTEFORMATTER_H 62 | -------------------------------------------------------------------------------- /src/note_sync_controller.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #ifndef NOTESYNCCONTROLLER_H 19 | #define NOTESYNCCONTROLLER_H 20 | 21 | #include 22 | 23 | #include 24 | 25 | 26 | struct GuidMap{ 27 | qevercloud::Guid local_guid; 28 | qevercloud::Guid official_guid; 29 | }; 30 | 31 | typedef enum tAuthState { 32 | UNAUTHORISED, 33 | AUTHORISED 34 | } tAuthState; 35 | 36 | class NoteSyncController : public QObject 37 | { 38 | Q_OBJECT 39 | 40 | public: 41 | NoteSyncController(QString authToken, qevercloud::Guid notebookGuid, QString notestoreUrl); 42 | 43 | static tAuthState login(); 44 | 45 | std::vector syncChanges(); 46 | bool syncFromServer(); 47 | 48 | signals: 49 | void authInvalid(); 50 | 51 | private: 52 | qevercloud::Guid notebookGuid; 53 | qevercloud::NoteStore* noteStore; 54 | 55 | static qevercloud::Guid createOrGetNotebookGUID(QString authToken, QString notestoreUrl); 56 | 57 | qevercloud::Guid syncCreateNote(Note note); 58 | void syncDeleteNote(Note note); 59 | void syncUpdateNote(Note note); 60 | }; 61 | 62 | #endif // NOTESYNCCONTROLLER_H 63 | -------------------------------------------------------------------------------- /src/qevercloud/AsyncResult.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #include 10 | #include 11 | #include "http.h" 12 | #include 13 | #include 14 | #include 15 | 16 | namespace qevercloud { 17 | 18 | class AsyncResultPrivate 19 | { 20 | public: 21 | explicit AsyncResultPrivate(QString url, QByteArray postData, AsyncResult::ReadFunctionType readFunction, 22 | bool autoDelete, AsyncResult * q); 23 | 24 | explicit AsyncResultPrivate(QNetworkRequest request, QByteArray postData, AsyncResult::ReadFunctionType readFunction, 25 | bool autoDelete, AsyncResult * q); 26 | 27 | virtual ~AsyncResultPrivate(); 28 | 29 | QNetworkRequest m_request; 30 | QByteArray m_postData; 31 | AsyncResult::ReadFunctionType m_readFunction; 32 | bool m_autoDelete; 33 | 34 | private: 35 | AsyncResult * const q_ptr; 36 | Q_DECLARE_PUBLIC(AsyncResult) 37 | }; 38 | 39 | AsyncResultPrivate::AsyncResultPrivate(QString url, QByteArray postData, AsyncResult::ReadFunctionType readFunction, 40 | bool autoDelete, AsyncResult * q) : 41 | m_request(createEvernoteRequest(url)), 42 | m_postData(postData), 43 | m_readFunction(readFunction), 44 | m_autoDelete(autoDelete), 45 | q_ptr(q) 46 | {} 47 | 48 | AsyncResultPrivate::AsyncResultPrivate(QNetworkRequest request, QByteArray postData, AsyncResult::ReadFunctionType readFunction, 49 | bool autoDelete, AsyncResult * q) : 50 | m_request(request), 51 | m_postData(postData), 52 | m_readFunction(readFunction), 53 | m_autoDelete(autoDelete), 54 | q_ptr(q) 55 | {} 56 | 57 | 58 | AsyncResultPrivate::~AsyncResultPrivate() 59 | {} 60 | 61 | QVariant AsyncResult::asIs(QByteArray replyData) 62 | { 63 | return replyData; 64 | } 65 | 66 | AsyncResult::AsyncResult(QString url, QByteArray postData, AsyncResult::ReadFunctionType readFunction, 67 | bool autoDelete, QObject * parent) : 68 | QObject(parent), 69 | d_ptr(new AsyncResultPrivate(url, postData, readFunction, autoDelete, this)) 70 | { 71 | QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection); 72 | } 73 | 74 | AsyncResult::AsyncResult(QNetworkRequest request, QByteArray postData, qevercloud::AsyncResult::ReadFunctionType readFunction, 75 | bool autoDelete, QObject * parent) : 76 | QObject(parent), 77 | d_ptr(new AsyncResultPrivate(request, postData, readFunction, autoDelete, this)) 78 | { 79 | QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection); 80 | } 81 | 82 | AsyncResult::~AsyncResult() 83 | { 84 | delete d_ptr; 85 | } 86 | 87 | bool AsyncResult::waitForFinished(int timeout) 88 | { 89 | QEventLoop loop; 90 | QObject::connect(this, SIGNAL(finished(QVariant,QSharedPointer)), &loop, SLOT(quit())); 91 | 92 | if(timeout >= 0) { 93 | QTimer timer; 94 | EventLoopFinisher finisher(&loop, 1); 95 | connect(&timer, SIGNAL(timeout()), &finisher, SLOT(stopEventLoop())); 96 | timer.setSingleShot(true); 97 | timer.setInterval(timeout); 98 | timer.start(); 99 | } 100 | 101 | bool res = (loop.exec(QEventLoop::ExcludeUserInputEvents) == 0); 102 | return res; 103 | } 104 | 105 | void AsyncResult::start() 106 | { 107 | Q_D(AsyncResult); 108 | ReplyFetcher * replyFetcher = new ReplyFetcher; 109 | QObject::connect(replyFetcher, QEC_SIGNAL(ReplyFetcher,replyFetched,QObject*), 110 | this, QEC_SLOT(AsyncResult,onReplyFetched,QObject*)); 111 | replyFetcher->start(evernoteNetworkAccessManager(), d->m_request, d->m_postData); 112 | } 113 | 114 | void AsyncResult::onReplyFetched(QObject * rp) 115 | { 116 | Q_D(AsyncResult); 117 | 118 | ReplyFetcher * reply = qobject_cast(rp); 119 | QSharedPointer error; 120 | QVariant result; 121 | 122 | try 123 | { 124 | if (reply->isError()) { 125 | error = QSharedPointer(new EverCloudExceptionData(reply->errorText())); 126 | } 127 | else if(reply->httpStatusCode() != 200) { 128 | error = QSharedPointer(new EverCloudExceptionData(QStringLiteral("HTTP Status Code = %1").arg(reply->httpStatusCode()))); 129 | } 130 | else { 131 | result = d->m_readFunction(reply->receivedData()); 132 | } 133 | } 134 | catch(const EverCloudException & e) { 135 | error = e.exceptionData(); 136 | } 137 | catch(const std::exception & e) { 138 | error = QSharedPointer(new EverCloudExceptionData(QStringLiteral("Exception of type \"%1\" with the message: %2") 139 | .arg(QString::fromUtf8(typeid(e).name()), QString::fromUtf8(e.what())))); 140 | } 141 | catch(...) { 142 | error = QSharedPointer(new EverCloudExceptionData(QStringLiteral("Unknown exception"))); 143 | } 144 | 145 | emit finished(result, error); 146 | reply->deleteLater(); 147 | 148 | if (d->m_autoDelete) { 149 | this->deleteLater(); 150 | } 151 | } 152 | 153 | } // namespace qevercloud 154 | -------------------------------------------------------------------------------- /src/qevercloud/AsyncResult.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #ifndef QEVERCLOUD_ASYNC_RESULT_H 10 | #define QEVERCLOUD_ASYNC_RESULT_H 11 | 12 | #include "qt4helpers.h" 13 | #include "export.h" 14 | #include "EverCloudException.h" 15 | #include 16 | #include 17 | 18 | namespace qevercloud { 19 | 20 | QT_FORWARD_DECLARE_CLASS(AsyncResultPrivate) 21 | 22 | /** 23 | * @brief Returned by asynchonous versions of functions. 24 | * 25 | * Wait for AsyncResult::finished signal. 26 | * 27 | * Intended usage is something like this: 28 | * 29 | * @code 30 | NoteStore* ns; 31 | Note note; 32 | ... 33 | QObject::connect(ns->createNoteAsync(note), &AsyncResult::finished, [ns](QVariant result, QSharedPointer error) { 34 | if(!error.isNull()) { 35 | // do something in case of an error 36 | } else { 37 | Note note = result.value(); 38 | // process returned result 39 | } 40 | }); 41 | @endcode 42 | */ 43 | class QEVERCLOUD_EXPORT AsyncResult: public QObject 44 | { 45 | Q_OBJECT 46 | Q_DISABLE_COPY(AsyncResult) 47 | private: 48 | static QVariant asIs(QByteArray replyData); 49 | 50 | public: 51 | typedef QVariant (*ReadFunctionType)(QByteArray replyData); 52 | 53 | AsyncResult(QString url, QByteArray postData, ReadFunctionType readFunction = AsyncResult::asIs, 54 | bool autoDelete = true, QObject * parent = Q_NULLPTR); 55 | 56 | AsyncResult(QNetworkRequest request, QByteArray postData, ReadFunctionType readFunction = AsyncResult::asIs, 57 | bool autoDelete = true, QObject * parent = Q_NULLPTR); 58 | 59 | ~AsyncResult(); 60 | 61 | /** 62 | * @brief Wait for asyncronous operation to complete. 63 | * @param timeout 64 | * Maximum time to wait in milliseconds. Forever if < 0. 65 | * @return true if finished successfully, false in case of the timeout 66 | */ 67 | bool waitForFinished(int timeout = -1); 68 | 69 | Q_SIGNALS: 70 | /** 71 | * @brief Emitted upon asyncronous call completition. 72 | * @param result 73 | * @param error 74 | * error.isNull() != true in case of an error. See EverCloudExceptionData for more details. 75 | * 76 | * AsyncResult deletes itself after emitting this signal. You don't have to manage it's lifetime explicitly. 77 | */ 78 | void finished(QVariant result, QSharedPointer error); 79 | 80 | private Q_SLOTS: 81 | void onReplyFetched(QObject * rp); 82 | void start(); 83 | 84 | private: 85 | AsyncResultPrivate * const d_ptr; 86 | Q_DECLARE_PRIVATE(AsyncResult) 87 | }; 88 | 89 | } // namespace qevercloud 90 | 91 | #endif // QEVERCLOUD_ASYNC_RESULT_H 92 | -------------------------------------------------------------------------------- /src/qevercloud/EventLoopFinisher.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #include 10 | 11 | namespace qevercloud { 12 | 13 | class EventLoopFinisherPrivate 14 | { 15 | public: 16 | EventLoopFinisherPrivate(QEventLoop * loop, int exitCode) : 17 | m_loop(loop), 18 | m_exitCode(exitCode) 19 | {} 20 | 21 | QEventLoop * m_loop; 22 | int m_exitCode; 23 | }; 24 | 25 | qevercloud::EventLoopFinisher::EventLoopFinisher(QEventLoop * loop, int exitCode, QObject * parent) : 26 | QObject(parent), 27 | d_ptr(new EventLoopFinisherPrivate(loop, exitCode)) 28 | {} 29 | 30 | EventLoopFinisher::~EventLoopFinisher() 31 | { 32 | delete d_ptr; 33 | } 34 | 35 | void qevercloud::EventLoopFinisher::stopEventLoop() 36 | { 37 | Q_D(EventLoopFinisher); 38 | d->m_loop->exit(d->m_exitCode); 39 | } 40 | 41 | } // namespace qevercloud 42 | -------------------------------------------------------------------------------- /src/qevercloud/EventLoopFinisher.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #ifndef QEVERCLOUD_EVENT_LOOP_FINISHER_H 10 | #define QEVERCLOUD_EVENT_LOOP_FINISHER_H 11 | 12 | #include "qt4helpers.h" 13 | #include "export.h" 14 | #include 15 | #include 16 | 17 | namespace qevercloud { 18 | 19 | QT_FORWARD_DECLARE_CLASS(EventLoopFinisherPrivate) 20 | 21 | class QEVERCLOUD_EXPORT EventLoopFinisher: public QObject 22 | { 23 | Q_OBJECT 24 | public: 25 | explicit EventLoopFinisher(QEventLoop * loop, int exitCode, QObject * parent = Q_NULLPTR); 26 | ~EventLoopFinisher(); 27 | 28 | public Q_SLOTS: 29 | void stopEventLoop(); 30 | 31 | private: 32 | EventLoopFinisherPrivate * const d_ptr; 33 | Q_DECLARE_PRIVATE(EventLoopFinisher) 34 | }; 35 | 36 | } // namespace qevercloud 37 | 38 | #endif // QEVERCLOUD_EVENT_LOOP_FINISHER_H 39 | -------------------------------------------------------------------------------- /src/qevercloud/EverCloudException.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #include 10 | 11 | namespace qevercloud { 12 | 13 | EverCloudException::EverCloudException() : 14 | m_error() 15 | {} 16 | 17 | EverCloudException::EverCloudException(QString error) : 18 | m_error(error.toUtf8()) 19 | {} 20 | 21 | EverCloudException::EverCloudException(const std::string & error) : 22 | m_error(error.c_str()) 23 | {} 24 | 25 | EverCloudException::EverCloudException(const char * error) : 26 | m_error(error) 27 | {} 28 | 29 | EverCloudException::~EverCloudException() throw() 30 | {} 31 | 32 | const char * EverCloudException::what() const throw() 33 | { 34 | return m_error.constData(); 35 | } 36 | 37 | QSharedPointer QEVERCLOUD_EXPORT EverCloudException::exceptionData() const 38 | { 39 | return QSharedPointer(new EverCloudExceptionData(QString::fromUtf8(what()))); 40 | } 41 | 42 | EverCloudExceptionData::EverCloudExceptionData(QString error) : 43 | errorMessage(error) 44 | {} 45 | 46 | void EverCloudExceptionData::throwException() const 47 | { 48 | throw EverCloudException(errorMessage); 49 | } 50 | 51 | EvernoteException::EvernoteException() : 52 | EverCloudException() 53 | {} 54 | 55 | EvernoteException::EvernoteException(QString error) : 56 | EverCloudException(error) 57 | {} 58 | 59 | EvernoteException::EvernoteException(const std::string & error) : 60 | EverCloudException(error) 61 | {} 62 | 63 | EvernoteException::EvernoteException(const char * error) : 64 | EverCloudException(error) 65 | {} 66 | 67 | QSharedPointer EvernoteException::exceptionData() const 68 | { 69 | return QSharedPointer(new EvernoteExceptionData(QString::fromUtf8(what()))); 70 | } 71 | 72 | EvernoteExceptionData::EvernoteExceptionData(QString error) : 73 | EverCloudExceptionData(error) 74 | {} 75 | 76 | void EvernoteExceptionData::throwException() const 77 | { 78 | throw EvernoteException(errorMessage); 79 | } 80 | 81 | } // namespace qevercloud 82 | -------------------------------------------------------------------------------- /src/qevercloud/EverCloudException.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #ifndef QEVERCLOUD_EVER_CLOUD_EXCEPTION_H 10 | #define QEVERCLOUD_EVER_CLOUD_EXCEPTION_H 11 | 12 | #include "qt4helpers.h" 13 | #include "export.h" 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace qevercloud { 20 | 21 | class QEVERCLOUD_EXPORT EverCloudExceptionData; 22 | 23 | /** 24 | * All exceptions throws by the library are of this class or its descendants. 25 | */ 26 | class QEVERCLOUD_EXPORT EverCloudException: public std::exception 27 | { 28 | protected: 29 | mutable QByteArray m_error; 30 | 31 | public: 32 | explicit EverCloudException(); 33 | explicit EverCloudException(QString error); 34 | explicit EverCloudException(const std::string & error); 35 | explicit EverCloudException(const char * error); 36 | ~EverCloudException() throw(); 37 | 38 | const char * what() const throw(); 39 | 40 | virtual QSharedPointer exceptionData() const; 41 | }; 42 | 43 | /** 44 | * @brief EverCloudException counterpart for asynchronous API. 45 | * 46 | * Asynchronous functions cannot throw exceptions so descendants of EverCloudExceptionData are retunded instead 47 | * in case of an error. Every exception class has its own counterpart. 48 | * The EverCloudExceptionData descendants hierarchy is a copy of the EverCloudException descendants hierarchy. 49 | * 50 | * The main reason not to use exception classes directly is that dynamic_cast does not work across module (exe, dll, etc) boundaries 51 | * in general, while `qobject_cast` do work as expected. That's why I decided to inherit my error classes from QObject. 52 | * 53 | * In general error checking in asynchronous API look like this: 54 | * 55 | * @code 56 | NoteStore* ns; 57 | ... 58 | QObject::connect(ns->getNotebook(notebookGuid), &AsyncResult::finished, [](QVariant result, QSharedPointer error) { 59 | if(!error.isNull()) { 60 | QSharedPointer errorNotFound = error.objectCast(); 61 | QSharedPointer errorUser = error.objectCast(); 62 | QSharedPointer errorSystem = error.objectCast(); 63 | if(!errorNotFound.isNull()) { 64 | qDebug() << "notebook not found" << errorNotFound.identifier << errorNotFound.key; 65 | } else if(!errorUser.isNull()) { 66 | qDebug() << errorUser.errorMessage; 67 | } else if(!errorSystem.isNull()) { 68 | if(errorSystem.errorCode == EDAMErrorCode::RATE_LIMIT_REACHED) { 69 | qDebug() << "Evernote API rate limits are reached"; 70 | } else if(errorSystem.errorCode == EDAMErrorCode::AUTH_EXPIRED) { 71 | qDebug() << "Authorization token is inspired"; 72 | } else { 73 | // some other Evernote trouble 74 | qDebug() << errorSystem.errorMessage; 75 | } 76 | } else { 77 | // some unexpected error 78 | qDebug() << error.errorMessage; 79 | } 80 | } else { 81 | // success 82 | } 83 | }); 84 | 85 | @endcode 86 | */ 87 | class QEVERCLOUD_EXPORT EverCloudExceptionData: public QObject 88 | { 89 | Q_OBJECT 90 | Q_DISABLE_COPY(EverCloudExceptionData) 91 | public: 92 | /** 93 | * Contains an error message. It's the std::exception::what() counterpart. 94 | */ 95 | QString errorMessage; 96 | 97 | explicit EverCloudExceptionData(QString error); 98 | 99 | /** 100 | * If you want to throw an exception that corresponds to a recrived EverCloudExceptionData 101 | * descendant than call this function. Do not use `throw` statement, it's not polymorphic. 102 | */ 103 | virtual void throwException() const; 104 | }; 105 | 106 | /** 107 | * All exception sent by Evernote servers (as opposed to other error conditions, for example http errors) are 108 | * descendants of this class. 109 | */ 110 | class QEVERCLOUD_EXPORT EvernoteException: public EverCloudException 111 | { 112 | public: 113 | explicit EvernoteException(); 114 | explicit EvernoteException(QString error); 115 | explicit EvernoteException(const std::string & error); 116 | explicit EvernoteException(const char * error); 117 | 118 | virtual QSharedPointer exceptionData() const Q_DECL_OVERRIDE; 119 | }; 120 | 121 | /** Asynchronous API conterpart of EvernoteException. See EverCloudExceptionData for more details.*/ 122 | class QEVERCLOUD_EXPORT EvernoteExceptionData: public EverCloudExceptionData 123 | { 124 | Q_OBJECT 125 | Q_DISABLE_COPY(EvernoteExceptionData) 126 | public: 127 | explicit EvernoteExceptionData(QString error); 128 | virtual void throwException() const Q_DECL_OVERRIDE; 129 | }; 130 | 131 | } // namespace qevercloud 132 | 133 | #endif // QEVERCLOUD_EVER_CLOUD_EXCEPTION_H 134 | -------------------------------------------------------------------------------- /src/qevercloud/InkNoteImageDownloader.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Dmitry Ivanov 3 | * 4 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 5 | * https://opensource.org/licenses/MIT 6 | */ 7 | 8 | #include 9 | #include 10 | #include "http.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace qevercloud { 17 | 18 | class InkNoteImageDownloaderPrivate 19 | { 20 | public: 21 | QPair createPostRequest(const QString & urlPart, 22 | const int sliceNumber, const bool isPublic = false); 23 | 24 | QString m_host; 25 | QString m_shardId; 26 | QString m_authenticationToken; 27 | int m_width; 28 | int m_height; 29 | }; 30 | 31 | InkNoteImageDownloader::InkNoteImageDownloader() : 32 | d_ptr(new InkNoteImageDownloaderPrivate) 33 | {} 34 | 35 | InkNoteImageDownloader::InkNoteImageDownloader(QString host, QString shardId, QString authenticationToken, 36 | int width, int height) : 37 | d_ptr(new InkNoteImageDownloaderPrivate) 38 | { 39 | d_ptr->m_host = host; 40 | d_ptr->m_shardId = shardId; 41 | d_ptr->m_authenticationToken = authenticationToken; 42 | d_ptr->m_width = width; 43 | d_ptr->m_height = height; 44 | } 45 | 46 | InkNoteImageDownloader::~InkNoteImageDownloader() 47 | { 48 | delete d_ptr; 49 | } 50 | 51 | InkNoteImageDownloader & InkNoteImageDownloader::setHost(QString host) 52 | { 53 | d_ptr->m_host = host; 54 | return *this; 55 | } 56 | 57 | InkNoteImageDownloader & InkNoteImageDownloader::setShardId(QString shardId) 58 | { 59 | d_ptr->m_shardId = shardId; 60 | return *this; 61 | } 62 | 63 | InkNoteImageDownloader & InkNoteImageDownloader::setAuthenticationToken(QString authenticationToken) 64 | { 65 | d_ptr->m_authenticationToken = authenticationToken; 66 | return *this; 67 | } 68 | 69 | InkNoteImageDownloader & InkNoteImageDownloader::setWidth(int width) 70 | { 71 | d_ptr->m_width = width; 72 | return *this; 73 | } 74 | 75 | InkNoteImageDownloader & InkNoteImageDownloader::setHeight(int height) 76 | { 77 | d_ptr->m_height = height; 78 | return *this; 79 | } 80 | 81 | QByteArray InkNoteImageDownloader::download(Guid guid, bool isPublic) 82 | { 83 | Q_D(InkNoteImageDownloader); 84 | 85 | QSize size(d_ptr->m_width, d_ptr->m_height); 86 | QImage inkNoteImage(size, QImage::Format_RGB32); 87 | 88 | QString urlPattern = QStringLiteral("https://%1/shard/%2/res/%3.ink?slice="); 89 | QString urlPart = urlPattern.arg(d->m_host, d->m_shardId, guid); 90 | 91 | int painterPosition = 0; 92 | int sliceCounter = 1; 93 | while(true) 94 | { 95 | int httpStatusCode = 0; 96 | QPair postRequest = d->createPostRequest(urlPart, sliceCounter, isPublic); 97 | 98 | QByteArray reply = simpleDownload(evernoteNetworkAccessManager(), postRequest.first, 99 | postRequest.second, &httpStatusCode); 100 | if (httpStatusCode != 200) { 101 | throw EverCloudException(QStringLiteral("HTTP Status Code = %1").arg(httpStatusCode)); 102 | } 103 | 104 | QImage replyImagePart; 105 | Q_UNUSED(replyImagePart.loadFromData(reply, "PNG")) 106 | if (replyImagePart.isNull()) 107 | { 108 | if (Q_UNLIKELY(inkNoteImage.isNull())) { 109 | throw EverCloudException(QStringLiteral("Ink note's image part is null before even starting to assemble")); 110 | } 111 | 112 | break; 113 | } 114 | 115 | if (replyImagePart.format() != inkNoteImage.format()) { 116 | inkNoteImage = inkNoteImage.convertToFormat(replyImagePart.format()); 117 | } 118 | 119 | QRect painterCurrentRect(0, painterPosition, replyImagePart.width(), replyImagePart.height()); 120 | painterPosition += replyImagePart.height(); 121 | 122 | QPainter painter(&inkNoteImage); 123 | painter.setRenderHints(QPainter::Antialiasing); 124 | painter.drawImage(painterCurrentRect, replyImagePart); 125 | 126 | if (painterPosition >= size.height()) { 127 | break; 128 | } 129 | } 130 | 131 | if (inkNoteImage.isNull()) { 132 | return QByteArray(); 133 | } 134 | 135 | QByteArray imageData; 136 | QBuffer buffer(&imageData); 137 | Q_UNUSED(buffer.open(QIODevice::WriteOnly)) 138 | inkNoteImage.save(&buffer, "PNG"); 139 | return imageData; 140 | } 141 | 142 | QPair InkNoteImageDownloaderPrivate::createPostRequest(const QString & urlPart, 143 | const int sliceNumber, 144 | const bool isPublic) 145 | { 146 | QNetworkRequest request; 147 | request.setUrl(QUrl(urlPart + QString::number(sliceNumber))); 148 | request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded")); 149 | 150 | QByteArray postData = ""; // not QByteArray()! or else ReplyFetcher will not work. 151 | if (!isPublic) { 152 | postData = QByteArray("auth=")+ QUrl::toPercentEncoding(m_authenticationToken); 153 | } 154 | 155 | return qMakePair(request, postData); 156 | } 157 | 158 | } // namespace qevercloud 159 | -------------------------------------------------------------------------------- /src/qevercloud/InkNoteImageDownloader.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Dmitry Ivanov 3 | * 4 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 5 | * https://opensource.org/licenses/MIT 6 | */ 7 | 8 | #ifndef QEVERCLOD_INK_NOTE_IMAGE_DOWNLOADER_H 9 | #define QEVERCLOD_INK_NOTE_IMAGE_DOWNLOADER_H 10 | 11 | #include "export.h" 12 | #include "AsyncResult.h" 13 | #include "generated/types.h" 14 | #include 15 | #include 16 | #include 17 | 18 | namespace qevercloud { 19 | 20 | /** @cond HIDDEN_SYMBOLS */ 21 | class InkNoteImageDownloaderPrivate; 22 | /** @endcond */ 23 | 24 | /** 25 | * @brief the class is for downloading the images of ink notes which can be created 26 | * with the official Evernote client on Windows (only with it, at least at the time 27 | * of this writing). 28 | * 29 | * On all other platforms the most one can get instead of the actual ink note 30 | * is its non-editable image. This class retrieves just these, exclusively in PNG format. 31 | * 32 | * NOTE: almost the entirety of this class' content represents an ad-hoc solution 33 | * to a completely undocumented feature of Evernote service. A very small glimpse 34 | * of information can be found e.g. here 35 | * but it is practically all one can find. 36 | */ 37 | class QEVERCLOUD_EXPORT InkNoteImageDownloader 38 | { 39 | public: 40 | /** 41 | * @brief Default constructor. 42 | * 43 | * host, shardId, authenticationToken, width, height have to be specified before calling 44 | * @link download @endlink or @link createPostRequest @endlink 45 | */ 46 | InkNoteImageDownloader(); 47 | 48 | /** 49 | * @brief Constructs InkNoteImageDownloader. 50 | * @param host 51 | * www.evernote.com or sandbox.evernote.com 52 | * @param shardId 53 | * You can get the value from UserStore service or as a result of an authentication. 54 | * @param authenticationToken 55 | * For working private ink notes you must supply a valid authentication token. 56 | * For public resources the value specified is not used. 57 | * @param width 58 | * Width of the ink note's resource 59 | * @param height 60 | * Height of the ink note's resource 61 | */ 62 | InkNoteImageDownloader(QString host, QString shardId, QString authenticationToken, int width, int height); 63 | 64 | virtual ~InkNoteImageDownloader(); 65 | 66 | /** 67 | * @param host 68 | * www.evernote.com or sandbox.evernote.com 69 | */ 70 | InkNoteImageDownloader & setHost(QString host); 71 | 72 | /** 73 | * @param shardId 74 | * You can get the value from UserStore service or as a result of an authentication. 75 | */ 76 | InkNoteImageDownloader & setShardId(QString shardId); 77 | 78 | /** 79 | * @param authenticationToken 80 | * For working private ink notes you must supply a valid authentication token. 81 | * For public resources the value specified is not used. 82 | */ 83 | InkNoteImageDownloader & setAuthenticationToken(QString authenticationToken); 84 | 85 | /** 86 | * @param width 87 | * Width of the ink note's resource 88 | */ 89 | InkNoteImageDownloader & setWidth(int width); 90 | 91 | /** 92 | * @param height 93 | * Height of the ink note's resource 94 | */ 95 | InkNoteImageDownloader & setHeight(int height); 96 | 97 | /** 98 | * @brief Downloads the image for the ink note. 99 | * 100 | * Unlike other pieces of QEverCloud API, downloading of ink note images is currently 101 | * synchronous only. The reason for that is that AsyncResult is bounded to a single 102 | * QNetworkRequest object but downloading of the ink note image might take multiple 103 | * requests for several ink note image's vertical stripes which are then merged 104 | * together to form a single image. Downloading the entire ink note's image 105 | * via a single request works sometimes but sometimes Evernote replies to such request 106 | * with messed up data which cannot be loaded into a QImage. The reason for that behaviour 107 | * is unknown at the moment, and, given the state of official documentation - missing - 108 | * it is likely to stay so. if someone has an idea how to make it more reliable, 109 | * please let me know. 110 | * 111 | * @param guid 112 | * The guid of the ink note's resource 113 | * @param isPublic 114 | * Specify true for public ink notes. In this case authentication token is not sent to 115 | * with the request as it shoud be according to the docs. 116 | * @return downloaded data. 117 | * 118 | */ 119 | QByteArray download(Guid guid, bool isPublic = false); 120 | 121 | private: 122 | InkNoteImageDownloaderPrivate * const d_ptr; 123 | Q_DECLARE_PRIVATE(InkNoteImageDownloader) 124 | }; 125 | 126 | } // namespace qevercloud 127 | 128 | #endif // QEVERCLOD_INK_NOTE_IMAGE_DOWNLOADER_H 129 | -------------------------------------------------------------------------------- /src/qevercloud/Optional.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #ifndef QEVERCLOUD_OPTIONAL_H 10 | #define QEVERCLOUD_OPTIONAL_H 11 | 12 | #include "EverCloudException.h" 13 | #include 14 | 15 | namespace qevercloud { 16 | 17 | /** 18 | * Supports optional values. 19 | * 20 | * Most of the fields in the Evernote API structs are optional. But C++ does not support this notion directly. 21 | * 22 | * To implement the concept of optional values conventional Thrift C++ wrapper uses a special field of a struct type 23 | * where each field is of type bool with the same name as a field in the struct. This bool flag indicated was 24 | * the field with the same name in the outer struct assigned or not. 25 | * 26 | * While this method have its advantages (obviousness and simplicity) I found it very inconvenient to work with. 27 | * You have to check by hand that both values (value itself and its __isset flag) are in sync. 28 | * There is no checks whatsoever against an error and such an error is too easy to make. 29 | * 30 | * So for my library I created a special class that supports the optional value notion explicitly. 31 | * Basically Optional class just holds a bool value that tracks the fact that a value was assigned. But this tracking 32 | * is done automatically and attempts to use unissigned values throw exceptions. In this way errors are much harder to 33 | * make and it's harder for them to slip through testing unnoticed too. 34 | * 35 | */ 36 | template 37 | class Optional 38 | { 39 | public: 40 | /** Default constructor. 41 | * Default Optional is not set. 42 | */ 43 | Optional() : 44 | m_isSet(false), 45 | m_value(T()) 46 | {} 47 | 48 | /** 49 | * Copy constructor. 50 | */ 51 | Optional(const Optional & o) : 52 | m_isSet(o.m_isSet), 53 | m_value(o.m_value) 54 | {} 55 | 56 | /** 57 | * Template copy constructor. Allows to be initialized with Optional of any compatible type. 58 | */ 59 | template 60 | Optional(const Optional & o) : 61 | m_isSet(o.m_isSet), 62 | m_value(o.m_value) 63 | {} 64 | 65 | /** 66 | * Initialization with a value of the type T. Note: it's implicit. 67 | */ 68 | Optional(const T & value) : 69 | m_isSet(true), 70 | m_value(value) 71 | {} 72 | 73 | /** 74 | * Template initialization with a value of any compatible type. 75 | */ 76 | template 77 | Optional(const X & value) : 78 | m_isSet(true), 79 | m_value(value) 80 | {} 81 | 82 | /** 83 | * Assignment. 84 | */ 85 | Optional & operator=(const Optional & o) 86 | { 87 | m_value = o.m_value; 88 | m_isSet = o.m_isSet; 89 | return *this; 90 | } 91 | 92 | /** 93 | * Template assignment with an Optional of any compatible value. 94 | */ 95 | template 96 | Optional & operator=(const Optional & o) 97 | { 98 | m_value = o.m_value; 99 | m_isSet = o.m_isSet; 100 | return *this; 101 | } 102 | 103 | /** 104 | * Assignment with a value of the type T. 105 | */ 106 | Optional & operator=(const T & value) 107 | { 108 | m_value = value; 109 | m_isSet = true; 110 | return *this; 111 | } 112 | 113 | /** 114 | * Template assignment with a value of any compatible type. 115 | */ 116 | template 117 | Optional & operator=(const X & value) 118 | { 119 | m_value = value; 120 | m_isSet = true; 121 | return *this; 122 | } 123 | 124 | /** 125 | * Implicit conversion of Optional to T. 126 | * 127 | * const version. 128 | */ 129 | operator const T&() const 130 | { 131 | if (!m_isSet) { 132 | throw EverCloudException("qevercloud::Optional: nonexistent value access"); 133 | } 134 | 135 | return m_value; 136 | } 137 | 138 | /** 139 | * Implicit conversion of Optional to T. 140 | * 141 | * Note: a reference is returned, not a copy. 142 | */ 143 | operator T&() 144 | { 145 | if (!m_isSet) { 146 | throw EverCloudException("qevercloud::Optional: nonexistent value access"); 147 | } 148 | 149 | return m_value; 150 | } 151 | 152 | /** 153 | * Returs a reference to the holded value. 154 | * 155 | * const version. 156 | * 157 | */ 158 | const T & ref() const 159 | { 160 | if (!m_isSet) { 161 | throw EverCloudException("qevercloud::Optional: nonexistent value access"); 162 | } 163 | 164 | return m_value; 165 | } 166 | 167 | /** 168 | * Returs reference to the holded value. 169 | * 170 | * There are contexts in C++ where impicit type conversions can't help. For example: 171 | * 172 | * @code 173 | Optional l; 174 | for(auto s : l); // you will hear from your compiler 175 | @endcode 176 | * 177 | * Explicit type conversion can be used... 178 | * 179 | * @code 180 | Optional l; 181 | for(auto s : static_cast(l)); // ugh... 182 | @endcode 183 | * 184 | * ... but this is indeed ugly as hell. 185 | * 186 | * So I implemented ref() function that returns a reference to the holded value. 187 | * @code 188 | Optional l; 189 | for(auto s : l.ref()); // not ideal but OK 190 | @endcode 191 | */ 192 | T & ref() 193 | { 194 | if (!m_isSet) { 195 | throw EverCloudException("qevercloud::Optional: nonexistent value access"); 196 | } 197 | 198 | return m_value; 199 | } 200 | 201 | /** 202 | * @brief Checks if value is set. 203 | * @return true if Optional have been assigned a value and false otherwise. 204 | * 205 | * Access to an unassigned ("not set") Optional lead to an exception. 206 | */ 207 | bool isSet() const 208 | { 209 | return m_isSet; 210 | } 211 | 212 | /** 213 | * Clears an Optional. 214 | * 215 | * @code 216 | 217 | Optional o(1); 218 | o.clear(); 219 | cout << o; // exception 220 | 221 | @endcode 222 | */ 223 | void clear() 224 | { 225 | m_isSet = false; 226 | m_value = T(); 227 | } 228 | 229 | /** 230 | * Fast way to initialize an Optional with a default value. 231 | * 232 | * It's very useful for structs. 233 | * 234 | * @code 235 | 236 | struct S2 {int f;}; 237 | struct S {int f1; Optional f2}; 238 | Optional o; // o.isSet() != ture 239 | 240 | // without init() it's cumbersome to access struct fields 241 | // it's especially true for nested Optionals 242 | o = S(); // now o is set 243 | o->f2 = S2(); // and o.f2 is set 244 | o->f2->f = 1; // so at last it can be used 245 | 246 | // with init() it's simpler 247 | o.init()->f2.init()->f = 1; 248 | 249 | @endcode 250 | * @return reference to itself 251 | */ 252 | Optional & init() 253 | { 254 | m_isSet = true; 255 | m_value = T(); 256 | return *this; 257 | } 258 | 259 | /** 260 | * Two syntatic constructs come to mind to use for implementation of access to a struct's/class's field directly from Optional. 261 | * 262 | * One is the dereference operator. 263 | * This is what boost::optional uses. While it's conceptually nice 264 | * I found it to be not a very convenient way to refer to structs, especially nested ones. 265 | * So I overloaded the operator-> and use smart pointer semantics. 266 | * 267 | * @code 268 | struct S1 {int f1;}; 269 | struct S2 {Optional f2;}; 270 | Optional o; 271 | 272 | *((*o).f2).f1; // boost way, not implemented 273 | 274 | o->f2->f1; // QEverCloud way 275 | 276 | @endcode 277 | * 278 | * I admit, boost::optional is much more elegant overall. It uses pointer semantics quite clearly and 279 | * in an instantly understandable way. It's universal (* works for any type and not just structs). There is 280 | * no need for implicit type concersions and so there is no subtleties because of it. And so on. 281 | * 282 | * But then referring to struct fields is a chore. And this is the most common use case of Optionals in QEverCloud. 283 | * 284 | * So I decided to use non-obvious-on-the-first-sight semantics for my Optional. IMO it's much more convenient when gotten used to. 285 | * 286 | */ 287 | T * operator->() 288 | { 289 | if (!m_isSet) { 290 | throw EverCloudException("qevercloud::Optional: nonexistent value access"); 291 | } 292 | 293 | return &m_value; 294 | } 295 | 296 | /** 297 | * const version. 298 | */ 299 | const T * operator->() const 300 | { 301 | if (!m_isSet) { 302 | throw EverCloudException("qevercloud::Optional: nonexistent value access"); 303 | } 304 | 305 | return &m_value; 306 | } 307 | 308 | /** 309 | * The function is sometimes useful to simplify checking for the value being set. 310 | * @param defaultValue 311 | * The value to return if Optional is not set. 312 | * @return Optional value if set and defaultValue otherwise. 313 | */ 314 | T value(T defaultValue = T()) const 315 | { 316 | return m_isSet ? m_value : defaultValue; 317 | } 318 | 319 | /** 320 | * Two optionals are equal if they are both not set or have 321 | * equal values. 322 | * 323 | * I do not define `operator==` due to not easily resolvable conflicts with `operator T&`. 324 | * 325 | * Note that `optional == other_optional` may throw but `optional.isEqual(other_optional)` will not. 326 | */ 327 | bool isEqual(const Optional & other) const 328 | { 329 | if(m_isSet != other.m_isSet) return false; 330 | return !m_isSet || (m_value == other.m_value); 331 | } 332 | 333 | template friend class Optional; 334 | 335 | friend void swap(Optional & first, Optional & second) 336 | { 337 | using std::swap; 338 | swap(first.m_isSet, second.m_isSet); 339 | swap(first.m_value, second.m_value); 340 | } 341 | 342 | // Visual C++ does not to generate implicit move constructors so this stuff doesn't work with even recent MSVC compilers 343 | #if defined(Q_COMPILER_RVALUE_REFS) && !defined(_MSC_VER) 344 | Optional(Optional && other) 345 | { 346 | swap(*this, other); 347 | } 348 | 349 | Optional & operator=(Optional && other) 350 | { 351 | swap(*this, other); 352 | return *this; 353 | } 354 | 355 | Optional(T && other) 356 | { 357 | using std::swap; 358 | m_isSet = true; 359 | swap(m_value, other); 360 | } 361 | 362 | Optional & operator=(T && other) 363 | { 364 | using std::swap; 365 | m_isSet = true; 366 | swap(m_value, other); 367 | return *this; 368 | } 369 | #endif 370 | 371 | private: 372 | bool m_isSet; 373 | T m_value; 374 | }; 375 | 376 | } // namespace qevercloud 377 | 378 | #endif // QEVERCLOUD_OPTIONAL_H 379 | -------------------------------------------------------------------------------- /src/qevercloud/QEverCloud.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #ifndef QEVERCLOUD_INFTHEADER_H 10 | #define QEVERCLOUD_INFTHEADER_H 11 | 12 | #include "AsyncResult.h" 13 | #include "EventLoopFinisher.h" 14 | #include "EverCloudException.h" 15 | #include "exceptions.h" 16 | #include "export.h" 17 | #include "globals.h" 18 | #include "Optional.h" 19 | #include "qt4helpers.h" 20 | #include "thumbnail.h" 21 | #include "InkNoteImageDownloader.h" 22 | //#include "VersionInfo.h" 23 | #include "generated/EDAMErrorCode.h" 24 | #include "generated/constants.h" 25 | #include "generated/services.h" 26 | #include "generated/types.h" 27 | 28 | #endif // QEVERCLOUD_INFTHEADER_H 29 | -------------------------------------------------------------------------------- /src/qevercloud/QEverCloudOAuth.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #ifndef QEVERCLOUDOAUTH_INFTHEADER_H 10 | #define QEVERCLOUDOAUTH_INFTHEADER_H 11 | 12 | #include "QEverCloud.h" 13 | #include "oauth.h" 14 | 15 | #endif // QEVERCLOUDOAUTH_INFTHEADER_H 16 | -------------------------------------------------------------------------------- /src/qevercloud/exceptions.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #ifndef QEVERCLOUD_EXCEPTIONS_H 10 | #define QEVERCLOUD_EXCEPTIONS_H 11 | 12 | #include "Optional.h" 13 | #include "export.h" 14 | #include "EverCloudException.h" 15 | #include "generated/EDAMErrorCode.h" 16 | #include "generated/types.h" 17 | #include 18 | #include 19 | #include 20 | 21 | namespace qevercloud { 22 | 23 | /** 24 | * Errors of the Thrift protocol level. It could be wrongly formatted parameters 25 | * or return values for example. 26 | */ 27 | class QEVERCLOUD_EXPORT ThriftException: public EverCloudException 28 | { 29 | public: 30 | struct Type { 31 | enum type { 32 | UNKNOWN = 0, 33 | UNKNOWN_METHOD = 1, 34 | INVALID_MESSAGE_TYPE = 2, 35 | WRONG_METHOD_NAME = 3, 36 | BAD_SEQUENCE_ID = 4, 37 | MISSING_RESULT = 5, 38 | INTERNAL_ERROR = 6, 39 | PROTOCOL_ERROR = 7, 40 | INVALID_DATA = 8 41 | }; 42 | }; 43 | 44 | ThriftException(); 45 | ThriftException(Type::type type); 46 | ThriftException(Type::type type, QString message); 47 | 48 | Type::type type() const; 49 | 50 | const char * what() const throw() Q_DECL_OVERRIDE; 51 | 52 | virtual QSharedPointer exceptionData() const Q_DECL_OVERRIDE; 53 | 54 | protected: 55 | Type::type m_type; 56 | }; 57 | 58 | /** Asynchronous API conterpart of ThriftException. See EverCloudExceptionData for more details.*/ 59 | class QEVERCLOUD_EXPORT ThriftExceptionData: public EverCloudExceptionData 60 | { 61 | Q_OBJECT 62 | Q_DISABLE_COPY(ThriftExceptionData) 63 | public: 64 | explicit ThriftExceptionData(QString error, ThriftException::Type::type type); 65 | virtual void throwException() const Q_DECL_OVERRIDE; 66 | 67 | protected: 68 | ThriftException::Type::type m_type; 69 | }; 70 | 71 | inline QSharedPointer ThriftException::exceptionData() const 72 | { 73 | return QSharedPointer(new ThriftExceptionData(QString::fromUtf8(what()), type())); 74 | } 75 | 76 | /** Asynchronous API conterpart of EDAMUserException. See EverCloudExceptionData for more details.*/ 77 | class QEVERCLOUD_EXPORT EDAMUserExceptionData: public EvernoteExceptionData 78 | { 79 | Q_OBJECT 80 | Q_DISABLE_COPY(EDAMUserExceptionData) 81 | public: 82 | explicit EDAMUserExceptionData(QString error, EDAMErrorCode::type errorCode, Optional parameter); 83 | virtual void throwException() const Q_DECL_OVERRIDE; 84 | 85 | protected: 86 | EDAMErrorCode::type m_errorCode; 87 | Optional m_parameter; 88 | }; 89 | 90 | /** Asynchronous API conterpart of EDAMSystemException. See EverCloudExceptionData for more details.*/ 91 | class QEVERCLOUD_EXPORT EDAMSystemExceptionData: public EvernoteExceptionData 92 | { 93 | Q_OBJECT 94 | Q_DISABLE_COPY(EDAMSystemExceptionData) 95 | public: 96 | explicit EDAMSystemExceptionData(QString err, EDAMErrorCode::type errorCode, Optional message, Optional rateLimitDuration); 97 | virtual void throwException() const Q_DECL_OVERRIDE; 98 | 99 | protected: 100 | EDAMErrorCode::type m_errorCode; 101 | Optional m_message; 102 | Optional m_rateLimitDuration; 103 | }; 104 | 105 | /** Asynchronous API conterpart of EDAMNotFoundException. See EverCloudExceptionData for more details.*/ 106 | class QEVERCLOUD_EXPORT EDAMNotFoundExceptionData: public EvernoteExceptionData 107 | { 108 | Q_OBJECT 109 | Q_DISABLE_COPY(EDAMNotFoundExceptionData) 110 | public: 111 | explicit EDAMNotFoundExceptionData(QString error, Optional identifier, Optional key); 112 | virtual void throwException() const Q_DECL_OVERRIDE; 113 | 114 | protected: 115 | Optional m_identifier; 116 | Optional m_key; 117 | }; 118 | 119 | /** Asynchronous API conterpart of EDAMInvalidContactsException. See EverCloudExceptionData for more details.*/ 120 | class QEVERCLOUD_EXPORT EDAMInvalidContactsExceptionData: public EvernoteExceptionData 121 | { 122 | Q_OBJECT 123 | Q_DISABLE_COPY(EDAMInvalidContactsExceptionData) 124 | public: 125 | explicit EDAMInvalidContactsExceptionData(QList contacts, Optional parameter, Optional > reasons); 126 | virtual void throwException() const Q_DECL_OVERRIDE; 127 | 128 | protected: 129 | QList< Contact > m_contacts; 130 | Optional< QString > m_parameter; 131 | Optional< QList< EDAMInvalidContactReason::type > > m_reasons; 132 | }; 133 | 134 | /** 135 | * EDAMSystemException for `errorCode = RATE_LIMIT_REACHED` 136 | */ 137 | class QEVERCLOUD_EXPORT EDAMSystemExceptionRateLimitReached: public EDAMSystemException 138 | { 139 | public: 140 | virtual QSharedPointer exceptionData() const Q_DECL_OVERRIDE; 141 | }; 142 | 143 | /** Asynchronous API conterpart of EDAMSystemExceptionRateLimitReached. See EverCloudExceptionData for more details.*/ 144 | class QEVERCLOUD_EXPORT EDAMSystemExceptionRateLimitReachedData: public EDAMSystemExceptionData 145 | { 146 | Q_OBJECT 147 | Q_DISABLE_COPY(EDAMSystemExceptionRateLimitReachedData) 148 | public: 149 | explicit EDAMSystemExceptionRateLimitReachedData(QString error, EDAMErrorCode::type errorCode, Optional message, 150 | Optional rateLimitDuration); 151 | virtual void throwException() const Q_DECL_OVERRIDE; 152 | }; 153 | 154 | /** 155 | * EDAMSystemException for `errorCode = AUTH_EXPIRED` 156 | */ 157 | class QEVERCLOUD_EXPORT EDAMSystemExceptionAuthExpired: public EDAMSystemException 158 | { 159 | public: 160 | virtual QSharedPointer exceptionData() const Q_DECL_OVERRIDE; 161 | }; 162 | 163 | /** Asynchronous API conterpart of EDAMSystemExceptionAuthExpired. See EverCloudExceptionData for more details.*/ 164 | class QEVERCLOUD_EXPORT EDAMSystemExceptionAuthExpiredData: public EDAMSystemExceptionData 165 | { 166 | Q_OBJECT 167 | Q_DISABLE_COPY(EDAMSystemExceptionAuthExpiredData) 168 | public: 169 | explicit EDAMSystemExceptionAuthExpiredData(QString error, EDAMErrorCode::type errorCode, Optional message, 170 | Optional rateLimitDuration); 171 | virtual void throwException() const Q_DECL_OVERRIDE; 172 | }; 173 | 174 | } // namespace qevercloud 175 | 176 | #endif // QEVERCLOUD_EXCEPTIONS_H 177 | -------------------------------------------------------------------------------- /src/qevercloud/export.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2017 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #ifndef QEVERCLOUD_EXPORT_H 10 | #define QEVERCLOUD_EXPORT_H 11 | 12 | #include 13 | 14 | #if defined(QEVERCLOUD_SHARED_LIBRARY) 15 | # define QEVERCLOUD_EXPORT Q_DECL_EXPORT 16 | #elif defined(QEVERCLOUD_STATIC_LIBRARY) 17 | # define QEVERCLOUD_EXPORT Q_DECL_EXPORT 18 | #else 19 | # define QEVERCLOUD_EXPORT Q_DECL_IMPORT 20 | #endif 21 | 22 | #endif // QEVERCLOUD_EXPORT_H 23 | -------------------------------------------------------------------------------- /src/qevercloud/generated/EDAMErrorCode.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | * 8 | * This file was generated from Evernote Thrift API 9 | */ 10 | 11 | 12 | #ifndef QEVERCLOUD_GENERATED_EDAMERRORCODE_H 13 | #define QEVERCLOUD_GENERATED_EDAMERRORCODE_H 14 | 15 | #include "../export.h" 16 | 17 | namespace qevercloud { 18 | 19 | /** 20 | * Numeric codes indicating the type of error that occurred on the 21 | * service. 22 | *
23 | *
UNKNOWN
24 | *
No information available about the error
25 | *
BAD_DATA_FORMAT
26 | *
The format of the request data was incorrect
27 | *
PERMISSION_DENIED
28 | *
Not permitted to perform action
29 | *
INTERNAL_ERROR
30 | *
Unexpected problem with the service
31 | *
DATA_REQUIRED
32 | *
A required parameter/field was absent
33 | *
LIMIT_REACHED
34 | *
Operation denied due to data model limit
35 | *
QUOTA_REACHED
36 | *
Operation denied due to user storage limit
37 | *
INVALID_AUTH
38 | *
Username and/or password incorrect
39 | *
AUTH_EXPIRED
40 | *
Authentication token expired
41 | *
DATA_CONFLICT
42 | *
Change denied due to data model conflict
43 | *
ENML_VALIDATION
44 | *
Content of submitted note was malformed
45 | *
SHARD_UNAVAILABLE
46 | *
Service shard with account data is temporarily down
47 | *
LEN_TOO_SHORT
48 | *
Operation denied due to data model limit, where something such 49 | * as a string length was too short
50 | *
LEN_TOO_LONG
51 | *
Operation denied due to data model limit, where something such 52 | * as a string length was too long
53 | *
TOO_FEW
54 | *
Operation denied due to data model limit, where there were 55 | * too few of something.
56 | *
TOO_MANY
57 | *
Operation denied due to data model limit, where there were 58 | * too many of something.
59 | *
UNSUPPORTED_OPERATION
60 | *
Operation denied because it is currently unsupported.
61 | *
TAKEN_DOWN
62 | *
Operation denied because access to the corresponding object is 63 | * prohibited in response to a take-down notice.
64 | *
RATE_LIMIT_REACHED
65 | *
Operation denied because the calling application has reached 66 | * its hourly API call limit for this user.
67 | *
BUSINESS_SECURITY_LOGIN_REQUIRED
68 | *
Access to a business account has been denied because the user must complete 69 | * additional steps in order to comply with business security requirements.
70 | *
DEVICE_LIMIT_REACHED
71 | *
Operation denied because the user has exceeded their maximum allowed 72 | * number of devices.
73 | *
74 | */ 75 | struct QEVERCLOUD_EXPORT EDAMErrorCode 76 | { 77 | enum type 78 | { 79 | UNKNOWN = 1, 80 | BAD_DATA_FORMAT = 2, 81 | PERMISSION_DENIED = 3, 82 | INTERNAL_ERROR = 4, 83 | DATA_REQUIRED = 5, 84 | LIMIT_REACHED = 6, 85 | QUOTA_REACHED = 7, 86 | INVALID_AUTH = 8, 87 | AUTH_EXPIRED = 9, 88 | DATA_CONFLICT = 10, 89 | ENML_VALIDATION = 11, 90 | SHARD_UNAVAILABLE = 12, 91 | LEN_TOO_SHORT = 13, 92 | LEN_TOO_LONG = 14, 93 | TOO_FEW = 15, 94 | TOO_MANY = 16, 95 | UNSUPPORTED_OPERATION = 17, 96 | TAKEN_DOWN = 18, 97 | RATE_LIMIT_REACHED = 19, 98 | BUSINESS_SECURITY_LOGIN_REQUIRED = 20, 99 | DEVICE_LIMIT_REACHED = 21 100 | }; 101 | }; 102 | 103 | 104 | } // namespace qevercloud 105 | 106 | #endif // QEVERCLOUD_GENERATED_EDAMERRORCODE_H 107 | -------------------------------------------------------------------------------- /src/qevercloud/globals.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace qevercloud { 7 | 8 | QNetworkAccessManager * evernoteNetworkAccessManager() 9 | { 10 | static QSharedPointer pNetworkAccessManager; 11 | static QMutex networkAccessManagerMutex; 12 | QMutexLocker mutexLocker(&networkAccessManagerMutex); 13 | if (pNetworkAccessManager.isNull()) { 14 | pNetworkAccessManager = QSharedPointer(new QNetworkAccessManager); 15 | } 16 | return pNetworkAccessManager.data(); 17 | } 18 | 19 | int libraryVersion() 20 | { 21 | return 4*10000 + 0*100 + 0; 22 | } 23 | 24 | } // namespace qevercloud 25 | -------------------------------------------------------------------------------- /src/qevercloud/globals.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #ifndef QEVERCLOUD_GLOBALS_H 10 | #define QEVERCLOUD_GLOBALS_H 11 | 12 | #include "export.h" 13 | #include 14 | 15 | /** 16 | * All the library lives in this namespace. 17 | */ 18 | namespace qevercloud { 19 | 20 | /** 21 | * All network request made by QEverCloud - including OAuth - are 22 | * served by this NetworkAccessManager. 23 | * 24 | * Use this function to handle proxy authentication requests etc. 25 | */ 26 | QEVERCLOUD_EXPORT QNetworkAccessManager * evernoteNetworkAccessManager(); 27 | 28 | /** 29 | * qevercloud library version. 30 | */ 31 | QEVERCLOUD_EXPORT int libraryVersion(); 32 | 33 | } // namespace qevercloud 34 | 35 | #endif // QEVERCLOUD_GLOBALS_H 36 | -------------------------------------------------------------------------------- /src/qevercloud/http.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include "http.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | /** @cond HIDDEN_SYMBOLS */ 19 | 20 | namespace qevercloud { 21 | 22 | ReplyFetcher::ReplyFetcher(QObject * parent) : 23 | QObject(parent), 24 | m_success(false), 25 | m_httpStatusCode(0) 26 | { 27 | m_ticker = new QTimer(this); 28 | QObject::connect(m_ticker, QEC_SIGNAL(QTimer,timeout), this, QEC_SLOT(ReplyFetcher,checkForTimeout)); 29 | } 30 | 31 | void ReplyFetcher::start(QNetworkAccessManager * nam, QUrl url) 32 | { 33 | QNetworkRequest request; 34 | request.setUrl(url); 35 | start(nam, request); 36 | } 37 | 38 | void ReplyFetcher::start(QNetworkAccessManager * nam, QNetworkRequest request, QByteArray postData) 39 | { 40 | m_httpStatusCode= 0; 41 | m_errorText.clear(); 42 | m_receivedData.clear(); 43 | m_success = true; // not in finished() signal handler, it might not be called according to the docs 44 | // besides, I've added timeout feature 45 | 46 | m_lastNetworkTime = QDateTime::currentMSecsSinceEpoch(); 47 | m_ticker->start(1000); 48 | 49 | if (postData.isNull()) { 50 | m_reply = QSharedPointer(nam->get(request), &QObject::deleteLater); 51 | } 52 | else { 53 | m_reply = QSharedPointer(nam->post(request, postData), &QObject::deleteLater); 54 | } 55 | 56 | QObject::connect(m_reply.data(), QEC_SIGNAL(QNetworkReply,finished), this, QEC_SLOT(ReplyFetcher,onFinished)); 57 | QObject::connect(m_reply.data(), SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError))); 58 | QObject::connect(m_reply.data(), QEC_SIGNAL(QNetworkReply,sslErrors,QList), this, QEC_SLOT(ReplyFetcher,onSslErrors,QList)); 59 | QObject::connect(m_reply.data(), QEC_SIGNAL(QNetworkReply,downloadProgress,qint64,qint64), this, QEC_SLOT(ReplyFetcher,onDownloadProgress,qint64,qint64)); 60 | } 61 | 62 | void ReplyFetcher::onDownloadProgress(qint64, qint64) 63 | { 64 | m_lastNetworkTime = QDateTime::currentMSecsSinceEpoch(); 65 | } 66 | 67 | void ReplyFetcher::checkForTimeout() 68 | { 69 | const int connectionTimeout = 30*1000; 70 | if ((m_lastNetworkTime - QDateTime::currentMSecsSinceEpoch()) > connectionTimeout) { 71 | setError(QStringLiteral("Connection timeout.")); 72 | } 73 | } 74 | 75 | void ReplyFetcher::onFinished() 76 | { 77 | m_ticker->stop(); 78 | 79 | if (!m_success) { 80 | return; 81 | } 82 | 83 | m_receivedData = m_reply->readAll(); 84 | m_httpStatusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 85 | 86 | QObject::disconnect(m_reply.data()); 87 | emit replyFetched(this); 88 | } 89 | 90 | void ReplyFetcher::onError(QNetworkReply::NetworkError error) 91 | { 92 | Q_UNUSED(error) 93 | setError(m_reply->errorString()); 94 | } 95 | 96 | void ReplyFetcher::onSslErrors(QList errors) 97 | { 98 | QString errorText = QStringLiteral("SSL Errors:\n"); 99 | 100 | for(int i = 0, numErrors = errors.size(); i < numErrors; ++i) { 101 | const QSslError & error = errors[i]; 102 | errorText += error.errorString().append(QStringLiteral("\n")); 103 | } 104 | 105 | setError(errorText); 106 | } 107 | 108 | void ReplyFetcher::setError(QString errorText) 109 | { 110 | m_success = false; 111 | m_ticker->stop(); 112 | m_errorText = errorText; 113 | QObject::disconnect(m_reply.data()); 114 | emit replyFetched(this); 115 | } 116 | 117 | QByteArray simpleDownload(QNetworkAccessManager* nam, QNetworkRequest request, 118 | QByteArray postData, int * httpStatusCode) 119 | { 120 | ReplyFetcher * fetcher = new ReplyFetcher; 121 | QEventLoop loop; 122 | QObject::connect(fetcher, SIGNAL(replyFetched(QObject*)), &loop, SLOT(quit())); 123 | 124 | ReplyFetcherLauncher * fetcherLauncher = new ReplyFetcherLauncher(fetcher, nam, request, postData); 125 | QTimer::singleShot(0, fetcherLauncher, SLOT(start())); 126 | loop.exec(QEventLoop::ExcludeUserInputEvents); 127 | 128 | fetcherLauncher->deleteLater(); 129 | 130 | if (httpStatusCode) { 131 | *httpStatusCode = fetcher->httpStatusCode(); 132 | } 133 | 134 | if (fetcher->isError()) { 135 | QString errorText = fetcher->errorText(); 136 | fetcher->deleteLater(); 137 | throw EverCloudException(errorText); 138 | } 139 | 140 | QByteArray receivedData = fetcher->receivedData(); 141 | fetcher->deleteLater(); 142 | return receivedData; 143 | } 144 | 145 | QNetworkRequest createEvernoteRequest(QString url) 146 | { 147 | QNetworkRequest request; 148 | request.setUrl(url); 149 | request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-thrift")); 150 | 151 | #if QT_VERSION < 0x050000 152 | request.setRawHeader("User-Agent", QString::fromUtf8("QEverCloud %1.%2").arg(libraryVersion() / 10000).arg(libraryVersion() % 10000).toLatin1()); 153 | #else 154 | request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("QEverCloud %1.%2").arg(libraryVersion() / 10000).arg(libraryVersion() % 10000)); 155 | #endif 156 | 157 | request.setRawHeader("Accept", "application/x-thrift"); 158 | return request; 159 | } 160 | 161 | QByteArray askEvernote(QString url, QByteArray postData) 162 | { 163 | int httpStatusCode = 0; 164 | QByteArray reply = simpleDownload(evernoteNetworkAccessManager(), createEvernoteRequest(url), postData, &httpStatusCode); 165 | 166 | if (httpStatusCode != 200) { 167 | throw EverCloudException(QStringLiteral("HTTP Status Code = %1").arg(httpStatusCode)); 168 | } 169 | 170 | return reply; 171 | } 172 | 173 | ReplyFetcherLauncher::ReplyFetcherLauncher(ReplyFetcher * fetcher, QNetworkAccessManager * nam, 174 | const QNetworkRequest & request, const QByteArray & postData) : 175 | QObject(nam), 176 | m_fetcher(fetcher), 177 | m_nam(nam), 178 | m_request(request), 179 | m_postData(postData) 180 | {} 181 | 182 | void ReplyFetcherLauncher::start() 183 | { 184 | m_fetcher->start(m_nam, m_request, m_postData); 185 | } 186 | 187 | } // namespace qevercloud 188 | 189 | /** @endcond */ 190 | -------------------------------------------------------------------------------- /src/qevercloud/http.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #ifndef QEVERCLOUD_HTTP_H 10 | #define QEVERCLOUD_HTTP_H 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | /** @cond HIDDEN_SYMBOLS */ 25 | 26 | namespace qevercloud { 27 | 28 | QNetworkAccessManager * evernoteNetworkAccessManager(); 29 | 30 | // the class greatly simplifies QNetworkReply handling 31 | class ReplyFetcher: public QObject 32 | { 33 | Q_OBJECT 34 | public: 35 | ReplyFetcher(QObject * parent = Q_NULLPTR); 36 | 37 | void start(QNetworkAccessManager * nam, QUrl url); 38 | // if !postData.isNull() then POST will be issued instead of GET 39 | void start(QNetworkAccessManager * nam, QNetworkRequest request, QByteArray postData = QByteArray()); 40 | bool isError() { return !m_success; } 41 | QString errorText() { return m_errorText; } 42 | QByteArray receivedData() { return m_receivedData; } 43 | int httpStatusCode() { return m_httpStatusCode; } 44 | 45 | Q_SIGNALS: 46 | void replyFetched(QObject*); // sends itself 47 | 48 | private Q_SLOTS: 49 | void onFinished(); 50 | void onError(QNetworkReply::NetworkError); 51 | void onSslErrors(QList l); 52 | void onDownloadProgress(qint64, qint64); 53 | void checkForTimeout(); 54 | 55 | private: 56 | void setError(QString errorText); 57 | 58 | private: 59 | QSharedPointer m_reply; 60 | bool m_success; 61 | QString m_errorText; 62 | QByteArray m_receivedData; 63 | int m_httpStatusCode; 64 | QTimer* m_ticker; 65 | qint64 m_lastNetworkTime; 66 | }; 67 | 68 | QNetworkRequest createEvernoteRequest(QString url); 69 | 70 | QByteArray askEvernote(QString url, QByteArray postData); 71 | 72 | QByteArray simpleDownload(QNetworkAccessManager * nam, QNetworkRequest request, 73 | QByteArray postData = QByteArray(), int * httpStatusCode = Q_NULLPTR); 74 | 75 | class ReplyFetcherLauncher: public QObject 76 | { 77 | Q_OBJECT 78 | public: 79 | explicit ReplyFetcherLauncher(ReplyFetcher * fetcher, QNetworkAccessManager * nam, 80 | const QNetworkRequest & request, const QByteArray & postData); 81 | 82 | public Q_SLOTS: 83 | void start(); 84 | 85 | private: 86 | ReplyFetcher * m_fetcher; 87 | QNetworkAccessManager * m_nam; 88 | QNetworkRequest m_request; 89 | QByteArray m_postData; 90 | }; 91 | 92 | } // namespace qevercloud 93 | 94 | /** @endcond */ 95 | 96 | #endif // QEVERCLOUD_HTTP_H 97 | -------------------------------------------------------------------------------- /src/qevercloud/impl.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #ifndef QEVERCLOUD_IMPL_H 10 | #define QEVERCLOUD_IMPL_H 11 | 12 | #include 13 | #include 14 | #include 15 | #include "http.h" 16 | #include "thrift.h" 17 | 18 | /** 19 | 20 | @mainpage About QEverCloud 21 | 22 | This library presents complete Evernote SDK for Qt. 23 | All the functionality that is described on Evernote site 24 | is implemented and ready to use. In particular OAuth autentication is implemented. 25 | 26 | Include *QEverCloud.h* or *QEverCloudOAuth.h* to use the library. The latter header is needed if you use OAuth functionality. 27 | 28 | QEverCloud on GitHub 29 | 30 | */ 31 | 32 | namespace qevercloud { 33 | 34 | /** @cond HIDDEN_SYMBOLS */ 35 | 36 | ThriftException readThriftException(ThriftBinaryBufferReader & reader); 37 | 38 | void throwEDAMSystemException(const EDAMSystemException & baseException); 39 | 40 | /** @endcond */ 41 | 42 | } // namespace qevercloud 43 | 44 | #endif // QEVERCLOUD_IMPL_H 45 | -------------------------------------------------------------------------------- /src/qevercloud/oauth.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #ifndef QEVERCLOUD_OAUTH_H 10 | #define QEVERCLOUD_OAUTH_H 11 | 12 | // Workarounding https://bugreports.qt.io/browse/QTBUG-28885 13 | #if defined(_MSC_VER) && (_MSC_VER <= 1600) 14 | #define QT_NO_UNICODE_LITERAL 15 | #endif 16 | 17 | #include "generated/types.h" 18 | #include "export.h" 19 | #include "qt4helpers.h" 20 | #include 21 | #include 22 | 23 | #if defined(_MSC_VER) && _MSC_VER <= 1600 // MSVC <= 2010 24 | // VS2010 is supposed to be C++11 but does not fulfull the entire standard. 25 | #ifdef QStringLiteral 26 | #undef QStringLiteral 27 | #define QStringLiteral(str) QString::fromUtf8("" str "", sizeof(str) - 1) 28 | #endif 29 | #endif 30 | 31 | namespace qevercloud { 32 | 33 | /** 34 | * @brief Sets the function to use for nonce generation for OAuth authentication. 35 | * 36 | * The default algorithm uses qrand() so do not forget to call qsrand() in your application! 37 | * 38 | * qrand() is not guaranteed to be cryptographically strong. I try to amend the fact by using 39 | * QUuid::createUuid() which uses /dev/urandom if it's available. But this is no guarantee either. 40 | * So if you want total control over nonce generation you can write you own algorithm. 41 | * 42 | * setNonceGenerator is NOT thread safe. 43 | */ 44 | void setNonceGenerator(quint64 (*nonceGenerator)()); 45 | 46 | /** @cond HIDDEN_SYMBOLS */ 47 | class EvernoteOAuthWebViewPrivate; 48 | /** @endcond */ 49 | 50 | /** 51 | * @brief The class is tailored specifically for OAuth authorization with Evernote. 52 | * 53 | * While it is functional by itself you probably will prefer to use EvernoteOAuthDialog. 54 | * 55 | * %Note that you have to include QEverCloudOAuth.h header. 56 | * 57 | * By deafult EvernoteOAuthWebView uses qrand() for generating nonce so do not forget to call qsrand() 58 | * in your application. See @link setNonceGenerator @endlink If you want more control over nonce generation. 59 | */ 60 | class QEVERCLOUD_EXPORT EvernoteOAuthWebView: public QWidget 61 | { 62 | Q_OBJECT 63 | public: 64 | EvernoteOAuthWebView(QWidget * parent = Q_NULLPTR); 65 | 66 | /** 67 | * This function starts the OAuth sequence. In the end of the sequence will be emitted one of the signals: authenticationSuceeded or authenticationFailed. 68 | * 69 | * Do not call the function while its call is in effect, i.e. one of the signals is not emitted. 70 | * 71 | * @param host 72 | * Evernote host to authorize with. You need one of this: 73 | *
    74 | *
  • "www.evernote.com" - the production service. It's the default value.
  • 75 | *
  • "sandox.evernote.com" - the developers "sandbox" service
  • 76 | *
77 | * @param consumerKey 78 | * get it from the Evernote 79 | * @param consumerSecret 80 | * along with this 81 | */ 82 | void authenticate(QString host, QString consumerKey, QString consumerSecret); 83 | 84 | /** @return true if the last call to authenticate resulted in a successful authentication. */ 85 | bool isSucceeded() const; 86 | 87 | /** @return error message resulted from the last call to authenticate */ 88 | QString oauthError() const; 89 | 90 | /** Holds data that is returned by Evernote on a successful authentication */ 91 | struct OAuthResult 92 | { 93 | QString noteStoreUrl; ///< note store url for the user; no need to question UserStore::getNoteStoreUrl for it. 94 | Timestamp expires; ///< authenticationToken time of expiration. 95 | QString shardId; ///< usually is not used 96 | UserID userId; ///< same as PublicUserInfo::userId 97 | QString webApiUrlPrefix; ///< see PublicUserInfo::webApiUrlPrefix 98 | QString authenticationToken; ///< This is what this all was for! 99 | }; 100 | 101 | /** @returns the result of the last authentication, i.e. authenticate() call.*/ 102 | OAuthResult oauthResult() const; 103 | 104 | /** The method is useful to specify default size for a EverOAuthWebView. */ 105 | void setSizeHint(QSize sizeHint); 106 | 107 | virtual QSize sizeHint() const Q_DECL_OVERRIDE; 108 | 109 | Q_SIGNALS: 110 | /** Emitted when the OAuth sequence started with authenticate() call is finished */ 111 | void authenticationFinished(bool success); 112 | 113 | /** Emitted when the OAuth sequence is successfully finished. Call oauthResult() to get the data.*/ 114 | void authenticationSuceeded(); 115 | 116 | /** Emitted when the OAuth sequence is finished with a failure. Some error info may be available with errorText().*/ 117 | void authenticationFailed(); 118 | 119 | private: 120 | EvernoteOAuthWebViewPrivate * const d_ptr; 121 | Q_DECLARE_PRIVATE(EvernoteOAuthWebView) 122 | }; 123 | 124 | /** @cond HIDDEN_SYMBOLS */ 125 | class EvernoteOAuthDialogPrivate; 126 | /** @endcond */ 127 | 128 | /** 129 | * @brief Authorizes your app with the Evernote service by means of OAuth authentication. 130 | * 131 | * Intended usage: 132 | * 133 | @code 134 | #include 135 | 136 | OAuthDialog d(myConsumerKey, myConsumerSecret); 137 | if(d.exec() == QDialog::Accepted) { 138 | OAuthDialog::OAuthResult res = d.oauthResult(); 139 | // Connect to Evernote 140 | ... 141 | } else { 142 | QString errorText = d.oauthError(); 143 | // handle an authentication error 144 | ... 145 | } 146 | 147 | @endcode 148 | * 149 | * %Note that you have to include QEverCloudOAuth.h header. 150 | * 151 | * By default EvernoteOAuthDialog uses qrand() for generating nonce so do not forget to call qsrand() 152 | * in your application. See @link setNonceGenerator @endlink If you want more control over nonce generation. 153 | */ 154 | 155 | class QEVERCLOUD_EXPORT EvernoteOAuthDialog: public QDialog 156 | { 157 | Q_OBJECT 158 | public: 159 | typedef EvernoteOAuthWebView::OAuthResult OAuthResult; 160 | 161 | /** Constructs the dialog. 162 | * 163 | * @param host 164 | * Evernote host to authorize with. You need one of this: 165 | *
    166 | *
  • "www.evernote.com" - the production service. It's the default value.
  • 167 | *
  • "sandox.evernote.com" - the developers "sandbox" service
  • 168 | *
169 | * @param consumerKey 170 | * get it from the Evernote 171 | * @param consumerSecret 172 | * along with this 173 | */ 174 | EvernoteOAuthDialog(QString consumerKey, QString consumerSecret, QString host = QStringLiteral("www.evernote.com"), QWidget * parent = Q_NULLPTR); 175 | ~EvernoteOAuthDialog(); 176 | 177 | /** 178 | * The dialog adjusts its initial size automatically based on the contained QWebView preffered size. 179 | * Use this method to set the size. 180 | * 181 | * @param sizeHint will be used as the preffered size of the contained QWebView. 182 | */ 183 | void setWebViewSizeHint(QSize sizeHint); 184 | 185 | /** @return true in case of a successful authentication. 186 | * You probably better chech exec() return value instead. 187 | */ 188 | bool isSucceeded() const; 189 | 190 | /** 191 | * @return In case of an authentification error may return some information about the error. 192 | */ 193 | QString oauthError() const; 194 | 195 | /** 196 | * @return the result of a successful authentication. 197 | */ 198 | OAuthResult oauthResult() const; 199 | 200 | /** 201 | * @return 202 | * QDialog::Accepted on a successful authentication. 203 | */ 204 | #if QT_VERSION < 0x050000 205 | int exec(); 206 | #else 207 | virtual int exec() Q_DECL_OVERRIDE; 208 | #endif 209 | 210 | /** Shows the dialog as a window modal dialog, returning immediately. 211 | */ 212 | #if QT_VERSION < 0x050000 213 | void open(); 214 | #else 215 | virtual void open() Q_DECL_OVERRIDE; 216 | #endif 217 | 218 | private: 219 | EvernoteOAuthDialogPrivate * const d_ptr; 220 | Q_DECLARE_PRIVATE(EvernoteOAuthDialog) 221 | }; 222 | 223 | } // namespace qevercloud 224 | 225 | #endif // QEVERCLOUD_OAUTH_H 226 | -------------------------------------------------------------------------------- /src/qevercloud/qt4helpers.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | * 8 | * This header provides the "backports" of several Qt5 macros into Qt4 9 | * so that one can use Qt5 candies with Qt4 as well 10 | */ 11 | 12 | #ifndef QEVERCLOUD_QT4_HELPERS_H 13 | #define QEVERCLOUD_QT4_HELPERS_H 14 | 15 | #include 16 | #include 17 | 18 | #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) 19 | 20 | #if __cplusplus >= 201103L 21 | 22 | #ifndef Q_DECL_OVERRIDE 23 | #define Q_DECL_OVERRIDE override 24 | #endif 25 | 26 | #ifndef Q_DECL_FINAL 27 | #define Q_DECL_FINAL final 28 | #endif 29 | 30 | #ifndef Q_STATIC_ASSERT_X 31 | #define Q_STATIC_ASSERT_X(x1,x2) static_assert(x1, x2) 32 | #endif 33 | 34 | #ifndef Q_DECL_EQ_DELETE 35 | #define Q_DECL_EQ_DELETE = delete 36 | #endif 37 | 38 | #ifndef Q_NULLPTR 39 | #define Q_NULLPTR nullptr 40 | #endif 41 | 42 | #else // __cplusplus 43 | 44 | #ifndef Q_DECL_OVERRIDE 45 | #define Q_DECL_OVERRIDE 46 | #endif 47 | 48 | #ifndef Q_DECL_FINAL 49 | #define Q_DECL_FINAL 50 | #endif 51 | 52 | #ifndef Q_STATIC_ASSERT_X 53 | #define Q_STATIC_ASSERT_X(x1,x2) 54 | #endif 55 | 56 | #ifndef Q_DECL_EQ_DELETE 57 | #define Q_DECL_EQ_DELETE 58 | #endif 59 | 60 | #ifndef Q_NULLPTR 61 | #define Q_NULLPTR NULL 62 | #endif 63 | 64 | #endif // __cplusplus 65 | 66 | #ifndef QStringLiteral 67 | #define QStringLiteral(x) QString::fromUtf8(x, sizeof(x) - 1) 68 | #endif 69 | 70 | #define QEC_SIGNAL(className, methodName, ...) SIGNAL(methodName(__VA_ARGS__)) 71 | #define QEC_SLOT(className, methodName, ...) SLOT(methodName(__VA_ARGS__)) 72 | 73 | #else // QT_VERSION 74 | 75 | // VS2010 is supposed to be C++11 but does not fulfull the entire standard. 76 | #if defined(_MSC_VER) && _MSC_VER <= 1600 // MSVC <= 2010 77 | 78 | #ifdef Q_DECL_OVERRIDE 79 | #undef Q_DECL_OVERRIDE 80 | #endif 81 | #define Q_DECL_OVERRIDE 82 | 83 | #endif // VS2010 84 | 85 | #define QEC_SIGNAL(className, methodName, ...) &className::methodName 86 | #define QEC_SLOT(className, methodName, ...) &className::methodName 87 | 88 | #endif // QT_VERSION 89 | 90 | #endif // QEVERCLOUD_QT4_HELPERS_H 91 | -------------------------------------------------------------------------------- /src/qevercloud/services_nongenerated.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace qevercloud { 14 | 15 | /** 16 | * @brief Constructs UserStore object. 17 | * @param host 18 | * www.evernote.com or sandbox.evernote.com 19 | * @param authenticationToken 20 | * This token that will be used as the default token. 21 | */ 22 | UserStore::UserStore(QString host, QString authenticationToken, QObject * parent) : 23 | QObject(parent) 24 | { 25 | QUrl url; 26 | url.setScheme(QStringLiteral("https")); 27 | url.setHost(host); 28 | url.setPath(QStringLiteral("/edam/user")); 29 | m_url = url.toString(QUrl::StripTrailingSlash); 30 | setAuthenticationToken(authenticationToken); 31 | } 32 | 33 | /** 34 | * Constructs NoteStore object. 35 | * @param noteStoreUrl 36 | * EDAM NoteStore service url. In general it's different for different users. 37 | * @param authenticationToken 38 | * This token that will be used as the default token. 39 | * 40 | */ 41 | NoteStore::NoteStore(QString noteStoreUrl, QString authenticationToken, QObject * parent) : 42 | QObject(parent) 43 | { 44 | setNoteStoreUrl(noteStoreUrl); 45 | setAuthenticationToken(authenticationToken); 46 | } 47 | 48 | /** 49 | * Constructs NoteStore object. 50 | * 51 | * noteStoreUrl and possibly authenticationToken are expected to be specified later. 52 | */ 53 | NoteStore::NoteStore(QObject * parent) : 54 | QObject(parent) 55 | {} 56 | 57 | /** @fn qevercloud::UserStore::setAuthenticationToken 58 | * Sets a value that will be used as the default token. 59 | * */ 60 | 61 | /** @fn qevercloud::UserStore::authenticationToken 62 | * @returns the default authentication token value. 63 | * */ 64 | 65 | /** @fn qevercloud::NoteStore::setAuthenticationToken 66 | * Sets a value that will be used as the default token. 67 | * */ 68 | 69 | /** @fn qevercloud::NoteStore::authenticationToken 70 | * @returns the default authentication token value. 71 | * */ 72 | 73 | /** @fn qevercloud::NoteStore::setNoteStoreUrl 74 | * Sets a value that will be used as EDAM NoteStore service url by this object. 75 | * */ 76 | 77 | /** @fn qevercloud::NoteStore::authenticationToken 78 | * @returns EDAM NoteStore service url that is used by this NoteStore object. 79 | * */ 80 | 81 | } // namespace qevercloud 82 | -------------------------------------------------------------------------------- /src/qevercloud/thumbnail.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #include 10 | #include "http.h" 11 | 12 | namespace qevercloud { 13 | 14 | class ThumbnailPrivate 15 | { 16 | public: 17 | QString m_host; 18 | QString m_shardId; 19 | QString m_authenticationToken; 20 | int m_size; 21 | Thumbnail::ImageType::type m_imageType; 22 | }; 23 | 24 | Thumbnail::Thumbnail(): 25 | d_ptr(new ThumbnailPrivate) 26 | { 27 | d_ptr->m_size = 300; 28 | d_ptr->m_imageType = ImageType::PNG; 29 | } 30 | 31 | 32 | Thumbnail::Thumbnail(QString host, QString shardId, QString authenticationToken, 33 | int size, Thumbnail::ImageType::type imageType) : 34 | d_ptr(new ThumbnailPrivate) 35 | { 36 | d_ptr->m_host = host; 37 | d_ptr->m_shardId = shardId; 38 | d_ptr->m_authenticationToken = authenticationToken; 39 | d_ptr->m_size = size; 40 | d_ptr->m_imageType = imageType; 41 | } 42 | 43 | Thumbnail::~Thumbnail() 44 | { 45 | delete d_ptr; 46 | } 47 | 48 | Thumbnail & Thumbnail::setHost(QString host) 49 | { 50 | d_ptr->m_host = host; 51 | return *this; 52 | } 53 | 54 | Thumbnail & Thumbnail::setShardId(QString shardId) 55 | { 56 | d_ptr->m_shardId = shardId; 57 | return *this; 58 | } 59 | 60 | Thumbnail & Thumbnail::setAuthenticationToken(QString authenticationToken) 61 | { 62 | d_ptr->m_authenticationToken = authenticationToken; 63 | return *this; 64 | } 65 | 66 | Thumbnail & Thumbnail::setSize(int size) 67 | { 68 | d_ptr->m_size = size; 69 | return *this; 70 | } 71 | 72 | Thumbnail & Thumbnail::setImageType(ImageType::type imageType) 73 | { 74 | d_ptr->m_imageType = imageType; 75 | return *this; 76 | } 77 | 78 | QByteArray Thumbnail::download(Guid guid, bool isPublic, bool isResourceGuid) 79 | { 80 | int httpStatusCode = 0; 81 | QPair request = createPostRequest(guid, isPublic, isResourceGuid); 82 | 83 | QByteArray reply = simpleDownload(evernoteNetworkAccessManager(), request.first, 84 | request.second, &httpStatusCode); 85 | if (httpStatusCode != 200) { 86 | throw EverCloudException(QStringLiteral("HTTP Status Code = %1").arg(httpStatusCode)); 87 | } 88 | 89 | return reply; 90 | } 91 | 92 | AsyncResult* Thumbnail::downloadAsync(Guid guid, bool isPublic, bool isResourceGuid) 93 | { 94 | QPair pair = createPostRequest(guid, isPublic, isResourceGuid); 95 | return new AsyncResult(pair.first, pair.second); 96 | } 97 | 98 | QPair Thumbnail::createPostRequest(Guid guid, bool isPublic, bool isResourceGuid) 99 | { 100 | Q_D(Thumbnail); 101 | 102 | QByteArray postData = ""; // not QByteArray()! or else ReplyFetcher will not work. 103 | QNetworkRequest request; 104 | 105 | QString urlPattern; 106 | if(isResourceGuid) { 107 | urlPattern = QStringLiteral("https://%1/shard/%2/thm/res/%3"); 108 | } 109 | else { 110 | urlPattern = QStringLiteral("https://%1/shard/%2/thm/note/%3"); 111 | } 112 | 113 | QString url = urlPattern.arg(d->m_host, d->m_shardId, guid); 114 | 115 | QString ext; 116 | switch(d->m_imageType) 117 | { 118 | case ImageType::BMP: 119 | ext = QStringLiteral(".bmp"); 120 | break; 121 | case ImageType::GIF: 122 | ext = QStringLiteral(".gif"); 123 | break; 124 | case ImageType::JPEG: 125 | ext = QStringLiteral(".jpg"); 126 | break; 127 | default: 128 | ext = QStringLiteral(".png"); 129 | break; 130 | } 131 | url += ext; 132 | 133 | if (d->m_size != 300) { 134 | url += QStringLiteral("?size=%1").arg(d->m_size); 135 | } 136 | 137 | request.setUrl(QUrl(url)); 138 | request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded")); 139 | 140 | if (!isPublic) { 141 | postData = QByteArray("auth=")+ QUrl::toPercentEncoding(d->m_authenticationToken); 142 | } 143 | 144 | return qMakePair(request, postData); 145 | } 146 | 147 | } // namespace qevercloud 148 | -------------------------------------------------------------------------------- /src/qevercloud/thumbnail.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Original work: Copyright (c) 2014 Sergey Skoblikov 3 | * Modified work: Copyright (c) 2015-2016 Dmitry Ivanov 4 | * 5 | * This file is a part of QEverCloud project and is distributed under the terms of MIT license: 6 | * https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #ifndef QEVERCLOUD_THUMBNAIL_H 10 | #define QEVERCLOUD_THUMBNAIL_H 11 | 12 | #include "export.h" 13 | #include "AsyncResult.h" 14 | #include "generated/types.h" 15 | #include 16 | #include 17 | #include 18 | 19 | namespace qevercloud { 20 | 21 | /** @cond HIDDEN_SYMBOLS */ 22 | class ThumbnailPrivate; 23 | /** @endcond */ 24 | 25 | /** 26 | * @brief The class is for downloading thumbnails for notes and resources from Evernote servers. 27 | * 28 | * These thumbnails are not available with general EDAM Thrift interface as explained in the 29 | * documentation. 30 | * 31 | * Usage: 32 | @code 33 | 34 | Thumbnail thumb("www.evernote.com", sharId, authenticationToken); 35 | QByteArray pngImage = thumb.download(noteGuid); 36 | 37 | @endcode 38 | * 39 | * By defualt 300x300 PNG images are requested. 40 | */ 41 | class QEVERCLOUD_EXPORT Thumbnail 42 | { 43 | public: 44 | /** 45 | * Specifies image type of the returned thumbnail. 46 | * 47 | * Can be PNG, JPEG, GIF or BMP. 48 | */ 49 | struct ImageType { 50 | enum type {PNG, JPEG, GIF, BMP}; 51 | }; 52 | 53 | /** 54 | * @brief Default constructor. 55 | * 56 | * host, shardId, authenticationToken have to be specified before calling 57 | * @link download @endlink or @link createPostRequest @endlink 58 | */ 59 | Thumbnail(); 60 | 61 | /** 62 | * @brief Constructs Thumbnail. 63 | * @param host 64 | * www.evernote.com or sandbox.evernote.com 65 | * @param shardId 66 | * You can get the value from UserStore service or as a result of an authentication. 67 | * @param authenticationToken 68 | * For working private notes/resources you must supply a valid authentication token. 69 | * For public resources the value specified is not used. 70 | * @param size 71 | * The size of the thumbnail. Evernote supports values from from 1 to 300. By defualt 300 is used. 72 | * @param imageType 73 | * Thumbnail image type. See ImageType. By default PNG is used. 74 | */ 75 | Thumbnail(QString host, QString shardId, QString authenticationToken, 76 | int size = 300, ImageType::type imageType = ImageType::PNG); 77 | 78 | virtual ~Thumbnail(); 79 | 80 | /** 81 | * @param host 82 | * www.evernote.com or sandbox.evernote.com 83 | */ 84 | Thumbnail & setHost(QString host); 85 | 86 | /** 87 | * @param shardId 88 | * You can get the value from UserStore service or as a result of an authentication. 89 | */ 90 | Thumbnail & setShardId(QString shardId); 91 | 92 | /** 93 | * @param authenticationToken 94 | * For working private notes/resources you must supply a valid authentication token. 95 | * For public resources the value specified is not used. 96 | */ 97 | Thumbnail & setAuthenticationToken(QString authenticationToken); 98 | 99 | /** 100 | * @param size 101 | * The size of the thumbnail. Evernote supports values from from 1 to 300. By defualt 300 is used. 102 | */ 103 | Thumbnail & setSize(int size); 104 | 105 | /** 106 | * @param imageType 107 | * Thumbnail image type. See ImageType. By default PNG is used. 108 | */ 109 | Thumbnail & setImageType(ImageType::type imageType); 110 | 111 | /** 112 | * @brief Downloads the thumbnail for a resource or a note. 113 | * @param guid 114 | * The note or resource guid 115 | * @param isPublic 116 | * Specify true for public notes/resources. In this case authentication token is not sent to 117 | * with the request as it shoud be according to the docs. 118 | * @param isResourceGuid 119 | * true if guid denotes a resource and false if it denotes a note. 120 | * @return downloaded data. 121 | * 122 | */ 123 | QByteArray download(Guid guid, bool isPublic = false, bool isResourceGuid = false); 124 | 125 | /** Asynchronous version of @link download @endlink function*/ 126 | AsyncResult * downloadAsync(Guid guid, bool isPublic = false, bool isResourceGuid = false); 127 | 128 | /** 129 | * @brief Prepares a POST request for a thumbnail download. 130 | * @param guid 131 | * The note or resource guid 132 | * @param isPublic 133 | * Specify true for public notes/resources. In this case authentication token is not sent to 134 | * with the request as it shoud be according to the docs. 135 | * @param isResourceGuid 136 | * true if guid denotes a resource and false if it denotes a note. 137 | * @return a pair of QNetworkRequest for the POST request and data that must be posted with the request. 138 | */ 139 | QPair createPostRequest(qevercloud::Guid guid, 140 | bool isPublic = false, 141 | bool isResourceGuid = false); 142 | 143 | private: 144 | ThumbnailPrivate * const d_ptr; 145 | Q_DECLARE_PRIVATE(Thumbnail) 146 | }; 147 | 148 | } // namespace qevercloud 149 | 150 | #endif // QEVERCLOUD_THUMBNAIL_H 151 | -------------------------------------------------------------------------------- /src/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | icon/appicon.ico 4 | icon/trayicon_black.ico 5 | icon/trayicon_white.ico 6 | icon/sizegripicon.svg 7 | style/about_stylesheet.qss 8 | style/note_stylesheet.qss 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/settings.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #include "settings.h" 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | 25 | static QMap defaultUserSettings = 26 | { 27 | {"sync_interval", 300}, 28 | {"check_for_updates", true}, 29 | {"dark_status_icon", false} 30 | }; 31 | 32 | QString Settings::getSessionSettingsFileLocation() 33 | { 34 | return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation).append("/session_settings.ini"); 35 | } 36 | 37 | QString Settings::getUserSettingsFileLocation() 38 | { 39 | return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation).append("/user_settings.ini"); 40 | } 41 | 42 | void Settings::deleteAllSessionSettings() 43 | { 44 | QFile(getSessionSettingsFileLocation()).remove(); 45 | } 46 | 47 | void Settings::deleteCurrentSessionSettings() 48 | { 49 | // Delete all session settings except for the session username. 50 | // This allows us to avoid deleting all unsynced notes if the same user is logging in again (i.e. after having to login again 51 | // after Evernote downtime, or auth key expiring). 52 | QString currentUsername = getSessionSetting("username"); 53 | deleteAllSessionSettings(); 54 | setSessionSetting("username", currentUsername); 55 | } 56 | 57 | QString Settings::getSessionSetting(QString settingKey) 58 | { 59 | QSettings settings(getSessionSettingsFileLocation(), QSettings::NativeFormat); 60 | return settings.value(settingKey).toString(); 61 | } 62 | 63 | void Settings::setSessionSetting(QString settingKey, QString settingValue) 64 | { 65 | QSettings settings(getSessionSettingsFileLocation(), QSettings::NativeFormat); 66 | settings.setValue(settingKey, settingValue); 67 | } 68 | 69 | QVariant Settings::getUserSetting(QString settingKey) 70 | { 71 | QSettings settings(getUserSettingsFileLocation(), QSettings::NativeFormat); 72 | 73 | if(settings.contains(settingKey)) { 74 | return settings.value(settingKey); 75 | } else { 76 | // If settings value doesn't exist, return default value. 77 | return defaultUserSettings.value(settingKey); 78 | } 79 | } 80 | 81 | void Settings::setUserSetting(QString settingKey, QVariant settingValue) 82 | { 83 | QSettings settings(getUserSettingsFileLocation(), QSettings::NativeFormat); 84 | settings.setValue(settingKey, settingValue); 85 | } 86 | -------------------------------------------------------------------------------- /src/settings.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #ifndef SETTINGS_H 19 | #define SETTINGS_H 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | 26 | class Settings 27 | { 28 | public: 29 | static void deleteAllSessionSettings(); 30 | static void deleteCurrentSessionSettings(); 31 | static QString getSessionSetting(QString settingKey); 32 | static void setSessionSetting(QString settingKey, QString settingValue); 33 | 34 | static QVariant getUserSetting(QString settingKey); 35 | static void setUserSetting(QString settingKey, QVariant settingValue); 36 | 37 | private: 38 | static QString getSessionSettingsFileLocation(); 39 | static QString getUserSettingsFileLocation(); 40 | }; 41 | 42 | #endif // SETTINGS_H 43 | -------------------------------------------------------------------------------- /src/style/about_stylesheet.qss: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the eversticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | 19 | /* 20 | * Widget styles 21 | */ 22 | 23 | #AboutWindow QLabel 24 | { 25 | qproperty-alignment: AlignCenter; 26 | qproperty-wordWrap: true; 27 | } 28 | -------------------------------------------------------------------------------- /src/style/note_stylesheet.qss: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the eversticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | 19 | /* 20 | * Main widgets 21 | */ 22 | 23 | #NoteTitleBar 24 | { 25 | /* Note titlebar darker colour */ 26 | background-color: #fedd51; 27 | } 28 | 29 | #NoteContainer 30 | { 31 | border: 1px solid grey; 32 | } 33 | 34 | #NoteContainer, 35 | QScrollBar 36 | { 37 | /* Main note background colour */ 38 | background-color: #ffec8e; 39 | } 40 | 41 | #NoteContainer QSizeGrip 42 | { 43 | image: url(:/icon/sizegripicon.svg); 44 | height: 24px; 45 | width: 24px; 46 | } 47 | 48 | 49 | /* 50 | * Widget styles 51 | */ 52 | 53 | #NoteHeader #NoteHeaderTextEdit, 54 | #NoteScrollArea, 55 | #NoteScrollAreaContainer, 56 | #NoteTitleBar QToolButton 57 | { 58 | /* Some widgets should have transparent background */ 59 | background: transparent; 60 | } 61 | 62 | #NoteTitleBar QToolButton 63 | { 64 | border: transparent; 65 | padding: 5px; 66 | color:#000000; 67 | } 68 | 69 | #NoteTitleBar QToolButton:hover 70 | { 71 | border: 1px solid grey; 72 | border-radius: 4px; 73 | } 74 | 75 | #NoteHeader #NoteHeaderTextEdit 76 | { 77 | border: 0px; 78 | color: #000000; 79 | font-size: 11pt; 80 | font-weight: bold; 81 | } 82 | 83 | #NoteHeader #NoteHeaderLine 84 | { 85 | margin-right: 100px; 86 | } 87 | 88 | #NoteScrollArea 89 | { 90 | border-style: none; 91 | } 92 | 93 | 94 | /* 95 | * Global scrollbar style 96 | */ 97 | 98 | QScrollBar:vertical 99 | { 100 | width:18px; 101 | padding: 5px 5px 5px 5px; 102 | } 103 | 104 | QScrollBar::handle:vertical 105 | { 106 | background: grey; 107 | border-radius: 4px; 108 | } 109 | 110 | QScrollBar::add-line:vertical 111 | { 112 | height: 0px; 113 | } 114 | 115 | QScrollBar::sub-line:vertical 116 | { 117 | height: 0px; 118 | } 119 | -------------------------------------------------------------------------------- /src/ui/about_widget.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #include "about_widget.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | 26 | AboutWidget::AboutWidget() : QDialog() 27 | { 28 | setObjectName("AboutWindow"); 29 | setWindowTitle("EverSticky | About"); 30 | 31 | QIcon icon(":/icon/appicon.ico"); 32 | this->setWindowIcon(icon); 33 | 34 | // Read and apply note stylesheet 35 | QFile File(":style/about_stylesheet.qss"); 36 | File.open(QFile::ReadOnly); 37 | QString styleSheet = QLatin1String(File.readAll()); 38 | setStyleSheet(styleSheet); 39 | 40 | 41 | // Parent layout 42 | QVBoxLayout *parentLayout = new QVBoxLayout(this); 43 | parentLayout->setAlignment(Qt::AlignCenter); 44 | parentLayout->setContentsMargins(50,50,50,50); 45 | parentLayout->setSpacing(25); 46 | 47 | QPixmap appLogoPixmap = icon.pixmap(128); 48 | 49 | QLabel *appLogo = new QLabel(this); 50 | appLogo->setPixmap(appLogoPixmap); 51 | QLabel *appTitle = new QLabel("

EverSticky

", this); 52 | QLabel *appVersion = new QLabel("

Version " + QString::fromStdString(APP_VERSION) + "

"); 53 | QLabel *appSubtitle = new QLabel("This project is open source and contributions are welcomed."); 54 | QLabel *appDescription = new QLabel("Visit https://eversticky.joeeey.com for more information or to report a bug or to suggest a new feature."); 55 | appDescription->setOpenExternalLinks(true); 56 | QLabel *appCopyright = new QLabel("Copyright © 2021 Joey Miller. See 'LICENSE' included with application source for license terms."); 57 | 58 | // Add all widgets to the dialog 59 | parentLayout->addWidget(appLogo); 60 | parentLayout->addWidget(appTitle); 61 | parentLayout->addWidget(appVersion); 62 | parentLayout->addWidget(appSubtitle); 63 | parentLayout->addWidget(appDescription); 64 | parentLayout->addWidget(appCopyright); 65 | 66 | show(); 67 | } 68 | -------------------------------------------------------------------------------- /src/ui/about_widget.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #ifndef ABOUTWIDGET_H 19 | #define ABOUTWIDGET_H 20 | 21 | #include 22 | 23 | 24 | class AboutWidget : public QDialog 25 | { 26 | Q_OBJECT 27 | 28 | public: 29 | AboutWidget(); 30 | }; 31 | 32 | #endif // ABOUTWIDGET_H 33 | -------------------------------------------------------------------------------- /src/ui/note_header.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #include "note_header.h" 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "note_header_text_edit.h" 28 | #include "note_widget.h" 29 | #include "webview/note_webview.h" 30 | 31 | 32 | NoteHeader::NoteHeader(QWidget *context) : QWidget(context) 33 | { 34 | setObjectName("NoteHeader"); 35 | 36 | setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); 37 | 38 | QVBoxLayout *layout = new QVBoxLayout(); 39 | setLayout(layout); 40 | 41 | // Create text box for note title 42 | headerTextEdit = new NoteHeaderTextEdit(this); 43 | connect(headerTextEdit, &NoteHeaderTextEdit::textUpdated, this, &NoteHeader::textUpdated); 44 | layout->addWidget(headerTextEdit); 45 | 46 | // Create line to underscore note title 47 | QFrame *line = new QFrame(this); 48 | line->setObjectName("NoteHeaderLine"); 49 | line->setFrameShape(QFrame::HLine); 50 | layout->addWidget(line); 51 | } 52 | 53 | void NoteHeader::clearText() 54 | { 55 | headerTextEdit->clear(); 56 | } 57 | 58 | void NoteHeader::setText(QString text) 59 | { 60 | headerTextEdit->setText(text); 61 | } 62 | -------------------------------------------------------------------------------- /src/ui/note_header.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #ifndef NOTE_HEADER_H 19 | #define NOTE_HEADER_H 20 | 21 | #include 22 | #include 23 | 24 | 25 | class NoteWebview; 26 | class NoteHeaderTextEdit; 27 | class NoteWidget; 28 | 29 | class NoteHeader : public QWidget 30 | { 31 | Q_OBJECT 32 | 33 | public: 34 | NoteHeader(QWidget *context); 35 | 36 | void setText(QString text); 37 | void clearText(); 38 | QString getText(); 39 | 40 | signals: 41 | void textUpdated(QString newText); 42 | 43 | private: 44 | NoteHeaderTextEdit *headerTextEdit; 45 | }; 46 | 47 | #endif // NOTE_HEADER_H 48 | -------------------------------------------------------------------------------- /src/ui/note_header_text_edit.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #include "note_header_text_edit.h" 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | 27 | NoteHeaderTextEdit::NoteHeaderTextEdit(QWidget *parent) : QPlainTextEdit(parent) 28 | { 29 | setObjectName("NoteHeaderTextEdit"); 30 | 31 | setFixedHeight(50); 32 | setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 33 | setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 34 | 35 | connect(this, &QPlainTextEdit::textChanged, this, &NoteHeaderTextEdit::textChanged); 36 | } 37 | 38 | void NoteHeaderTextEdit::contextMenuEvent(QContextMenuEvent *event) 39 | { 40 | QMenu *menu = createStandardContextMenu(); 41 | 42 | QAction *lastSeparator; 43 | foreach (QAction *action, menu->actions()) { 44 | // Remove 'shortcut' text from action to match context menu of note body 45 | QString actionText = action->text(); 46 | action->setText(actionText.split("\t").at(0)); 47 | 48 | action->setIcon(QIcon()); 49 | 50 | if(action->isSeparator()) { 51 | lastSeparator = action; 52 | } 53 | } 54 | // Remove the last separator (between 'Delete' and 'Select All') to match context menu of note body 55 | menu->removeAction(lastSeparator); 56 | 57 | menu->exec(event->globalPos()); 58 | delete menu; 59 | } 60 | 61 | void NoteHeaderTextEdit::textChanged() 62 | { 63 | resizeToFitContent(); 64 | 65 | emit textUpdated(getText()); 66 | } 67 | 68 | void NoteHeaderTextEdit::resizeToFitContent() 69 | { 70 | // Calculate and resize to new height of header text 71 | const QTextBlock block = document()->begin(); 72 | const int height = document()->documentLayout()->blockBoundingRect(block).height(); 73 | setFixedHeight(height + 15); 74 | updateGeometry(); 75 | } 76 | 77 | void NoteHeaderTextEdit::keyPressEvent(QKeyEvent *e) 78 | { 79 | if(e->key() == Qt::Key_Return) { 80 | return; 81 | } 82 | QPlainTextEdit::keyPressEvent(e); 83 | } 84 | 85 | void NoteHeaderTextEdit::resizeEvent(QResizeEvent *event) 86 | { 87 | QPlainTextEdit::resizeEvent(event); 88 | resizeToFitContent(); 89 | } 90 | 91 | void NoteHeaderTextEdit::setText(QString text) 92 | { 93 | setPlainText(text); 94 | resizeToFitContent(); 95 | } 96 | 97 | QString NoteHeaderTextEdit::getText() 98 | { 99 | return toPlainText(); 100 | } 101 | -------------------------------------------------------------------------------- /src/ui/note_header_text_edit.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #ifndef NOTE_HEADER_TEXT_EDIT_H 19 | #define NOTE_HEADER_TEXT_EDIT_H 20 | 21 | #include 22 | #include 23 | 24 | #include "note_header.h" 25 | 26 | 27 | class NoteHeaderTextEdit : public QPlainTextEdit 28 | { 29 | Q_OBJECT 30 | 31 | public: 32 | NoteHeaderTextEdit(QWidget *parent); 33 | 34 | void setText(QString text); 35 | 36 | signals: 37 | void textUpdated(QString newText); 38 | 39 | private slots: 40 | void textChanged(); 41 | 42 | private: 43 | QString getText(); 44 | 45 | void resizeEvent(QResizeEvent *event) override; 46 | void resizeToFitContent(); 47 | 48 | protected: 49 | void contextMenuEvent(QContextMenuEvent *event); 50 | bool eventFilter(QObject *object, QKeyEvent *event); 51 | void keyPressEvent(QKeyEvent *e) override; 52 | }; 53 | 54 | #endif // NOTE_HEADER_TEXT_EDIT_H 55 | -------------------------------------------------------------------------------- /src/ui/note_scroll_area.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #include "note_scroll_area.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "ui/note_header.h" 26 | #include "webview/note_webview.h" 27 | 28 | 29 | NoteScrollArea::NoteScrollArea(QWidget *context) : QScrollArea(context) 30 | { 31 | setObjectName("NoteScrollArea"); 32 | 33 | setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); 34 | setWidgetResizable(true); 35 | 36 | QWidget *scrollContainer = new QWidget(this); 37 | scrollContainer->setObjectName("NoteScrollAreaContainer"); 38 | dockLayout = new QVBoxLayout(scrollContainer); 39 | dockLayout->setSpacing(0); 40 | dockLayout->setContentsMargins(QMargins()); 41 | dockLayout->setAlignment(Qt::AlignTop); 42 | 43 | scrollContainer->setLayout(dockLayout); 44 | setWidget(scrollContainer); 45 | } 46 | 47 | void NoteScrollArea::addWidget(QWidget *widget) 48 | { 49 | dockLayout->addWidget(widget); 50 | } 51 | 52 | void NoteScrollArea::scrollNote(int dx, int dy) 53 | { 54 | const int x = horizontalScrollBar()->value() - dx; 55 | horizontalScrollBar()->setValue( x ); 56 | 57 | const int y = verticalScrollBar()->value() - dy; 58 | verticalScrollBar()->setValue( y ); 59 | } 60 | 61 | void NoteScrollArea::wheelEvent(QWheelEvent *event) 62 | { 63 | QPoint numPixels = event->pixelDelta(); 64 | QPoint numDegrees = event->angleDelta(); 65 | 66 | if (!numPixels.isNull()) { 67 | scrollNote(numPixels.rx(), numPixels.ry()); 68 | } else if (!numDegrees.isNull()) { 69 | // May need changing. I left this because I noticed numPixels and numDegrees 70 | // were identical on my system. 71 | scrollNote(numDegrees.rx(), numDegrees.ry()); 72 | } 73 | 74 | event->accept(); 75 | } 76 | -------------------------------------------------------------------------------- /src/ui/note_scroll_area.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #ifndef NOTESCROLLAREA_H 19 | #define NOTESCROLLAREA_H 20 | 21 | #include 22 | #include 23 | 24 | 25 | class NoteWebview; 26 | class NoteHeader; 27 | class NoteWidget; 28 | 29 | class NoteScrollArea : public QScrollArea 30 | { 31 | Q_OBJECT 32 | 33 | public: 34 | NoteScrollArea(QWidget *context); 35 | 36 | void addWidget(QWidget *widget); 37 | 38 | signals: 39 | void noteScrollChanged(int x, int y); 40 | 41 | private: 42 | QVBoxLayout *dockLayout; 43 | void scrollNote(int dx, int dy); 44 | void wheelEvent(QWheelEvent *event); 45 | }; 46 | 47 | #endif // NOTESCROLLAREA_H 48 | -------------------------------------------------------------------------------- /src/ui/note_title_bar.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #include "note_title_bar.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | 29 | NoteTitleBar::NoteTitleBar(QWidget* parent) : QWidget(parent) 30 | { 31 | setObjectName("NoteTitleBar"); 32 | 33 | mousePressed = false; 34 | mouseCursorChanged = false; 35 | 36 | // Needed to ensure background colour is drawn 37 | // https://stackoverflow.com/a/23113643/3213602 38 | setAttribute(Qt::WidgetAttribute::WA_StyledBackground); 39 | 40 | setFixedHeight(40); 41 | 42 | QHBoxLayout *layout = new QHBoxLayout(); 43 | 44 | QToolButton *addAction = new QToolButton(this); 45 | addAction->setFocusPolicy(Qt::NoFocus); 46 | addAction->setText("+"); 47 | QToolButton *deleteAction = new QToolButton(this); 48 | deleteAction->setFocusPolicy(Qt::NoFocus); 49 | deleteAction->setText("x"); 50 | connect(addAction, &QToolButton::clicked, this, &NoteTitleBar::addPressed); 51 | connect(deleteAction, &QToolButton::clicked, this, &NoteTitleBar::deletePressed); 52 | 53 | layout->addWidget(addAction); 54 | layout->addStretch(); 55 | layout->addWidget(deleteAction); 56 | 57 | this->setLayout(layout); 58 | } 59 | 60 | void NoteTitleBar::mousePressEvent(QMouseEvent* event) 61 | { 62 | if(event->button() == Qt::LeftButton) { 63 | // Set 'drag' cursor 64 | QApplication::setOverrideCursor(QCursor(Qt::SizeAllCursor)); 65 | 66 | mousePressPosition = event->pos(); 67 | mousePressed = true; 68 | } 69 | } 70 | 71 | void NoteTitleBar::mouseMoveEvent(QMouseEvent* event) 72 | { 73 | if(!mousePressed) { 74 | return; 75 | } 76 | 77 | window()->move(event->globalX()-mousePressPosition.x(), event->globalY()-mousePressPosition.y()); 78 | } 79 | 80 | void NoteTitleBar::mouseReleaseEvent(QMouseEvent* event) 81 | { 82 | // Return cursor to normal cursor (after being changed when window began 83 | // moving in NoteTitleBar::mousePressEvent()) 84 | QApplication::restoreOverrideCursor(); 85 | 86 | mousePressed = false; 87 | } 88 | -------------------------------------------------------------------------------- /src/ui/note_title_bar.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #ifndef NOTETITLEBAR_H 19 | #define NOTETITLEBAR_H 20 | 21 | #include 22 | #include 23 | 24 | 25 | class NoteTitleBar: public QWidget 26 | { 27 | Q_OBJECT 28 | 29 | public: 30 | NoteTitleBar(QWidget* parent); 31 | 32 | signals: 33 | void addPressed(); 34 | void deletePressed(); 35 | 36 | protected: 37 | void mouseMoveEvent(QMouseEvent* event); 38 | void mousePressEvent(QMouseEvent* event); 39 | void mouseReleaseEvent(QMouseEvent* event); 40 | 41 | private: 42 | bool mousePressed; 43 | QPoint mousePressPosition; 44 | bool mouseCursorChanged; 45 | }; 46 | 47 | #endif // NOTETITLEBAR_H 48 | -------------------------------------------------------------------------------- /src/ui/note_widget.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #include "note_widget.h" 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | #include "note_controller.h" 37 | #include "note_formatter.h" 38 | #include "ui/note_header.h" 39 | #include "ui/note_scroll_area.h" 40 | #include "ui/note_title_bar.h" 41 | 42 | 43 | NoteWidget::NoteWidget(NoteController *parent, Note* note, noteItem size) : parent(parent), note(note), initialNoteState(size), QMainWindow() 44 | { 45 | setObjectName("NoteWindow"); 46 | setWindowTitle("EverSticky | " + note->title); 47 | 48 | QIcon icon(":/icon/appicon.ico"); 49 | this->setWindowIcon(icon); 50 | 51 | setAttribute(Qt::WA_DeleteOnClose); 52 | setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); 53 | // Disable window border 54 | setWindowFlags(Qt::FramelessWindowHint); 55 | 56 | // Read and apply note stylesheet 57 | QFile File(":style/note_stylesheet.qss"); 58 | File.open(QFile::ReadOnly); 59 | QString styleSheet = QLatin1String(File.readAll()); 60 | setStyleSheet(styleSheet); 61 | 62 | QFrame *noteContainer = new QFrame(this); 63 | noteContainer->setObjectName("NoteContainer"); 64 | noteContainer->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); 65 | noteContainer->setLineWidth(4); 66 | 67 | QVBoxLayout *parentLayout = new QVBoxLayout(noteContainer); 68 | parentLayout->setSpacing(0); 69 | parentLayout->setContentsMargins(QMargins()); 70 | 71 | // Main sticky note toolbar 72 | NoteTitleBar* headerBar = new NoteTitleBar(noteContainer); 73 | connect(headerBar, &NoteTitleBar::addPressed, this, &NoteWidget::createNote); 74 | connect(headerBar, &NoteTitleBar::deletePressed, this, &NoteWidget::deleteNote); 75 | parentLayout->setMenuBar(headerBar); 76 | 77 | /* 78 | * Main note header and content (webview) 79 | */ 80 | int notePadding = 8; 81 | noteScrollArea = new NoteScrollArea(noteContainer); 82 | 83 | noteHeader = new NoteHeader(noteScrollArea); 84 | noteHeader->setContentsMargins(notePadding, notePadding, notePadding, 0); 85 | noteScrollArea->addWidget(noteHeader); 86 | 87 | noteWebview = new NoteWebview(noteScrollArea); 88 | noteWebview->setContentsMargins(notePadding, notePadding, notePadding, 0); 89 | noteScrollArea->addWidget(noteWebview); 90 | 91 | parentLayout->addWidget(noteScrollArea); 92 | 93 | 94 | // These signals need to be connected AFTER title and content have been first set (after `updateUI()`) 95 | connect(noteHeader, &NoteHeader::textUpdated, this, &NoteWidget::titleTextUpdated); 96 | connect(noteWebview, &NoteWebview::ensureWebviewCaretVisible, this, &NoteWidget::scrollToWebviewCaret); 97 | connect(noteWebview, &NoteWebview::initialNoteLoad, this, &NoteWidget::updateUI); 98 | connect(noteWebview, &NoteWebview::textUpdated, this, &NoteWidget::contentTextChanged); 99 | 100 | 101 | // Add resize grips to the bottom corners of the note 102 | QHBoxLayout *gripLayout = new QHBoxLayout(); 103 | gripLayout->addWidget(new QSizeGrip(noteContainer), 0, Qt::AlignBottom | Qt::AlignLeft); 104 | gripLayout->addWidget(new QSizeGrip(noteContainer), 0, Qt::AlignBottom | Qt::AlignRight); 105 | parentLayout->addLayout(gripLayout); 106 | 107 | noteContainer->setLayout(parentLayout); 108 | 109 | setCentralWidget(noteContainer); 110 | 111 | // Set window to previously cached size if possible 112 | setNoteSize(); 113 | 114 | show(); 115 | hideFromTaskbar(); 116 | }; 117 | 118 | void NoteWidget::hideFromTaskbar() 119 | { 120 | // Needed to ensure the window can be found and have its state altered 121 | QThread::msleep(20); 122 | 123 | // TODO: Look into using window groups 124 | // http://manpages.org/x11protocolwm/3 (search for "window_group" and have a read) 125 | 126 | /* 127 | * Hide the window from the taskbar 128 | */ 129 | Display *display = QX11Info::display(); 130 | int screen = DefaultScreen(display); 131 | Atom type = ((Atom) 4); 132 | Atom property = XInternAtom(display, "_NET_WM_STATE", False); 133 | Atom state = XInternAtom(display, "_NET_WM_STATE_SKIP_TASKBAR", False); 134 | 135 | WId window = this->winId(); 136 | 137 | XChangeProperty(display, window, property, type, 32, PropModeAppend, (unsigned char*)&state, 1); 138 | 139 | // Tell the XServer that the state of window has been updated 140 | // https://mailman.linuxchix.org/pipermail/techtalk/2003-May/015402.html 141 | XEvent ev; 142 | ev.xclient.type = ClientMessage; 143 | ev.xclient.message_type = property; 144 | ev.xclient.window = window; 145 | ev.xclient.format = 32; 146 | ev.xclient.data.l[0] = 1; 147 | ev.xclient.data.l[1] = state; 148 | ev.xclient.data.l[2] = 0; 149 | 150 | XSendEvent(display, QX11Info::appRootWindow(), False, 151 | SubstructureRedirectMask | SubstructureNotifyMask, &ev); 152 | } 153 | 154 | void NoteWidget::deleteNote() 155 | { 156 | QMessageBox confirmationBox; 157 | confirmationBox.setWindowTitle("Confirm delete"); 158 | confirmationBox.setText("Are you sure you want to delete this note?"); 159 | confirmationBox.setStandardButtons(QMessageBox::Yes); 160 | confirmationBox.addButton(QMessageBox::No); 161 | confirmationBox.setDefaultButton(QMessageBox::No); 162 | 163 | // Only delete note and close window if user confirms 'YES' 164 | if(confirmationBox.exec() == QMessageBox::Yes) { 165 | this->close(); 166 | Cache::deleteQueueTable(*note); 167 | Cache::deleteFromNotesTable(note->guid); 168 | } 169 | } 170 | 171 | void NoteWidget::createNote() 172 | { 173 | parent->createNote(); 174 | } 175 | 176 | void NoteWidget::updateUI() 177 | { 178 | noteHeader->setText(this->note->title); 179 | noteWebview->setText(NoteFormatter(this->note->content).convertFromEML()); 180 | } 181 | 182 | void NoteWidget::scrollToWebviewCaret(int caretY) 183 | { 184 | // Ensure scroll area has webview caret visible (typically after use starts typing 185 | // on a new line). 186 | noteScrollArea->ensureVisible(0, noteHeader->height() + caretY); 187 | } 188 | 189 | void NoteWidget::titleTextUpdated(QString newText) 190 | { 191 | // If note title has changed, add it to the queue 192 | if(note->title != newText) 193 | { 194 | note->title = newText; 195 | if(note->changed == false) { 196 | note->changed = true; 197 | } 198 | 199 | Cache::insertQueueTable(*note); 200 | } 201 | } 202 | 203 | void NoteWidget::contentTextChanged(QString newText) 204 | { 205 | // Convert back to ENML format 206 | newText = NoteFormatter(newText).convertToEML(); 207 | 208 | std::optional syncedNote = Cache::retrieveFromSyncTable(this->note->guid); 209 | // If note content has changed, add it to the queue 210 | if(syncedNote->content != newText) { 211 | note->changed = true; 212 | note->content = newText; 213 | Cache::insertQueueTable(*note); 214 | } else { 215 | note->changed = false; 216 | note->content = newText; 217 | Cache::removeGuidFromQueueTable(this->note->guid); 218 | } 219 | } 220 | 221 | void NoteWidget::syncModel() 222 | { 223 | std::optional updatedNote = Cache::retrieveFromSyncTable(this->note->guid); 224 | std::optional queuedNote = Cache::retrieveFromQueueTable(this->note->guid); 225 | 226 | // If note doesn't exist in sync table (all fields are NULL), note has been deleted. 227 | if(!queuedNote && !updatedNote) { 228 | // Close the window 229 | this->close(); 230 | return; 231 | } else if(queuedNote) { 232 | if((*queuedNote).type == "DELETE") { 233 | this->close(); 234 | return; 235 | } 236 | // If note exists in the queue table (and isn't deleted), show the changed content. 237 | this->note = new Note((*queuedNote).note); 238 | } else { 239 | // Otherwise, show the original note. 240 | this->note = new Note(*updatedNote); 241 | } 242 | 243 | this->updateUI(); 244 | } 245 | 246 | void NoteWidget::bringToForeground() 247 | { 248 | // All three calls were needed to ensure window is always brought to front 249 | // https://stackoverflow.com/a/10808934/3213602 250 | this->setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); 251 | this->raise(); 252 | this->activateWindow(); 253 | } 254 | 255 | void NoteWidget::moveEvent(QMoveEvent *event) 256 | { 257 | const int newX = event->pos().x(); 258 | const int newY = event->pos().y(); 259 | 260 | parent->updateNoteDimensions(note->guid, newX, newY, this->width(), this->height()); 261 | } 262 | 263 | void NoteWidget::resizeEvent(QResizeEvent *event) 264 | { 265 | const int newWidth = event->size().width(); 266 | const int newHeight = event->size().height(); 267 | 268 | parent->updateNoteDimensions(note->guid, this->x(), this->y(), newWidth, newHeight); 269 | 270 | event->ignore(); 271 | } 272 | 273 | void NoteWidget::setNoteSize() 274 | { 275 | if(initialNoteState.pos_x && initialNoteState.pos_y && 276 | initialNoteState.size_x && initialNoteState.size_y) 277 | { 278 | this->move(initialNoteState.pos_x, initialNoteState.pos_y); 279 | this->resize(initialNoteState.size_x, initialNoteState.size_y); 280 | } else { 281 | resize(400,400); 282 | } 283 | } 284 | 285 | qevercloud::Guid NoteWidget::getNoteGuid() 286 | { 287 | return note->guid; 288 | } 289 | 290 | void NoteWidget::updateNoteGuid(qevercloud::Guid newGuid) 291 | { 292 | qevercloud::Guid oldGuid = note->guid; 293 | note->guid = newGuid; 294 | 295 | // Remove old note dimensions, and create entry for new note GUID. 296 | Cache::deleteFromNotesTable(oldGuid); 297 | parent->updateNoteDimensions(note->guid, this->x(), this->y(), this->width(), this->height()); 298 | } 299 | -------------------------------------------------------------------------------- /src/ui/note_widget.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #ifndef NOTE_WIDGET_H 19 | #define NOTE_WIDGET_H 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "cache.h" 30 | #include "note.h" 31 | #include "note_controller.h" 32 | #include "note_scroll_area.h" 33 | #include "webview/note_webview.h" 34 | 35 | 36 | class NoteWidget : public QMainWindow 37 | { 38 | Q_OBJECT 39 | 40 | public: 41 | NoteWidget(NoteController *parent, Note* note, noteItem size); 42 | void testFunc(); 43 | 44 | void syncModel(); 45 | 46 | void bringToForeground(); 47 | 48 | qevercloud::Guid getNoteGuid(); 49 | void updateNoteGuid(qevercloud::Guid newGuid); 50 | 51 | void scrollNote(QPoint amount); 52 | 53 | public slots: 54 | void updateUI(); 55 | 56 | private slots: 57 | void deleteNote(); 58 | void createNote(); 59 | 60 | void titleTextUpdated(QString newText); 61 | void contentTextChanged(QString newText); 62 | void scrollToWebviewCaret(int caretY); 63 | 64 | private: 65 | NoteController *parent; 66 | Note* note; 67 | NoteWebview * noteWebview; 68 | NoteHeader * noteHeader; 69 | NoteScrollArea *noteScrollArea; 70 | 71 | noteItem initialNoteState; 72 | 73 | void setNoteSize(); 74 | 75 | void hideFromTaskbar(); 76 | void moveEvent(QMoveEvent *event); 77 | void resizeEvent(QResizeEvent *event); 78 | }; 79 | 80 | #endif // NOTE_WIDGET_H 81 | -------------------------------------------------------------------------------- /src/ui/settings_widget.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #include "settings_widget.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "settings.h" 28 | 29 | 30 | SettingsWidget::SettingsWidget() : QDialog() 31 | { 32 | setObjectName("SettingsWindow"); 33 | setWindowTitle("EverSticky | Settings"); 34 | 35 | QIcon icon(":/icon/appicon.ico"); 36 | this->setWindowIcon(icon); 37 | 38 | 39 | /* 40 | * First settings group - general settings 41 | */ 42 | QGroupBox *generalGroupBox = new QGroupBox("General settings", this); 43 | 44 | QGridLayout *generalGridLayout = new QGridLayout(generalGroupBox); 45 | 46 | QLabel *syncIntervalLabel = new QLabel("Sync interval (seconds)", this); 47 | syncIntervalInput = new QSpinBox(this); 48 | syncIntervalInput->setMinimum(30); 49 | syncIntervalInput->setMaximum(100000); 50 | // Set initial spinbox state 51 | syncIntervalInput->setValue(Settings::getUserSetting("sync_interval").toInt()); 52 | 53 | QLabel *checkUpdatesLabel = new QLabel("Check for updates", this); 54 | checkUpdatesCheckbox = new QCheckBox(this); 55 | // Set initial checkbox state 56 | if(Settings::getUserSetting("check_for_updates").toBool()) 57 | checkUpdatesCheckbox->setCheckState(Qt::Checked); 58 | else 59 | checkUpdatesCheckbox->setCheckState(Qt::Unchecked); 60 | 61 | generalGridLayout->addWidget(syncIntervalLabel, 0, 0); 62 | generalGridLayout->addWidget(syncIntervalInput, 0, 1); 63 | generalGridLayout->addWidget(checkUpdatesLabel, 1, 0); 64 | generalGridLayout->addWidget(checkUpdatesCheckbox, 1, 1); 65 | 66 | 67 | /* 68 | * Second settings group - ui settings 69 | */ 70 | QGroupBox *uiGroupBox = new QGroupBox("UI settings", this); 71 | 72 | QGridLayout *uiGridLayout = new QGridLayout(uiGroupBox); 73 | 74 | QLabel *trayIconStyleLabel = new QLabel("Tray icon style", this); 75 | trayIconStyleCombobox = new QComboBox(this); 76 | trayIconStyleCombobox->addItems({"Light", "Dark"}); 77 | // Set initial combobox state 78 | trayIconStyleCombobox->setCurrentIndex((int)Settings::getUserSetting("dark_status_icon").toBool()); 79 | 80 | uiGridLayout->addWidget(trayIconStyleLabel, 0, 0); 81 | uiGridLayout->addWidget(trayIconStyleCombobox, 0, 1); 82 | 83 | 84 | // Parent layout 85 | QVBoxLayout *parentLayout = new QVBoxLayout(this); 86 | 87 | QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); 88 | connect(buttonBox, &QDialogButtonBox::accepted, this, &SettingsWidget::saveSettings); 89 | connect(buttonBox, &QDialogButtonBox::rejected, this, &SettingsWidget::exitSettings); 90 | 91 | // Add all widgets to the dialog 92 | parentLayout->addWidget(generalGroupBox); 93 | parentLayout->addWidget(uiGroupBox); 94 | parentLayout->addWidget(buttonBox); 95 | 96 | show(); 97 | } 98 | 99 | void SettingsWidget::exitSettings() 100 | { 101 | close(); 102 | } 103 | 104 | void SettingsWidget::saveSettings() 105 | { 106 | Settings::setUserSetting("sync_interval", syncIntervalInput->value()); 107 | Settings::setUserSetting("check_for_updates", (bool)checkUpdatesCheckbox->checkState()); 108 | Settings::setUserSetting("dark_status_icon", (bool)trayIconStyleCombobox->currentIndex()); 109 | 110 | // Causes XCB error to show in application output on modal dialog close. 111 | // See unresolved Qt bug: https://bugreports.qt.io/browse/QTBUG-56893 112 | QMessageBox msgBox(this); 113 | msgBox.setIcon(QMessageBox::Information); 114 | msgBox.setWindowTitle("Information"); 115 | msgBox.setText("Settings saved."); 116 | msgBox.setInformativeText("Restart EverSticky for changes to take effect."); 117 | msgBox.exec(); 118 | 119 | close(); 120 | } 121 | -------------------------------------------------------------------------------- /src/ui/settings_widget.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #ifndef SETTINGSWIDGET_H 19 | #define SETTINGSWIDGET_H 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | 27 | class SettingsWidget : public QDialog 28 | { 29 | Q_OBJECT 30 | 31 | public: 32 | SettingsWidget(); 33 | 34 | private slots: 35 | void exitSettings(); 36 | void saveSettings(); 37 | 38 | private: 39 | QSpinBox *syncIntervalInput; 40 | QCheckBox *checkUpdatesCheckbox; 41 | QComboBox *trayIconStyleCombobox; 42 | }; 43 | 44 | #endif // SETTINGSWIDGET_H 45 | -------------------------------------------------------------------------------- /src/ui/tray_icon.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #include "tray_icon.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | 38 | TrayIcon::TrayIcon(NoteController* parent) : parent(parent), QSystemTrayIcon() 39 | { 40 | QIcon icon; 41 | if(Settings::getUserSetting("dark_status_icon").toBool()) 42 | icon = QIcon(":/icon/trayicon_black.ico"); 43 | else 44 | icon = QIcon(":/icon/trayicon_white.ico"); 45 | setIcon(icon); 46 | 47 | trayMenu = new QMenu(); 48 | trayMenu->setContextMenuPolicy(Qt::CustomContextMenu); 49 | updateTrayMenu(); 50 | setContextMenu(trayMenu); 51 | 52 | // Bring notes to foreground on tray icon click. 53 | connect(this, &QSystemTrayIcon::activated, this, &TrayIcon::foregroundAction); 54 | 55 | show(); 56 | 57 | if(Settings::getUserSetting("check_for_updates").toBool() && checkUpdateAvailable()) 58 | { 59 | // Delay showing the notification until the application should be fully loaded (10s later). 60 | QTimer::singleShot(10000, this, [this](){ 61 | showUpdateAvailableNotification(); 62 | }); 63 | } 64 | } 65 | 66 | TrayIcon::~TrayIcon() 67 | { 68 | delete trayMenu; 69 | } 70 | 71 | // Returns true if a newer version of EverSticky is available. 72 | bool TrayIcon::checkUpdateAvailable() 73 | { 74 | QString url = "https://api.github.com/repos/itsmejoeeey/eversticky/releases/latest"; 75 | QNetworkAccessManager manager; 76 | QNetworkReply *response = manager.get(QNetworkRequest(QUrl(url))); 77 | 78 | QEventLoop event; 79 | connect(response, SIGNAL(finished()), &event, SLOT(quit())); 80 | event.exec(); 81 | 82 | QJsonDocument doc = QJsonDocument::fromJson(response->readAll()); 83 | response->deleteLater(); 84 | 85 | QString current_tag = "v" + QString::fromStdString(APP_VERSION); 86 | QString github_tag = doc.object().toVariantMap()["tag_name"].toString(); 87 | 88 | if(github_tag.size() != 0 && github_tag > current_tag) { 89 | return true; 90 | } 91 | 92 | return false; 93 | } 94 | 95 | void TrayIcon::showUpdateAvailableNotification() 96 | { 97 | showMessage("Update Available", "A new version of eversticky has been released - click to download.", icon(), 5000); 98 | 99 | // TODO: Known problems with KDE Plasma - needs more investigation. 100 | // See https://bugreports.qt.io/browse/QTBUG-87329 101 | connect(this, &QSystemTrayIcon::messageClicked, this, [](){ 102 | QDesktopServices::openUrl(QUrl("https://github.com/itsmejoeeey/eversticky/releases")); 103 | }); 104 | } 105 | 106 | void TrayIcon::updateTrayMenu() 107 | { 108 | trayMenu->clear(); 109 | 110 | if(parent->isAuthorised()) { 111 | QAction *login_action = new QAction(); 112 | login_action->setText("Logged in as " + Settings::getSessionSetting("username")); 113 | login_action->setEnabled(false); 114 | trayMenu->addAction(login_action); 115 | 116 | trayMenu->addAction("↪ Logout", this, SLOT(logout())); 117 | } else { 118 | QAction *login_action = new QAction(); 119 | login_action->setText("Not logged in!"); 120 | login_action->setEnabled(false); 121 | trayMenu->addAction(login_action); 122 | } 123 | 124 | trayMenu->addSeparator(); 125 | 126 | if(parent->isAuthorised()) { 127 | trayMenu->addAction("Create note", this, SLOT(createAction())); 128 | trayMenu->addAction("Force sync now", this, SLOT(syncAction())); 129 | trayMenu->addAction("Bring notes to foreground", this, SLOT(foregroundAction())); 130 | } else { 131 | trayMenu->addAction("Login", this, SLOT(loginAction())); 132 | } 133 | 134 | trayMenu->addSeparator(); 135 | 136 | trayMenu->addAction("Settings", this, SLOT(settingsAction())); 137 | trayMenu->addAction("About", this, SLOT(aboutAction())); 138 | trayMenu->addAction("Exit", this, SLOT(exitAction())); 139 | } 140 | 141 | void TrayIcon::loginAction() 142 | { 143 | parent->login(); 144 | } 145 | 146 | void TrayIcon::createAction() 147 | { 148 | parent->createNote(); 149 | } 150 | 151 | void TrayIcon::syncAction() 152 | { 153 | parent->periodicUpdate(); 154 | } 155 | 156 | void TrayIcon::foregroundAction() 157 | { 158 | parent->bringAllToFront(); 159 | } 160 | 161 | void TrayIcon::settingsAction() 162 | { 163 | new SettingsWidget(); 164 | } 165 | 166 | void TrayIcon::aboutAction() 167 | { 168 | new AboutWidget(); 169 | } 170 | 171 | void TrayIcon::exitAction() 172 | { 173 | // Not recommended, cheap solution 174 | QCoreApplication::quit(); 175 | } 176 | 177 | void TrayIcon::logout() 178 | { 179 | parent->showLogoutDialog(); 180 | } 181 | -------------------------------------------------------------------------------- /src/ui/tray_icon.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #ifndef TRAY_ICON_H 19 | #define TRAY_ICON_H 20 | 21 | #include 22 | 23 | 24 | class NoteController; 25 | 26 | class TrayIcon : public QSystemTrayIcon 27 | { 28 | Q_OBJECT 29 | 30 | public: 31 | TrayIcon(NoteController* parent); 32 | ~TrayIcon(); 33 | 34 | void updateTrayMenu(); 35 | 36 | private slots: 37 | void loginAction(); 38 | void createAction(); 39 | void foregroundAction(); 40 | void syncAction(); 41 | void settingsAction(); 42 | void aboutAction(); 43 | void exitAction(); 44 | void logout(); 45 | 46 | private: 47 | NoteController *parent; 48 | QMenu* trayMenu; 49 | 50 | bool checkUpdateAvailable(); 51 | void showUpdateAvailableNotification(); 52 | 53 | void createIconMenu(QPoint point); 54 | }; 55 | 56 | #endif // TRAY_ICON_H 57 | -------------------------------------------------------------------------------- /src/ui/webview/js_interface.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #include "js_interface.h" 19 | 20 | #include "note_webview.h" 21 | 22 | 23 | JsInterface::JsInterface(NoteWebview* parent) : QObject(), m_Parent(parent) 24 | { 25 | } 26 | 27 | void JsInterface::log(const QString& str) const 28 | { 29 | qInfo() << "Message from js: " << str; 30 | } 31 | 32 | void JsInterface::domCaretYPosChanged(int caretY) 33 | { 34 | emit ensureWebviewCaretVisible(caretY); 35 | } 36 | 37 | void JsInterface::domChanged(QString newText) 38 | { 39 | emit textUpdated(newText); 40 | } 41 | 42 | void JsInterface::domHeightResized(int newDomHeight) 43 | { 44 | emit pageHeightUpdated(newDomHeight); 45 | } 46 | 47 | void JsInterface::domLoaded() 48 | { 49 | emit pageLoaded(); 50 | } 51 | 52 | void JsInterface::setInnerHtml(QString text) 53 | { 54 | emit updateInnerHtml(text); 55 | } 56 | -------------------------------------------------------------------------------- /src/ui/webview/js_interface.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #ifndef JS_INTERFACE_H 19 | #define JS_INTERFACE_H 20 | 21 | #include 22 | #include 23 | 24 | 25 | class NoteWebview; 26 | 27 | class JsInterface: public QObject 28 | { 29 | Q_OBJECT 30 | 31 | public: 32 | JsInterface(NoteWebview* parent); 33 | 34 | /// Log, for debugging 35 | Q_INVOKABLE void log(const QString& str) const; 36 | void setInnerHtml(QString text); 37 | 38 | public slots: 39 | void domCaretYPosChanged(int caretY); 40 | void domChanged(QString newText); 41 | void domHeightResized(int newDomHeight); 42 | void domLoaded(); 43 | 44 | signals: 45 | void textUpdated(QString newText); 46 | void updateInnerHtml(QString text); 47 | void pageHeightUpdated(int newHeight); 48 | void pageLoaded(); 49 | void ensureWebviewCaretVisible(int cursorY); 50 | 51 | void insertCheckbox(); 52 | void insertDivider(); 53 | void insertBulletedList(); 54 | void insertNumberedList(); 55 | 56 | private: 57 | NoteWebview* m_Parent; 58 | }; 59 | 60 | #endif // JS_INTERFACE_H 61 | -------------------------------------------------------------------------------- /src/ui/webview/note_webview.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #include "note_webview.h" 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "js_interface.h" 33 | #include "note_formatter.h" 34 | #include "ui/note_widget.h" 35 | 36 | 37 | NoteWebview::NoteWebview(QWidget *context) : QWebEngineView(context) 38 | { 39 | setObjectName("NoteWebview"); 40 | 41 | setFixedHeight(200); 42 | setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 43 | 44 | page()->setBackgroundColor(Qt::transparent); 45 | settings()->setAttribute(QWebEngineSettings::ShowScrollBars, false); 46 | 47 | // Create channel to communicate with the webview 48 | channel = new QWebChannel(this); 49 | this->page()->setWebChannel(channel); 50 | jsInterface = new JsInterface(this); 51 | channel->registerObject(QString("parentWebEngine"), jsInterface); 52 | 53 | // When webview javascript indicates page is loaded, emit another signal. 54 | // This signal indicates the webview is ready to be sent content to display (via setText()) 55 | connect(jsInterface, &JsInterface::pageLoaded, this, &NoteWebview::initialNoteLoad); 56 | 57 | connect(jsInterface, &JsInterface::pageHeightUpdated, this, &NoteWebview::updatePageHeight); 58 | connect(jsInterface, &JsInterface::textUpdated, this, &NoteWebview::textUpdated); 59 | connect(jsInterface, &JsInterface::ensureWebviewCaretVisible, this, &NoteWebview::ensureWebviewCaretVisible); 60 | 61 | installEventFilter(this); 62 | 63 | openBlankPage(); 64 | } 65 | 66 | void NoteWebview::updatePageHeight(int newHeight) 67 | { 68 | int pageBottomPadding = 16; 69 | this->setFixedHeight(newHeight + pageBottomPadding); 70 | } 71 | 72 | void NoteWebview::openBlankPage() 73 | { 74 | QFile apiFile(":/qtwebchannel/qwebchannel.js"); 75 | if(!apiFile.open(QIODevice::ReadOnly)) 76 | qCritical() << "Error loading qwebchannel.js to inject into webview..."; 77 | QString apiScript = QString::fromLatin1(apiFile.readAll()); 78 | apiFile.close(); 79 | this->page()->runJavaScript(apiScript); 80 | 81 | 82 | // Load note js resource 83 | QFile jsFile(":/ui/webview/note_webview.js"); 84 | if(!jsFile.open(QIODevice::ReadOnly)) 85 | qCritical() << "Error loading note_webview.js to inject into webview..."; 86 | QString jsScript = QString::fromLatin1(jsFile.readAll()); 87 | this->page()->runJavaScript(jsScript); 88 | 89 | // Load note helpers js resource 90 | QFile jsHelperFile(":/ui/webview/note_webview_helpers.js"); 91 | if(!jsHelperFile.open(QIODevice::ReadOnly)) 92 | qCritical() << "Error loading note_webview_helpers.js to inject into webview..."; 93 | QString jsHelperScript = QString::fromLatin1(jsHelperFile.readAll()); 94 | 95 | // Load note keyhandler js resource 96 | QFile jsKeyHandlerFile(":/ui/webview/note_webview_keyhandler.js"); 97 | if(!jsKeyHandlerFile.open(QIODevice::ReadOnly)) 98 | qCritical() << "Error loading note_webview_keyhandler.js to inject into webview..."; 99 | QString jsKeyHandlerScript = QString::fromLatin1(jsKeyHandlerFile.readAll()); 100 | 101 | // Load note css resource 102 | QFile cssFile(":/ui/webview/note_webview.css"); 103 | if(!cssFile.open(QIODevice::ReadOnly)) 104 | qCritical() << "Error loading note_webview.css to inject into webview..."; 105 | QString noteCss = QString::fromLatin1(cssFile.readAll()); 106 | 107 | setHtml("\ 108 | \ 109 | \ 110 | \ 111 | \ 112 | \ 113 | \ 114 | \ 115 | \ 116 | \ 117 | " 118 | ); 119 | } 120 | 121 | // Don't call until first page load is finished. 122 | void NoteWebview::setText(QString text) 123 | { 124 | jsInterface->setInnerHtml(text); 125 | } 126 | 127 | bool NoteWebview::eventFilter(QObject*, QEvent *event) 128 | { 129 | // Would have rather used wheelEvent(), but on some Qt versions (such as Qt 5.12.8 default on Ubuntu 20.04) 130 | // wheel events aren't caught by the QWebEngineView itself. Need to attach an event filter to child QWidgets that appear 131 | // during page load. 132 | // https://bugreports.qt.io/browse/QTBUG-43602 133 | 134 | if (event->type() == QEvent::ChildAdded) 135 | { 136 | // Attach event filter to all children as they appear 137 | QChildEvent *e = static_cast(event); 138 | QObject *child = e->child(); 139 | child->installEventFilter(this); 140 | 141 | return true; 142 | } 143 | 144 | if (event->type() == QEvent::Wheel) 145 | { 146 | // Consume wheel event so it can be handled by NoteScrollArea 147 | return true; 148 | } 149 | 150 | return false; 151 | } 152 | 153 | void NoteWebview::contextMenuEvent(QContextMenuEvent *event) 154 | { 155 | QMenu *menu = page()->createStandardContextMenu(); 156 | 157 | QAction *separator = menu->insertSeparator(menu->actions()[0]); 158 | 159 | QMenu *insertMenu = new QMenu("Insert"); 160 | QAction *insertCheckbox = insertMenu->addAction("Checkbox"); 161 | QAction *insertDivider = insertMenu->addAction("Divider line"); 162 | QAction *insertBulletedList = insertMenu->addAction("Bulleted list"); 163 | QAction *insertNumberedList = insertMenu->addAction("Numbered list"); 164 | connect(insertCheckbox, &QAction::triggered, jsInterface, &JsInterface::insertCheckbox); 165 | connect(insertDivider, &QAction::triggered, jsInterface, &JsInterface::insertDivider); 166 | connect(insertBulletedList, &QAction::triggered, jsInterface, &JsInterface::insertBulletedList); 167 | connect(insertNumberedList, &QAction::triggered, jsInterface, &JsInterface::insertNumberedList); 168 | 169 | menu->insertMenu(separator, insertMenu); 170 | 171 | menu->exec(event->globalPos()); 172 | delete menu; 173 | } 174 | -------------------------------------------------------------------------------- /src/ui/webview/note_webview.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the eversticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | body { 19 | /* Use similar default font to Evernote Web */ 20 | font-family: "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 21 | font-size: 14px; 22 | 23 | /* Ensures that webview is not scrollable */ 24 | /* Scrolling should be handled by NoteScrollArea */ 25 | overflow: hidden; 26 | } 27 | 28 | input.en-todo { 29 | background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='16' height='16' version='1.1' viewBox='0 0 4.2333 4.2333' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='.26455' y='.26455' width='3.7042' height='3.7042' ry='.39519' fill='%23fff' stroke='%234d4d4d' stroke-width='.52911' style='paint-order:stroke markers fill'/%3E%3C/svg%3E%0A"); 30 | background-position: center; 31 | background-repeat: no-repeat; 32 | background-size: contain; 33 | margin-top: 4px; 34 | margin-bottom:4px; 35 | margin-right: 10px; 36 | object-fit: scale-down; 37 | vertical-align: middle; 38 | width: 16px; 39 | } 40 | 41 | input.en-todo[checked=true] { 42 | background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='16' height='16' version='1.1' viewBox='0 0 4.2333 4.2333' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Crect x='.26455' y='.26455' width='3.7042' height='3.7042' ry='.39519' fill='%232dbe65' stroke='%234d4d4d' stroke-width='.52911' style='paint-order:stroke markers fill'/%3E%3Cpath d='m0.83615 2.3839 1.006 0.6958 1.5218-2.2003' fill='none' stroke='%23fff' stroke-width='.52917'/%3E%3C/g%3E%3C/svg%3E%0A"); 43 | } 44 | 45 | ul, ol { 46 | padding-inline-start: 20px; 47 | } 48 | 49 | #en-note, 50 | /* Override default input font style for checkboxes */ 51 | input.en-todo { 52 | font-size: 14px; 53 | line-height: 1.5em; 54 | } 55 | 56 | .unsupported { 57 | align-items: center; 58 | border: red 4px solid; 59 | display: inline; 60 | height: 7em; 61 | text-align: center; 62 | width: 7em; 63 | 64 | /* Display cross as image */ 65 | background-image: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cg id='layer1' transform='translate(0 -1036.4)'%3E%3Cpath id='rect3809' style='fill:%23ffffff' d='m5.5312 1039.8-2.125 2.125 2.4687 2.4688-2.4687 2.4687 2.125 2.125 2.4688-2.4687 2.4688 2.4687 2.125-2.125-2.4688-2.4687 2.4688-2.4688-2.125-2.125l-2.469 2.4-2.4688-2.4z' /%3E%3Cpath id='rect3801' style='stroke-linejoin:round;stroke:%23bf030f;stroke-width:.25;fill:%23e2080c' d='m5.5 2-3.5 3.5v5l3.5 3.5h5l3.5-3.5v-5l-3.5-3.5h-5zm0.0312 1.4062 2.4688 2.4688l2.4688-2.4688 2.125 2.125-2.469 2.4688 2.4688 2.4688-2.125 2.125-2.469-2.469-2.4688 2.469-2.125-2.125 2.4688-2.469-2.4688-2.4688 2.125-2.125z' transform='translate(0 1036.4)' /%3E%3C/g%3E%3C/svg%3E%0A"); 66 | background-position: center; 67 | background-repeat: no-repeat; 68 | background-size: 4em; 69 | } 70 | -------------------------------------------------------------------------------- /src/ui/webview/note_webview.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EverSticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | #ifndef NOTE_WEBVIEW_H 19 | #define NOTE_WEBVIEW_H 20 | 21 | #include 22 | 23 | #include "js_interface.h" 24 | #include "ui/note_header.h" 25 | 26 | 27 | class NoteWebview : public QWebEngineView 28 | { 29 | Q_OBJECT 30 | 31 | public: 32 | NoteWebview(QWidget *context); 33 | 34 | void clearText(); 35 | void setText(QString text); 36 | QString getText(); 37 | 38 | public slots: 39 | void updatePageHeight(int newHeight); 40 | 41 | signals: 42 | void textUpdated(QString newText); 43 | void initialNoteLoad(); 44 | void ensureWebviewCaretVisible(int caretY); 45 | void noteLoaded(); 46 | 47 | protected: 48 | void contextMenuEvent(QContextMenuEvent *event); 49 | 50 | private: 51 | QWebChannel* channel; 52 | JsInterface* jsInterface; 53 | 54 | void openBlankPage(); 55 | 56 | bool eventFilter(QObject *obj, QEvent *event); 57 | }; 58 | 59 | #endif // NOTE_WEBVIEW_H 60 | -------------------------------------------------------------------------------- /src/ui/webview/note_webview.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the eversticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | var content; 19 | var parentWebEngine; 20 | 21 | var prevCaretY; 22 | var prevCaretYStart; 23 | var prevCaretYEnd; 24 | 25 | window.onload = function() { 26 | main(); 27 | } 28 | 29 | function main() 30 | { 31 | // Allow content of webview to be user-editable 32 | document.documentElement.contentEditable = true; 33 | 34 | const webChannel = new QWebChannel(qt.webChannelTransport, function (channel) { 35 | parentWebEngine = channel.objects.parentWebEngine; 36 | 37 | // Declare note observers 38 | var observer = new MutationObserver(function() 39 | { 40 | domChanged(); 41 | }); 42 | // 43 | var resizeObserver = new ResizeObserver(function() 44 | { 45 | let body = document.body; 46 | let bodyStyle = window.getComputedStyle(body); 47 | let bodyMarginTop = parseInt(bodyStyle.getPropertyValue('margin-top')); 48 | let bodyMarginBottom = parseInt(bodyStyle.getPropertyValue('margin-bottom')); 49 | 50 | let newDomHeight = content.offsetHeight + bodyMarginTop + bodyMarginBottom; 51 | parentWebEngine.domHeightResized(newDomHeight); 52 | }); 53 | 54 | 55 | // Handler for signal updateInnerHtml() from JsInterface 56 | parentWebEngine.updateInnerHtml.connect(function(text) 57 | { 58 | // Apply the html text to a temporary element and retreive it to reconcile differences when comparing 59 | // to the current html. This ensures br and input elements are rendered the same way (without the trailing slash). 60 | temporaryElement = document.createElement('body'); 61 | temporaryElement.innerHTML = text; 62 | text = temporaryElement.innerHTML; 63 | 64 | // Remove whitespace characters between tags 65 | let regex = /(?<=\>)\s+(?=\<)/g; 66 | text = text.replace(regex, ''); 67 | 68 | // If (after the above formatting) they are the same, don't replace the body. This allows the undo history to prevail. 69 | if(text === document.body.innerHTML) { 70 | return; 71 | } 72 | 73 | 74 | // Replace body with new note 75 | document.body.innerHTML = text; 76 | 77 | // Connect observers to new note container div 78 | content = document.getElementById('en-note'); 79 | observer.observe(content, {attributes: true, characterData: true, childList: true, subtree: true}); 80 | resizeObserver.observe(content); 81 | 82 | // Watch for selection changes 83 | document.addEventListener("selectionchange", selectionChangeEvent); 84 | 85 | 86 | /* 87 | * Usability enhancements 88 | */ 89 | 90 | // Connect event listener to all checkboxes to ensure 'clicked' attribute 91 | // indicates the state of the checkbox. 92 | let checkboxes = document.querySelectorAll("input.en-todo"); 93 | for(var i = 0; i < checkboxes.length; i++) { 94 | let checkbox = checkboxes[i]; 95 | checkboxAddEventListener(checkbox); 96 | }; 97 | 98 | // Attached to window instead of #en-note to ensure we always 99 | // get the event (regardless of what's explicitly focussed). 100 | window.addEventListener("keydown", handleKeyDownEvent); 101 | 102 | // Disable note zoom 103 | window.addEventListener("wheel", wheelEvent, { passive: false }); 104 | }); 105 | 106 | 107 | parentWebEngine.insertCheckbox.connect(function(){ 108 | insertCheckbox(); 109 | }); 110 | parentWebEngine.insertDivider.connect(function(){ 111 | insertDivider(); 112 | }); 113 | parentWebEngine.insertBulletedList.connect(function(){ 114 | insertBulletedList(); 115 | }); 116 | parentWebEngine.insertNumberedList.connect(function(){ 117 | insertNumberedList(); 118 | }); 119 | 120 | 121 | // Notify JsInterface that initial DOM load is complete 122 | parentWebEngine.domLoaded(); 123 | }) 124 | } 125 | 126 | 127 | /* 128 | * 129 | */ 130 | function checkboxAddEventListener(checkbox) 131 | { 132 | checkbox.addEventListener("click", checkboxClickEvent); 133 | 134 | // Don't allow right-clicking on checkboxes 135 | checkbox.addEventListener("contextmenu", checkboxContextMenuEvent); 136 | } 137 | 138 | function checkboxClickEvent(event) 139 | { 140 | let checkbox = event.currentTarget; 141 | if(checkbox.checked) { 142 | checkbox.removeAttribute('checked'); 143 | } else { 144 | checkbox.setAttribute('checked', 'true'); 145 | } 146 | } 147 | 148 | function checkboxContextMenuEvent(event) 149 | { 150 | event.preventDefault(); 151 | return; 152 | } 153 | 154 | function checkCaretYPosChanged(selection) 155 | { 156 | const selectionRangeStart = selection.getRangeAt(0).cloneRange(); 157 | selectionRangeStart.collapse(true); 158 | const caretYStart = getCaretPositionAtSelection(selectionRangeStart).top; 159 | 160 | const selectionRangeEnd = selection.getRangeAt(0).cloneRange(); 161 | selectionRangeEnd.collapse(false); 162 | const caretYEnd = getCaretPositionAtSelection(selectionRangeEnd).top; 163 | 164 | if(caretYStart !== prevCaretYStart) { 165 | parentWebEngine.domCaretYPosChanged(caretYStart); 166 | } else if(caretYEnd !== prevCaretYEnd) { 167 | parentWebEngine.domCaretYPosChanged(caretYEnd); 168 | } 169 | 170 | prevCaretYStart = caretYStart; 171 | prevCaretYEnd = caretYEnd; 172 | } 173 | 174 | function domChanged() 175 | { 176 | // XMLSerializer needed to ensure string is valid XML 177 | // Otherwise, for example, .outerHTML returns
instead of
178 | parentWebEngine.domChanged(new XMLSerializer().serializeToString(content)); 179 | } 180 | 181 | function insertCheckbox() 182 | { 183 | document.execCommand('insertHTML', false, ''); 184 | } 185 | 186 | function insertDivider() 187 | { 188 | document.execCommand('insertHorizontalRule', false); 189 | } 190 | 191 | function insertBulletedList() 192 | { 193 | document.execCommand('insertUnorderedList', false); 194 | } 195 | 196 | function insertNumberedList() 197 | { 198 | document.execCommand('insertOrderedList', false); 199 | } 200 | 201 | function selectionChangeEvent(event) 202 | { 203 | const selection = window.getSelection(); 204 | checkCaretYPosChanged(selection); 205 | } 206 | 207 | function wheelEvent(event) 208 | { 209 | if(event.ctrlKey) 210 | event.preventDefault(); 211 | return; 212 | } 213 | -------------------------------------------------------------------------------- /src/ui/webview/note_webview_helpers.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the eversticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | // This function is necessary because the `:first-child` CSS selector does not account for text nodes. 19 | function checkFirstChildNodeMatchesSelector(node, selector) 20 | { 21 | let i; 22 | for (i = 0; i < node.childNodes.length; i++) { 23 | let child = node.childNodes[i]; 24 | 25 | // Skip first child nodes that contain only whitespace. 26 | if(!(child.nodeType === Node.TEXT_NODE && child.textContent.trim().length === 0)) { 27 | break; 28 | } 29 | } 30 | 31 | let firstChild = node.childNodes[i]; 32 | 33 | return (firstChild.nodeType !== Node.TEXT_NODE && firstChild.matches(selector)) 34 | } 35 | 36 | // Check if selector matches any nodes that are the first child (descending from node). 37 | function checkNestedFirstChildMatchesSelector(node, selector) 38 | { 39 | let result = checkFirstChildNodeMatchesSelector(node, selector); 40 | if(!result && node.childNodes.length > 0) { 41 | return checkNestedFirstChildMatchesSelector(node.childNodes[0], selector); 42 | } else { 43 | return result; 44 | } 45 | } 46 | 47 | function checkIfEmptyTextContent(node) 48 | { 49 | let textContent = node.textContent; 50 | 51 | // Remove zero-width Unicode element 52 | textContent = textContent.replace('\u200B', ''); 53 | 54 | return (textContent === null || textContent.trim() === ""); 55 | } 56 | 57 | function checkIfOnlyChildOfNode(parentNode, childNode) 58 | { 59 | let node = childNode; 60 | 61 | while(node !== parentNode) { 62 | node = node.parentNode; 63 | if(node.children.length > 1) { 64 | return false; 65 | } 66 | } 67 | return true; 68 | } 69 | 70 | function getCaretPositionAtSelection(selectionRange) 71 | { 72 | // Doesn't work with empty newline, need to insert empty span 73 | // https://stackoverflow.com/questions/6846230/coordinates-of-selected-text-in-browser-page/6847328#6847328 74 | 75 | // Insert temporary element so we can get the caret location 76 | let span = document.createElement("span"); 77 | selectionRange.insertNode(span) 78 | const caretPosition = span.getClientRects()[0]; 79 | span.parentNode.removeChild(span); 80 | 81 | return caretPosition 82 | } 83 | 84 | function getDescendantsAndLeaves(node, result) 85 | { 86 | result = result || []; 87 | for (var i = 0; i < node.childNodes.length; i++) { 88 | result.push(node.childNodes[i]) 89 | getDescendantsAndLeaves(node.childNodes[i], result); 90 | } 91 | return result; 92 | } 93 | 94 | function getFirstDescendantTextNode(element) 95 | { 96 | nodeList = getDescendantsAndLeaves(element); 97 | for (var i = 0; i < nodeList.length; i++) { 98 | // Check for text node that is not blank 99 | if ((nodeList[i].nodeType === Node.TEXT_NODE && nodeList[i].nodeValue.trim().length != 0)) { 100 | return nodeList[i]; 101 | } 102 | } 103 | 104 | return null; 105 | } 106 | 107 | function getSurroundingElementsWithFirstChildMatching(element, selector) 108 | { 109 | let surroundingElements = []; 110 | 111 | let prevElement = element.previousElementSibling; 112 | while(prevElement && prevElement.querySelector(selector)) { 113 | if(!checkFirstChildNodeMatchesSelector(prevElement, selector)) { 114 | break; 115 | } 116 | 117 | surroundingElements.unshift(prevElement) 118 | prevElement = prevElement.previousElementSibling; 119 | } 120 | 121 | surroundingElements.push(element); 122 | 123 | let nextElement = element.nextElementSibling; 124 | while(nextElement && nextElement.querySelector(selector)) { 125 | if(!checkFirstChildNodeMatchesSelector(nextElement, selector)) { 126 | break; 127 | } 128 | 129 | surroundingElements.push(nextElement) 130 | nextElement = nextElement.nextElementSibling; 131 | } 132 | 133 | return surroundingElements; 134 | } 135 | 136 | function trimAllLineBreaks(string) 137 | { 138 | if(string == null) { 139 | return; 140 | } 141 | 142 | return string.replace(/[\n\r]/g, ""); 143 | } 144 | 145 | function wrapElementInTagName(element, tagName) 146 | { 147 | wrapper = document.createElement(tagName); 148 | element.parentNode.insertBefore(wrapper, element); 149 | wrapper.appendChild(element); 150 | 151 | return wrapper; 152 | } 153 | -------------------------------------------------------------------------------- /src/ui/webview/note_webview_keyhandler.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the eversticky project (https://eversticky.joeeey.com). 3 | * Copyright (c) 2021 Joey Miller. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * 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, see . 16 | */ 17 | 18 | /* 19 | * Main keyhandler 20 | */ 21 | function handleKeyDownEvent(event) 22 | { 23 | const selection = window.getSelection(); 24 | 25 | if (event.key === "Backspace" || event.key === "Delete") 26 | { 27 | // If only a single empty div left in note when backspacing, prevent default 28 | // action from deleting the note parent node. 29 | if(content.children.length === 1 && // Quick check to prevent unnecessary traversal of the node tree 30 | trimAllLineBreaks(selection.focusNode.outerHTML) === "

" && 31 | checkIfOnlyChildOfNode(content, selection.focusNode)) 32 | { 33 | event.preventDefault(); 34 | } 35 | } 36 | 37 | if (event.key === "Enter") 38 | { 39 | // Get the parent element at the start of the selection 40 | let selectionParent = selection.getRangeAt(0).startContainer; 41 | 42 | // Traverse out of span, font or text nodes 43 | while(selectionParent.tagName === 'SPAN' || 44 | selectionParent.tagName === 'FONT' || 45 | selectionParent.nodeType === Node.TEXT_NODE) 46 | { 47 | selectionParent = selectionParent.parentNode; 48 | } 49 | 50 | // If selection is inside an element that starts with a checkbox, we want to insert and focus 51 | // a checkbox on a new line. 52 | if(checkNestedFirstChildMatchesSelector(selectionParent, "input.en-todo")) 53 | { 54 | handleReturnOnCheckbox(event, selection, selectionParent); 55 | } 56 | } 57 | } 58 | 59 | 60 | /* 61 | * Handlers 62 | */ 63 | 64 | function handleReturnOnCheckbox(event, selection, selectionParent) 65 | { 66 | // Do default behaviour if user does a "SHIFT+ENTER" 67 | if(event.shiftKey) { 68 | return; 69 | } 70 | 71 | // If checkbox is not inside an unordered-list, migrate the block of checkboxes to one 72 | if(selectionParent.parentNode.parentNode.tagName !== 'UL') 73 | { 74 | const currentSelectionOffset = selection.getRangeAt(0).startOffset; 75 | 76 | let siblingElements = getSurroundingElementsWithFirstChildMatching(selectionParent, "input.en-todo"); 77 | 78 | // Wrap the first checkbox row in an unordered-list, before adding the rest of the rows to the list. 79 | let newLiElement = wrapElementInTagName(siblingElements[0], "LI"); 80 | let newUlElement = wrapElementInTagName(newLiElement, "UL"); 81 | for(let i = 1; i < siblingElements.length; i++) { 82 | newUlElement.appendChild( 83 | wrapElementInTagName(siblingElements[i], "LI") 84 | ); 85 | } 86 | 87 | // Restore the cursor position 88 | let newSelection = document.createRange(); 89 | newSelection.setStart(getFirstDescendantTextNode(selectionParent), currentSelectionOffset); 90 | selection.removeAllRanges(); 91 | selection.addRange(newSelection); 92 | } 93 | // If return pressed on empty checkbox element, insert a newline without the checkbox + unordered-list. 94 | else if(checkIfEmptyTextContent(selectionParent)) 95 | { 96 | document.execCommand('delete', false); // delete checkbox element 97 | document.execCommand('insertUnorderedList', false); // toggle unordered list 98 | } 99 | // Otherwise just insert a checkbox on a newline. 100 | else 101 | { 102 | document.execCommand('insertParagraph', false); 103 | insertCheckbox(); 104 | } 105 | 106 | event.preventDefault(); 107 | return; 108 | } 109 | --------------------------------------------------------------------------------