├── .classpath ├── .gitignore ├── .project ├── LICENSE ├── META-INF └── MANIFEST.MF ├── OSGI-INF └── l10n │ ├── bundle.properties │ └── bundle_fr.properties ├── README.md ├── build.properties ├── contexts.xml ├── icons ├── bullets.png ├── change.gif ├── clear.png ├── clear_co.png ├── deadlock_view.png ├── file-new-16x16.png ├── note.png ├── save_edit.png ├── settings_obj.png ├── style_bold.gif ├── style_italic.gif ├── style_strikeout.png ├── style_underline.gif └── web.png ├── plugin.xml ├── screenshot.png └── src └── io └── github └── pyvesb └── notepad4e ├── Notepad4e.java ├── preferences ├── PreferenceInitializer.java ├── PreferencePage.java └── Preferences.java ├── strings ├── LocalStrings.java ├── localstrings.properties └── localstrings_fr.properties ├── utils ├── AbstractSelectedNoteAction.java ├── NotepadAction.java ├── ShortcutHandler.java └── UndoRedoManager.java └── views ├── Note.java └── NotepadView.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | # Eclipse files 15 | .settings 16 | /bin/ 17 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notepad4e 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.pde.ManifestBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.pde.SchemaBuilder 20 | 21 | 22 | 23 | 24 | 25 | org.eclipse.pde.PluginNature 26 | org.eclipse.jdt.core.javanature 27 | 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 2.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION 5 | OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial content 12 | Distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | i) changes to the Program, and 16 | ii) additions to the Program; 17 | where such changes and/or additions to the Program originate from 18 | and are Distributed by that particular Contributor. A Contribution 19 | "originates" from a Contributor if it was added to the Program by 20 | such Contributor itself or anyone acting on such Contributor's behalf. 21 | Contributions do not include changes or additions to the Program that 22 | are not Modified Works. 23 | 24 | "Contributor" means any person or entity that Distributes the Program. 25 | 26 | "Licensed Patents" mean patent claims licensable by a Contributor which 27 | are necessarily infringed by the use or sale of its Contribution alone 28 | or when combined with the Program. 29 | 30 | "Program" means the Contributions Distributed in accordance with this 31 | Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement 34 | or any Secondary License (as applicable), including Contributors. 35 | 36 | "Derivative Works" shall mean any work, whether in Source Code or other 37 | form, that is based on (or derived from) the Program and for which the 38 | editorial revisions, annotations, elaborations, or other modifications 39 | represent, as a whole, an original work of authorship. 40 | 41 | "Modified Works" shall mean any work in Source Code or other form that 42 | results from an addition to, deletion from, or modification of the 43 | contents of the Program, including, for purposes of clarity any new file 44 | in Source Code form that contains any contents of the Program. Modified 45 | Works shall not include works that contain only declarations, 46 | interfaces, types, classes, structures, or files of the Program solely 47 | in each case in order to link to, bind by name, or subclass the Program 48 | or Modified Works thereof. 49 | 50 | "Distribute" means the acts of a) distributing or b) making available 51 | in any manner that enables the transfer of a copy. 52 | 53 | "Source Code" means the form of a Program preferred for making 54 | modifications, including but not limited to software source code, 55 | documentation source, and configuration files. 56 | 57 | "Secondary License" means either the GNU General Public License, 58 | Version 2.0, or any later versions of that license, including any 59 | exceptions or additional permissions as identified by the initial 60 | Contributor. 61 | 62 | 2. GRANT OF RIGHTS 63 | 64 | a) Subject to the terms of this Agreement, each Contributor hereby 65 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 66 | license to reproduce, prepare Derivative Works of, publicly display, 67 | publicly perform, Distribute and sublicense the Contribution of such 68 | Contributor, if any, and such Derivative Works. 69 | 70 | b) Subject to the terms of this Agreement, each Contributor hereby 71 | grants Recipient a non-exclusive, worldwide, royalty-free patent 72 | license under Licensed Patents to make, use, sell, offer to sell, 73 | import and otherwise transfer the Contribution of such Contributor, 74 | if any, in Source Code or other form. This patent license shall 75 | apply to the combination of the Contribution and the Program if, at 76 | the time the Contribution is added by the Contributor, such addition 77 | of the Contribution causes such combination to be covered by the 78 | Licensed Patents. The patent license shall not apply to any other 79 | combinations which include the Contribution. No hardware per se is 80 | licensed hereunder. 81 | 82 | c) Recipient understands that although each Contributor grants the 83 | licenses to its Contributions set forth herein, no assurances are 84 | provided by any Contributor that the Program does not infringe the 85 | patent or other intellectual property rights of any other entity. 86 | Each Contributor disclaims any liability to Recipient for claims 87 | brought by any other entity based on infringement of intellectual 88 | property rights or otherwise. As a condition to exercising the 89 | rights and licenses granted hereunder, each Recipient hereby 90 | assumes sole responsibility to secure any other intellectual 91 | property rights needed, if any. For example, if a third party 92 | patent license is required to allow Recipient to Distribute the 93 | Program, it is Recipient's responsibility to acquire that license 94 | before distributing the Program. 95 | 96 | d) Each Contributor represents that to its knowledge it has 97 | sufficient copyright rights in its Contribution, if any, to grant 98 | the copyright license set forth in this Agreement. 99 | 100 | e) Notwithstanding the terms of any Secondary License, no 101 | Contributor makes additional grants to any Recipient (other than 102 | those set forth in this Agreement) as a result of such Recipient's 103 | receipt of the Program under the terms of a Secondary License 104 | (if permitted under the terms of Section 3). 105 | 106 | 3. REQUIREMENTS 107 | 108 | 3.1 If a Contributor Distributes the Program in any form, then: 109 | 110 | a) the Program must also be made available as Source Code, in 111 | accordance with section 3.2, and the Contributor must accompany 112 | the Program with a statement that the Source Code for the Program 113 | is available under this Agreement, and informs Recipients how to 114 | obtain it in a reasonable manner on or through a medium customarily 115 | used for software exchange; and 116 | 117 | b) the Contributor may Distribute the Program under a license 118 | different than this Agreement, provided that such license: 119 | i) effectively disclaims on behalf of all other Contributors all 120 | warranties and conditions, express and implied, including 121 | warranties or conditions of title and non-infringement, and 122 | implied warranties or conditions of merchantability and fitness 123 | for a particular purpose; 124 | 125 | ii) effectively excludes on behalf of all other Contributors all 126 | liability for damages, including direct, indirect, special, 127 | incidental and consequential damages, such as lost profits; 128 | 129 | iii) does not attempt to limit or alter the recipients' rights 130 | in the Source Code under section 3.2; and 131 | 132 | iv) requires any subsequent distribution of the Program by any 133 | party to be under a license that satisfies the requirements 134 | of this section 3. 135 | 136 | 3.2 When the Program is Distributed as Source Code: 137 | 138 | a) it must be made available under this Agreement, or if the 139 | Program (i) is combined with other material in a separate file or 140 | files made available under a Secondary License, and (ii) the initial 141 | Contributor attached to the Source Code the notice described in 142 | Exhibit A of this Agreement, then the Program may be made available 143 | under the terms of such Secondary Licenses, and 144 | 145 | b) a copy of this Agreement must be included with each copy of 146 | the Program. 147 | 148 | 3.3 Contributors may not remove or alter any copyright, patent, 149 | trademark, attribution notices, disclaimers of warranty, or limitations 150 | of liability ("notices") contained within the Program from any copy of 151 | the Program which they Distribute, provided that Contributors may add 152 | their own appropriate notices. 153 | 154 | 4. COMMERCIAL DISTRIBUTION 155 | 156 | Commercial distributors of software may accept certain responsibilities 157 | with respect to end users, business partners and the like. While this 158 | license is intended to facilitate the commercial use of the Program, 159 | the Contributor who includes the Program in a commercial product 160 | offering should do so in a manner which does not create potential 161 | liability for other Contributors. Therefore, if a Contributor includes 162 | the Program in a commercial product offering, such Contributor 163 | ("Commercial Contributor") hereby agrees to defend and indemnify every 164 | other Contributor ("Indemnified Contributor") against any losses, 165 | damages and costs (collectively "Losses") arising from claims, lawsuits 166 | and other legal actions brought by a third party against the Indemnified 167 | Contributor to the extent caused by the acts or omissions of such 168 | Commercial Contributor in connection with its distribution of the Program 169 | in a commercial product offering. The obligations in this section do not 170 | apply to any claims or Losses relating to any actual or alleged 171 | intellectual property infringement. In order to qualify, an Indemnified 172 | Contributor must: a) promptly notify the Commercial Contributor in 173 | writing of such claim, and b) allow the Commercial Contributor to control, 174 | and cooperate with the Commercial Contributor in, the defense and any 175 | related settlement negotiations. The Indemnified Contributor may 176 | participate in any such claim at its own expense. 177 | 178 | For example, a Contributor might include the Program in a commercial 179 | product offering, Product X. That Contributor is then a Commercial 180 | Contributor. If that Commercial Contributor then makes performance 181 | claims, or offers warranties related to Product X, those performance 182 | claims and warranties are such Commercial Contributor's responsibility 183 | alone. Under this section, the Commercial Contributor would have to 184 | defend claims against the other Contributors related to those performance 185 | claims and warranties, and if a court requires any other Contributor to 186 | pay any damages as a result, the Commercial Contributor must pay 187 | those damages. 188 | 189 | 5. NO WARRANTY 190 | 191 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 192 | PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" 193 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 194 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF 195 | TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 196 | PURPOSE. Each Recipient is solely responsible for determining the 197 | appropriateness of using and distributing the Program and assumes all 198 | risks associated with its exercise of rights under this Agreement, 199 | including but not limited to the risks and costs of program errors, 200 | compliance with applicable laws, damage to or loss of data, programs 201 | or equipment, and unavailability or interruption of operations. 202 | 203 | 6. DISCLAIMER OF LIABILITY 204 | 205 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 206 | PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS 207 | SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 208 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 209 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 210 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 211 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 212 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE 213 | POSSIBILITY OF SUCH DAMAGES. 214 | 215 | 7. GENERAL 216 | 217 | If any provision of this Agreement is invalid or unenforceable under 218 | applicable law, it shall not affect the validity or enforceability of 219 | the remainder of the terms of this Agreement, and without further 220 | action by the parties hereto, such provision shall be reformed to the 221 | minimum extent necessary to make such provision valid and enforceable. 222 | 223 | If Recipient institutes patent litigation against any entity 224 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 225 | Program itself (excluding combinations of the Program with other software 226 | or hardware) infringes such Recipient's patent(s), then such Recipient's 227 | rights granted under Section 2(b) shall terminate as of the date such 228 | litigation is filed. 229 | 230 | All Recipient's rights under this Agreement shall terminate if it 231 | fails to comply with any of the material terms or conditions of this 232 | Agreement and does not cure such failure in a reasonable period of 233 | time after becoming aware of such noncompliance. If all Recipient's 234 | rights under this Agreement terminate, Recipient agrees to cease use 235 | and distribution of the Program as soon as reasonably practicable. 236 | However, Recipient's obligations under this Agreement and any licenses 237 | granted by Recipient relating to the Program shall continue and survive. 238 | 239 | Everyone is permitted to copy and distribute copies of this Agreement, 240 | but in order to avoid inconsistency the Agreement is copyrighted and 241 | may only be modified in the following manner. The Agreement Steward 242 | reserves the right to publish new versions (including revisions) of 243 | this Agreement from time to time. No one other than the Agreement 244 | Steward has the right to modify this Agreement. The Eclipse Foundation 245 | is the initial Agreement Steward. The Eclipse Foundation may assign the 246 | responsibility to serve as the Agreement Steward to a suitable separate 247 | entity. Each new version of the Agreement will be given a distinguishing 248 | version number. The Program (including Contributions) may always be 249 | Distributed subject to the version of the Agreement under which it was 250 | received. In addition, after a new version of the Agreement is published, 251 | Contributor may elect to Distribute the Program (including its 252 | Contributions) under the new version. 253 | 254 | Except as expressly stated in Sections 2(a) and 2(b) above, Recipient 255 | receives no rights or licenses to the intellectual property of any 256 | Contributor under this Agreement, whether expressly, by implication, 257 | estoppel or otherwise. All rights in the Program not expressly granted 258 | under this Agreement are reserved. Nothing in this Agreement is intended 259 | to be enforceable by any entity that is not a Contributor or Recipient. 260 | No third-party beneficiary rights are created under this Agreement. 261 | 262 | Exhibit A - Form of Secondary Licenses Notice 263 | 264 | "This Source Code may also be made available under the following 265 | Secondary Licenses when the conditions for such availability set forth 266 | in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), 267 | version(s), and exceptions or additional permissions here}." 268 | 269 | Simply including a copy of this Agreement, including this Exhibit A 270 | is not sufficient to license the Source Code under Secondary Licenses. 271 | 272 | If it is not possible or desirable to put the notice in a particular 273 | file, then You may include the notice in a location (such as a LICENSE 274 | file in a relevant directory) where a recipient would be likely to 275 | look for such a notice. 276 | 277 | You may add additional accurate notices of copyright ownership. 278 | -------------------------------------------------------------------------------- /META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Bundle-ManifestVersion: 2 3 | Bundle-Name: Notepad4e 4 | Bundle-SymbolicName: Notepad4e;singleton:=true 5 | Bundle-Version: 1.5.3 6 | Bundle-Activator: io.github.pyvesb.notepad4e.Notepad4e 7 | Require-Bundle: org.eclipse.ui, 8 | org.eclipse.core.runtime 9 | Bundle-RequiredExecutionEnvironment: JavaSE-1.8 10 | Bundle-ActivationPolicy: lazy 11 | Bundle-Localization: OSGI-INF/l10n/bundle 12 | Export-Package: io.github.pyvesb.notepad4e, 13 | io.github.pyvesb.notepad4e.preferences, 14 | io.github.pyvesb.notepad4e.views, 15 | io.github.pyvesb.notepad4e.utils, 16 | io.github.pyvesb.notepad4e.strings 17 | -------------------------------------------------------------------------------- /OSGI-INF/l10n/bundle.properties: -------------------------------------------------------------------------------- 1 | #Properties file for Notepad4e 2 | category.name = General 3 | category.description = Category for Notepad4e commands 4 | command.description.new_note = Creates a new note 5 | command.name.new_note = New Note 6 | command.description.bold = Sets the style of the currently selected text to bold 7 | command.name.bold = Bold 8 | command.description.italic = Sets the style of the currently selected text to italic 9 | command.name.italic = Italic 10 | command.description.underline = Underlines the currently selected text 11 | command.name.underline = Underline 12 | command.description.strikeout = Strikes out the currently selected text 13 | command.name.strikeout = Strikethrough 14 | command.description.list = Turns the currently selected lines into a list 15 | command.name.list = Bullet List 16 | command.description.clear_style = Clears the style of the currently selected text 17 | command.name.clear_style = Clear Style 18 | command.description.clear_note = Clears the current note 19 | command.name.clear_note = Clear Note 20 | command.description.undo = Performs an undo action on the current note 21 | command.name.undo = Undo 22 | command.description.redo = Performs a redo action on the current note 23 | command.name.redo = Redo 24 | command.description.close = Closes the currently selected note 25 | command.name.close = Close Note 26 | context.name = In Notepad4e -------------------------------------------------------------------------------- /OSGI-INF/l10n/bundle_fr.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyvesB/Notepad4e/0b19e1207091c7ecf15c7de516d1d3388a2252a5/OSGI-INF/l10n/bundle_fr.properties -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Notepad4e 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | **Create highly customisable notes in Eclipse, and never lose track of your thoughts, ideas or code snippets!** 22 | 23 |

24 | 25 |
26 | A simple example of how the plugin can be used and customised. You have countless possibilities! 27 |

28 | 29 | # Features at a glance 30 | 31 | * Create as many notes as you like in a dedicated Eclipse view. 32 | * Modify the text style (font, color, bold, italic, underlined, strikeout, etc.). 33 | * Customise the appearance and functionality of the plugin via its preference page. 34 | * Create lists of text elements. 35 | * Automatically save the contents of your notes to a default or custom location. 36 | * Use editable shortcuts to boost your productivity. 37 | * Export notes as text files. 38 | * Lightweight and user-friendly interface. 39 | * Source code fully available. 40 | * More to discover and to come in future updates! 41 | 42 | Check out what's new in the [latest releases](https://github.com/PyvesB/Notepad4e/releases)! 43 | 44 | # Getting started 45 | 46 | #### :cd: Plugin installation 47 | 48 | You can download and install the plugin via the [Eclipse Marketplace](https://marketplace.eclipse.org/content/notepad4e), or simply drag the below button to your running Eclipse workspace: 49 | 50 |

51 | Drag to your running Eclipse workspace to install Notepad4e 52 |

53 | 54 | Alternatively, head to the [update site](https://pyvesb.github.io/Notepad4e/) and follow the instructions listed there. 55 | 56 | #### :wrench: Setting your own working copy of the project 57 | 58 | * Ensure you have a working version of the [JDK](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) (Java Development Kit). 59 | * Download the [RCP and RAP Developers](https://eclipse.org/downloads/eclipse-packages/) version of Eclipse. 60 | * Make a copy of the repository on your computer. You can also create your own fork by clicking on the *Fork* icon on the top right of this page. 61 | * In Eclipse, go to `File` -> `Import...` -> `Existing Projects into Workspace`. 62 | * In the `Select root directory` field, indicate the location where you downloaded the Notepad4e repository. 63 | * Tick the project that appears in the `Projects` field and click `Finish`. 64 | * You're ready to go! You can now either launch an instance of Eclipse running the plugin by right-clicking on the project and selecting `Run As` -> `Eclipse Application`, or you can export a plugin archive file by selecting `Export` -> `Deployable plug-ins and fragments`. 65 | 66 | # Contributing 67 | 68 | #### `$ code` 69 | 70 | Want to make Notepad4e better, faster, stronger? Contributions are more than welcome, open a **pull request** and share your code! Simply **fork** the repository by clicking on the icon on the top right of this page and you're ready to go! 71 | 72 | #### :speech_balloon: Support 73 | 74 | Thought of a cool idea? Found a problem or need some help? Simply open an [**issue**](https://github.com/PyvesB/Notepad4e/issues)! 75 | 76 | #### :star: Thanks 77 | 78 | Find the project useful, fun or interesting? **Star** the repository by clicking on the icon on the top right of this page! 79 | 80 | # License 81 | 82 | GNU General Public License v3.0 83 | -------------------------------------------------------------------------------- /build.properties: -------------------------------------------------------------------------------- 1 | source.. = src/ 2 | output.. = bin/ 3 | bin.includes = plugin.xml,\ 4 | META-INF/,\ 5 | .,\ 6 | icons/,\ 7 | contexts.xml,\ 8 | OSGI-INF/ 9 | -------------------------------------------------------------------------------- /contexts.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Create highly customisable notes in Eclipse, and never lose track of your thoughts, ideas or code snippets! 5 | 6 | 7 | -------------------------------------------------------------------------------- /icons/bullets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyvesB/Notepad4e/0b19e1207091c7ecf15c7de516d1d3388a2252a5/icons/bullets.png -------------------------------------------------------------------------------- /icons/change.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyvesB/Notepad4e/0b19e1207091c7ecf15c7de516d1d3388a2252a5/icons/change.gif -------------------------------------------------------------------------------- /icons/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyvesB/Notepad4e/0b19e1207091c7ecf15c7de516d1d3388a2252a5/icons/clear.png -------------------------------------------------------------------------------- /icons/clear_co.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyvesB/Notepad4e/0b19e1207091c7ecf15c7de516d1d3388a2252a5/icons/clear_co.png -------------------------------------------------------------------------------- /icons/deadlock_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyvesB/Notepad4e/0b19e1207091c7ecf15c7de516d1d3388a2252a5/icons/deadlock_view.png -------------------------------------------------------------------------------- /icons/file-new-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyvesB/Notepad4e/0b19e1207091c7ecf15c7de516d1d3388a2252a5/icons/file-new-16x16.png -------------------------------------------------------------------------------- /icons/note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyvesB/Notepad4e/0b19e1207091c7ecf15c7de516d1d3388a2252a5/icons/note.png -------------------------------------------------------------------------------- /icons/save_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyvesB/Notepad4e/0b19e1207091c7ecf15c7de516d1d3388a2252a5/icons/save_edit.png -------------------------------------------------------------------------------- /icons/settings_obj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyvesB/Notepad4e/0b19e1207091c7ecf15c7de516d1d3388a2252a5/icons/settings_obj.png -------------------------------------------------------------------------------- /icons/style_bold.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyvesB/Notepad4e/0b19e1207091c7ecf15c7de516d1d3388a2252a5/icons/style_bold.gif -------------------------------------------------------------------------------- /icons/style_italic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyvesB/Notepad4e/0b19e1207091c7ecf15c7de516d1d3388a2252a5/icons/style_italic.gif -------------------------------------------------------------------------------- /icons/style_strikeout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyvesB/Notepad4e/0b19e1207091c7ecf15c7de516d1d3388a2252a5/icons/style_strikeout.png -------------------------------------------------------------------------------- /icons/style_underline.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyvesB/Notepad4e/0b19e1207091c7ecf15c7de516d1d3388a2252a5/icons/style_underline.gif -------------------------------------------------------------------------------- /icons/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyvesB/Notepad4e/0b19e1207091c7ecf15c7de516d1d3388a2252a5/icons/web.png -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 11 | 12 | 13 | 15 | 17 | 18 | 19 | 21 | 24 | 25 | 31 | 32 | 33 | 35 | 37 | 42 | 43 | 44 | 45 | 47 | 49 | 50 | 51 | 53 | 57 | 58 | 63 | 64 | 69 | 70 | 75 | 76 | 81 | 82 | 87 | 88 | 93 | 94 | 99 | 100 | 105 | 106 | 111 | 112 | 117 | 118 | 123 | 124 | 125 | 127 | 132 | 133 | 138 | 139 | 144 | 145 | 150 | 151 | 156 | 157 | 162 | 163 | 168 | 169 | 174 | 175 | 180 | 181 | 186 | 187 | 192 | 193 | 194 | 196 | 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyvesB/Notepad4e/0b19e1207091c7ecf15c7de516d1d3388a2252a5/screenshot.png -------------------------------------------------------------------------------- /src/io/github/pyvesb/notepad4e/Notepad4e.java: -------------------------------------------------------------------------------- 1 | package io.github.pyvesb.notepad4e; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | import org.eclipse.core.runtime.IStatus; 7 | import org.eclipse.core.runtime.Status; 8 | import org.eclipse.core.runtime.preferences.InstanceScope; 9 | import org.eclipse.jface.dialogs.DialogSettings; 10 | import org.eclipse.jface.dialogs.IDialogSettings; 11 | import org.eclipse.ui.plugin.AbstractUIPlugin; 12 | import org.osgi.framework.BundleContext; 13 | 14 | import io.github.pyvesb.notepad4e.preferences.Preferences; 15 | import io.github.pyvesb.notepad4e.strings.LocalStrings; 16 | 17 | /** 18 | * Class used to control the plugin's life cycle. 19 | * 20 | * @author Pyves 21 | * 22 | */ 23 | public class Notepad4e extends AbstractUIPlugin { 24 | 25 | // Plugin's ID. 26 | public static final String PLUGIN_ID = "Notepad4e"; 27 | 28 | private static final String FN_DIALOG_SETTINGS_CUSTOM = "notepad4e.xml"; 29 | private static final String FN_DIALOG_SETTINGS = "dialog_settings.xml"; 30 | 31 | // Shared instance. Not ideal to use a static field here, but common practice to do this with AbstractUIPlugin. 32 | private static Notepad4e plugin; 33 | 34 | private IDialogSettings dialogSettings; 35 | 36 | @Override 37 | public void start(BundleContext context) throws Exception { 38 | super.start(context); 39 | plugin = this; 40 | } 41 | 42 | @Override 43 | public void stop(BundleContext context) throws Exception { 44 | plugin = null; 45 | super.stop(context); 46 | } 47 | 48 | @Override 49 | public IDialogSettings getDialogSettings() { 50 | if (dialogSettings == null) { 51 | restoreDialogSettings(); 52 | } 53 | return dialogSettings; 54 | } 55 | 56 | @Override 57 | public void saveDialogSettings() { 58 | String directory = getDialogSettingsDirectory(); 59 | saveDialogSettings(directory); 60 | } 61 | 62 | public void saveDialogSettings(String directory) { 63 | String settingsPath; 64 | if (directory == null || directory.isEmpty()) { 65 | settingsPath = getStateLocation().append(FN_DIALOG_SETTINGS).toOSString(); 66 | } else { 67 | settingsPath = directory + File.separator + FN_DIALOG_SETTINGS_CUSTOM; 68 | } 69 | try { 70 | dialogSettings.save(settingsPath); 71 | } catch (IOException | IllegalStateException e) { 72 | // Ignore problems as in super.saveDialogSettings(). 73 | } 74 | } 75 | 76 | public void restoreDialogSettings() { 77 | String directory = getDialogSettingsDirectory(); 78 | if (directory == null || directory.isEmpty()) { 79 | dialogSettings = super.getDialogSettings(); 80 | } else { 81 | dialogSettings = new DialogSettings("Workbench"); 82 | String settingsPath = directory + File.separator + FN_DIALOG_SETTINGS_CUSTOM; 83 | File settingsFile = new File(settingsPath); 84 | if (settingsFile.exists()) { 85 | try { 86 | dialogSettings.load(settingsPath); 87 | } catch (IOException e) { 88 | getLog().log(new Status(IStatus.ERROR, PLUGIN_ID, LocalStrings.getDialogSettingsErrorMsg, e)); 89 | dialogSettings = super.getDialogSettings(); 90 | } 91 | } 92 | } 93 | } 94 | 95 | private String getDialogSettingsDirectory() { 96 | return InstanceScope.INSTANCE.getNode(PLUGIN_ID).get(Preferences.SAVE_LOCATION, Preferences.SAVE_LOCATION_DEFAULT); 97 | } 98 | 99 | /** 100 | * Returns the shared instance. 101 | * 102 | * @return the shared instance 103 | */ 104 | public static Notepad4e getDefault() { 105 | return plugin; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/io/github/pyvesb/notepad4e/preferences/PreferenceInitializer.java: -------------------------------------------------------------------------------- 1 | package io.github.pyvesb.notepad4e.preferences; 2 | 3 | import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; 4 | import org.eclipse.jface.preference.IPreferenceStore; 5 | 6 | import io.github.pyvesb.notepad4e.Notepad4e; 7 | 8 | /** 9 | * Class used to initialise the default preference values. 10 | * 11 | * @author Pyves 12 | * 13 | */ 14 | public class PreferenceInitializer extends AbstractPreferenceInitializer { 15 | 16 | /** 17 | * Initialises the default values of the parameters in the plugin's preference page. 18 | */ 19 | @Override 20 | public void initializeDefaultPreferences() { 21 | IPreferenceStore store = Notepad4e.getDefault().getPreferenceStore(); 22 | store.setDefault(Preferences.WRAP, Preferences.WRAP_DEFAULT); 23 | store.setDefault(Preferences.JUSTIFY, Preferences.JUSTIFY_DEFAULT); 24 | store.setDefault(Preferences.ALIGNMENT, Preferences.ALIGNMENT_DEFAULT); 25 | store.setDefault(Preferences.FONT, Preferences.FONT_DEFAULT); 26 | store.setDefault(Preferences.FONT_COLOR, Preferences.FONT_COLOR_DEFAULT); 27 | store.setDefault(Preferences.BACKGROUND_COLOR, Preferences.BACKGROUND_COLOR_DEFAULT); 28 | store.setDefault(Preferences.LINE_SPACING, Preferences.LINE_SPACING_DEFAULT); 29 | store.setDefault(Preferences.NAME_PREFIX, Preferences.NAME_PREFIX_DEFAULT); 30 | store.setDefault(Preferences.CLOSE_CONFIRMATION, Preferences.CLOSE_CONFIRMATION_DEFAULT); 31 | store.setDefault(Preferences.PASTE_CLIPBOARD_IN_NEW_NOTES, Preferences.PASTE_CLIPBOARD_IN_NEW_NOTES_DEFAULT); 32 | store.setDefault(Preferences.BULLET_SPACING, Preferences.BULLET_SPACING_DEFAULT); 33 | store.setDefault(Preferences.SAVE_INTERVAL, Preferences.SAVE_INTERVAL_DEFAULT); 34 | store.setDefault(Preferences.SAVE_LOCATION, Preferences.SAVE_LOCATION_DEFAULT); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/io/github/pyvesb/notepad4e/preferences/PreferencePage.java: -------------------------------------------------------------------------------- 1 | package io.github.pyvesb.notepad4e.preferences; 2 | 3 | import org.eclipse.jface.preference.*; 4 | import org.eclipse.ui.IWorkbenchPreferencePage; 5 | 6 | import io.github.pyvesb.notepad4e.Notepad4e; 7 | import io.github.pyvesb.notepad4e.strings.LocalStrings; 8 | 9 | import org.eclipse.ui.IWorkbench; 10 | 11 | /** 12 | * Class representing the preference page of the plugin. 13 | * 14 | * @author Pyves 15 | * 16 | */ 17 | public class PreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage { 18 | 19 | /** 20 | * Creates the field editors corresponding to the different preferences. 21 | */ 22 | @Override 23 | public void createFieldEditors() { 24 | addField(new RadioGroupFieldEditor(Preferences.ALIGNMENT, LocalStrings.prefAlignment, 2, 25 | new String[][] { { LocalStrings.prefLeft, "left" }, { LocalStrings.prefRight, "right" } }, 26 | getFieldEditorParent(), true)); 27 | addField(new BooleanFieldEditor(Preferences.CLOSE_CONFIRMATION, LocalStrings.prefCloseConfirmation, 28 | getFieldEditorParent())); 29 | addField(new BooleanFieldEditor(Preferences.PASTE_CLIPBOARD_IN_NEW_NOTES, LocalStrings.prefPasteClipboardInNewNotes, 30 | getFieldEditorParent())); 31 | addField(new BooleanFieldEditor(Preferences.WRAP, LocalStrings.prefWrap, getFieldEditorParent())); 32 | addField(new BooleanFieldEditor(Preferences.JUSTIFY, LocalStrings.prefJustify, getFieldEditorParent())); 33 | addField(new IntegerFieldEditor(Preferences.LINE_SPACING, LocalStrings.prefLineSpacing, getFieldEditorParent())); 34 | addField(new IntegerFieldEditor(Preferences.BULLET_SPACING, LocalStrings.prefBulletSpacing, getFieldEditorParent())); 35 | addField(new StringFieldEditor(Preferences.NAME_PREFIX, LocalStrings.prefNamePrefix, getFieldEditorParent())); 36 | addField(new ColorFieldEditor(Preferences.FONT_COLOR, LocalStrings.prefFontColor, getFieldEditorParent())); 37 | addField(new ColorFieldEditor(Preferences.BACKGROUND_COLOR, LocalStrings.prefBackgroundColor, 38 | getFieldEditorParent())); 39 | addField(new FontFieldEditor(Preferences.FONT, LocalStrings.prefFont, getFieldEditorParent())); 40 | addField(new IntegerFieldEditor(Preferences.SAVE_INTERVAL, LocalStrings.prefSaveInterval, getFieldEditorParent())); 41 | addField(new DirectoryFieldEditor(Preferences.SAVE_LOCATION, LocalStrings.prefSaveLocation, getFieldEditorParent())); 42 | } 43 | 44 | /** 45 | * Initialises the preference page. 46 | */ 47 | @Override 48 | public void init(IWorkbench workbench) { 49 | setPreferenceStore(Notepad4e.getDefault().getPreferenceStore()); 50 | setDescription(LocalStrings.prefDesc); 51 | } 52 | } -------------------------------------------------------------------------------- /src/io/github/pyvesb/notepad4e/preferences/Preferences.java: -------------------------------------------------------------------------------- 1 | package io.github.pyvesb.notepad4e.preferences; 2 | 3 | /** 4 | * Class used to list the names of the preferences used by the plugin as well as their default values. 5 | * 6 | * @author Pyves 7 | * 8 | */ 9 | public class Preferences { 10 | 11 | // Names of preferences. 12 | public static final String WRAP = "Wrap"; 13 | public static final String JUSTIFY = "Justify"; 14 | public static final String ALIGNMENT = "Alignment"; 15 | public static final String FONT = "Font"; 16 | public static final String FONT_COLOR = "FontColor"; 17 | public static final String BACKGROUND_COLOR = "BackgroundColor"; 18 | public static final String LINE_SPACING = "LineSpacing"; 19 | public static final String NAME_PREFIX = "NamePrefix"; 20 | public static final String CLOSE_CONFIRMATION = "CloseConfirmation"; 21 | public static final String PASTE_CLIPBOARD_IN_NEW_NOTES = "PasteClipboardInNewNotes"; 22 | public static final String BULLET_SPACING = "BulletSpacing"; 23 | public static final String SAVE_INTERVAL = "SaveInterval"; 24 | public static final String SAVE_LOCATION = "SaveLocation"; 25 | 26 | // Default values of preferences. 27 | public static final boolean WRAP_DEFAULT = true; 28 | public static final boolean JUSTIFY_DEFAULT = false; 29 | public static final String ALIGNMENT_DEFAULT = "left"; 30 | public static final String FONT_DEFAULT = ""; 31 | public static final String FONT_COLOR_DEFAULT = "0,0,0"; 32 | public static final String BACKGROUND_COLOR_DEFAULT = "255,255,255"; 33 | public static final int LINE_SPACING_DEFAULT = 0; 34 | public static final String NAME_PREFIX_DEFAULT = "Note"; 35 | public static final boolean CLOSE_CONFIRMATION_DEFAULT = true; 36 | public static final boolean PASTE_CLIPBOARD_IN_NEW_NOTES_DEFAULT = false; 37 | public static final int BULLET_SPACING_DEFAULT = 15; 38 | public static final int SAVE_INTERVAL_DEFAULT = 120; 39 | public static final String SAVE_LOCATION_DEFAULT = ""; 40 | 41 | private Preferences() { 42 | // Not called. 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/io/github/pyvesb/notepad4e/strings/LocalStrings.java: -------------------------------------------------------------------------------- 1 | package io.github.pyvesb.notepad4e.strings; 2 | 3 | import org.eclipse.osgi.util.NLS; 4 | 5 | /** 6 | * Class used to translate the plugin's messages into different languages. Each translation must be contained in a 7 | * localstring_XX.properties file, where XX represents the locale. 8 | * 9 | * @author Pyves 10 | * 11 | */ 12 | public class LocalStrings extends NLS { 13 | 14 | private static final String BUNDLE_NAME = "io.github.pyvesb.notepad4e.strings.localstrings"; 15 | 16 | public static String dialogCloseLockedMsg; 17 | public static String dialogCloseLockedTitle; 18 | public static String dialogCloseMsg; 19 | public static String dialogCloseTitle; 20 | public static String dialogErrorMsg; 21 | public static String dialogErrorTitle; 22 | public static String dialogExportTitle; 23 | public static String dialogExportedMsg; 24 | public static String dialogExportedTitle; 25 | public static String dialogOverwriteMsg; 26 | public static String dialogOverwriteTitle; 27 | public static String dialogRenameMsg; 28 | public static String dialogRenameTitle; 29 | public static String iconBold; 30 | public static String iconBulletList; 31 | public static String iconChangelog; 32 | public static String iconClearNote; 33 | public static String iconClearStyle; 34 | public static String iconExport; 35 | public static String iconItalic; 36 | public static String iconLock; 37 | public static String iconNewNote; 38 | public static String iconPreferences; 39 | public static String iconStrikeout; 40 | public static String iconUnderline; 41 | public static String iconWebpage; 42 | public static String menuCopy; 43 | public static String menuCut; 44 | public static String menuPaste; 45 | public static String menuRedo; 46 | public static String menuSelectAll; 47 | public static String menuUndo; 48 | public static String prefAlignment; 49 | public static String prefBackgroundColor; 50 | public static String prefBulletSpacing; 51 | public static String prefCloseConfirmation; 52 | public static String prefDesc; 53 | public static String prefFont; 54 | public static String prefFontColor; 55 | public static String prefJustify; 56 | public static String prefLeft; 57 | public static String prefLineSpacing; 58 | public static String prefNamePrefix; 59 | public static String prefPasteClipboardInNewNotes; 60 | public static String prefRight; 61 | public static String prefWrap; 62 | public static String prefSaveInterval; 63 | public static String prefSaveLocation; 64 | public static String getDialogSettingsErrorMsg; 65 | 66 | static { 67 | // Initialise resource bundle. 68 | NLS.initializeMessages(BUNDLE_NAME, LocalStrings.class); 69 | } 70 | 71 | private LocalStrings() { 72 | // Not called. 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/io/github/pyvesb/notepad4e/strings/localstrings.properties: -------------------------------------------------------------------------------- 1 | dialogCloseLockedMsg=This note is locked. Are you really sure you want to close it? 2 | dialogCloseLockedTitle=Close Locked Note 3 | dialogCloseMsg=Are you sure you want to close this note? 4 | dialogCloseTitle=Close Note 5 | dialogErrorMsg=Error while attempting to save the file. 6 | dialogErrorTitle=Error 7 | dialogExportTitle=Export to File 8 | dialogExportedMsg=The note has been succesfully exported. 9 | dialogExportedTitle=Note Exported 10 | dialogOverwriteMsg=Do you want to overwrite? 11 | dialogOverwriteTitle=File Already Exists 12 | dialogRenameMsg=Please select the new name of the note: 13 | dialogRenameTitle=Rename Note 14 | iconBold=Bold 15 | iconBulletList=Bullet List 16 | iconChangelog=Changelog 17 | iconClearNote=Clear Note 18 | iconClearStyle=Clear Style 19 | iconExport=Export Note 20 | iconItalic=Italic 21 | iconLock=Lock/Unlock Note 22 | iconNewNote=New Note 23 | iconPreferences=Preferences 24 | iconStrikeout=Strikeout 25 | iconUnderline=Underline 26 | iconWebpage=Project Webpage 27 | menuCopy=Copy 28 | menuCut=Cut 29 | menuPaste=Paste 30 | menuRedo=Redo 31 | menuSelectAll=Select All 32 | menuUndo=Undo 33 | prefAlignment=Text alignment: 34 | prefBackgroundColor=Background color: 35 | prefBulletSpacing=Bullet list indentation: 36 | prefCloseConfirmation=Confirmation when closing a note (overriden if note locked) 37 | prefDesc=Modify the appearance and several editor properties of Notepad4e.\nEdit shortcuts in the General -> Keys section of Eclipse preferences. 38 | prefFont=Text font: 39 | prefFontColor=Font color: 40 | prefJustify=Justify lines 41 | prefLeft=Left 42 | prefLineSpacing=Line spacing: 43 | prefNamePrefix=Name prefix of new notes: 44 | prefPasteClipboardInNewNotes=Paste current contents of clipboard when creating a new note 45 | prefRight=Right 46 | prefWrap=Wrap lines in notes (automatically enforced if right alignment) 47 | prefSaveInterval=Autosave interval (seconds): 48 | prefSaveLocation=Override state save location: 49 | getDialogSettingsErrorMsg=Error whilst loading DialogSettings. Unable to restore the plugin's state. 50 | -------------------------------------------------------------------------------- /src/io/github/pyvesb/notepad4e/strings/localstrings_fr.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyvesB/Notepad4e/0b19e1207091c7ecf15c7de516d1d3388a2252a5/src/io/github/pyvesb/notepad4e/strings/localstrings_fr.properties -------------------------------------------------------------------------------- /src/io/github/pyvesb/notepad4e/utils/AbstractSelectedNoteAction.java: -------------------------------------------------------------------------------- 1 | package io.github.pyvesb.notepad4e.utils; 2 | 3 | import org.eclipse.jface.action.Action; 4 | 5 | import io.github.pyvesb.notepad4e.views.Note; 6 | import io.github.pyvesb.notepad4e.views.NotepadView; 7 | 8 | /** 9 | * Class used to run an action on the currently selected note. 10 | * 11 | * @author Pyves 12 | * 13 | */ 14 | public abstract class AbstractSelectedNoteAction extends Action { 15 | 16 | private final NotepadView notepadView; 17 | 18 | /** 19 | * Constructor. Sets a reference to the main plugin's view. 20 | * 21 | * @param notepadView 22 | */ 23 | public AbstractSelectedNoteAction(NotepadView notepadView) { 24 | this.notepadView = notepadView; 25 | } 26 | 27 | @Override 28 | public void run() { 29 | Note selectedNote = notepadView.getSelectedNote(); 30 | if (selectedNote != null) { 31 | runSelectedNoteAction(selectedNote); 32 | } 33 | } 34 | 35 | /** 36 | * Runs an action on the currently selected note. 37 | * 38 | * @param selectedNote 39 | */ 40 | protected abstract void runSelectedNoteAction(Note selectedNote); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/io/github/pyvesb/notepad4e/utils/NotepadAction.java: -------------------------------------------------------------------------------- 1 | package io.github.pyvesb.notepad4e.utils; 2 | 3 | import io.github.pyvesb.notepad4e.strings.LocalStrings; 4 | 5 | /** 6 | * Enum used to represent all the possible user actions in the plugin. These are either handled by shortcuts or by 7 | * NotepadView buttons or by both. 8 | * 9 | * @author Pyves 10 | * 11 | */ 12 | public enum NotepadAction { 13 | 14 | NEW_NOTE(LocalStrings.iconNewNote, "notepad4e.command.note.new", "/icons/file-new-16x16.png"), 15 | BOLD_TEXT(LocalStrings.iconBold, "notepad4e.command.text.bold", "/icons/style_bold.gif"), 16 | ITALIC_TEXT(LocalStrings.iconItalic, "notepad4e.command.text.italic", "/icons/style_italic.gif"), 17 | UNDERLINE_TEXT(LocalStrings.iconUnderline, "notepad4e.command.text.underline", "/icons/style_underline.gif"), 18 | STRIKEOUT_TEXT(LocalStrings.iconStrikeout, "notepad4e.command.text.strikeout", "/icons/style_strikeout.png"), 19 | CLEAR_STYLE_TEXT(LocalStrings.iconClearStyle, "notepad4e.command.text.clear", "/icons/clear_co.png"), 20 | BULLET_LIST(LocalStrings.iconBulletList, "notepad4e.command.bullet.list", "/icons/bullets.png"), 21 | CLEAR_NOTE(LocalStrings.iconClearNote, "notepad4e.command.note.clear", "/icons/clear.png"), 22 | UNDO_TEXT(null, "notepad4e.command.text.undo", null), 23 | REDO_TEXT(null, "notepad4e.command.text.redo", null), 24 | CLOSE_NOTE(null, "notepad4e.command.note.close", null), 25 | TOGGLE_EDITABLE_NOTE(LocalStrings.iconLock, null, "/icons/deadlock_view.png"), 26 | EXPORT_NOTE(LocalStrings.iconExport, null, "/icons/save_edit.png"), 27 | PREFERENCES(LocalStrings.iconPreferences, null, "/icons/settings_obj.png"), 28 | WEBSITE(LocalStrings.iconWebpage, null, "/icons/web.png"), 29 | CHANGELOG(LocalStrings.iconChangelog, null, "/icons/change.gif"); 30 | 31 | private final String text; 32 | private final String commandID; 33 | private final String imagePath; 34 | 35 | private NotepadAction(String text, String commandID, String image) { 36 | this.text = text; 37 | this.commandID = commandID; 38 | this.imagePath = image; 39 | } 40 | 41 | public static NotepadAction of(String commandID) { 42 | for (NotepadAction notepadAction : values()) { 43 | if (commandID.equals(notepadAction.commandID)) { 44 | return notepadAction; 45 | } 46 | } 47 | return null; 48 | } 49 | 50 | public final String getText() { 51 | return text; 52 | } 53 | 54 | public final String getCommandID() { 55 | return commandID; 56 | } 57 | 58 | public final String getImagePath() { 59 | return imagePath; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/io/github/pyvesb/notepad4e/utils/ShortcutHandler.java: -------------------------------------------------------------------------------- 1 | package io.github.pyvesb.notepad4e.utils; 2 | 3 | import org.eclipse.core.commands.AbstractHandler; 4 | import org.eclipse.core.commands.ExecutionEvent; 5 | import org.eclipse.core.commands.ExecutionException; 6 | 7 | import io.github.pyvesb.notepad4e.views.Note; 8 | import io.github.pyvesb.notepad4e.views.NotepadView; 9 | 10 | /** 11 | * Class used to listen to keyboard events and launch actions accordingly. 12 | * 13 | * @author Pyves 14 | * 15 | */ 16 | public class ShortcutHandler extends AbstractHandler { 17 | 18 | private final NotepadView notepadView; 19 | 20 | /** 21 | * Constructor. Sets a reference to the main plugin's view. 22 | * 23 | * @param notepadView 24 | */ 25 | public ShortcutHandler(NotepadView notepadView) { 26 | this.notepadView = notepadView; 27 | } 28 | 29 | /** 30 | * Deals with keyboard events, in other words Notepad4e shortcuts. This handler listens to all events, filtering 31 | * must therefore be done accordingly. 32 | * 33 | * @param event 34 | */ 35 | @Override 36 | public Object execute(ExecutionEvent event) throws ExecutionException { 37 | if (event.getCommand() == null) { 38 | return null; 39 | } 40 | 41 | NotepadAction action = NotepadAction.of(event.getCommand().getId()); 42 | if (action == NotepadAction.NEW_NOTE) { 43 | notepadView.addNewNote(); 44 | } else if (action == NotepadAction.CLOSE_NOTE) { 45 | notepadView.closeCurrentSelection(); 46 | } else { 47 | Note selectedNote = notepadView.getSelectedNote(); 48 | if (selectedNote != null) { 49 | executeNoteAction(action, selectedNote); 50 | } 51 | } 52 | return null; 53 | } 54 | 55 | /** 56 | * Executes an action on the selected note. 57 | * 58 | * @param action 59 | * @param selectedNote 60 | */ 61 | private void executeNoteAction(NotepadAction action, Note selectedNote) { 62 | switch (action) { 63 | case BOLD_TEXT: 64 | selectedNote.boldSelection(); 65 | break; 66 | case ITALIC_TEXT: 67 | selectedNote.italicSelection(); 68 | break; 69 | case UNDERLINE_TEXT: 70 | selectedNote.underlineSelection(); 71 | break; 72 | case STRIKEOUT_TEXT: 73 | selectedNote.strikeoutSelection(); 74 | break; 75 | case BULLET_LIST: 76 | selectedNote.bulletListSelection(); 77 | break; 78 | case CLEAR_STYLE_TEXT: 79 | selectedNote.clearSelectionStyles(); 80 | break; 81 | case CLEAR_NOTE: 82 | selectedNote.clearText(); 83 | break; 84 | case UNDO_TEXT: 85 | selectedNote.undo(); 86 | break; 87 | case REDO_TEXT: 88 | selectedNote.redo(); 89 | break; 90 | default: 91 | break; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/io/github/pyvesb/notepad4e/utils/UndoRedoManager.java: -------------------------------------------------------------------------------- 1 | package io.github.pyvesb.notepad4e.utils; 2 | 3 | import java.util.ArrayDeque; 4 | import java.util.Deque; 5 | 6 | import org.eclipse.swt.custom.StyleRange; 7 | 8 | import io.github.pyvesb.notepad4e.views.Note; 9 | 10 | /** 11 | * Class in charge of handling the undo and redo actions of a note. 12 | * 13 | * @author Pyves 14 | * 15 | */ 16 | public class UndoRedoManager { 17 | 18 | // Used to prevent the size of undo and deques queues from growing indefinitely. 19 | private static final int MAX_DEQUE_SIZES = 200; 20 | 21 | // Reference to the note this manager is handling. 22 | private final Note note; 23 | // Deques used to store note states. 24 | private final Deque undoDeque = new ArrayDeque<>(MAX_DEQUE_SIZES); 25 | private final Deque redoDeque = new ArrayDeque<>(MAX_DEQUE_SIZES); 26 | 27 | /** 28 | * Constructor, connects the note and the new manager instance. 29 | * 30 | * @param note 31 | */ 32 | public UndoRedoManager(Note note) { 33 | this.note = note; 34 | } 35 | 36 | /** 37 | * Saves a note's state to allow undo and redo operations to be performed. 38 | */ 39 | public void saveNoteState() { 40 | // Empty redo deque. 41 | redoDeque.clear(); 42 | 43 | undoDeque.push(new NoteState(note.getText(), note.getCaretOffset(), note.getStyleRanges(), getBulletLineMapping())); 44 | 45 | // Limit maximum size of deque by clearing oldest states. 46 | if (undoDeque.size() > MAX_DEQUE_SIZES) { 47 | undoDeque.pollLast(); 48 | } 49 | } 50 | 51 | /** 52 | * Indicates whether the manager has no currently saved state. 53 | * 54 | * @return true if undoDeque is empty, false otherwise 55 | */ 56 | public boolean isNoteStateEmpty() { 57 | return undoDeque.isEmpty(); 58 | } 59 | 60 | /** 61 | * Performs an undo action. 62 | */ 63 | public void undo() { 64 | NoteState noteState = undoDeque.pollFirst(); 65 | if (noteState != null) { // Something to undo. 66 | if (redoDeque.isEmpty()) { 67 | // Pushes the current state of the note before a sequence of undo operations is performed. 68 | redoDeque.push(new NoteState(note.getText(), note.getCaretOffset(), note.getStyleRanges(), 69 | getBulletLineMapping())); 70 | } 71 | restoreState(noteState); 72 | redoDeque.push(noteState); 73 | } 74 | } 75 | 76 | /** 77 | * Performs a redo action. 78 | */ 79 | public void redo() { 80 | if (!redoDeque.isEmpty()) { // Something to redo. 81 | undoDeque.push(redoDeque.pollFirst()); 82 | if (redoDeque.size() == 1) { 83 | // Last possible redo operation, clear deque. 84 | restoreState(redoDeque.pollFirst()); 85 | } else { 86 | restoreState(redoDeque.peekFirst()); 87 | } 88 | } 89 | } 90 | 91 | /** 92 | * Restores the note to a previous state. 93 | * 94 | * @param noteState 95 | */ 96 | private void restoreState(NoteState noteState) { 97 | // Set the text via the content to avoid firing events which would be picked up by the manager whilst 98 | // performing undo redo operations. 99 | note.getContent().setText(noteState.getText()); 100 | 101 | note.setCaretOffset(noteState.getCaretOffset()); 102 | 103 | note.setStyleRanges(noteState.getStyles()); 104 | 105 | setBulletLineMapping(noteState.getBulletLineMapping()); 106 | } 107 | 108 | /** 109 | * Constructs an array containing the bullets for the note. 110 | * 111 | * @return array of bullets indexed by line number; null value if no bullet on the line 112 | */ 113 | private boolean[] getBulletLineMapping() { 114 | boolean[] bullets = new boolean[note.getLineCount()]; 115 | for (int line = 0; line < bullets.length; ++line) { 116 | bullets[line] = (note.getLineBullet(line) != null); 117 | } 118 | return bullets; 119 | } 120 | 121 | /** 122 | * Sets bullets in the note given a bullet line mapping array. 123 | * 124 | * @param bulletLineMapping 125 | */ 126 | private void setBulletLineMapping(boolean[] bulletLineMapping) { 127 | if (bulletLineMapping.length > 0) { 128 | // It's more efficient to set several bullets at the same time, we therefore look for the longest sequence 129 | // where all the bullets have the same state (i.e. they exist or don't). 130 | boolean currentSequenceState = bulletLineMapping[0]; 131 | int sequenceLineStart = 0; 132 | for (int line = 1; line < bulletLineMapping.length; ++line) { 133 | if (currentSequenceState != bulletLineMapping[line]) { 134 | note.setLineBullet(sequenceLineStart, line - sequenceLineStart, currentSequenceState); 135 | sequenceLineStart = line; 136 | currentSequenceState = !currentSequenceState; 137 | } 138 | } 139 | note.setLineBullet(sequenceLineStart, bulletLineMapping.length - sequenceLineStart, currentSequenceState); 140 | } 141 | } 142 | 143 | /** 144 | * Class used to keep track of the state of the note in order to perform undo and redo actions. 145 | * 146 | * @author Pyves 147 | * 148 | */ 149 | private static final class NoteState { 150 | 151 | // Text content of the note. 152 | final String text; 153 | // Offset of the caret relative to the start of the text. 154 | final int caretOffset; 155 | // Styles of the text, e.g. bold, italic, etc. 156 | final StyleRange[] styles; 157 | // Indicates whether a bullet is present at the beginning of each text line. 158 | final boolean[] bulletLineMapping; 159 | 160 | NoteState(String text, int caretOffset, StyleRange[] styles, boolean[] bulletLineMapping) { 161 | this.text = text; 162 | this.caretOffset = caretOffset; 163 | this.styles = styles; 164 | this.bulletLineMapping = bulletLineMapping; 165 | } 166 | 167 | String getText() { 168 | return text; 169 | } 170 | 171 | int getCaretOffset() { 172 | return caretOffset; 173 | } 174 | 175 | StyleRange[] getStyles() { 176 | return styles; 177 | } 178 | 179 | boolean[] getBulletLineMapping() { 180 | return bulletLineMapping; 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/io/github/pyvesb/notepad4e/views/Note.java: -------------------------------------------------------------------------------- 1 | package io.github.pyvesb.notepad4e.views; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.io.PrintWriter; 7 | 8 | import org.eclipse.core.runtime.IStatus; 9 | import org.eclipse.core.runtime.Status; 10 | import org.eclipse.core.runtime.preferences.IEclipsePreferences; 11 | import org.eclipse.core.runtime.preferences.InstanceScope; 12 | import org.eclipse.jface.dialogs.MessageDialog; 13 | import org.eclipse.swt.SWT; 14 | import org.eclipse.swt.custom.Bullet; 15 | import org.eclipse.swt.custom.ST; 16 | import org.eclipse.swt.custom.StyleRange; 17 | import org.eclipse.swt.custom.StyledText; 18 | import org.eclipse.swt.events.SelectionAdapter; 19 | import org.eclipse.swt.events.SelectionEvent; 20 | import org.eclipse.swt.graphics.Color; 21 | import org.eclipse.swt.graphics.Font; 22 | import org.eclipse.swt.graphics.FontData; 23 | import org.eclipse.swt.graphics.GlyphMetrics; 24 | import org.eclipse.swt.graphics.Point; 25 | import org.eclipse.swt.widgets.Composite; 26 | import org.eclipse.swt.widgets.Display; 27 | import org.eclipse.swt.widgets.FileDialog; 28 | import org.eclipse.swt.widgets.Menu; 29 | import org.eclipse.swt.widgets.MenuItem; 30 | import org.eclipse.ui.IWorkbenchPartSite; 31 | 32 | import io.github.pyvesb.notepad4e.Notepad4e; 33 | import io.github.pyvesb.notepad4e.preferences.Preferences; 34 | import io.github.pyvesb.notepad4e.strings.LocalStrings; 35 | import io.github.pyvesb.notepad4e.utils.UndoRedoManager; 36 | 37 | /** 38 | * Class representing an individual note in the plugin's view. 39 | * 40 | * @author Pyves 41 | * 42 | */ 43 | public class Note extends StyledText { 44 | 45 | // Used to parse strings. 46 | private static final String SERIALISATION_DELIMITER = ","; 47 | 48 | // Used to enable undo and redo actions. 49 | private final UndoRedoManager undoRedoManager; 50 | // User defined preferences. 51 | private final IEclipsePreferences preferences; 52 | // Used at the beginning of each line in lists. 53 | private final Bullet bullet; 54 | 55 | // Appearance parameters of the note. 56 | private Color fontColor; 57 | private Color backgroundColor; 58 | private Font font; 59 | // Menu items (mouse right-click). 60 | private MenuItem menuItemUndo; 61 | private MenuItem menuItemRedo; 62 | private MenuItem menuItemCut; 63 | private MenuItem menuItemCopy; 64 | private MenuItem menuItemPaste; 65 | private MenuItem menuItemSelectAll; 66 | private MenuItem menuItemSeparator1; 67 | private MenuItem menuItemSeparator2; 68 | 69 | private enum TextStyle { 70 | BOLD, ITALIC, UNDERLINE, STRIKEOUT 71 | } 72 | 73 | /** 74 | * Constructor. Sets properties of the editor window. 75 | * 76 | * @param parent 77 | * @param text 78 | * @param bullets 79 | * @param style 80 | * @param editable 81 | */ 82 | public Note(Composite parent, String text, String style, String bullets, boolean editable) { 83 | // Enable multiple lines and scroll bars. 84 | super(parent, SWT.V_SCROLL | SWT.H_SCROLL); 85 | 86 | preferences = InstanceScope.INSTANCE.getNode(Notepad4e.PLUGIN_ID); 87 | 88 | StyleRange bulletStyle = new StyleRange(); 89 | bulletStyle.metrics = new GlyphMetrics(0, 0, 0); 90 | bullet = new Bullet(ST.BULLET_DOT, bulletStyle); 91 | 92 | // Scroll bars only appear when the text extends beyond the note window. 93 | setAlwaysShowScrollBars(false); 94 | setParametersFromPreferences(); 95 | setText(text); 96 | deserialiseStyle(style); 97 | deserialiseBullets(bullets); 98 | initialiseMenu(); 99 | 100 | undoRedoManager = new UndoRedoManager(this); 101 | // Listen to text modifications. 102 | addVerifyListener(event -> { 103 | // Save state if new word OR no previously state OR overwriting existing text OR pasting several chars. 104 | if (" ".equals(event.text) || undoRedoManager.isNoteStateEmpty() || event.end - event.start > 0 105 | || event.text.length() > 1) { 106 | undoRedoManager.saveNoteState(); 107 | } 108 | }); 109 | 110 | if (!editable) { 111 | toggleEditable(); 112 | } 113 | } 114 | 115 | /** 116 | * Disposes the resources owned by the note. 117 | */ 118 | @Override 119 | public void dispose() { 120 | fontColor.dispose(); 121 | backgroundColor.dispose(); 122 | if (font != null) { 123 | font.dispose(); 124 | } 125 | menuItemUndo.dispose(); 126 | menuItemRedo.dispose(); 127 | menuItemCut.dispose(); 128 | menuItemCopy.dispose(); 129 | menuItemPaste.dispose(); 130 | menuItemSelectAll.dispose(); 131 | menuItemSeparator1.dispose(); 132 | menuItemSeparator2.dispose(); 133 | super.dispose(); 134 | } 135 | 136 | /** 137 | * Sets properties that can be defined by the user in the plugin's preferences page. 138 | */ 139 | public void setParametersFromPreferences() { 140 | // Line spacing parameter. 141 | setLineSpacing(preferences.getInt(Preferences.LINE_SPACING, Preferences.LINE_SPACING_DEFAULT)); 142 | 143 | // Set bullet indentation spacing (width of GlyphMetrics) parameter. 144 | bullet.style.metrics.width = preferences.getInt(Preferences.BULLET_SPACING, Preferences.BULLET_SPACING_DEFAULT); 145 | 146 | // Line wrap parameter. 147 | setWordWrap(preferences.getBoolean(Preferences.WRAP, Preferences.WRAP_DEFAULT)); 148 | 149 | // Text justify parameter. 150 | setJustify(preferences.getBoolean(Preferences.JUSTIFY, Preferences.JUSTIFY_DEFAULT)); 151 | 152 | // Alignment parameter (left or right). 153 | if ("right".equals(preferences.get(Preferences.ALIGNMENT, Preferences.ALIGNMENT_DEFAULT))) { 154 | setAlignment(SWT.RIGHT); 155 | // Word wrapping must be enabled for right alignment to be effective. 156 | setWordWrap(true); 157 | } else { 158 | setAlignment(SWT.LEFT); 159 | } 160 | 161 | // Font color parameter. 162 | String[] fontColorRGBStrings = preferences 163 | .get(Preferences.FONT_COLOR, Preferences.FONT_COLOR_DEFAULT) 164 | .split(SERIALISATION_DELIMITER); 165 | // The strings in the above array correspond to the red, green and blue colors. 166 | fontColor = new Color(Display.getCurrent(), Integer.parseInt(fontColorRGBStrings[0]), 167 | Integer.parseInt(fontColorRGBStrings[1]), Integer.parseInt(fontColorRGBStrings[2])); 168 | setForeground(fontColor); 169 | 170 | // Background color parameter. 171 | String[] backgroundColorRGBStrings = preferences 172 | .get(Preferences.BACKGROUND_COLOR, Preferences.BACKGROUND_COLOR_DEFAULT) 173 | .split(SERIALISATION_DELIMITER); 174 | // The strings in the above array correspond to the red, green and blue colors. 175 | backgroundColor = new Color(Display.getCurrent(), Integer.parseInt(backgroundColorRGBStrings[0]), 176 | Integer.parseInt(backgroundColorRGBStrings[1]), Integer.parseInt(backgroundColorRGBStrings[2])); 177 | setBackground(backgroundColor); 178 | 179 | // Font parameter; a semicolon is appended by the Eclipse API when retrieving it from the plugin's preference 180 | // page, it must be deleted. 181 | String fontString = preferences.get(Preferences.FONT, Preferences.FONT_DEFAULT).replace(";", ""); 182 | // An empty string is returned when the user has not set the font in the preferences; do not set the font so the 183 | // plugin will display the default font of the StyledText component instead. 184 | if (!fontString.isEmpty()) { 185 | font = new Font(Display.getCurrent(), new FontData(fontString)); 186 | setFont(font); 187 | } 188 | } 189 | 190 | /** 191 | * Undos latest Note modification. 192 | */ 193 | public void undo() { 194 | if (getEditable()) { 195 | undoRedoManager.undo(); 196 | } 197 | } 198 | 199 | /** 200 | * Redos latest Note modification. 201 | */ 202 | public void redo() { 203 | if (getEditable()) { 204 | undoRedoManager.redo(); 205 | } 206 | } 207 | 208 | /** 209 | * Removes all the text from the note. 210 | */ 211 | public void clearText() { 212 | if (getEditable()) { 213 | setText(""); 214 | } 215 | } 216 | 217 | /** 218 | * Applies a bold style to the currently selected text. 219 | */ 220 | public void boldSelection() { 221 | addStyleToSelection(TextStyle.BOLD); 222 | } 223 | 224 | /** 225 | * Applies an italic style to the currently selected text. 226 | */ 227 | public void italicSelection() { 228 | addStyleToSelection(TextStyle.ITALIC); 229 | } 230 | 231 | /** 232 | * Applies an underlined style to the currently selected text. 233 | */ 234 | public void underlineSelection() { 235 | addStyleToSelection(TextStyle.UNDERLINE); 236 | } 237 | 238 | /** 239 | * Applies a strikeout style to the currently selected text. 240 | */ 241 | public void strikeoutSelection() { 242 | addStyleToSelection(TextStyle.STRIKEOUT); 243 | } 244 | 245 | /** 246 | * Applies a bullet list style to the currently selected lines. 247 | */ 248 | public void bulletListSelection() { 249 | if (getEditable()) { 250 | // Save bullet state prior to modification for undo actions. 251 | undoRedoManager.saveNoteState(); 252 | 253 | Point selection = getSelectionRange(); 254 | int selectionStartLine = getLineAtOffset(selection.x); 255 | int selectionEndLine = getLineAtOffset(selection.x + selection.y); 256 | int bulletsInSelection = 0; 257 | // Count number of lines that currently have a bullet. 258 | for (int line = selectionStartLine; line <= selectionEndLine; ++line) { 259 | if (getLineBullet(line) != null) { 260 | ++bulletsInSelection; 261 | } 262 | } 263 | 264 | int selectedLines = selectionEndLine - selectionStartLine + 1; 265 | // If all lines already have bullets, remove them all, otherwise add them. 266 | setLineBullet(selectionStartLine, selectedLines, bulletsInSelection != selectedLines); 267 | } 268 | } 269 | 270 | /** 271 | * Removes all styles from the current selection. 272 | */ 273 | public void clearSelectionStyles() { 274 | Point selectionRange = getSelectionRange(); 275 | if (getEditable() && selectionRange.y != 0) { 276 | // Save style state prior to modification for undo actions. 277 | undoRedoManager.saveNoteState(); 278 | 279 | // No colors are specified as they are defined by the plugin's preferences. 280 | StyleRange styleRange = new StyleRange(selectionRange.x, selectionRange.y, null, null, SWT.NORMAL); 281 | setStyleRange(styleRange); 282 | } 283 | } 284 | 285 | /** 286 | * Makes the note read-only or editable again. 287 | */ 288 | public void toggleEditable() { 289 | boolean newState = !getEditable(); 290 | setEditable(newState); 291 | menuItemUndo.setEnabled(newState); 292 | menuItemRedo.setEnabled(newState); 293 | menuItemCut.setEnabled(newState); 294 | menuItemPaste.setEnabled(newState); 295 | } 296 | 297 | /** 298 | * Creates a string giving a description of the styles in the current note. 299 | * 300 | * @return CSV string containing a serialised representation of the styles 301 | */ 302 | public String serialiseStyle() { 303 | StringBuilder styleSerialisation = new StringBuilder(); 304 | StyleRange[] currentStyles = getStyleRanges(); 305 | // Append integers corresponding to various information of each style range object, separated by 306 | // STRING_SEPARATOR. 307 | for (StyleRange currentStyle : currentStyles) { 308 | styleSerialisation.append(currentStyle.start).append(SERIALISATION_DELIMITER); 309 | styleSerialisation.append(currentStyle.length).append(SERIALISATION_DELIMITER); 310 | styleSerialisation.append(currentStyle.fontStyle).append(SERIALISATION_DELIMITER); 311 | // If underlined, 1, else 0. 312 | styleSerialisation.append(currentStyle.underline ? 1 : 0).append(SERIALISATION_DELIMITER); 313 | // If strikeout, 1, else 0. 314 | styleSerialisation.append(currentStyle.strikeout ? 1 : 0).append(SERIALISATION_DELIMITER); 315 | } 316 | return styleSerialisation.toString(); 317 | } 318 | 319 | /** 320 | * Creates a string giving a description of the bullets in the current note. 321 | * 322 | * @return CSV string containing a serialised representation of the bullets 323 | */ 324 | public String serialiseBullets() { 325 | StringBuilder bulletLines = new StringBuilder(); 326 | for (int line = 0; line < getLineCount(); ++line) { 327 | if (getLineBullet(line) != null) { 328 | // Bullet found: add line number with separator. 329 | bulletLines.append(line).append(SERIALISATION_DELIMITER); 330 | } 331 | } 332 | // Remove trailing separator. 333 | return bulletLines.length() > 1 ? bulletLines.substring(0, bulletLines.length() - 1) : ""; 334 | } 335 | 336 | /** 337 | * Exports the brute text in the current note as a text file. 338 | * 339 | * @param iWorkbenchPartSite 340 | */ 341 | public void exportToFile(IWorkbenchPartSite iWorkbenchPartSite) { 342 | // Retrieve the file to save to with an explorer window. 343 | FileDialog fileDialog = new FileDialog(iWorkbenchPartSite.getShell(), SWT.SAVE); 344 | fileDialog.setText(LocalStrings.dialogExportTitle); 345 | String fileName = fileDialog.open(); 346 | // Invalid name specified. 347 | if (fileName == null || fileName.isEmpty()) { 348 | return; 349 | } 350 | 351 | File file = new File(fileName); 352 | if (!file.exists() || MessageDialog.openQuestion(iWorkbenchPartSite.getShell(), 353 | LocalStrings.dialogOverwriteTitle, LocalStrings.dialogOverwriteMsg)) { 354 | // Write the current note's text to the file, with handling of IO exceptions. 355 | try (FileOutputStream outStream = new FileOutputStream(file); 356 | PrintWriter printStream = new PrintWriter(outStream)) { 357 | printStream.print(getText()); 358 | printStream.flush(); 359 | MessageDialog.openInformation(iWorkbenchPartSite.getShell(), LocalStrings.dialogExportedTitle, 360 | LocalStrings.dialogExportedMsg); 361 | } catch (IOException e) { 362 | MessageDialog.openInformation(iWorkbenchPartSite.getShell(), LocalStrings.dialogErrorTitle, 363 | LocalStrings.dialogErrorMsg); 364 | Notepad4e.getDefault().getLog() 365 | .log(new Status(IStatus.ERROR, Notepad4e.PLUGIN_ID, LocalStrings.dialogErrorMsg, e)); 366 | } 367 | } 368 | } 369 | 370 | /** 371 | * Adds or removes a bullet at the start of the specified line. 372 | * 373 | * @param line 374 | * @param count 375 | * @param isPresent 376 | */ 377 | public void setLineBullet(int line, int count, boolean isPresent) { 378 | setLineBullet(line, count, isPresent ? bullet : null); 379 | } 380 | 381 | /** 382 | * Applies styles to the current note based on a styles' serialisation string. 383 | * 384 | * @param serialisation 385 | */ 386 | private void deserialiseStyle(String serialisation) { 387 | // Style can be null if new note. 388 | if (serialisation != null && !serialisation.isEmpty()) { 389 | String[] integers = serialisation.split(SERIALISATION_DELIMITER); 390 | StyleRange[] styles = new StyleRange[integers.length / 5]; 391 | // Do the parsing. 392 | for (int styleIndex = 0; styleIndex < styles.length; ++styleIndex) { 393 | // Each StyleRange object has 5 corresponding integers in the CSV string. 394 | int integerIndex = 5 * styleIndex; 395 | styles[styleIndex] = new StyleRange(); 396 | styles[styleIndex].start = Integer.parseInt(integers[integerIndex]); 397 | styles[styleIndex].length = Integer.parseInt(integers[integerIndex + 1]); 398 | styles[styleIndex].fontStyle = Integer.parseInt(integers[integerIndex + 2]); 399 | styles[styleIndex].underline = (Integer.parseInt(integers[integerIndex + 3]) == 1) ? true : false; 400 | styles[styleIndex].strikeout = (Integer.parseInt(integers[integerIndex + 4]) == 1) ? true : false; 401 | } 402 | // Apply the parsed styles. 403 | setStyleRanges(styles); 404 | } 405 | } 406 | 407 | /** 408 | * Adds bullets to the current note based on a bullets' serialisation string (for instance "0,1,4"). 409 | * 410 | * @param serialisation 411 | */ 412 | private void deserialiseBullets(String serialisation) { 413 | // Bullets can be null if new note or upgrading from old plugin version. 414 | if (serialisation != null && !serialisation.isEmpty()) { 415 | for (String lineNumber : serialisation.split(SERIALISATION_DELIMITER)) { 416 | setLineBullet(Integer.parseInt(lineNumber), 1, bullet); 417 | } 418 | } 419 | } 420 | 421 | /** 422 | * Initialises the menu triggered by a right-click inside the note. 423 | */ 424 | private void initialiseMenu() { 425 | Menu menu = new Menu(getShell(), SWT.POP_UP); 426 | menuItemUndo = new MenuItem(menu, SWT.NONE); 427 | menuItemUndo.setText(LocalStrings.menuUndo); 428 | menuItemUndo.addSelectionListener(new SelectionAdapter() { 429 | @Override 430 | public void widgetSelected(SelectionEvent event) { 431 | undo(); 432 | } 433 | }); 434 | menuItemRedo = new MenuItem(menu, SWT.NONE); 435 | menuItemRedo.setText(LocalStrings.menuRedo); 436 | menuItemRedo.addSelectionListener(new SelectionAdapter() { 437 | @Override 438 | public void widgetSelected(SelectionEvent event) { 439 | redo(); 440 | } 441 | }); 442 | menuItemSeparator1 = new MenuItem(menu, SWT.SEPARATOR); 443 | menuItemCut = new MenuItem(menu, SWT.NONE); 444 | menuItemCut.setText(LocalStrings.menuCut); 445 | menuItemCut.addSelectionListener(new SelectionAdapter() { 446 | @Override 447 | public void widgetSelected(SelectionEvent event) { 448 | cut(); 449 | } 450 | }); 451 | menuItemCopy = new MenuItem(menu, SWT.NONE); 452 | menuItemCopy.setText(LocalStrings.menuCopy); 453 | menuItemCopy.addSelectionListener(new SelectionAdapter() { 454 | @Override 455 | public void widgetSelected(SelectionEvent event) { 456 | copy(); 457 | } 458 | }); 459 | menuItemPaste = new MenuItem(menu, SWT.NONE); 460 | menuItemPaste.setText(LocalStrings.menuPaste); 461 | menuItemPaste.addSelectionListener(new SelectionAdapter() { 462 | @Override 463 | public void widgetSelected(SelectionEvent event) { 464 | paste(); 465 | } 466 | }); 467 | menuItemSeparator2 = new MenuItem(menu, SWT.SEPARATOR); 468 | menuItemSelectAll = new MenuItem(menu, SWT.NONE); 469 | menuItemSelectAll.setText(LocalStrings.menuSelectAll); 470 | menuItemSelectAll.addSelectionListener(new SelectionAdapter() { 471 | @Override 472 | public void widgetSelected(SelectionEvent event) { 473 | selectAll(); 474 | } 475 | }); 476 | setMenu(menu); 477 | } 478 | 479 | /** 480 | * Applies a new style to the currently selected text. 481 | * 482 | * @param newStyle 483 | */ 484 | private void addStyleToSelection(TextStyle newStyle) { 485 | Point selectionRange = getSelectionRange(); 486 | // Only attempt to apply styles if text is selected and note editable. 487 | if (getEditable() && selectionRange.y != 0) { 488 | // Save style state prior to modification for undo actions. 489 | undoRedoManager.saveNoteState(); 490 | 491 | // Retrieve the current styles in the selection. If the selection (or parts of it) does not have any style, 492 | // there are no corresponding entries in the following array. 493 | StyleRange[] currentStyles = getStyleRanges(selectionRange.x, selectionRange.y); 494 | 495 | StyleRange selectionStyleRange = new StyleRange(selectionRange.x, selectionRange.y, null, null); 496 | addStyleToStyleRange(newStyle, selectionStyleRange); 497 | // Apply the style to the whole selection range; ranges that previously had no style and that are are not 498 | // accounted for in currentStyles now have the wanted style. 499 | setStyleRange(selectionStyleRange); 500 | 501 | // The above call overwrote the previous styles; the previous styles are re-applied with the additional 502 | // new one. 503 | for (StyleRange currentStyle : currentStyles) { 504 | addStyleToStyleRange(newStyle, currentStyle); 505 | setStyleRange(currentStyle); 506 | } 507 | } 508 | } 509 | 510 | /** 511 | * Adds a new style to the StyleRange object. 512 | * 513 | * @param newStyle 514 | * @param styleRange 515 | */ 516 | private void addStyleToStyleRange(TextStyle newStyle, StyleRange styleRange) { 517 | switch (newStyle) { 518 | case BOLD: 519 | styleRange.fontStyle |= SWT.BOLD; 520 | break; 521 | case ITALIC: 522 | styleRange.fontStyle |= SWT.ITALIC; 523 | break; 524 | case UNDERLINE: 525 | styleRange.underline = true; 526 | break; 527 | case STRIKEOUT: 528 | styleRange.strikeout = true; 529 | break; 530 | default: 531 | break; 532 | } 533 | } 534 | } 535 | -------------------------------------------------------------------------------- /src/io/github/pyvesb/notepad4e/views/NotepadView.java: -------------------------------------------------------------------------------- 1 | package io.github.pyvesb.notepad4e.views; 2 | 3 | import java.net.URL; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | import org.eclipse.core.runtime.FileLocator; 7 | import org.eclipse.core.runtime.IProgressMonitor; 8 | import org.eclipse.core.runtime.IStatus; 9 | import org.eclipse.core.runtime.Path; 10 | import org.eclipse.core.runtime.Platform; 11 | import org.eclipse.core.runtime.Status; 12 | import org.eclipse.core.runtime.jobs.Job; 13 | import org.eclipse.core.runtime.preferences.IEclipsePreferences; 14 | import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; 15 | import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; 16 | import org.eclipse.core.runtime.preferences.InstanceScope; 17 | import org.eclipse.jface.action.Action; 18 | import org.eclipse.jface.action.IMenuManager; 19 | import org.eclipse.jface.action.IToolBarManager; 20 | import org.eclipse.jface.action.Separator; 21 | import org.eclipse.jface.bindings.Binding; 22 | import org.eclipse.jface.dialogs.IDialogSettings; 23 | import org.eclipse.jface.dialogs.InputDialog; 24 | import org.eclipse.jface.dialogs.MessageDialog; 25 | import org.eclipse.jface.preference.PreferenceDialog; 26 | import org.eclipse.jface.resource.ImageDescriptor; 27 | import org.eclipse.jface.util.Geometry; 28 | import org.eclipse.swt.SWT; 29 | import org.eclipse.swt.custom.CTabFolder; 30 | import org.eclipse.swt.custom.CTabFolder2Adapter; 31 | import org.eclipse.swt.custom.CTabFolderEvent; 32 | import org.eclipse.swt.custom.CTabItem; 33 | import org.eclipse.swt.dnd.Clipboard; 34 | import org.eclipse.swt.dnd.DND; 35 | import org.eclipse.swt.dnd.TextTransfer; 36 | import org.eclipse.swt.events.MouseAdapter; 37 | import org.eclipse.swt.events.MouseEvent; 38 | import org.eclipse.swt.events.SelectionAdapter; 39 | import org.eclipse.swt.events.SelectionEvent; 40 | import org.eclipse.swt.graphics.Point; 41 | import org.eclipse.swt.graphics.Rectangle; 42 | import org.eclipse.swt.program.Program; 43 | import org.eclipse.swt.widgets.Composite; 44 | import org.eclipse.swt.widgets.Display; 45 | import org.eclipse.swt.widgets.Tracker; 46 | import org.eclipse.ui.IActionBars; 47 | import org.eclipse.ui.PlatformUI; 48 | import org.eclipse.ui.contexts.IContextService; 49 | import org.eclipse.ui.dialogs.PreferencesUtil; 50 | import org.eclipse.ui.handlers.IHandlerService; 51 | import org.eclipse.ui.keys.IBindingService; 52 | import org.eclipse.ui.part.ViewPart; 53 | 54 | import io.github.pyvesb.notepad4e.Notepad4e; 55 | import io.github.pyvesb.notepad4e.preferences.Preferences; 56 | import io.github.pyvesb.notepad4e.strings.LocalStrings; 57 | import io.github.pyvesb.notepad4e.utils.AbstractSelectedNoteAction; 58 | import io.github.pyvesb.notepad4e.utils.NotepadAction; 59 | import io.github.pyvesb.notepad4e.utils.ShortcutHandler; 60 | 61 | /** 62 | * Class handling the plugin's view with the different note tabs. 63 | * 64 | * @author Pyves 65 | * 66 | */ 67 | public class NotepadView extends ViewPart implements IPreferenceChangeListener { 68 | 69 | private static final String LOCK_PREFIX = "\uD83D\uDD12 "; 70 | // The ID of the view as specified by the extension. 71 | public static final String ID = "notepad4e.views.NotepadView"; 72 | // Keys used to store and retrieve the plugin's view between Eclipse sessions. 73 | private static final String STORE_COUNT_KEY = "NumOfTabs"; 74 | private static final String STORE_TEXT_PREFIX_KEY = "TabText"; 75 | private static final String STORE_STYLE_PREFIX_KEY = "TabStyle"; 76 | private static final String STORE_TITLE_PREFIX_KEY = "TabTitle"; 77 | private static final String STORE_EDITABLE_PREFIX_KEY = "TabEditable"; 78 | private static final String STORE_BULLETS_PREFIX_KEY = "TabBullets"; 79 | 80 | // Keyboard events listener. 81 | private final ShortcutHandler shortcutHandler = new ShortcutHandler(this); 82 | 83 | // Actions corresponding to the different buttons in the view. 84 | private Action addNewNoteAction; 85 | private Action clearNoteAction; 86 | private Action boldTextAction; 87 | private Action italicTextAction; 88 | private Action underlineTextAction; 89 | private Action strikeoutTextAction; 90 | private Action bulletListAction; 91 | private Action clearTextStyleAction; 92 | private Action toggleEditableAction; 93 | private Action exportNoteAction; 94 | private Action preferencesAction; 95 | private Action websiteAction; 96 | private Action changelogAction; 97 | // User defined preferences. 98 | private IEclipsePreferences preferences; 99 | // Object handling the different tabs. 100 | private CTabFolder tabFolder; 101 | // Current clipboard, used for the paste contents of clipboard in new notes feature. 102 | private Clipboard clipboard; 103 | // Note autosave interval. 104 | private long saveIntervalMillis; 105 | 106 | /** 107 | * Allows to create the viewer and initialise it. 108 | */ 109 | @Override 110 | public void createPartControl(Composite parent) { 111 | preferences = InstanceScope.INSTANCE.getNode(Notepad4e.PLUGIN_ID); 112 | // Listen to any change to the preferences of the plugin. 113 | preferences.addPreferenceChangeListener(this); 114 | 115 | clipboard = new Clipboard(Display.getCurrent()); 116 | 117 | tabFolder = new CTabFolder(parent, SWT.MULTI | SWT.WRAP); 118 | 119 | addPluginDisposeListener(); 120 | addCloseTabListener(); 121 | addSwapTabListener(); 122 | addRenameTabListener(); 123 | addTabSelectionListener(); 124 | 125 | restoreViewFromPreviousSession(); 126 | 127 | saveIntervalMillis = TimeUnit.SECONDS 128 | .toMillis(preferences.getInt(Preferences.SAVE_INTERVAL, Preferences.SAVE_INTERVAL_DEFAULT)); 129 | if (saveIntervalMillis >= 0) { 130 | new Job("Notepad4e scheduled autosave") { 131 | @Override 132 | protected IStatus run(IProgressMonitor monitor) { 133 | Display.getDefault().asyncExec(() -> savePluginState(preferences.get(Preferences.SAVE_LOCATION, 134 | Preferences.SAVE_LOCATION_DEFAULT))); 135 | schedule(saveIntervalMillis); 136 | return Status.OK_STATUS; 137 | } 138 | }.schedule(saveIntervalMillis); 139 | } 140 | 141 | PlatformUI.getWorkbench().getHelpSystem().setHelp(tabFolder, "Notepad4e.viewer"); 142 | 143 | IContextService contextService = getSite().getService(IContextService.class); 144 | contextService.activateContext("notepad4e.context"); 145 | 146 | IHandlerService handlerService = getSite().getService(IHandlerService.class); 147 | // Associate each shortcut command with the shortcut handler. 148 | for (NotepadAction notepadAction : NotepadAction.values()) { 149 | if (notepadAction.getCommandID() != null) { 150 | handlerService.activateHandler(notepadAction.getCommandID(), shortcutHandler); 151 | } 152 | } 153 | 154 | makeActions(); 155 | contributeToActionBars(); 156 | } 157 | 158 | /** 159 | * Unregisters listeners and cleans up. 160 | */ 161 | @Override 162 | public void dispose() { 163 | shortcutHandler.dispose(); 164 | tabFolder.dispose(); 165 | clipboard.dispose(); 166 | preferences.removePreferenceChangeListener(this); 167 | super.dispose(); 168 | } 169 | 170 | /** 171 | * Refreshes all notes when a change in the plugin's preferences is detected. 172 | * 173 | * @param event 174 | */ 175 | @Override 176 | public void preferenceChange(PreferenceChangeEvent event) { 177 | if (Preferences.SAVE_LOCATION.equals(event.getKey())) { 178 | savePluginState((String) event.getOldValue()); 179 | // Load dialog settings using new location. 180 | Notepad4e.getDefault().restoreDialogSettings(); 181 | // This will merge newly restored dialog settings with current state of notes. 182 | restoreViewFromPreviousSession(); 183 | } 184 | for (int tabIndex = 0; tabIndex < tabFolder.getItemCount(); ++tabIndex) { 185 | getNote(tabIndex).setParametersFromPreferences(); 186 | } 187 | saveIntervalMillis = TimeUnit.SECONDS 188 | .toMillis(preferences.getInt(Preferences.SAVE_INTERVAL, Preferences.SAVE_INTERVAL_DEFAULT)); 189 | } 190 | 191 | /** 192 | * Passes the focus request to the viewer's control. 193 | */ 194 | @Override 195 | public void setFocus() { 196 | if (tabFolder.getItemCount() == 0) { 197 | // Give focus to the plugin; hack-ish trick to "steal" focus from other elements in some scenarios (example: 198 | // no tabs and try to open view again via quick access). 199 | tabFolder.getAccessible().getControl().setFocus(); 200 | } else { 201 | // Set focus on the last item in the tabs folder component. 202 | tabFolder.getItem(tabFolder.getItemCount() - 1).getControl().setFocus(); 203 | } 204 | } 205 | 206 | /** 207 | * Adds a new note to the notepad. 208 | */ 209 | public void addNewNote() { 210 | String noteTitle = getNewNoteTitle(); 211 | String noteText = ""; 212 | if (preferences.getBoolean(Preferences.PASTE_CLIPBOARD_IN_NEW_NOTES, 213 | Preferences.PASTE_CLIPBOARD_IN_NEW_NOTES_DEFAULT)) { 214 | noteText = (String) clipboard.getContents(TextTransfer.getInstance(), DND.CLIPBOARD); 215 | } 216 | // Add a new note tab with a number appended to its name (Note 1, Note 2, Note 3, etc.). 217 | addNewNoteTab(noteTitle, noteText, null, true, null); 218 | CTabItem previousSelectedTab = tabFolder.getSelection(); 219 | // Remove lock for currently selected tab. 220 | if (previousSelectedTab != null && previousSelectedTab.getText().startsWith(LOCK_PREFIX)) { 221 | previousSelectedTab.setText(previousSelectedTab.getText().substring(LOCK_PREFIX.length())); 222 | } 223 | tabFolder.setSelection(tabFolder.getItemCount() - 1); 224 | } 225 | 226 | /** 227 | * Closes the currently selected tab in the view and disposes resources appropriately. 228 | */ 229 | public void closeCurrentSelection() { 230 | Note selectedNote = getSelectedNote(); 231 | if (selectedNote != null) { 232 | if (!selectedNote.getEditable()) { 233 | if (MessageDialog.openQuestion(getSite().getShell(), LocalStrings.dialogCloseLockedTitle, 234 | LocalStrings.dialogCloseLockedMsg)) { 235 | tabFolder.getSelection().dispose(); 236 | } 237 | } else if (!preferences.getBoolean(Preferences.CLOSE_CONFIRMATION, Preferences.CLOSE_CONFIRMATION_DEFAULT) 238 | || MessageDialog.openQuestion(getSite().getShell(), LocalStrings.dialogCloseTitle, 239 | LocalStrings.dialogCloseMsg)) { 240 | tabFolder.getSelection().dispose(); 241 | } 242 | } 243 | } 244 | 245 | /** 246 | * Returns the currently selected Note or null. 247 | * 248 | * @return selected Note 249 | */ 250 | public Note getSelectedNote() { 251 | return tabFolder.getSelectionIndex() >= 0 ? getNote(tabFolder.getSelectionIndex()) : null; 252 | } 253 | 254 | /** 255 | * Constructs the title of a new note. The title does not match the ones of extisting notes. 256 | * 257 | * @return the note title, for instance "Note 2" 258 | */ 259 | private String getNewNoteTitle() { 260 | int noteNumber = tabFolder.getItemCount() + 1; 261 | String title = preferences.get(Preferences.NAME_PREFIX, Preferences.NAME_PREFIX_DEFAULT) + " " + noteNumber; 262 | if (tabFolder.getItemCount() == 0) { 263 | return title; 264 | } 265 | while (true) { 266 | for (int tabIndex = 0; tabIndex < tabFolder.getItemCount(); ++tabIndex) { 267 | if (tabFolder.getItem(tabIndex).getText().contains(title)) { 268 | break; 269 | } else if (tabIndex == tabFolder.getItemCount() - 1) { 270 | return title; 271 | } 272 | } 273 | ++noteNumber; 274 | title = preferences.get(Preferences.NAME_PREFIX, Preferences.NAME_PREFIX_DEFAULT) + " " + noteNumber; 275 | } 276 | } 277 | 278 | /** 279 | * Returns a Note object given an index in the tab folder. 280 | * 281 | * @param index 282 | * @return Note at the given index 283 | */ 284 | private Note getNote(int index) { 285 | return (Note) (tabFolder.getItem(index).getControl()); 286 | } 287 | 288 | /** 289 | * Listens to disposal of the tab folder and saves state for next Eclipse session or when reopening the view. 290 | */ 291 | private void addPluginDisposeListener() { 292 | tabFolder.addDisposeListener(event -> savePluginState(preferences.get(Preferences.SAVE_LOCATION, 293 | Preferences.SAVE_LOCATION_DEFAULT))); 294 | } 295 | 296 | /** 297 | * Saves plugin state for next Eclipse session or when reopening the view. 298 | * 299 | * @param directory 300 | */ 301 | private void savePluginState(String directory) { 302 | if (!tabFolder.isDisposed()) { 303 | IDialogSettings section = Notepad4e.getDefault().getDialogSettings().getSection(ID); 304 | section.put(STORE_COUNT_KEY, tabFolder.getItemCount()); 305 | for (int tabIndex = 0; tabIndex < tabFolder.getItemCount(); ++tabIndex) { 306 | CTabItem tab = tabFolder.getItem(tabIndex); 307 | if (!tab.isDisposed()) { 308 | Note note = getNote(tabIndex); 309 | section.put(STORE_TEXT_PREFIX_KEY + tabIndex, note.getText()); 310 | section.put(STORE_STYLE_PREFIX_KEY + tabIndex, note.serialiseStyle()); 311 | if (tab.getText().startsWith(LOCK_PREFIX)) { 312 | // Do not save lock symbol. 313 | section.put(STORE_TITLE_PREFIX_KEY + tabIndex, tab.getText().substring(LOCK_PREFIX.length())); 314 | } else { 315 | section.put(STORE_TITLE_PREFIX_KEY + tabIndex, tab.getText()); 316 | } 317 | section.put(STORE_EDITABLE_PREFIX_KEY + tabIndex, note.getEditable()); 318 | section.put(STORE_BULLETS_PREFIX_KEY + tabIndex, note.serialiseBullets()); 319 | } 320 | } 321 | Notepad4e.getDefault().saveDialogSettings(directory); 322 | } 323 | } 324 | 325 | /** 326 | * Displays a confirmation dialog when closing a note tab, if enabled in preferences. 327 | */ 328 | private void addCloseTabListener() { 329 | tabFolder.addCTabFolder2Listener(new CTabFolder2Adapter() { 330 | @Override 331 | public void close(CTabFolderEvent event) { 332 | // Selected tab may not be the one being closed, the one provided by the event must be used. 333 | if (!getNote(tabFolder.indexOf((CTabItem) event.item)).getEditable()) { 334 | event.doit = MessageDialog.openQuestion(getSite().getShell(), LocalStrings.dialogCloseLockedTitle, 335 | LocalStrings.dialogCloseLockedMsg); 336 | } else if (preferences.getBoolean(Preferences.CLOSE_CONFIRMATION, Preferences.CLOSE_CONFIRMATION_DEFAULT)) { 337 | event.doit = MessageDialog.openQuestion(getSite().getShell(), LocalStrings.dialogCloseTitle, 338 | LocalStrings.dialogCloseMsg); 339 | } 340 | } 341 | }); 342 | } 343 | 344 | /** 345 | * Allows to rename a tab when user double clicks on its title. 346 | */ 347 | private void addRenameTabListener() { 348 | tabFolder.addMouseListener(new MouseAdapter() { 349 | @Override 350 | public void mouseDoubleClick(MouseEvent event) { 351 | CTabItem clickedTab = tabFolder.getItem(new Point(event.x, event.y)); 352 | if (clickedTab == null) { 353 | return; 354 | } 355 | boolean isLocked = false; 356 | String dialogText = clickedTab.getText(); 357 | if (dialogText.startsWith(LOCK_PREFIX)) { 358 | isLocked = true; 359 | dialogText = dialogText.substring(LOCK_PREFIX.length()); 360 | } 361 | // Open a dialog window so user can enter the new name of his note. 362 | InputDialog inputDialog = new InputDialog(null, LocalStrings.dialogRenameTitle, 363 | LocalStrings.dialogRenameMsg, dialogText, null); 364 | inputDialog.open(); 365 | // If user selected Cancel, text will be null. 366 | if (inputDialog.getValue() != null && !inputDialog.getValue().isEmpty()) { 367 | if (isLocked) { 368 | clickedTab.setText(LOCK_PREFIX + inputDialog.getValue()); 369 | } else { 370 | clickedTab.setText(inputDialog.getValue()); 371 | } 372 | } 373 | } 374 | }); 375 | } 376 | 377 | /** 378 | * Swaps two tabs and corresponding notes when a user drags one to another. 379 | */ 380 | private void addSwapTabListener() { 381 | tabFolder.addDragDetectListener(dragDetectedEvent -> { 382 | Rectangle viewRectangle = Geometry.toDisplay(tabFolder.getParent(), tabFolder.getBounds()); 383 | Tracker tracker = new Tracker(tabFolder, SWT.NONE); 384 | tracker.setStippled(true); 385 | tracker.addListener(SWT.Move, event -> { 386 | Point location = new Point(event.x - viewRectangle.x, event.y - viewRectangle.y); 387 | CTabItem tabAtLocation = tabFolder.getItem(location); 388 | if (tabAtLocation != null) { 389 | // Move tracker to follow mouse cursor. 390 | tracker.setRectangles(new Rectangle[] { tabAtLocation.getBounds() }); 391 | } else { 392 | // Mouse cursor no longer above any tab in the action bar, hide tacker. 393 | tracker.setRectangles(new Rectangle[0]); 394 | } 395 | }); 396 | if (tracker.open()) { 397 | Rectangle[] rectangles = tracker.getRectangles(); 398 | if (rectangles.length > 0) { 399 | CTabItem tabToSwap = tabFolder.getItem(new Point(rectangles[0].x, rectangles[0].y)); 400 | // Swap selected tab with the one situated at the mouse cursor's position. 401 | if (tabToSwap != null) { 402 | swapNoteTabs(tabFolder.indexOf(tabToSwap)); 403 | } 404 | } 405 | } 406 | tracker.close(); 407 | tracker.dispose(); 408 | }); 409 | } 410 | 411 | /** 412 | * Listens for tab selections and displays or removes lock symbol when a locked tab is selected. 413 | */ 414 | private void addTabSelectionListener() { 415 | tabFolder.addSelectionListener(new SelectionAdapter() { 416 | @Override 417 | public void widgetSelected(SelectionEvent event) { 418 | // Remove lock symbols from all tabs. 419 | for (int tabIndex = 0; tabIndex < tabFolder.getItemCount(); ++tabIndex) { 420 | CTabItem tab = tabFolder.getItem(tabIndex); 421 | if (tab.getText().startsWith(LOCK_PREFIX)) { 422 | tab.setText(tab.getText().substring(LOCK_PREFIX.length())); 423 | } 424 | } 425 | // Put lock symbol on selected tab, if non editable. 426 | if (!getSelectedNote().getEditable()) { 427 | CTabItem selectedTab = (CTabItem) event.item; 428 | selectedTab.setText(LOCK_PREFIX + selectedTab.getText()); 429 | } 430 | } 431 | }); 432 | } 433 | 434 | /** 435 | * Allows to restore the plugin's view as it was in a previous session of Eclipse. 436 | */ 437 | private void restoreViewFromPreviousSession() { 438 | IDialogSettings settings = Notepad4e.getDefault().getDialogSettings(); 439 | IDialogSettings section = settings.getSection(ID); 440 | if (section == null) { 441 | section = settings.addNewSection(ID); 442 | } 443 | 444 | int numOfTabs = 0; 445 | String numOfTabsString = section.get(STORE_COUNT_KEY); 446 | // numOfTabsString can be null if plugin was not previously launched in this working environment. 447 | if (numOfTabsString != null) { 448 | numOfTabs = Integer.parseInt(numOfTabsString); 449 | } 450 | 451 | if (numOfTabs == 0 && tabFolder.getItemCount() == 0) { 452 | // No notes were previously opened: create new one. 453 | String prefixName = preferences.get(Preferences.NAME_PREFIX, Preferences.NAME_PREFIX_DEFAULT); 454 | addNewNoteTab(prefixName + " 1", "", null, true, null); 455 | // Set selection on this tab. 456 | tabFolder.setSelection(0); 457 | } else { 458 | // Populate with tabs opened in previous session. 459 | for (int tabIndex = 0; tabIndex < numOfTabs; ++tabIndex) { 460 | String tabTitle = section.get(STORE_TITLE_PREFIX_KEY + tabIndex); 461 | boolean editable = section.get(STORE_EDITABLE_PREFIX_KEY + tabIndex) == null ? true 462 | : section.getBoolean(STORE_EDITABLE_PREFIX_KEY + tabIndex); 463 | String noteText = section.get(STORE_TEXT_PREFIX_KEY + tabIndex); 464 | String noteStyle = section.get(STORE_STYLE_PREFIX_KEY + tabIndex); 465 | String noteBullets = section.get(STORE_BULLETS_PREFIX_KEY + tabIndex); 466 | if (tabTitle != null && noteText != null) { 467 | addNewNoteTab(tabTitle, noteText, noteStyle, editable, noteBullets); 468 | } 469 | } 470 | // Set selection on the last tab. 471 | tabFolder.setSelection(tabFolder.getItemCount() - 1); 472 | if (!getSelectedNote().getEditable()) { 473 | tabFolder.getSelection().setText(LOCK_PREFIX + tabFolder.getSelection().getText()); 474 | } 475 | } 476 | } 477 | 478 | /** 479 | * Adds a new note to the view. 480 | * 481 | * @param title 482 | * @param text 483 | * @param style 484 | * @param editable 485 | * @param bullets 486 | */ 487 | private void addNewNoteTab(String title, String text, String style, boolean editable, String bullets) { 488 | CTabItem tab = new CTabItem(tabFolder, SWT.NONE); 489 | tab.setText(title); 490 | // Add listener to clean up corresponding note when disposing the tab. 491 | tab.addDisposeListener(event -> { 492 | CTabItem itemToDispose = (CTabItem) event.getSource(); 493 | ((Note) itemToDispose.getControl()).dispose(); 494 | }); 495 | Note note = new Note(tabFolder, text, style, bullets, editable); 496 | tab.setControl(note); 497 | } 498 | 499 | /** 500 | * Populates the different action bars of the view. 501 | */ 502 | private void contributeToActionBars() { 503 | IActionBars bars = getViewSite().getActionBars(); 504 | fillLocalPullDown(bars.getMenuManager()); 505 | fillLocalToolBar(bars.getToolBarManager()); 506 | } 507 | 508 | /** 509 | * Populates the drop down menu action bar. 510 | * 511 | * @param manager 512 | */ 513 | private void fillLocalPullDown(IMenuManager manager) { 514 | manager.add(toggleEditableAction); 515 | manager.add(exportNoteAction); 516 | manager.add(new Separator()); 517 | manager.add(preferencesAction); 518 | manager.add(websiteAction); 519 | manager.add(changelogAction); 520 | } 521 | 522 | /** 523 | * Populates the tool bar. 524 | * 525 | * @param manager 526 | */ 527 | private void fillLocalToolBar(IToolBarManager manager) { 528 | manager.add(boldTextAction); 529 | manager.add(italicTextAction); 530 | manager.add(underlineTextAction); 531 | manager.add(strikeoutTextAction); 532 | manager.add(bulletListAction); 533 | manager.add(clearTextStyleAction); 534 | manager.add(new Separator()); 535 | manager.add(addNewNoteAction); 536 | manager.add(clearNoteAction); 537 | } 538 | 539 | /** 540 | * Defines all the plugin's actions corresponding to the different buttons in the view. 541 | */ 542 | private void makeActions() { 543 | addNewNoteAction = new Action() { 544 | @Override 545 | public void run() { 546 | addNewNote(); 547 | } 548 | }; 549 | setTextAndImageToAction(addNewNoteAction, NotepadAction.NEW_NOTE); 550 | 551 | clearNoteAction = new AbstractSelectedNoteAction(this) { 552 | @Override 553 | protected void runSelectedNoteAction(Note selectedNote) { 554 | selectedNote.clearText(); 555 | } 556 | }; 557 | setTextAndImageToAction(clearNoteAction, NotepadAction.CLEAR_NOTE); 558 | 559 | boldTextAction = new AbstractSelectedNoteAction(this) { 560 | @Override 561 | protected void runSelectedNoteAction(Note selectedNote) { 562 | selectedNote.boldSelection(); 563 | } 564 | }; 565 | setTextAndImageToAction(boldTextAction, NotepadAction.BOLD_TEXT); 566 | 567 | italicTextAction = new AbstractSelectedNoteAction(this) { 568 | @Override 569 | protected void runSelectedNoteAction(Note selectedNote) { 570 | selectedNote.italicSelection(); 571 | } 572 | }; 573 | setTextAndImageToAction(italicTextAction, NotepadAction.ITALIC_TEXT); 574 | 575 | underlineTextAction = new AbstractSelectedNoteAction(this) { 576 | @Override 577 | protected void runSelectedNoteAction(Note selectedNote) { 578 | selectedNote.underlineSelection(); 579 | } 580 | }; 581 | setTextAndImageToAction(underlineTextAction, NotepadAction.UNDERLINE_TEXT); 582 | 583 | strikeoutTextAction = new AbstractSelectedNoteAction(this) { 584 | @Override 585 | protected void runSelectedNoteAction(Note selectedNote) { 586 | selectedNote.strikeoutSelection(); 587 | } 588 | }; 589 | setTextAndImageToAction(strikeoutTextAction, NotepadAction.STRIKEOUT_TEXT); 590 | 591 | bulletListAction = new AbstractSelectedNoteAction(this) { 592 | @Override 593 | protected void runSelectedNoteAction(Note selectedNote) { 594 | selectedNote.bulletListSelection(); 595 | } 596 | }; 597 | setTextAndImageToAction(bulletListAction, NotepadAction.BULLET_LIST); 598 | 599 | clearTextStyleAction = new AbstractSelectedNoteAction(this) { 600 | @Override 601 | protected void runSelectedNoteAction(Note selectedNote) { 602 | selectedNote.clearSelectionStyles(); 603 | } 604 | }; 605 | setTextAndImageToAction(clearTextStyleAction, NotepadAction.CLEAR_STYLE_TEXT); 606 | 607 | toggleEditableAction = new AbstractSelectedNoteAction(this) { 608 | @Override 609 | protected void runSelectedNoteAction(Note selectedNote) { 610 | CTabItem tab = tabFolder.getSelection(); 611 | if (!selectedNote.getEditable()) { 612 | tab.setText(tab.getText().substring(LOCK_PREFIX.length())); 613 | } else { 614 | tab.setText(LOCK_PREFIX + tab.getText()); 615 | } 616 | selectedNote.toggleEditable(); 617 | } 618 | }; 619 | setTextAndImageToAction(toggleEditableAction, NotepadAction.TOGGLE_EDITABLE_NOTE); 620 | 621 | exportNoteAction = new AbstractSelectedNoteAction(this) { 622 | @Override 623 | protected void runSelectedNoteAction(Note selectedNote) { 624 | selectedNote.exportToFile(getSite()); 625 | } 626 | }; 627 | setTextAndImageToAction(exportNoteAction, NotepadAction.EXPORT_NOTE); 628 | 629 | preferencesAction = new Action() { 630 | @Override 631 | public void run() { 632 | // Create preference dialog page that will appear in current workbench window. 633 | PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(null, 634 | "notepad4e.preferences.PreferencePage", new String[] { "notepad4e.preferences.PreferencePage" }, 635 | null); 636 | dialog.open(); 637 | } 638 | }; 639 | setTextAndImageToAction(preferencesAction, NotepadAction.PREFERENCES); 640 | 641 | websiteAction = new Action() { 642 | @Override 643 | public void run() { 644 | // Open website in the user's external browser. 645 | Program.launch("https://github.com/PyvesB/Notepad4e"); 646 | } 647 | }; 648 | setTextAndImageToAction(websiteAction, NotepadAction.WEBSITE); 649 | 650 | changelogAction = new Action() { 651 | @Override 652 | public void run() { 653 | // Open changelog page in the user's external browser. 654 | Program.launch("https://github.com/PyvesB/Notepad4e/releases"); 655 | } 656 | }; 657 | setTextAndImageToAction(changelogAction, NotepadAction.CHANGELOG); 658 | } 659 | 660 | /** 661 | * Sets the image and the tool tip to an action button. 662 | * 663 | * @param action 664 | * @param notepadAction 665 | */ 666 | private void setTextAndImageToAction(Action action, NotepadAction notepadAction) { 667 | if (notepadAction.getCommandID() != null) { 668 | // Action appears in action bar with an associated shortcut. 669 | action.setToolTipText(notepadAction.getText() + getKeyBindingDescription(notepadAction.getCommandID())); 670 | } else { 671 | // Action appears in drop down menu. 672 | action.setText(notepadAction.getText()); 673 | } 674 | 675 | // The URL matches an image in the plugin's icons folder. 676 | URL url = FileLocator.find(Platform.getBundle(Notepad4e.PLUGIN_ID), new Path(notepadAction.getImagePath()), 677 | null); 678 | action.setImageDescriptor(ImageDescriptor.createFromURL(url)); 679 | } 680 | 681 | /** 682 | * Returns key binding as a String for a given command ID. 683 | * 684 | * @param commandID 685 | * @return the key binding, for instance Ctrl + B 686 | */ 687 | private String getKeyBindingDescription(String commandID) { 688 | Binding bestBinding = null; 689 | for (Binding binding : getViewSite().getService(IBindingService.class).getBindings()) { 690 | if (binding.getParameterizedCommand() != null 691 | && commandID.equals(binding.getParameterizedCommand().getId())) { 692 | if (bestBinding == null) { 693 | bestBinding = binding; 694 | } else if (binding.getType() == Binding.USER) { 695 | // Give higher priority to a user type binding (user has overriden default). 696 | bestBinding = binding; 697 | break; 698 | } 699 | } 700 | } 701 | return bestBinding == null ? "" : " " + bestBinding.getTriggerSequence().format(); 702 | } 703 | 704 | /** 705 | * Swaps two tabs and corresponding notes in the view. 706 | * 707 | * @param swappedIndex 708 | */ 709 | private void swapNoteTabs(int swappedIndex) { 710 | Note selectedNote = getSelectedNote(); 711 | Note swappedNote = getNote(swappedIndex); 712 | tabFolder.getItem(swappedIndex).setControl(selectedNote); 713 | tabFolder.getSelection().setControl(swappedNote); 714 | 715 | String selectedTitle = tabFolder.getSelection().getText(); 716 | String swappedTitle = tabFolder.getItem(swappedIndex).getText(); 717 | tabFolder.getItem(swappedIndex).setText(selectedTitle); 718 | tabFolder.getSelection().setText(swappedTitle); 719 | 720 | tabFolder.setSelection(swappedIndex); 721 | } 722 | } 723 | --------------------------------------------------------------------------------