View Javadoc
1   /**
2    * Metaphase Editor - WYSIWYG HTML Editor Component
3    * Copyright (C) 2010  Rudolf Visagie
4    * Full text of license can be found in com/metaphaseeditor/LICENSE.txt
5    *
6    * This library is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License as published by the Free Software Foundation; either
9    * version 3 of the License, or (at your option) any later version.
10   *
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public
17   * License along with this library; if not, write to the Free Software
18   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19   *
20   * The author can be contacted at metaphase.editor@gmail.com.
21   */
22  
23  package com.metaphaseeditor;
24  
25  import java.awt.BorderLayout;
26  import java.awt.Color;
27  import java.awt.Component;
28  import java.awt.Desktop;
29  import java.awt.FlowLayout;
30  import java.awt.Font;
31  import java.awt.GridBagConstraints;
32  import java.awt.GridBagLayout;
33  import java.awt.Insets;
34  import java.awt.datatransfer.Clipboard;
35  import java.awt.datatransfer.DataFlavor;
36  import java.awt.datatransfer.Transferable;
37  import java.awt.datatransfer.UnsupportedFlavorException;
38  import java.awt.event.ActionEvent;
39  import java.awt.event.ActionListener;
40  import java.awt.event.FocusAdapter;
41  import java.awt.event.FocusEvent;
42  import java.awt.event.KeyEvent;
43  import java.awt.event.MouseEvent;
44  import java.awt.event.MouseMotionListener;
45  import java.awt.print.PrinterException;
46  import java.io.File;
47  import java.io.FileInputStream;
48  import java.io.FileNotFoundException;
49  import java.io.FileReader;
50  import java.io.FileWriter;
51  import java.io.IOException;
52  import java.io.InputStream;
53  import java.io.StringReader;
54  import java.util.ArrayList;
55  import java.util.Collections;
56  import java.util.Comparator;
57  import java.util.Enumeration;
58  import java.util.Hashtable;
59  import java.util.List;
60  import java.util.Map;
61  import java.util.Vector;
62  import java.util.zip.ZipInputStream;
63  
64  import javax.swing.AbstractAction;
65  import javax.swing.Action;
66  import javax.swing.BorderFactory;
67  import javax.swing.DefaultListCellRenderer;
68  import javax.swing.JButton;
69  import javax.swing.JColorChooser;
70  import javax.swing.JComboBox;
71  import javax.swing.JComponent;
72  import javax.swing.JEditorPane;
73  import javax.swing.JFileChooser;
74  import javax.swing.JList;
75  import javax.swing.JMenuItem;
76  import javax.swing.JOptionPane;
77  import javax.swing.JPanel;
78  import javax.swing.JPopupMenu;
79  import javax.swing.JScrollPane;
80  import javax.swing.JTextArea;
81  import javax.swing.JTextPane;
82  import javax.swing.KeyStroke;
83  import javax.swing.SwingUtilities;
84  import javax.swing.event.UndoableEditEvent;
85  import javax.swing.event.UndoableEditListener;
86  import javax.swing.text.AttributeSet;
87  import javax.swing.text.BadLocationException;
88  import javax.swing.text.DefaultEditorKit;
89  import javax.swing.text.Document;
90  import javax.swing.text.Element;
91  import javax.swing.text.MutableAttributeSet;
92  import javax.swing.text.SimpleAttributeSet;
93  import javax.swing.text.StyleConstants;
94  import javax.swing.text.StyledEditorKit;
95  import javax.swing.text.html.HTML;
96  import javax.swing.text.html.HTML.Tag;
97  import javax.swing.text.html.HTMLDocument;
98  import javax.swing.text.html.HTMLEditorKit;
99  import javax.swing.text.html.StyleSheet;
100 import javax.swing.undo.CannotRedoException;
101 import javax.swing.undo.CannotUndoException;
102 import javax.swing.undo.UndoManager;
103 
104 import org.openflexo.icon.ImageIconResource;
105 import org.openflexo.rm.ResourceLocator;
106 import org.openflexo.swing.layout.WrapLayout;
107 
108 import com.metaphaseeditor.action.AddAttributesAction;
109 import com.metaphaseeditor.action.ClearFormattingAction;
110 import com.metaphaseeditor.action.DecreaseIndentAction;
111 import com.metaphaseeditor.action.FindReplaceAction;
112 import com.metaphaseeditor.action.FormatAction;
113 import com.metaphaseeditor.action.IncreaseIndentAction;
114 import com.metaphaseeditor.action.InsertHtmlAction;
115 import com.metaphaseeditor.action.InsertTextAction;
116 import com.metaphaseeditor.action.RemoveAttributesAction;
117 import com.metaphaseeditor.action.UnlinkAction;
118 
119 /**
120  * 
121  * @author Rudolf Visagie
122  */
123 public class MetaphaseEditorPanel extends JPanel {
124 
125 	public static final String SOURCE_PANEL_KEY = "SourcePanel";
126 	public static final String SOURCE_BUTTON_KEY = "SourcePanel.SourceButton";
127 
128 	public static final String PAGE_PANEL_KEY = "PagePanel";
129 	public static final String OPEN_BUTTON_KEY = "PagePanel.OpenButton";
130 	public static final String SAVE_BUTTON_KEY = "PagePanel.SaveButton";
131 	public static final String NEW_BUTTON_KEY = "PagePanel.NewButton";
132 	public static final String PREVIEW_BUTTON_KEY = "PagePanel.PreviewButton";
133 
134 	public static final String EDIT_PANEL_KEY = "EditPanel";
135 	public static final String CUT_BUTTON_KEY = "EditPanel.CutButton";
136 	public static final String COPY_BUTTON_KEY = "EditPanel.CopyButton";
137 	public static final String PASTE_BUTTON_KEY = "EditPanel.PasteButton";
138 	public static final String PASTE_AS_TEXT_BUTTON_KEY = "EditPanel.PasteAsTextButton";
139 
140 	public static final String TOOLS_PANEL_KEY = "ToolsPanel";
141 	public static final String PRINT_BUTTON_KEY = "ToolsPanel.PrintButton";
142 	public static final String SPELL_CHECK_BUTTON_KEY = "ToolsPanel.SpellcheckButton";
143 
144 	public static final String UNDO_REDO_PANEL_KEY = "UndoRedoPanel";
145 	public static final String UNDO_BUTTON_KEY = "UndoRedoPanel.UndoButton";
146 	public static final String REDO_BUTTON_KEY = "UndoRedoPanel.RedoButton";
147 
148 	public static final String SEARCH_PANEL_KEY = "SearchPanel";
149 	public static final String FIND_BUTTON_KEY = "SearchPanel.FindButton";
150 	public static final String REPLACE_BUTTON_KEY = "SearchPanel.ReplaceButton";
151 
152 	public static final String FORMAT_PANEL_KEY = "FormatPanel";
153 	public static final String SELECT_ALL_BUTTON_KEY = "FormatPanel.SelectAllButton";
154 	public static final String CLEAR_FORMATTING_BUTTON_KEY = "FormatPanel.ClearFormattingButton";
155 
156 	public static final String TEXT_EFFECT_PANEL_KEY = "TextEffectPanel";
157 	public static final String BOLD_BUTTON_KEY = "TextEffectPanel.BoldButton";
158 	public static final String ITALIC_BUTTON_KEY = "TextEffectPanel.ItalicButton";
159 	public static final String UNDERLINE_BUTTON_KEY = "TextEffectPanel.UnderlineButton";
160 	public static final String STRIKE_BUTTON_KEY = "TextEffectPanel.StrikethroughButton";
161 
162 	public static final String SUB_SUPER_SCRIPT_PANEL_KEY = "SubSuperScriptPanel";
163 	public static final String SUB_SCRIPT_BUTTON_KEY = "SubSuperScriptPanel.SubscriptButton";
164 	public static final String SUPER_SCRIPT_BUTTON_KEY = "SubSuperScriptPanel.SuperscriptButton";
165 
166 	public static final String LIST_PANEL_KEY = "ListPanel";
167 	public static final String NUMBERED_LIST_BUTTON_KEY = "ListPanel.InsertRemoveNumberedListButton";
168 	public static final String BULLETED_BUTTON_KEY = "ListPanel.InsertRemoveBulletedListButton";
169 
170 	public static final String BLOCK_PANEL_KEY = "BlockPanel";
171 	public static final String DECREASE_INDENT_BUTTON_KEY = "BlockPanel.DecreaseIndentButton";
172 	public static final String INCREASE_INDENT_BUTTON_KEY = "BlockPanel.IncreaseIndentButton";
173 	public static final String BLOCK_QUOTE_BUTTON_KEY = "BlockPanel.BlockQuoteButton";
174 	public static final String DIV_BUTTON_KEY = "BlockPanel.CreateDivButton";
175 	public static final String PARAGRAPH_BUTTON_KEY = "BlockPanel.CreateParagraphButton";
176 
177 	public static final String JUSTIFICATION_PANEL_KEY = "JustificationPanel";
178 	public static final String LEFT_JUSTIFY_BUTTON_KEY = "JustificationPanel.LeftJustifyButton";
179 	public static final String CENTER_JUSTIFY_BUTTON_KEY = "JustificationPanel.CenterJustifyButton";
180 	public static final String RIGHT_JUSTIFY_BUTTON_KEY = "JustificationPanel.RightJustifyButton";
181 	public static final String BLOCK_JUSTIFY_BUTTON_KEY = "JustificationPanel.BlockJustifyButton";
182 
183 	public static final String LINK_PANEL_KEY = "LinkPanel";
184 	public static final String LINK_BUTTON_KEY = "LinkPanel.LinkButton";
185 	public static final String UNLINK_BUTTON_KEY = "LinkPanel.UnlinkButton";
186 	public static final String ANCHOR_BUTTON_KEY = "LinkPanel.AnchorButton";
187 
188 	public static final String MISC_PANEL_KEY = "MiscPanel";
189 	public static final String IMAGE_BUTTON_KEY = "MiscPanel.InsertImage";
190 	public static final String TABLE_BUTTON_KEY = "MiscPanel.InsertTableButton";
191 	public static final String HORIZONTAL_LINE_BUTTON_KEY = "MiscPanel.InsertHorizontalLineButton";
192 	public static final String SPECIAL_CHAR_BUTTON_KEY = "MiscPanel.InsertSpecialCharButton";
193 
194 	public static final String FONT_PANEL_KEY = "FontComboBox";
195 	public static final String FONT_SIZE_PANEL_KEY = "FontSizeComboBox";
196 	public static final String PARAGRAPH_FORMAT_PANEL_KEY = "ParagraphFormatComboBox";
197 
198 	public static final String COLOR_PANEL_KEY = "ColorPanel";
199 	public static final String TEXT_COLOR_BUTTON_KEY = "ColorPanel.TextColorButton";
200 	public static final String BACKGROUND_COLOR_BUTTON_KEY = "ColorPanel.BackgroundColorButton";
201 
202 	public static final String ABOUT_PANEL_KEY = "AboutPanel";
203 	public static final String ABOUT_BUTTON_KEY = "AboutPanel.AboutButton";
204 
205 	// TODO, spelling needed? FD
206 	// private JTextComponentSpellChecker spellChecker = null;
207 	// private SpellDictionary dictionary = null;
208 	private JTextArea htmlTextArea;
209 	private boolean htmlSourceMode = false;
210 	private SpecialCharacterDialog specialCharacterDialog = new SpecialCharacterDialog(null, true);
211 	private Hashtable<Object, Action> editorKitActions;
212 	private SpellCheckDictionaryVersion spellCheckDictionaryVersion = SpellCheckDictionaryVersion.LIBERAL_US;
213 	private String customDictionaryFilename = null;
214 
215 	/** Listener for the edits on the current document. */
216 	protected UndoableEditListener undoHandler = new UndoHandler();
217 
218 	/** UndoManager that we add edits to. */
219 	protected UndoManager undo = new UndoManager();
220 
221 	private UndoAction undoAction = new UndoAction();
222 	private RedoAction redoAction = new RedoAction();
223 	private HTMLEditorKit.CutAction cutAction = new HTMLEditorKit.CutAction();
224 	private HTMLEditorKit.CopyAction copyAction = new HTMLEditorKit.CopyAction();
225 	private HTMLEditorKit.PasteAction pasteAction = new HTMLEditorKit.PasteAction();
226 	private FindReplaceAction findReplaceAction;
227 
228 	private HTMLEditorKit editorKit = new HTMLEditorKit();
229 
230 	private JPopupMenu contextMenu;
231 
232 	public static interface ImageInsertRequestHandler {
233 		public void insertImage(JTextPane htmlTextPane);
234 	}
235 
236 	private List<ContextMenuListener> contextMenuListeners = new ArrayList<>();
237 	private List<EditorMouseMotionListener> editorMouseMotionListeners = new ArrayList<>();
238 
239 	private enum ParagraphFormat {
240 		PARAGRAPH_FORMAT("Format", null),
241 		NORMAL("Normal", Tag.P),
242 		HEADING1("Heading 1", Tag.H1),
243 		HEADING2("Heading 2", Tag.H2),
244 		HEADING3("Heading 3", Tag.H3),
245 		HEADING4("Heading 4", Tag.H4),
246 		HEADING5("Heading 5", Tag.H5),
247 		HEADING6("Heading 6", Tag.H6),
248 		FORMATTED("Formatted", Tag.PRE),
249 		ADDRESS("Address", Tag.ADDRESS);
250 
251 		private String text;
252 		private Tag tag;
253 
254 		ParagraphFormat(String text, Tag tag) {
255 			this.text = text;
256 			this.tag = tag;
257 		}
258 
259 		public Tag getTag() {
260 			return tag;
261 		}
262 
263 		@Override
264 		public String toString() {
265 			return text;
266 		}
267 	}
268 
269 	private enum FontItem {
270 		FONT("Font", null),
271 		ARIAL("Arial", "Arial"),
272 		COMIC_SANS_MS("Comic Sans MS", "Comic Sans MS"),
273 		COURIER_NEW("Courier New", "Courier New"),
274 		GEORGIA("Georgia", "Georgia"),
275 		LUCINDA_SANS_UNICODE("Lucinda Sans Unicode", "Lucinda Sans Unicode"),
276 		TAHOMA("Tahoma", "Tahoma"),
277 		TIMES_NEW_ROMAN("Times New Roman", "Times New Roman"),
278 		TREBUCHET_MS("Trebuchet MS", "Trebuchet MS"),
279 		VERDANA("Verdana", "Verdana");
280 
281 		private String text;
282 		private String fontName;
283 
284 		FontItem(String text, String fontName) {
285 			this.text = text;
286 			this.fontName = fontName;
287 		}
288 
289 		public String getText() {
290 			return text;
291 		}
292 
293 		public String getFontName() {
294 			return fontName;
295 		}
296 
297 		@Override
298 		public String toString() {
299 			return text;
300 		}
301 	}
302 
303 	private enum FontSize {
304 		FONT_SIZE("Size", -1),
305 		SIZE8("8", 8),
306 		SIZE9("9", 9),
307 		SIZE10("10", 10),
308 		SIZE11("11", 11),
309 		SIZE12("12", 12),
310 		SIZE14("14", 14),
311 		SIZE16("16", 16),
312 		SIZE18("18", 18),
313 		SIZE20("20", 20),
314 		SIZE22("22", 22),
315 		SIZE24("24", 24),
316 		SIZE26("26", 26),
317 		SIZE28("28", 28),
318 		SIZE36("36", 36),
319 		SIZE48("48", 48),
320 		SIZE72("72", 72);
321 
322 		private String text;
323 		private int size;
324 
325 		FontSize(String text, int size) {
326 			this.text = text;
327 			this.size = size;
328 		}
329 
330 		public String getText() {
331 			return text;
332 		}
333 
334 		public int getSize() {
335 			return size;
336 		}
337 
338 		@Override
339 		public String toString() {
340 			return text;
341 		}
342 	}
343 
344 	private javax.swing.JPanel toolbarPanel;
345 	private javax.swing.JTextPane htmlTextPane;
346 	private javax.swing.JScrollPane mainScrollPane;
347 
348 	private javax.swing.text.html.HTMLDocument htmlDocument;
349 
350 	private javax.swing.JPanel sourcePanel;
351 	private javax.swing.JButton sourceButton;
352 
353 	private javax.swing.JPanel pagePanel;
354 	private javax.swing.JButton openButton;
355 	private javax.swing.JButton saveButton;
356 	private javax.swing.JButton newButton;
357 	private javax.swing.JButton previewButton;
358 
359 	private javax.swing.JPanel editPanel;
360 	private javax.swing.JButton cutButton;
361 	private javax.swing.JButton copyButton;
362 	private javax.swing.JButton pasteButton;
363 	private javax.swing.JButton pasteAsTextButton;
364 
365 	private javax.swing.JPanel toolsPanel;
366 	private javax.swing.JButton printButton;
367 	private javax.swing.JButton spellcheckButton;
368 
369 	private javax.swing.JPanel undoRedoPanel;
370 	private javax.swing.JButton undoButton;
371 	private javax.swing.JButton redoButton;
372 
373 	private javax.swing.JPanel searchPanel;
374 	private javax.swing.JButton findButton;
375 	private javax.swing.JButton replaceButton;
376 
377 	private javax.swing.JPanel formatPanel;
378 	private javax.swing.JButton selectAllButton;
379 	private javax.swing.JButton clearFormattingButton;
380 
381 	private javax.swing.JPanel textEffectPanel;
382 	private javax.swing.JButton boldButton;
383 	private javax.swing.JButton italicButton;
384 	private javax.swing.JButton underlineButton;
385 	private javax.swing.JButton strikethroughButton;
386 
387 	private javax.swing.JPanel subSuperScriptPanel;
388 	private javax.swing.JButton subscriptButton;
389 	private javax.swing.JButton superscriptButton;
390 
391 	private javax.swing.JPanel listPanel;
392 	private javax.swing.JButton insertRemoveNumberedListButton;
393 	private javax.swing.JButton insertRemoveBulletedListButton;
394 
395 	private javax.swing.JPanel blockPanel;
396 	private javax.swing.JButton decreaseIndentButton;
397 	private javax.swing.JButton increaseIndentButton;
398 	private javax.swing.JButton blockQuoteButton;
399 	private javax.swing.JButton createDivButton;
400 	private javax.swing.JButton createParagraphButton;
401 
402 	private javax.swing.JPanel justificationPanel;
403 	private javax.swing.JButton leftJustifyButton;
404 	private javax.swing.JButton centerJustifyButton;
405 	private javax.swing.JButton rightJustifyButton;
406 	private javax.swing.JButton blockJustifyButton;
407 
408 	private javax.swing.JPanel linkPanel;
409 	private javax.swing.JButton linkButton;
410 	private javax.swing.JButton unlinkButton;
411 	private javax.swing.JButton anchorButton;
412 
413 	private javax.swing.JPanel miscPanel;
414 	private javax.swing.JButton insertImage;
415 	private javax.swing.JButton insertTableButton;
416 	private javax.swing.JButton insertHorizontalLineButton;
417 	private javax.swing.JButton insertSpecialCharButton;
418 
419 	private javax.swing.JComboBox<FontItem> fontComboBox;
420 	private javax.swing.JComboBox<FontSize> fontSizeComboBox;
421 	private javax.swing.JComboBox<Object> paragraphFormatComboBox;
422 
423 	private javax.swing.JPanel colorPanel;
424 	private javax.swing.JButton textColorButton;
425 	private javax.swing.JButton backgroundColorButton;
426 
427 	private javax.swing.JPanel aboutPanel;
428 	private javax.swing.JButton aboutButton;
429 	private ImageInsertRequestHandler insertImageRequestHandler;
430 
431 	public void documentWasEdited() {
432 	}
433 
434 	/** Creates new form MetaphaseEditorPanel */
435 	public MetaphaseEditorPanel(MetaphaseEditorConfiguration configuration) {
436 		insertImageRequestHandler = new DefaultImageInsertRequestHandler();
437 		initComponents();
438 		updateComponents(configuration);
439 
440 		createEditorKitActionTable();
441 
442 		htmlTextArea = new JTextArea();
443 		htmlTextArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
444 
445 		htmlTextPane.setContentType("text/html");
446 		htmlTextPane.addFocusListener(new FocusAdapter() {
447 			@Override
448 			public void focusGained(FocusEvent e) {
449 				super.focusGained(e);
450 			}
451 
452 			@Override
453 			public void focusLost(FocusEvent e) {
454 				super.focusLost(e);
455 				documentWasEdited();
456 			}
457 		});
458 
459 		findReplaceAction = new FindReplaceAction("Find/Replace", htmlTextPane);
460 
461 		cutButton.setAction(cutAction);
462 		cutButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/cut.png")));
463 		cutButton.setText("");
464 		cutButton.setToolTipText("Cut");
465 
466 		copyButton.setAction(copyAction);
467 		copyButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/copy.png")));
468 		copyButton.setText("");
469 		copyButton.setToolTipText("Copy");
470 
471 		pasteButton.setAction(pasteAction);
472 		pasteButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/paste.png")));
473 		pasteButton.setText("");
474 		pasteButton.setToolTipText("Paste");
475 
476 		undoButton.setAction(undoAction);
477 		undoButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/undo.png")));
478 		undoButton.setText("");
479 		undoButton.setToolTipText("Undo");
480 
481 		redoButton.setAction(redoAction);
482 		redoButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/redo.png")));
483 		redoButton.setText("");
484 		redoButton.setToolTipText("Redo");
485 
486 		findButton.setAction(findReplaceAction);
487 		findButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/find.png")));
488 		findButton.setText("");
489 		findButton.setToolTipText("Find");
490 
491 		replaceButton.setAction(findReplaceAction);
492 		replaceButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/replace.png")));
493 		replaceButton.setText("");
494 		replaceButton.setToolTipText("Replace");
495 
496 		clearFormattingButton.setAction(new ClearFormattingAction(this, "Remove Format"));
497 		clearFormattingButton
498 				.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/removeformat.png")));
499 		clearFormattingButton.setText("");
500 		clearFormattingButton.setToolTipText("Remove Format");
501 
502 		boldButton.setAction(new HTMLEditorKit.BoldAction());
503 		boldButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/bold.png")));
504 		boldButton.setText("");
505 		boldButton.setToolTipText("Bold");
506 
507 		italicButton.setAction(new HTMLEditorKit.ItalicAction());
508 		italicButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/italic.png")));
509 		italicButton.setText("");
510 		italicButton.setToolTipText("Italic");
511 
512 		underlineButton.setAction(new HTMLEditorKit.UnderlineAction());
513 		underlineButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/underline.png")));
514 		underlineButton.setText("");
515 		underlineButton.setToolTipText("Underline");
516 
517 		strikethroughButton.setAction(new StrikeThroughAction());
518 		strikethroughButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/strikethrough.png")));
519 		strikethroughButton.setText("");
520 		strikethroughButton.setToolTipText("Strike Through");
521 
522 		subscriptButton.setAction(new SubscriptAction());
523 		subscriptButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/subscript.png")));
524 		subscriptButton.setText("");
525 		subscriptButton.setToolTipText("Subscript");
526 
527 		superscriptButton.setAction(new SuperscriptAction());
528 		superscriptButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/superscript.png")));
529 		superscriptButton.setText("");
530 		superscriptButton.setToolTipText("Superscript");
531 
532 		// TODO: change increase and decrease indent to add inner <li> when
533 		// inside bulleted or numbered list
534 		increaseIndentButton.setAction(new IncreaseIndentAction("Increase Indent", this));
535 		increaseIndentButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/incindent.png")));
536 		increaseIndentButton.setText("");
537 		increaseIndentButton.setToolTipText("Increase Indent");
538 
539 		decreaseIndentButton.setAction(new DecreaseIndentAction("Decrease Indent", this));
540 		decreaseIndentButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/decindent.png")));
541 		decreaseIndentButton.setText("");
542 		decreaseIndentButton.setToolTipText("Decrease Indent");
543 
544 		blockQuoteButton.setAction(new FormatAction(this, "Block Quote", Tag.BLOCKQUOTE));
545 		blockQuoteButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/blockquote.png")));
546 		blockQuoteButton.setText("");
547 		blockQuoteButton.setToolTipText("Block Quote");
548 
549 		leftJustifyButton.setAction(new HTMLEditorKit.AlignmentAction("Left Align", StyleConstants.ALIGN_LEFT));
550 		leftJustifyButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/leftjustify.png")));
551 		leftJustifyButton.setText("");
552 		leftJustifyButton.setToolTipText("Left Justify");
553 
554 		centerJustifyButton.setAction(new HTMLEditorKit.AlignmentAction("Center Align", StyleConstants.ALIGN_CENTER));
555 		centerJustifyButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/centerjustify.png")));
556 		centerJustifyButton.setText("");
557 		centerJustifyButton.setToolTipText("Center Justify");
558 
559 		rightJustifyButton.setAction(new HTMLEditorKit.AlignmentAction("Left Align", StyleConstants.ALIGN_RIGHT));
560 		rightJustifyButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/rightjustify.png")));
561 		rightJustifyButton.setText("");
562 		rightJustifyButton.setToolTipText("Right Justify");
563 
564 		blockJustifyButton.setAction(new HTMLEditorKit.AlignmentAction("Justified Align", StyleConstants.ALIGN_JUSTIFIED));
565 		blockJustifyButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/blockjustify.png")));
566 		blockJustifyButton.setText("");
567 		blockJustifyButton.setToolTipText("Block Justify");
568 
569 		unlinkButton.setAction(new UnlinkAction(this, "Unlink"));
570 		unlinkButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/unlink.png")));
571 		unlinkButton.setText("");
572 		unlinkButton.setToolTipText("Unlink");
573 
574 		// TODO: horizontal rule - doesn't insert correctly if within anything
575 		// other than P, ie. TD or H1
576 		insertHorizontalLineButton
577 				.setAction(new HTMLEditorKit.InsertHTMLTextAction("Insert Horizontal Line", "<hr/>", Tag.P, Tag.HR, Tag.BODY, Tag.HR));
578 		insertHorizontalLineButton
579 				.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/horizontalline.png")));
580 		insertHorizontalLineButton.setText("");
581 		insertHorizontalLineButton.setToolTipText("Insert Horizontal Line");
582 
583 		paragraphFormatComboBox.setRenderer(new ParagraphFormatListCellRenderer());
584 		paragraphFormatComboBox.removeAllItems();
585 		ParagraphFormat[] paragraphFormats = ParagraphFormat.values();
586 		for (int i = 0; i < paragraphFormats.length; i++) {
587 			paragraphFormatComboBox.addItem(paragraphFormats[i]);
588 		}
589 
590 		fontComboBox.setRenderer(new FontListCellRenderer());
591 		fontComboBox.removeAllItems();
592 		FontItem[] fontItems = FontItem.values();
593 		for (int i = 0; i < fontItems.length; i++) {
594 			fontComboBox.addItem(fontItems[i]);
595 		}
596 
597 		fontSizeComboBox.setRenderer(new FontSizeListCellRenderer());
598 		fontSizeComboBox.removeAllItems();
599 		FontSize[] fontSizes = FontSize.values();
600 		for (int i = 0; i < fontSizes.length; i++) {
601 			fontSizeComboBox.addItem(fontSizes[i]);
602 		}
603 
604 		setToolbarFocusActionListener(this);
605 
606 		htmlTextPane.getInputMap().put(KeyStroke.getKeyStroke("control Z"), "Undo");
607 		htmlTextPane.getActionMap().put("Undo", undoAction);
608 
609 		htmlTextPane.getInputMap().put(KeyStroke.getKeyStroke("control Y"), "Redo");
610 		htmlTextPane.getActionMap().put("Redo", redoAction);
611 
612 		htmlTextPane.getInputMap().put(KeyStroke.getKeyStroke("control F"), "Find");
613 		htmlTextPane.getActionMap().put("Find", findReplaceAction);
614 
615 		htmlTextPane.getInputMap().put(KeyStroke.getKeyStroke("control R"), "Replace");
616 		htmlTextPane.getActionMap().put("Replace", findReplaceAction);
617 
618 		contextMenu = new JPopupMenu();
619 		JMenuItem cutMenuItem = new JMenuItem();
620 		cutMenuItem.setAction(cutAction);
621 		cutMenuItem.setText("Cut");
622 		cutMenuItem.setMnemonic('C');
623 		cutMenuItem.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/cut.png")));
624 		JMenuItem copyMenuItem = new JMenuItem();
625 		copyMenuItem.setAction(copyAction);
626 		copyMenuItem.setText("Copy");
627 		copyMenuItem.setMnemonic('o');
628 		copyMenuItem.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/copy.png")));
629 		JMenuItem pasteMenuItem = new JMenuItem();
630 		pasteMenuItem.setAction(pasteAction);
631 		pasteMenuItem.setText("Paste");
632 		pasteMenuItem.setMnemonic('P');
633 		pasteMenuItem.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/paste.png")));
634 		contextMenu.add(cutMenuItem);
635 		contextMenu.add(copyMenuItem);
636 		contextMenu.add(pasteMenuItem);
637 
638 		htmlTextPane.addMouseMotionListener(new DefaultEditorMouseMotionListener());
639 		htmlTextPane.setEditorKit(editorKit);
640 
641 		startNewDocument();
642 
643 		// initSpellChecker();
644 	}
645 
646 	// The following two methods allow us to find an
647 	// action provided by the editor kit by its name.
648 	private void createEditorKitActionTable() {
649 		editorKitActions = new Hashtable<>();
650 		Action[] actionsArray = editorKit.getActions();
651 		for (int i = 0; i < actionsArray.length; i++) {
652 			Action a = actionsArray[i];
653 			editorKitActions.put(a.getValue(Action.NAME), a);
654 		}
655 	}
656 
657 	private Action getEditorKitActionByName(String name) {
658 		return editorKitActions.get(name);
659 	}
660 
661 	protected void resetUndoManager() {
662 		undo.discardAllEdits();
663 		undoAction.update();
664 		redoAction.update();
665 	}
666 
667 	public void startNewDocument() {
668 		Document oldDoc = htmlTextPane.getDocument();
669 		if (oldDoc != null) {
670 			oldDoc.removeUndoableEditListener(undoHandler);
671 		}
672 		htmlDocument = (HTMLDocument) editorKit.createDefaultDocument();
673 		htmlTextPane.setDocument(htmlDocument);
674 		htmlTextPane.getDocument().addUndoableEditListener(undoHandler);
675 		resetUndoManager();
676 		// TODO: check if necessary
677 		htmlDocument.putProperty("IgnoreCharsetDirective", Boolean.TRUE);
678 		htmlDocument.setPreservesUnknownTags(false);
679 	}
680 
681 	/**
682 	 * This method is called from within the constructor to initialize the form. WARNING: Do NOT modify this code. The content of this
683 	 * method is always regenerated by the Form Editor.
684 	 */
685 	private void initComponents() {
686 
687 		htmlDocument = new javax.swing.text.html.HTMLDocument();
688 
689 		cutButton = new javax.swing.JButton();
690 		copyButton = new javax.swing.JButton();
691 		pasteAsTextButton = new javax.swing.JButton();
692 		pasteButton = new javax.swing.JButton();
693 		saveButton = new javax.swing.JButton();
694 		newButton = new javax.swing.JButton();
695 		previewButton = new javax.swing.JButton();
696 		openButton = new javax.swing.JButton();
697 		printButton = new javax.swing.JButton();
698 		spellcheckButton = new javax.swing.JButton();
699 		undoButton = new javax.swing.JButton();
700 		redoButton = new javax.swing.JButton();
701 		findButton = new javax.swing.JButton();
702 		replaceButton = new javax.swing.JButton();
703 		selectAllButton = new javax.swing.JButton();
704 		clearFormattingButton = new javax.swing.JButton();
705 		boldButton = new javax.swing.JButton();
706 		italicButton = new javax.swing.JButton();
707 		strikethroughButton = new javax.swing.JButton();
708 		underlineButton = new javax.swing.JButton();
709 		subscriptButton = new javax.swing.JButton();
710 		superscriptButton = new javax.swing.JButton();
711 		insertRemoveNumberedListButton = new javax.swing.JButton();
712 		insertRemoveBulletedListButton = new javax.swing.JButton();
713 		decreaseIndentButton = new javax.swing.JButton();
714 		increaseIndentButton = new javax.swing.JButton();
715 		createDivButton = new javax.swing.JButton();
716 		blockQuoteButton = new javax.swing.JButton();
717 		createParagraphButton = new javax.swing.JButton();
718 		leftJustifyButton = new javax.swing.JButton();
719 		centerJustifyButton = new javax.swing.JButton();
720 		blockJustifyButton = new javax.swing.JButton();
721 		rightJustifyButton = new javax.swing.JButton();
722 		insertTableButton = new javax.swing.JButton();
723 		insertHorizontalLineButton = new javax.swing.JButton();
724 		insertSpecialCharButton = new javax.swing.JButton();
725 		insertImage = new javax.swing.JButton();
726 		paragraphFormatComboBox = new javax.swing.JComboBox<>();
727 		fontComboBox = new javax.swing.JComboBox<>();
728 		fontSizeComboBox = new javax.swing.JComboBox<>();
729 		textColorButton = new javax.swing.JButton();
730 		backgroundColorButton = new javax.swing.JButton();
731 		aboutButton = new javax.swing.JButton();
732 		sourceButton = new javax.swing.JButton();
733 		linkButton = new javax.swing.JButton();
734 		unlinkButton = new javax.swing.JButton();
735 		anchorButton = new javax.swing.JButton();
736 
737 		htmlTextPane = new javax.swing.JTextPane();
738 
739 		cutButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/cut.png"))); // NOI18N
740 		cutButton.setToolTipText("Cut");
741 		cutButton.setName(CUT_BUTTON_KEY);
742 
743 		copyButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/copy.png"))); // NOI18N
744 		copyButton.setToolTipText("Copy");
745 		copyButton.setName(COPY_BUTTON_KEY);
746 
747 		pasteAsTextButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/paste_as_text.png"))); // NOI18N
748 		pasteAsTextButton.setToolTipText("Paste as plain text");
749 		pasteAsTextButton.addActionListener(new java.awt.event.ActionListener() {
750 			@Override
751 			public void actionPerformed(java.awt.event.ActionEvent evt) {
752 				pasteAsTextButtonActionPerformed(evt);
753 			}
754 		});
755 		pasteAsTextButton.setName(PASTE_AS_TEXT_BUTTON_KEY);
756 
757 		pasteButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/paste.png"))); // NOI18N
758 		pasteButton.setToolTipText("Paste");
759 		pasteButton.setName(PASTE_BUTTON_KEY);
760 
761 		saveButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/save.png"))); // NOI18N
762 		saveButton.setToolTipText("Save");
763 		saveButton.addActionListener(new java.awt.event.ActionListener() {
764 			@Override
765 			public void actionPerformed(java.awt.event.ActionEvent evt) {
766 				saveButtonActionPerformed(evt);
767 			}
768 		});
769 		saveButton.setName(SAVE_BUTTON_KEY);
770 
771 		newButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/newpage.png"))); // NOI18N
772 		newButton.setToolTipText("New");
773 		newButton.addActionListener(new java.awt.event.ActionListener() {
774 			@Override
775 			public void actionPerformed(java.awt.event.ActionEvent evt) {
776 				newButtonActionPerformed(evt);
777 			}
778 		});
779 		newButton.setName(NEW_BUTTON_KEY);
780 
781 		previewButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/preview.png"))); // NOI18N
782 		previewButton.setToolTipText("Preview");
783 		previewButton.addActionListener(new java.awt.event.ActionListener() {
784 			@Override
785 			public void actionPerformed(java.awt.event.ActionEvent evt) {
786 				previewButtonActionPerformed(evt);
787 			}
788 		});
789 		previewButton.setName(PREVIEW_BUTTON_KEY);
790 
791 		openButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/open.png"))); // NOI18N
792 		openButton.setToolTipText("Open");
793 		openButton.addActionListener(new java.awt.event.ActionListener() {
794 			@Override
795 			public void actionPerformed(java.awt.event.ActionEvent evt) {
796 				openButtonActionPerformed(evt);
797 			}
798 		});
799 		openButton.setName(OPEN_BUTTON_KEY);
800 
801 		printButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/print.png"))); // NOI18N
802 		printButton.setToolTipText("Print");
803 		printButton.addActionListener(new java.awt.event.ActionListener() {
804 			@Override
805 			public void actionPerformed(java.awt.event.ActionEvent evt) {
806 				printButtonActionPerformed(evt);
807 			}
808 		});
809 		printButton.setName(PRINT_BUTTON_KEY);
810 
811 		spellcheckButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/spellcheck.png"))); // NOI18N
812 		spellcheckButton.setToolTipText("Check Spelling");
813 		spellcheckButton.addActionListener(new java.awt.event.ActionListener() {
814 			@Override
815 			public void actionPerformed(java.awt.event.ActionEvent evt) {
816 				spellcheckButtonActionPerformed(evt);
817 			}
818 		});
819 		spellcheckButton.setName(SPELL_CHECK_BUTTON_KEY);
820 
821 		undoButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/undo.png"))); // NOI18N
822 		undoButton.setToolTipText("Undo");
823 		undoButton.setName(UNDO_BUTTON_KEY);
824 
825 		redoButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/redo.png"))); // NOI18N
826 		redoButton.setToolTipText("Redo");
827 		redoButton.setName(REDO_BUTTON_KEY);
828 
829 		findButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/find.png"))); // NOI18N
830 		findButton.setToolTipText("Find");
831 		findButton.setName(FIND_BUTTON_KEY);
832 
833 		replaceButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/replace.png"))); // NOI18N
834 		replaceButton.setToolTipText("Replace");
835 		replaceButton.setName(REPLACE_BUTTON_KEY);
836 
837 		selectAllButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/selectall.png"))); // NOI18N
838 		selectAllButton.setToolTipText("Select All");
839 		selectAllButton.addActionListener(new java.awt.event.ActionListener() {
840 			@Override
841 			public void actionPerformed(java.awt.event.ActionEvent evt) {
842 				selectAllButtonActionPerformed(evt);
843 			}
844 		});
845 		selectAllButton.setName(SELECT_ALL_BUTTON_KEY);
846 
847 		clearFormattingButton
848 				.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/removeformat.png"))); // NOI18N
849 		clearFormattingButton.setToolTipText("Remove Format");
850 		clearFormattingButton.setName(CLEAR_FORMATTING_BUTTON_KEY);
851 
852 		boldButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/bold.png"))); // NOI18N
853 		boldButton.setToolTipText("Bold");
854 		boldButton.setName(BOLD_BUTTON_KEY);
855 
856 		italicButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/italic.png"))); // NOI18N
857 		italicButton.setToolTipText("Italic");
858 		italicButton.setName(ITALIC_BUTTON_KEY);
859 
860 		strikethroughButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/strikethrough.png"))); // NOI18N
861 		strikethroughButton.setToolTipText("Strike Through");
862 		strikethroughButton.setName(STRIKE_BUTTON_KEY);
863 
864 		underlineButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/underline.png"))); // NOI18N
865 		underlineButton.setToolTipText("Underline");
866 		underlineButton.setName(UNDERLINE_BUTTON_KEY);
867 
868 		subscriptButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/subscript.png"))); // NOI18N
869 		subscriptButton.setToolTipText("Subscript");
870 		subscriptButton.setName(SUB_SCRIPT_BUTTON_KEY);
871 
872 		superscriptButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/superscript.png"))); // NOI18N
873 		superscriptButton.setToolTipText("Superscript");
874 		superscriptButton.setName(SUPER_SCRIPT_BUTTON_KEY);
875 
876 		insertRemoveNumberedListButton
877 				.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/numberlist.png"))); // NOI18N
878 		insertRemoveNumberedListButton.setToolTipText("Insert/Remove Numbered List");
879 		insertRemoveNumberedListButton.addActionListener(new java.awt.event.ActionListener() {
880 			@Override
881 			public void actionPerformed(java.awt.event.ActionEvent evt) {
882 				insertRemoveNumberedListButtonActionPerformed(evt);
883 			}
884 		});
885 		insertRemoveNumberedListButton.setName(NUMBERED_LIST_BUTTON_KEY);
886 
887 		insertRemoveBulletedListButton
888 				.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/bulletlist.png"))); // NOI18N
889 		insertRemoveBulletedListButton.setToolTipText("Insert/Remove Bulleted List");
890 		insertRemoveBulletedListButton.addActionListener(new java.awt.event.ActionListener() {
891 			@Override
892 			public void actionPerformed(java.awt.event.ActionEvent evt) {
893 				insertRemoveBulletedListButtonActionPerformed(evt);
894 			}
895 		});
896 		insertRemoveBulletedListButton.setName(BULLETED_BUTTON_KEY);
897 
898 		decreaseIndentButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/decindent.png"))); // NOI18N
899 		decreaseIndentButton.setToolTipText("Decrease Indent");
900 		decreaseIndentButton.setName(DECREASE_INDENT_BUTTON_KEY);
901 
902 		increaseIndentButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/incindent.png"))); // NOI18N
903 		increaseIndentButton.setToolTipText("Increase Indent");
904 		increaseIndentButton.setName(INCREASE_INDENT_BUTTON_KEY);
905 
906 		createDivButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/creatediv.png"))); // NOI18N
907 		createDivButton.setToolTipText("Create Div Container");
908 		createDivButton.addActionListener(new java.awt.event.ActionListener() {
909 			@Override
910 			public void actionPerformed(java.awt.event.ActionEvent evt) {
911 				createDivButtonActionPerformed(evt);
912 			}
913 		});
914 		createDivButton.setName(DIV_BUTTON_KEY);
915 
916 		blockQuoteButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/blockquote.png"))); // NOI18N
917 		blockQuoteButton.setToolTipText("Block Quote");
918 		blockQuoteButton.setName(BLOCK_QUOTE_BUTTON_KEY);
919 
920 		createParagraphButton
921 				.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/createparagraph.png"))); // NOI18N
922 		createParagraphButton.setToolTipText("Create Paragraph");
923 		createParagraphButton.addActionListener(new java.awt.event.ActionListener() {
924 			@Override
925 			public void actionPerformed(java.awt.event.ActionEvent evt) {
926 				createParagraphButtonActionPerformed(evt);
927 			}
928 		});
929 		createParagraphButton.setName(PARAGRAPH_BUTTON_KEY);
930 
931 		leftJustifyButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/leftjustify.png"))); // NOI18N
932 		leftJustifyButton.setToolTipText("Left Justify");
933 		leftJustifyButton.setName(LEFT_JUSTIFY_BUTTON_KEY);
934 
935 		centerJustifyButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/centerjustify.png"))); // NOI18N
936 		centerJustifyButton.setToolTipText("Center Justify");
937 		centerJustifyButton.setName(CENTER_JUSTIFY_BUTTON_KEY);
938 
939 		blockJustifyButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/blockjustify.png"))); // NOI18N
940 		blockJustifyButton.setToolTipText("Block Justify");
941 		blockJustifyButton.setName(BLOCK_JUSTIFY_BUTTON_KEY);
942 
943 		rightJustifyButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/rightjustify.png"))); // NOI18N
944 		rightJustifyButton.setToolTipText("Right Justify");
945 		rightJustifyButton.setName(RIGHT_JUSTIFY_BUTTON_KEY);
946 
947 		insertTableButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/table.png"))); // NOI18N
948 		insertTableButton.setToolTipText("Table");
949 		insertTableButton.addActionListener(new java.awt.event.ActionListener() {
950 			@Override
951 			public void actionPerformed(java.awt.event.ActionEvent evt) {
952 				insertTableButtonActionPerformed(evt);
953 			}
954 		});
955 		insertTableButton.setName(TABLE_BUTTON_KEY);
956 
957 		insertHorizontalLineButton
958 				.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/horizontalline.png"))); // NOI18N
959 		insertHorizontalLineButton.setToolTipText("Insert Horizontal Line");
960 		insertHorizontalLineButton.addActionListener(new java.awt.event.ActionListener() {
961 			@Override
962 			public void actionPerformed(java.awt.event.ActionEvent evt) {
963 				insertHorizontalLineButtonActionPerformed(evt);
964 			}
965 		});
966 		insertHorizontalLineButton.setName(HORIZONTAL_LINE_BUTTON_KEY);
967 
968 		insertSpecialCharButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/symbol.png"))); // NOI18N
969 		insertSpecialCharButton.setToolTipText("Insert Special Character");
970 		insertSpecialCharButton.addActionListener(new java.awt.event.ActionListener() {
971 			@Override
972 			public void actionPerformed(java.awt.event.ActionEvent evt) {
973 				insertSpecialCharButtonActionPerformed(evt);
974 			}
975 		});
976 		insertSpecialCharButton.setName(SPECIAL_CHAR_BUTTON_KEY);
977 
978 		insertImage.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/image.png"))); // NOI18N
979 		insertImage.setToolTipText("Image");
980 		insertImage.addActionListener(new java.awt.event.ActionListener() {
981 			@Override
982 			public void actionPerformed(java.awt.event.ActionEvent evt) {
983 				insertImageActionPerformed(evt);
984 			}
985 		});
986 		insertImage.setName(IMAGE_BUTTON_KEY);
987 
988 		paragraphFormatComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Normal", "Heading 1", "Heading 2",
989 				"Heading 3", "Heading 4", "Heading 5", "Heading 6", "Formatted", "Address", "Normal (DIV)" }));
990 		paragraphFormatComboBox.setToolTipText("Paragraph Format");
991 		paragraphFormatComboBox.addActionListener(new java.awt.event.ActionListener() {
992 			@Override
993 			public void actionPerformed(java.awt.event.ActionEvent evt) {
994 				paragraphFormatComboBoxActionPerformed(evt);
995 			}
996 		});
997 		paragraphFormatComboBox.setName(PARAGRAPH_FORMAT_PANEL_KEY);
998 
999 		fontComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new FontItem[] { FontItem.ARIAL, FontItem.COMIC_SANS_MS,
1000 				FontItem.COURIER_NEW, FontItem.GEORGIA, FontItem.LUCINDA_SANS_UNICODE, FontItem.TAHOMA, FontItem.TIMES_NEW_ROMAN,
1001 				FontItem.TREBUCHET_MS, FontItem.VERDANA }));
1002 		fontComboBox.setToolTipText("Font");
1003 		fontComboBox.addActionListener(new java.awt.event.ActionListener() {
1004 			@Override
1005 			public void actionPerformed(java.awt.event.ActionEvent evt) {
1006 				fontComboBoxActionPerformed(evt);
1007 			}
1008 		});
1009 		fontComboBox.setName(FONT_PANEL_KEY);
1010 
1011 		fontSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new FontSize[] { FontSize.SIZE8, FontSize.SIZE9, FontSize.SIZE10,
1012 				FontSize.SIZE11, FontSize.SIZE12, FontSize.SIZE14, FontSize.SIZE16, FontSize.SIZE18, FontSize.SIZE20, FontSize.SIZE22,
1013 				FontSize.SIZE24, FontSize.SIZE26, FontSize.SIZE28, FontSize.SIZE36, FontSize.SIZE48, FontSize.SIZE72 }));
1014 		fontSizeComboBox.setToolTipText("Font Size");
1015 		fontSizeComboBox.addActionListener(new java.awt.event.ActionListener() {
1016 			@Override
1017 			public void actionPerformed(java.awt.event.ActionEvent evt) {
1018 				fontSizeComboBoxActionPerformed(evt);
1019 			}
1020 		});
1021 		fontSizeComboBox.setName(FONT_SIZE_PANEL_KEY);
1022 
1023 		textColorButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/textcolor.png"))); // NOI18N
1024 		textColorButton.setToolTipText("Text Color");
1025 		textColorButton.addActionListener(new java.awt.event.ActionListener() {
1026 			@Override
1027 			public void actionPerformed(java.awt.event.ActionEvent evt) {
1028 				textColorButtonActionPerformed(evt);
1029 			}
1030 		});
1031 		textColorButton.setName(TEXT_COLOR_BUTTON_KEY);
1032 
1033 		backgroundColorButton
1034 				.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/backgroundcolor.png"))); // NOI18N
1035 		backgroundColorButton.setToolTipText("Background Color");
1036 		backgroundColorButton.addActionListener(new java.awt.event.ActionListener() {
1037 			@Override
1038 			public void actionPerformed(java.awt.event.ActionEvent evt) {
1039 				backgroundColorButtonActionPerformed(evt);
1040 			}
1041 		});
1042 		backgroundColorButton.setName(BACKGROUND_COLOR_BUTTON_KEY);
1043 
1044 		aboutButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/about.png"))); // NOI18N
1045 		aboutButton.setToolTipText("About Metaphase Editor");
1046 		aboutButton.addActionListener(new java.awt.event.ActionListener() {
1047 			@Override
1048 			public void actionPerformed(java.awt.event.ActionEvent evt) {
1049 				aboutButtonActionPerformed(evt);
1050 			}
1051 		});
1052 		aboutButton.setName(ABOUT_BUTTON_KEY);
1053 
1054 		sourceButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/source.png"))); // NOI18N
1055 		sourceButton.setText("Source");
1056 		sourceButton.setToolTipText("Source");
1057 		// sourceButton.setPreferredSize(new java.awt.Dimension(87, 24));
1058 		sourceButton.addActionListener(new java.awt.event.ActionListener() {
1059 			@Override
1060 			public void actionPerformed(java.awt.event.ActionEvent evt) {
1061 				sourceButtonActionPerformed(evt);
1062 			}
1063 		});
1064 		sourceButton.setName(SOURCE_BUTTON_KEY);
1065 
1066 		linkButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/link.png"))); // NOI18N
1067 		linkButton.setToolTipText("Link");
1068 		linkButton.addActionListener(new java.awt.event.ActionListener() {
1069 			@Override
1070 			public void actionPerformed(java.awt.event.ActionEvent evt) {
1071 				linkButtonActionPerformed(evt);
1072 			}
1073 		});
1074 		linkButton.setName(LINK_BUTTON_KEY);
1075 
1076 		unlinkButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/unlink.png"))); // NOI18N
1077 		unlinkButton.setToolTipText("Unlink");
1078 		unlinkButton.setName(UNLINK_BUTTON_KEY);
1079 
1080 		anchorButton.setIcon(new ImageIconResource(ResourceLocator.locateResource("Icons/MetaphaseEditor/icons/anchor.png"))); // NOI18N
1081 		anchorButton.setToolTipText("Anchor");
1082 		anchorButton.addActionListener(new java.awt.event.ActionListener() {
1083 			@Override
1084 			public void actionPerformed(java.awt.event.ActionEvent evt) {
1085 				anchorButtonActionPerformed(evt);
1086 			}
1087 		});
1088 		anchorButton.setName(ANCHOR_BUTTON_KEY);
1089 
1090 		htmlTextPane.addMouseListener(new java.awt.event.MouseAdapter() {
1091 			@Override
1092 			public void mouseClicked(java.awt.event.MouseEvent evt) {
1093 				htmlTextPaneMouseClicked(evt);
1094 			}
1095 		});
1096 		Action insertBreakAction = htmlTextPane.getActionMap().get(DefaultEditorKit.insertBreakAction);
1097 		Action deletePrevCharAction = htmlTextPane.getActionMap().get(DefaultEditorKit.deletePrevCharAction);
1098 		htmlTextPane.getActionMap().put(DefaultEditorKit.insertBreakAction, new InsertBreakAction(insertBreakAction, deletePrevCharAction));
1099 		/*htmlTextPane.addKeyListener(new java.awt.event.KeyAdapter() {
1100 			@Override
1101 			public void keyPressed(java.awt.event.KeyEvent evt) {
1102 				htmlTextPaneKeyPressed(evt);
1103 			}
1104 		
1105 			@Override
1106 			public void keyReleased(java.awt.event.KeyEvent evt) {
1107 				htmlTextPaneKeyReleased(evt);
1108 			}
1109 		
1110 			@Override
1111 			public void keyTyped(java.awt.event.KeyEvent evt) {
1112 				htmlTextPaneKeyTyped(evt);
1113 			}
1114 		});*/
1115 		mainScrollPane = new JScrollPane();
1116 		mainScrollPane.setOpaque(false);
1117 		mainScrollPane.setViewportView(htmlTextPane);
1118 
1119 		setLayout(new BorderLayout());
1120 
1121 		toolbarPanel = new JPanel(new GridBagLayout());
1122 		toolbarPanel.setOpaque(false);
1123 		// toolbarPanel.setLayout(new BoxLayout(toolbarPanel, BoxLayout.Y_AXIS));
1124 
1125 		add(toolbarPanel, BorderLayout.NORTH);
1126 		add(mainScrollPane, BorderLayout.CENTER);
1127 
1128 	}
1129 
1130 	public void updateComponents(MetaphaseEditorConfiguration configuration) {
1131 
1132 		toolbarPanel.removeAll();
1133 
1134 		if (configuration.hasOption(SOURCE_PANEL_KEY)) {
1135 			sourcePanel = makeGroup(SOURCE_PANEL_KEY, configuration, sourceButton);
1136 		}
1137 
1138 		if (configuration.hasOption(PAGE_PANEL_KEY)) {
1139 			pagePanel = makeGroup(PAGE_PANEL_KEY, configuration, openButton, saveButton, newButton, previewButton);
1140 		}
1141 
1142 		if (configuration.hasOption(EDIT_PANEL_KEY)) {
1143 			editPanel = makeGroup(EDIT_PANEL_KEY, configuration, cutButton, copyButton, pasteButton, pasteAsTextButton);
1144 		}
1145 
1146 		if (configuration.hasOption(TOOLS_PANEL_KEY)) {
1147 			toolsPanel = makeGroup(TOOLS_PANEL_KEY, configuration, printButton, spellcheckButton);
1148 		}
1149 
1150 		if (configuration.hasOption(UNDO_REDO_PANEL_KEY)) {
1151 			undoRedoPanel = makeGroup(UNDO_REDO_PANEL_KEY, configuration, undoButton, redoButton);
1152 		}
1153 
1154 		if (configuration.hasOption(SEARCH_PANEL_KEY)) {
1155 			searchPanel = makeGroup(SEARCH_PANEL_KEY, configuration, findButton, replaceButton);
1156 		}
1157 
1158 		if (configuration.hasOption(FORMAT_PANEL_KEY)) {
1159 			formatPanel = makeGroup(FORMAT_PANEL_KEY, configuration, selectAllButton, clearFormattingButton);
1160 		}
1161 
1162 		if (configuration.hasOption(TEXT_EFFECT_PANEL_KEY)) {
1163 			textEffectPanel = makeGroup(TEXT_EFFECT_PANEL_KEY, configuration, boldButton, italicButton, strikethroughButton,
1164 					underlineButton);
1165 		}
1166 
1167 		if (configuration.hasOption(SUB_SUPER_SCRIPT_PANEL_KEY)) {
1168 			subSuperScriptPanel = makeGroup(SUB_SUPER_SCRIPT_PANEL_KEY, configuration, subscriptButton, superscriptButton);
1169 		}
1170 
1171 		if (configuration.hasOption(LIST_PANEL_KEY)) {
1172 			listPanel = makeGroup(LIST_PANEL_KEY, configuration, insertRemoveNumberedListButton, insertRemoveBulletedListButton);
1173 		}
1174 
1175 		if (configuration.hasOption(BLOCK_PANEL_KEY)) {
1176 			blockPanel = makeGroup(BLOCK_PANEL_KEY, configuration, decreaseIndentButton, increaseIndentButton, blockQuoteButton,
1177 					createDivButton, createParagraphButton);
1178 		}
1179 
1180 		if (configuration.hasOption(JUSTIFICATION_PANEL_KEY)) {
1181 			justificationPanel = makeGroup(JUSTIFICATION_PANEL_KEY, configuration, leftJustifyButton, centerJustifyButton,
1182 					rightJustifyButton, blockJustifyButton);
1183 		}
1184 
1185 		if (configuration.hasOption(LINK_PANEL_KEY)) {
1186 			linkPanel = makeGroup(LINK_PANEL_KEY, configuration, linkButton, unlinkButton, anchorButton);
1187 		}
1188 
1189 		if (configuration.hasOption(MISC_PANEL_KEY)) {
1190 			miscPanel = makeGroup(MISC_PANEL_KEY, configuration, insertImage, insertTableButton, insertHorizontalLineButton,
1191 					insertSpecialCharButton);
1192 		}
1193 
1194 		if (configuration.hasOption(COLOR_PANEL_KEY)) {
1195 			colorPanel = makeGroup(COLOR_PANEL_KEY, configuration, textColorButton, backgroundColorButton);
1196 		}
1197 
1198 		if (configuration.hasOption(ABOUT_PANEL_KEY)) {
1199 			aboutPanel = makeGroup(ABOUT_PANEL_KEY, configuration, aboutButton);
1200 		}
1201 		GridBagConstraints gbc = new GridBagConstraints();
1202 		gbc.weightx = 1.0;
1203 		gbc.fill = GridBagConstraints.HORIZONTAL;
1204 		gbc.anchor = GridBagConstraints.LINE_START;
1205 		gbc.gridwidth = GridBagConstraints.REMAINDER;
1206 		gbc.insets = new Insets(3, 3, 3, 3);
1207 		toolbarPanel.add(makeLinePanel(1, configuration), gbc);
1208 		toolbarPanel.add(makeLinePanel(2, configuration), gbc);
1209 		toolbarPanel.add(makeLinePanel(3, configuration), gbc);
1210 
1211 		toolbarPanel.revalidate();
1212 		toolbarPanel.repaint();
1213 	}
1214 
1215 	private static JPanel makeGroup(String groupName, final MetaphaseEditorConfiguration configuration, JButton... buttons) {
1216 		JPanel returned = new JPanel();
1217 		returned.setOpaque(false);
1218 		returned.setName(groupName);
1219 		// returned.setBorder(BorderFactory.createEtchedBorder());
1220 		returned.setBorder(BorderFactory.createEmptyBorder());
1221 		returned.setLayout(new FlowLayout(FlowLayout.LEADING, 0, 0));
1222 
1223 		Vector<JButton> buttonList = new Vector<>();
1224 
1225 		for (JButton b : buttons) {
1226 			if (configuration.hasOption(b.getName())) {
1227 				buttonList.add(b);
1228 			}
1229 		}
1230 
1231 		Collections.sort(buttonList, new Comparator<JButton>() {
1232 			@Override
1233 			public int compare(JButton o1, JButton o2) {
1234 				return configuration.getOption(o1.getName()).index - configuration.getOption(o2.getName()).index;
1235 			}
1236 		});
1237 
1238 		for (JButton c : buttonList) {
1239 			returned.add(c);
1240 		}
1241 		return returned;
1242 	}
1243 
1244 	private JPanel makeLinePanel(int line, final MetaphaseEditorConfiguration configuration) {
1245 		Vector<JComponent> components = new Vector<>();
1246 		if (configuration.hasOption(SOURCE_PANEL_KEY, line)) {
1247 			components.add(sourcePanel);
1248 		}
1249 		if (configuration.hasOption(PAGE_PANEL_KEY, line)) {
1250 			components.add(pagePanel);
1251 		}
1252 		if (configuration.hasOption(EDIT_PANEL_KEY, line)) {
1253 			components.add(editPanel);
1254 		}
1255 		if (configuration.hasOption(TOOLS_PANEL_KEY, line)) {
1256 			components.add(toolsPanel);
1257 		}
1258 		if (configuration.hasOption(UNDO_REDO_PANEL_KEY, line)) {
1259 			components.add(undoRedoPanel);
1260 		}
1261 		if (configuration.hasOption(SEARCH_PANEL_KEY, line)) {
1262 			components.add(searchPanel);
1263 		}
1264 		if (configuration.hasOption(FORMAT_PANEL_KEY, line)) {
1265 			components.add(formatPanel);
1266 		}
1267 		if (configuration.hasOption(TEXT_EFFECT_PANEL_KEY, line)) {
1268 			components.add(textEffectPanel);
1269 		}
1270 		if (configuration.hasOption(SUB_SUPER_SCRIPT_PANEL_KEY, line)) {
1271 			components.add(subSuperScriptPanel);
1272 		}
1273 		if (configuration.hasOption(LIST_PANEL_KEY, line)) {
1274 			components.add(listPanel);
1275 		}
1276 		if (configuration.hasOption(BLOCK_PANEL_KEY, line)) {
1277 			components.add(blockPanel);
1278 		}
1279 		if (configuration.hasOption(JUSTIFICATION_PANEL_KEY, line)) {
1280 			components.add(justificationPanel);
1281 		}
1282 		if (configuration.hasOption(LINK_PANEL_KEY, line)) {
1283 			components.add(linkPanel);
1284 		}
1285 		if (configuration.hasOption(MISC_PANEL_KEY, line)) {
1286 			components.add(miscPanel);
1287 		}
1288 		if (configuration.hasOption(PARAGRAPH_FORMAT_PANEL_KEY, line)) {
1289 			components.add(paragraphFormatComboBox);
1290 		}
1291 		if (configuration.hasOption(FONT_PANEL_KEY, line)) {
1292 			components.add(fontComboBox);
1293 		}
1294 		if (configuration.hasOption(FONT_SIZE_PANEL_KEY, line)) {
1295 			components.add(fontSizeComboBox);
1296 		}
1297 		if (configuration.hasOption(COLOR_PANEL_KEY, line)) {
1298 			components.add(colorPanel);
1299 		}
1300 		if (configuration.hasOption(ABOUT_PANEL_KEY, line)) {
1301 			components.add(aboutPanel);
1302 		}
1303 
1304 		Collections.sort(components, new Comparator<JComponent>() {
1305 			@Override
1306 			public int compare(JComponent o1, JComponent o2) {
1307 				return configuration.getOption(o1.getName()).index - configuration.getOption(o2.getName()).index;
1308 			}
1309 		});
1310 
1311 		JPanel returned = new JPanel();
1312 		returned.setOpaque(false);
1313 		returned.setVisible(components.size() > 0);
1314 		returned.setLayout(new WrapLayout(FlowLayout.LEADING, 10, 0));
1315 		for (JComponent c : components) {
1316 			returned.add(c);
1317 		}
1318 		return returned;
1319 	}
1320 
1321 	private void printButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_printButtonActionPerformed
1322 		try {
1323 			htmlTextPane.print();
1324 		} catch (PrinterException e) {
1325 			throw new MetaphaseEditorException(e.getMessage(), e);
1326 		}
1327 	}// GEN-LAST:event_printButtonActionPerformed
1328 
1329 	public JTextPane getHtmlTextPane() {
1330 		return htmlTextPane;
1331 	}
1332 
1333 	public void setEditorToolTipText(String string) {
1334 		htmlTextPane.setToolTipText(string);
1335 	}
1336 
1337 	public void addEditorMouseMotionListener(EditorMouseMotionListener editorMouseMotionListener) {
1338 		editorMouseMotionListeners.add(editorMouseMotionListener);
1339 	}
1340 
1341 	public void removeEditorMouseMotionListener(EditorMouseMotionListener editorMouseMotionListener) {
1342 		editorMouseMotionListeners.remove(editorMouseMotionListener);
1343 	}
1344 
1345 	public Element getParentTag(HTML.Tag tag) {
1346 		Element e = htmlDocument.getCharacterElement(htmlTextPane.getSelectionStart());
1347 		while (!e.getName().equalsIgnoreCase("html")) {
1348 			if (e.getName().equalsIgnoreCase(tag.toString())) {
1349 				return e;
1350 			}
1351 			e = e.getParentElement();
1352 		}
1353 		return null;
1354 	}
1355 
1356 	public AttributeSet getSelectedParagraphAttributes() {
1357 		int start = htmlTextPane.getSelectionStart();
1358 
1359 		Element element = htmlDocument.getParagraphElement(start);
1360 		MutableAttributeSet attributes = new SimpleAttributeSet(element.getAttributes());
1361 
1362 		Element charElement = htmlDocument.getCharacterElement(start);
1363 		if (charElement != null) {
1364 			Element impliedParagraph = charElement.getParentElement();
1365 			if (impliedParagraph != null) {
1366 				Element listElement = impliedParagraph.getParentElement();
1367 				if (listElement.getName().equals("li")) {
1368 					// re-add the existing attributes to the list item
1369 					AttributeSet listElementAttrs = listElement.getAttributes();
1370 					Enumeration<?> currentAttrEnum = listElementAttrs.getAttributeNames();
1371 					while (currentAttrEnum.hasMoreElements()) {
1372 						Object attrName = currentAttrEnum.nextElement();
1373 						Object attrValue = listElement.getAttributes().getAttribute(attrName);
1374 						if ((attrName instanceof String || attrName instanceof HTML.Attribute) && attrValue instanceof String) {
1375 							attributes.addAttribute(attrName, attrValue);
1376 						}
1377 					}
1378 				}
1379 			}
1380 		}
1381 
1382 		return attributes;
1383 	}
1384 
1385 	public void addAttributesToSelectedParagraph(Map<String, String> attributes) {
1386 		new AddAttributesAction(this, "Add Attributes To Selected Paragraph", attributes).actionPerformed(null);
1387 	}
1388 
1389 	public void removeAttributesFromSelectedParagraph(String[] attributeNames) {
1390 		new RemoveAttributesAction(this, "Remove Attributes From Selected Paragraph", attributeNames).actionPerformed(null);
1391 	}
1392 
1393 	public String getDocument() {
1394 		return htmlTextPane.getText();
1395 	}
1396 
1397 	public void setDocument(String value) {
1398 		try {
1399 			StringReader reader = new StringReader(value);
1400 			Document oldDoc = htmlTextPane.getDocument();
1401 			if (oldDoc != null) {
1402 				oldDoc.removeUndoableEditListener(undoHandler);
1403 			}
1404 			htmlDocument = (HTMLDocument) editorKit.createDefaultDocument();
1405 			editorKit.read(reader, htmlDocument, 0);
1406 			htmlDocument.addUndoableEditListener(undoHandler);
1407 			htmlTextPane.setDocument(htmlDocument);
1408 			resetUndoManager();
1409 		} catch (BadLocationException e) {
1410 			throw new MetaphaseEditorException(e.getMessage(), e);
1411 		} catch (IOException e) {
1412 			throw new MetaphaseEditorException(e.getMessage(), e);
1413 		}
1414 	}
1415 
1416 	public JPopupMenu getContextMenu() {
1417 		return contextMenu;
1418 	}
1419 
1420 	public void addStyleSheetRule(String rule) {
1421 		StyleSheet styleSheet = editorKit.getStyleSheet();
1422 		styleSheet.addRule(rule);
1423 	}
1424 
1425 	public void refreshAfterAction() {
1426 		int pos = htmlTextPane.getCaretPosition();
1427 		htmlTextPane.setText(htmlTextPane.getText());
1428 		htmlTextPane.validate();
1429 		try {
1430 			htmlTextPane.setCaretPosition(pos);
1431 		} catch (IllegalArgumentException e) {
1432 			// swallow the exception
1433 			// seems like a bug in the JTextPane component
1434 			// only happens occasionally when pasting text at the end of a
1435 			// document
1436 			System.err.println(e.getMessage());
1437 		}
1438 	}
1439 
1440 	private static void insertRemoveNumberedListButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_insertRemoveNumberedListButtonActionPerformed
1441 		new HTMLEditorKit.InsertHTMLTextAction("Insert Bulleted List", "<ol><li></li></ol>", Tag.BODY, Tag.OL).actionPerformed(evt);
1442 	}// GEN-LAST:event_insertRemoveNumberedListButtonActionPerformed
1443 
1444 	private static void textColorButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_textColorButtonActionPerformed
1445 		Color color = JColorChooser.showDialog(null, "Text Color", null);
1446 		if (color != null) {
1447 			new StyledEditorKit.ForegroundAction("Color", color).actionPerformed(evt);
1448 		}
1449 	}// GEN-LAST:event_textColorButtonActionPerformed
1450 
1451 	private void selectAllButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_selectAllButtonActionPerformed
1452 		htmlTextPane.selectAll();
1453 	}// GEN-LAST:event_selectAllButtonActionPerformed
1454 
1455 	private static void aboutButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_aboutButtonActionPerformed
1456 		AboutDialog aboutDialog = new AboutDialog(null, true);
1457 		aboutDialog.setVisible(true);
1458 	}// GEN-LAST:event_aboutButtonActionPerformed
1459 
1460 	private void backgroundColorButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_backgroundColorButtonActionPerformed
1461 		Color color = JColorChooser.showDialog(null, "Text Color", null);
1462 		if (color != null) {
1463 			new BackgroundColorAction(color).actionPerformed(evt);
1464 		}
1465 	}// GEN-LAST:event_backgroundColorButtonActionPerformed
1466 
1467 	private void sourceButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_sourceButtonActionPerformed
1468 		if (htmlSourceMode) {
1469 			htmlTextPane.setText(htmlTextArea.getText());
1470 			mainScrollPane.setViewportView(htmlTextPane);
1471 			htmlSourceMode = false;
1472 
1473 			setToolbarComponentEnable(this, true);
1474 		}
1475 		else {
1476 			htmlTextArea.setText(htmlTextPane.getText());
1477 			mainScrollPane.setViewportView(htmlTextArea);
1478 			htmlSourceMode = true;
1479 
1480 			setToolbarComponentEnable(this, false);
1481 		}
1482 	}// GEN-LAST:event_sourceButtonActionPerformed
1483 
1484 	private void insertHorizontalLineButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_insertHorizontalLineButtonActionPerformed
1485 
1486 	}// GEN-LAST:event_insertHorizontalLineButtonActionPerformed
1487 
1488 	private void insertSpecialCharButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_insertSpecialCharButtonActionPerformed
1489 		String specialChars = specialCharacterDialog.showDialog();
1490 		if (specialChars != null) {
1491 			new InsertTextAction(this, "Insert Special Character", specialChars).actionPerformed(evt);
1492 		}
1493 	}// GEN-LAST:event_insertSpecialCharButtonActionPerformed
1494 
1495 	private void saveButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_saveButtonActionPerformed
1496 		try {
1497 			File current = new File(".");
1498 			JFileChooser chooser = new JFileChooser(current);
1499 			chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
1500 			chooser.setFileFilter(new HTMLFileFilter());
1501 			for (;;) {
1502 				int approval = chooser.showSaveDialog(this);
1503 				if (approval == JFileChooser.APPROVE_OPTION) {
1504 					File newFile = chooser.getSelectedFile();
1505 					if (newFile.exists()) {
1506 						String message = newFile.getAbsolutePath() + " already exists. \n" + "Do you want to replace it?";
1507 						int option = JOptionPane.showConfirmDialog(this, message, "Save", JOptionPane.YES_NO_CANCEL_OPTION);
1508 						if (option == JOptionPane.YES_OPTION) {
1509 							File currentFile = newFile;
1510 							FileWriter fw = new FileWriter(currentFile);
1511 							fw.write(htmlTextPane.getText());
1512 							fw.close();
1513 							break;
1514 						}
1515 						else if (option == JOptionPane.NO_OPTION) {
1516 							continue;
1517 						}
1518 						else if (option == JOptionPane.CANCEL_OPTION) {
1519 							break;
1520 						}
1521 					}
1522 					else {
1523 						File currentFile = new File(newFile.getAbsolutePath());
1524 						FileWriter fw = new FileWriter(currentFile);
1525 						fw.write(htmlTextPane.getText());
1526 						fw.close();
1527 						break;
1528 					}
1529 				}
1530 				else {
1531 					break;
1532 				}
1533 			}
1534 		} catch (FileNotFoundException e) {
1535 			throw new MetaphaseEditorException(e.getMessage(), e);
1536 		} catch (IOException e) {
1537 			throw new MetaphaseEditorException(e.getMessage(), e);
1538 		}
1539 	}// GEN-LAST:event_saveButtonActionPerformed
1540 
1541 	private void newButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_newButtonActionPerformed
1542 		if (JOptionPane.showConfirmDialog(this, "Are you sure you want to erase all the current content and start a new document?", "New",
1543 				JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
1544 			startNewDocument();
1545 
1546 			if (htmlSourceMode) {
1547 				htmlTextArea.setText(htmlTextPane.getText());
1548 			}
1549 		}
1550 	}// GEN-LAST:event_newButtonActionPerformed
1551 
1552 	private void paragraphFormatComboBoxActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_paragraphFormatComboBoxActionPerformed
1553 		ParagraphFormat paragraphFormat = (ParagraphFormat) paragraphFormatComboBox.getSelectedItem();
1554 		if (paragraphFormat != null && paragraphFormat.getTag() != null) {
1555 			new FormatAction(this, "Paragraph Format", paragraphFormat.getTag()).actionPerformed(evt);
1556 		}
1557 		if (paragraphFormatComboBox.getItemCount() > 0) {
1558 			paragraphFormatComboBox.setSelectedIndex(0);
1559 		}
1560 	}// GEN-LAST:event_paragraphFormatComboBoxActionPerformed
1561 
1562 	private void fontComboBoxActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_fontComboBoxActionPerformed
1563 		FontItem fontItem = (FontItem) fontComboBox.getSelectedItem();
1564 		if (fontItem != null && fontItem.getFontName() != null) {
1565 			new HTMLEditorKit.FontFamilyAction(fontItem.getText(), fontItem.getFontName()).actionPerformed(evt);
1566 		}
1567 		if (fontComboBox.getItemCount() > 0) {
1568 			fontComboBox.setSelectedIndex(0);
1569 		}
1570 	}// GEN-LAST:event_fontComboBoxActionPerformed
1571 
1572 	private void fontSizeComboBoxActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_fontSizeComboBoxActionPerformed
1573 		FontSize fontSize = (FontSize) fontSizeComboBox.getSelectedItem();
1574 		if (fontSize != null && fontSize.getSize() != -1) {
1575 			new HTMLEditorKit.FontSizeAction(fontSize.getText(), fontSize.getSize()).actionPerformed(evt);
1576 		}
1577 		if (fontSizeComboBox.getItemCount() > 0) {
1578 			fontSizeComboBox.setSelectedIndex(0);
1579 		}
1580 	}// GEN-LAST:event_fontSizeComboBoxActionPerformed
1581 
1582 	private void previewButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_previewButtonActionPerformed
1583 		try {
1584 			if (htmlSourceMode) {
1585 				htmlTextPane.setText(htmlTextArea.getText());
1586 			}
1587 			File tempFile = File.createTempFile("metaphaseeditorpreview", ".html");
1588 			tempFile.deleteOnExit();
1589 			FileWriter fw = new FileWriter(tempFile);
1590 			fw.write(htmlTextPane.getText());
1591 			fw.close();
1592 
1593 			Desktop.getDesktop().browse(tempFile.toURI());
1594 		} catch (IOException e) {
1595 			throw new MetaphaseEditorException(e.getMessage(), e);
1596 		}
1597 	}// GEN-LAST:event_previewButtonActionPerformed
1598 
1599 	private void insertTableButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_insertTableButtonActionPerformed
1600 		TableDialog tableDialog = new TableDialog(null, true);
1601 		String tableHtml = tableDialog.showDialog();
1602 		if (tableHtml != null) {
1603 			try {
1604 				editorKit.insertHTML(htmlDocument, htmlTextPane.getCaretPosition(), tableHtml, 0, 0, Tag.TABLE);
1605 				refreshAfterAction();
1606 			} catch (BadLocationException e) {
1607 				throw new MetaphaseEditorException(e.getMessage(), e);
1608 			} catch (IOException e) {
1609 				throw new MetaphaseEditorException(e.getMessage(), e);
1610 			}
1611 		}
1612 	}// GEN-LAST:event_insertTableButtonActionPerformed
1613 
1614 	private void openButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_openButtonActionPerformed
1615 		try {
1616 			File current = new File(".");
1617 			JFileChooser chooser = new JFileChooser(current);
1618 			chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
1619 			chooser.setFileFilter(new HTMLFileFilter());
1620 			int approval = chooser.showOpenDialog(this);
1621 			if (approval == JFileChooser.APPROVE_OPTION) {
1622 				File currentFile = chooser.getSelectedFile();
1623 				if (currentFile.exists()) {
1624 					FileReader fr = new FileReader(currentFile);
1625 					Document oldDoc = htmlTextPane.getDocument();
1626 					if (oldDoc != null) {
1627 						oldDoc.removeUndoableEditListener(undoHandler);
1628 					}
1629 					htmlDocument = (HTMLDocument) editorKit.createDefaultDocument();
1630 					editorKit.read(fr, htmlDocument, 0);
1631 					htmlDocument.addUndoableEditListener(undoHandler);
1632 					htmlTextPane.setDocument(htmlDocument);
1633 					resetUndoManager();
1634 				}
1635 				else {
1636 					JOptionPane.showMessageDialog(null, "The selected file does not exist.", "Error", JOptionPane.ERROR_MESSAGE);
1637 				}
1638 			}
1639 		} catch (BadLocationException e) {
1640 			throw new MetaphaseEditorException(e.getMessage(), e);
1641 		} catch (FileNotFoundException e) {
1642 			throw new MetaphaseEditorException(e.getMessage(), e);
1643 		} catch (IOException e) {
1644 			throw new MetaphaseEditorException(e.getMessage(), e);
1645 		}
1646 	}// GEN-LAST:event_openButtonActionPerformed
1647 
1648 	private void pasteAsTextButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_pasteAsTextButtonActionPerformed
1649 		Clipboard clipboard = getToolkit().getSystemClipboard();
1650 		Transferable transferable = clipboard.getContents(null);
1651 		if (transferable != null) {
1652 			try {
1653 				String plainText = (String) transferable.getTransferData(DataFlavor.stringFlavor);
1654 				plainText = plainText.replaceAll("\\r\\n", "<br/>");
1655 				plainText = plainText.replaceAll("\\n", "<br/>");
1656 				plainText = plainText.replaceAll("\\r", "<br/>");
1657 				new InsertHtmlAction(this, "Paste as Text", "<p>" + plainText + "</p>", Tag.P).actionPerformed(null);
1658 			} catch (UnsupportedFlavorException e) {
1659 				throw new MetaphaseEditorException(e.getMessage(), e);
1660 			} catch (IOException e) {
1661 				throw new MetaphaseEditorException(e.getMessage(), e);
1662 			}
1663 		}
1664 	}// GEN-LAST:event_pasteAsTextButtonActionPerformed
1665 
1666 	private void htmlTextPaneMouseClicked(java.awt.event.MouseEvent evt) {// GEN-FIRST:event_htmlTextPaneMouseClicked
1667 		if (evt.getButton() == MouseEvent.BUTTON3) {
1668 			for (int i = 0; i < contextMenuListeners.size(); i++) {
1669 				contextMenuListeners.get(i).beforeContextMenuPopup();
1670 			}
1671 			contextMenu.show(evt.getComponent(), evt.getX(), evt.getY());
1672 		}
1673 	}// GEN-LAST:event_htmlTextPaneMouseClicked
1674 
1675 	private void anchorButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_anchorButtonActionPerformed
1676 		AnchorDialog anchorDialog = new AnchorDialog(null, true);
1677 		String anchorHtml = anchorDialog.showDialog();
1678 		if (anchorHtml != null) {
1679 			new InsertHtmlAction(this, "Anchor", anchorHtml, Tag.A).actionPerformed(evt);
1680 		}
1681 	}// GEN-LAST:event_anchorButtonActionPerformed
1682 
1683 	private void insertImageActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_insertImageActionPerformed
1684 		insertImageRequestHandler.insertImage(htmlTextPane);
1685 	}// GEN-LAST:event_insertImageActionPerformed
1686 
1687 	private static void insertRemoveBulletedListButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_insertRemoveBulletedListButtonActionPerformed
1688 		new HTMLEditorKit.InsertHTMLTextAction("Insert Bulleted List", "<ul><li></li></ul>", Tag.BODY, Tag.UL).actionPerformed(evt);
1689 	}// GEN-LAST:event_insertRemoveBulletedListButtonActionPerformed
1690 
1691 	private static void htmlTextPaneKeyPressed(java.awt.event.KeyEvent evt) {// GEN-FIRST:event_htmlTextPaneKeyPressed
1692 	}// GEN-LAST:event_htmlTextPaneKeyPressed
1693 
1694 	private static void htmlTextPaneKeyReleased(java.awt.event.KeyEvent evt) {// GEN-FIRST:event_htmlTextPaneKeyReleased
1695 
1696 	}// GEN-LAST:event_htmlTextPaneKeyReleased
1697 
1698 	private static void htmlTextPaneKeyTyped(java.awt.event.KeyEvent evt) {// GEN-FIRST:event_htmlTextPaneKeyTyped
1699 		if (evt.getKeyChar() == KeyEvent.VK_ENTER) {
1700 
1701 		}
1702 	}// GEN-LAST:event_htmlTextPaneKeyTyped
1703 
1704 	private void createDivButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_createDivButtonActionPerformed
1705 		DivDialog divDialog = new DivDialog(null, true);
1706 		String divHtml = divDialog.showDialog();
1707 		if (divHtml != null) {
1708 			try {
1709 				editorKit.insertHTML(htmlDocument, htmlTextPane.getCaretPosition(), divHtml, 0, 0, Tag.DIV);
1710 				refreshAfterAction();
1711 			} catch (BadLocationException e) {
1712 				throw new MetaphaseEditorException(e.getMessage(), e);
1713 			} catch (IOException e) {
1714 				throw new MetaphaseEditorException(e.getMessage(), e);
1715 			}
1716 		}
1717 	}// GEN-LAST:event_createDivButtonActionPerformed
1718 
1719 	private void linkButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_linkButtonActionPerformed
1720 		LinkDialog linkDialog = new LinkDialog(null, true);
1721 		String html = linkDialog.showDialog();
1722 		if (html != null) {
1723 			new InsertHtmlAction(this, "Anchor", html, Tag.A).actionPerformed(evt);
1724 		}
1725 	}// GEN-LAST:event_linkButtonActionPerformed
1726 
1727 	private static void spellcheckButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_spellcheckButtonActionPerformed
1728 		// JOptionPane.showMessageDialog(null,
1729 		// "The spelling checker functionality is currently unavailable.");
1730 		Thread thread = new Thread() {
1731 			@Override
1732 			public void run() {
1733 				try {
1734 					// TODO, spelling needed? FD
1735 					// spellChecker.spellCheck(htmlTextPane);
1736 					JOptionPane.showMessageDialog(null, "The spelling check is complete.", "Check Spelling",
1737 							JOptionPane.INFORMATION_MESSAGE);
1738 				} catch (Exception ex) {
1739 					throw new MetaphaseEditorException(ex.getMessage(), ex);
1740 				}
1741 			}
1742 		};
1743 		thread.start();
1744 	}// GEN-LAST:event_spellcheckButtonActionPerformed
1745 
1746 	private void createParagraphButtonActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_createParagraphButtonActionPerformed
1747 		new InsertHtmlAction(this, "Paragraph", "<p>TODO: modify paragraph contents</p>", Tag.P).actionPerformed(evt);
1748 	}// GEN-LAST:event_createParagraphButtonActionPerformed
1749 
1750 	private void setToolbarFocusActionListener(JComponent component) {
1751 		Component[] vComponents = component.getComponents();
1752 		for (int i = 0; i < vComponents.length; i++) {
1753 			if (vComponents[i] instanceof JButton) {
1754 				JButton button = (JButton) vComponents[i];
1755 				button.addActionListener(new ActionListener() {
1756 					@Override
1757 					public void actionPerformed(ActionEvent ae) {
1758 						htmlTextPane.requestFocusInWindow();
1759 					}
1760 				});
1761 			}
1762 			else if (vComponents[i] instanceof JComboBox) {
1763 				JComboBox<?> comboBox = (JComboBox<?>) vComponents[i];
1764 				comboBox.addActionListener(new ActionListener() {
1765 
1766 					@Override
1767 					public void actionPerformed(ActionEvent ae) {
1768 						htmlTextPane.requestFocusInWindow();
1769 					}
1770 				});
1771 			}
1772 			else if (vComponents[i] instanceof JPanel) {
1773 				JPanel panel = (JPanel) vComponents[i];
1774 				setToolbarFocusActionListener(panel);
1775 			}
1776 		}
1777 	}
1778 
1779 	private void setToolbarComponentEnable(JComponent component, boolean enabled) {
1780 		Component[] vComponents = component.getComponents();
1781 		for (int i = 0; i < vComponents.length; i++) {
1782 			if (vComponents[i] == sourceButton || vComponents[i] == newButton || vComponents[i] == previewButton
1783 					|| vComponents[i] == aboutButton) {
1784 				return;
1785 			}
1786 			else if (vComponents[i] instanceof JButton) {
1787 				JButton button = (JButton) vComponents[i];
1788 				button.setEnabled(enabled);
1789 			}
1790 			else if (vComponents[i] instanceof JComboBox) {
1791 				JComboBox<?> comboBox = (JComboBox<?>) vComponents[i];
1792 				comboBox.setEnabled(enabled);
1793 			}
1794 			else if (vComponents[i] instanceof JPanel) {
1795 				JPanel panel = (JPanel) vComponents[i];
1796 				setToolbarComponentEnable(panel, enabled);
1797 			}
1798 		}
1799 	}
1800 
1801 	public void addContextMenuListener(ContextMenuListener contextMenuListener) {
1802 		contextMenuListeners.add(contextMenuListener);
1803 	}
1804 
1805 	public void removeContextMenuListener(ContextMenuListener contextMenuListener) {
1806 		contextMenuListeners.remove(contextMenuListener);
1807 	}
1808 
1809 	public ImageInsertRequestHandler getInsertImageRequestHandler() {
1810 		return insertImageRequestHandler;
1811 	}
1812 
1813 	public void setInsertImageRequestHandler(ImageInsertRequestHandler insertImageRequestHandler) {
1814 		if (insertImageRequestHandler != null) {
1815 			this.insertImageRequestHandler = insertImageRequestHandler;
1816 		}
1817 		else {
1818 			this.insertImageRequestHandler = new DefaultImageInsertRequestHandler();
1819 		}
1820 	}
1821 
1822 	public void initSpellChecker() {
1823 		try {
1824 			ZipInputStream zipInputStream = null;
1825 			InputStream inputStream = null;
1826 			if (spellCheckDictionaryVersion == SpellCheckDictionaryVersion.CUSTOM) {
1827 				if (customDictionaryFilename == null) {
1828 					throw new MetaphaseEditorException(
1829 							"The dictionary version has been set to CUSTOM but no custom dictionary file name has been specified.");
1830 				}
1831 				inputStream = new FileInputStream(customDictionaryFilename);
1832 			}
1833 			else {
1834 				inputStream = spellCheckDictionaryVersion.openStream();// this.getClass().getResourceAsStream(spellCheckDictionaryVersion.getFilename());
1835 			}
1836 			zipInputStream = new ZipInputStream(inputStream);
1837 			zipInputStream.getNextEntry();
1838 			// TODO, spelling needed? FD
1839 			// dictionary = new SpellDictionaryHashMap(new BufferedReader(new InputStreamReader(zipInputStream)));
1840 			// spellChecker = new JTextComponentSpellChecker(dictionary, null, "Check Spelling");
1841 		} catch (FileNotFoundException e) {
1842 			throw new MetaphaseEditorException(e.getMessage(), e);
1843 		} catch (IOException e) {
1844 			throw new MetaphaseEditorException(e.getMessage(), e);
1845 		}
1846 	}
1847 
1848 	public void setCustomDictionaryFilename(String customDictionaryFilename) {
1849 		this.customDictionaryFilename = customDictionaryFilename;
1850 	}
1851 
1852 	public String getCustomDictionaryFilename() {
1853 		return customDictionaryFilename;
1854 	}
1855 
1856 	public void setDictionaryVersion(SpellCheckDictionaryVersion spellCheckDictionaryVersion) {
1857 		this.spellCheckDictionaryVersion = spellCheckDictionaryVersion;
1858 
1859 		initSpellChecker();
1860 	}
1861 
1862 	public SpellCheckDictionaryVersion getDictionaryVersion() {
1863 		return spellCheckDictionaryVersion;
1864 	}
1865 
1866 	class SubscriptAction extends StyledEditorKit.StyledTextAction {
1867 		public SubscriptAction() {
1868 			super(StyleConstants.Subscript.toString());
1869 		}
1870 
1871 		@Override
1872 		public void actionPerformed(ActionEvent ae) {
1873 			JEditorPane editor = getEditor(ae);
1874 			if (editor != null) {
1875 				boolean subscript = StyleConstants.isSubscript(getStyledEditorKit(editor).getInputAttributes()) ? false : true;
1876 				SimpleAttributeSet sas = new SimpleAttributeSet();
1877 				StyleConstants.setSubscript(sas, subscript);
1878 				setCharacterAttributes(editor, sas, false);
1879 			}
1880 		}
1881 	}
1882 
1883 	class SuperscriptAction extends StyledEditorKit.StyledTextAction {
1884 		public SuperscriptAction() {
1885 			super(StyleConstants.Superscript.toString());
1886 		}
1887 
1888 		@Override
1889 		public void actionPerformed(ActionEvent ae) {
1890 			JEditorPane editor = getEditor(ae);
1891 			if (editor != null) {
1892 				StyledEditorKit kit = getStyledEditorKit(editor);
1893 				boolean superscript = StyleConstants.isSuperscript(kit.getInputAttributes()) ? false : true;
1894 				SimpleAttributeSet sas = new SimpleAttributeSet();
1895 				StyleConstants.setSuperscript(sas, superscript);
1896 				setCharacterAttributes(editor, sas, false);
1897 			}
1898 		}
1899 	}
1900 
1901 	class StrikeThroughAction extends StyledEditorKit.StyledTextAction {
1902 		public StrikeThroughAction() {
1903 			super(StyleConstants.StrikeThrough.toString());
1904 		}
1905 
1906 		@Override
1907 		public void actionPerformed(ActionEvent ae) {
1908 			JEditorPane editor = getEditor(ae);
1909 			if (editor != null) {
1910 				StyledEditorKit kit = getStyledEditorKit(editor);
1911 				boolean strikeThrough = StyleConstants.isStrikeThrough(kit.getInputAttributes()) ? false : true;
1912 				SimpleAttributeSet sas = new SimpleAttributeSet();
1913 				StyleConstants.setStrikeThrough(sas, strikeThrough);
1914 				setCharacterAttributes(editor, sas, false);
1915 			}
1916 		}
1917 	}
1918 
1919 	private static String toHexString(Color color) {
1920 		return String.format("%1$02X%2$02X%3$02X", color.getRed(), color.getGreen(), color.getBlue());
1921 	}
1922 
1923 	class BackgroundColorAction extends StyledEditorKit.StyledTextAction {
1924 		private Color color;
1925 
1926 		public BackgroundColorAction(Color color) {
1927 			super(StyleConstants.Background.toString());
1928 			this.color = color;
1929 		}
1930 
1931 		@Override
1932 		public void actionPerformed(ActionEvent ae) {
1933 			JEditorPane editor = getEditor(ae);
1934 			// Add span Tag
1935 			String htmlStyle = "background-color: #" + toHexString(color);
1936 			SimpleAttributeSet attr = new SimpleAttributeSet();
1937 			attr.addAttribute(HTML.Attribute.STYLE, htmlStyle);
1938 			MutableAttributeSet outerAttr = new SimpleAttributeSet();
1939 			outerAttr.addAttribute(HTML.Tag.SPAN, attr);
1940 			// Next line is just an instruction to editor to change color
1941 			StyleConstants.setBackground(outerAttr, this.color);
1942 			setCharacterAttributes(editor, outerAttr, false);
1943 
1944 		}
1945 	}
1946 
1947 	class InsertBreakAction extends HTMLEditorKit.InsertHTMLTextAction {
1948 
1949 		private final Action insertBreakAction;
1950 		private final Action deletePreviousCharAction;
1951 
1952 		public InsertBreakAction(Action insertBreakAction, Action deletePreviousCharAction) {
1953 			super(DefaultEditorKit.insertBreakAction, null, null, null, null, null);
1954 			this.insertBreakAction = insertBreakAction;
1955 			this.deletePreviousCharAction = deletePreviousCharAction;
1956 		}
1957 
1958 		@Override
1959 		public void actionPerformed(ActionEvent ae) {
1960 			try {
1961 				Element parentListTag = getParentTag(HTML.Tag.UL);
1962 				if (parentListTag == null) {
1963 					parentListTag = getParentTag(HTML.Tag.OL);
1964 				}
1965 				if (parentListTag != null) {
1966 					Element li = getParentTag(HTML.Tag.LI);
1967 					int start = li.getStartOffset();
1968 					final int end = li.getEndOffset();
1969 					String liText = htmlTextPane.getText(start, end - start);
1970 					boolean content = false;
1971 					for (int i = 0; i < liText.length() && !content; i++) {
1972 						if (!Character.isWhitespace(liText.charAt(i))) {
1973 							content = true;
1974 						}
1975 					}
1976 					if (content) {
1977 						htmlDocument.insertAfterEnd(li, "<li></li>");
1978 						htmlTextPane.setText(getDocument());
1979 						htmlTextPane.setCaretPosition(end);
1980 					}
1981 					else {
1982 						deletePreviousCharAction.actionPerformed(ae);
1983 						htmlDocument.insertAfterEnd(parentListTag, "<p></p>");
1984 						htmlTextPane.setText(getDocument());
1985 						htmlTextPane.setCaretPosition(Math.min(Math.max(0, end - 1), htmlDocument.getLength()));
1986 					}
1987 				}
1988 				else if (insertBreakAction != null) {
1989 					insertBreakAction.actionPerformed(ae);
1990 				}
1991 			} catch (BadLocationException e) {
1992 				e.printStackTrace();
1993 			} catch (IOException e) {
1994 				e.printStackTrace();
1995 			}
1996 
1997 		}
1998 	}
1999 
2000 	class UndoHandler implements UndoableEditListener {
2001 		/**
2002 		 * Messaged when the Document has created an edit, the edit is added to <code>undo</code>, an instance of UndoManager.
2003 		 */
2004 		@Override
2005 		public void undoableEditHappened(UndoableEditEvent e) {
2006 			undo.addEdit(e.getEdit());
2007 			undoAction.update();
2008 			redoAction.update();
2009 		}
2010 	}
2011 
2012 	class UndoAction extends AbstractAction {
2013 		public UndoAction() {
2014 			super("Undo");
2015 			setEnabled(false);
2016 		}
2017 
2018 		@Override
2019 		public void actionPerformed(ActionEvent actionEvent) {
2020 			try {
2021 				undo.undo();
2022 			} catch (CannotUndoException e) {
2023 				throw new MetaphaseEditorException(e.getMessage(), e);
2024 			}
2025 			update();
2026 			redoAction.update();
2027 		}
2028 
2029 		protected void update() {
2030 			if (undo.canUndo()) {
2031 				setEnabled(true);
2032 			}
2033 			else {
2034 				setEnabled(false);
2035 			}
2036 		}
2037 	}
2038 
2039 	class RedoAction extends AbstractAction {
2040 
2041 		public RedoAction() {
2042 			super("Redo");
2043 			setEnabled(false);
2044 		}
2045 
2046 		@Override
2047 		public void actionPerformed(ActionEvent actionEvent) {
2048 			try {
2049 				undo.redo();
2050 			} catch (CannotRedoException e) {
2051 				throw new MetaphaseEditorException(e.getMessage(), e);
2052 			}
2053 			update();
2054 			undoAction.update();
2055 		}
2056 
2057 		protected void update() {
2058 			if (undo.canRedo()) {
2059 				setEnabled(true);
2060 			}
2061 			else {
2062 				setEnabled(false);
2063 			}
2064 		}
2065 	}
2066 
2067 	class HTMLFileFilter extends javax.swing.filechooser.FileFilter {
2068 		@Override
2069 		public boolean accept(File f) {
2070 			return f.isDirectory() || f.getName().toLowerCase().indexOf(".htm") > 0;
2071 		}
2072 
2073 		@Override
2074 		public String getDescription() {
2075 			return "html";
2076 		}
2077 	}
2078 
2079 	class ParagraphFormatListCellRenderer extends DefaultListCellRenderer {
2080 		@Override
2081 		public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
2082 			Component component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
2083 			if (index == 0) {
2084 				component.setEnabled(false);
2085 			}
2086 			ParagraphFormat paragraphFormat = (ParagraphFormat) value;
2087 			if (paragraphFormat.getTag() != null) {
2088 				// Unused JLabel label = (JLabel) component;
2089 				// label.setText("<html><" + paragraphFormat.getTag().toString()
2090 				// + ">" + label.getText() + "</" +
2091 				// paragraphFormat.getTag().toString() + ">");
2092 			}
2093 			return component;
2094 		}
2095 	}
2096 
2097 	class FontListCellRenderer extends DefaultListCellRenderer {
2098 		@Override
2099 		public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
2100 			Component component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
2101 			if (index == 0) {
2102 				component.setEnabled(false);
2103 			}
2104 			FontItem fontItem = (FontItem) value;
2105 			if (fontItem.getFontName() != null) {
2106 				Font currentFont = component.getFont();
2107 				component.setFont(new Font(fontItem.getFontName(), currentFont.getStyle(), currentFont.getSize()));
2108 			}
2109 			return component;
2110 		}
2111 	}
2112 
2113 	class FontSizeListCellRenderer extends DefaultListCellRenderer {
2114 		@Override
2115 		public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
2116 			Component component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
2117 			if (index == 0) {
2118 				component.setEnabled(false);
2119 			}
2120 			FontSize fontSize = (FontSize) value;
2121 			if (fontSize.getSize() != -1) {
2122 				Font currentFont = component.getFont();
2123 				component.setFont(new Font(currentFont.getName(), currentFont.getStyle(), fontSize.getSize()));
2124 			}
2125 			return component;
2126 		}
2127 	}
2128 
2129 	class DefaultEditorMouseMotionListener implements MouseMotionListener {
2130 
2131 		@Override
2132 		public void mouseDragged(MouseEvent me) {
2133 			int pos = htmlTextPane.viewToModel(me.getPoint());
2134 
2135 			if (pos >= 0) {
2136 				Element element = htmlDocument.getParagraphElement(pos);
2137 				MutableAttributeSet attributes = new SimpleAttributeSet(element.getAttributes());
2138 
2139 				EditorMouseEvent editorMouseEvent = new EditorMouseEvent();
2140 				editorMouseEvent.setNearestParagraphAttributes(attributes);
2141 				for (int i = 0; i < editorMouseMotionListeners.size(); i++) {
2142 					editorMouseMotionListeners.get(i).mouseDragged(editorMouseEvent);
2143 				}
2144 			}
2145 		}
2146 
2147 		@Override
2148 		public void mouseMoved(MouseEvent me) {
2149 			int pos = htmlTextPane.viewToModel(me.getPoint());
2150 
2151 			if (pos >= 0) {
2152 				Element element = htmlDocument.getParagraphElement(pos);
2153 				MutableAttributeSet attributes = new SimpleAttributeSet(element.getAttributes());
2154 
2155 				EditorMouseEvent editorMouseEvent = new EditorMouseEvent();
2156 				editorMouseEvent.setNearestParagraphAttributes(attributes);
2157 				for (int i = 0; i < editorMouseMotionListeners.size(); i++) {
2158 					editorMouseMotionListeners.get(i).mouseMoved(editorMouseEvent);
2159 				}
2160 			}
2161 		}
2162 	}
2163 
2164 	class DefaultImageInsertRequestHandler implements ImageInsertRequestHandler {
2165 		@Override
2166 		public void insertImage(JTextPane htmlTextPane) {
2167 			try {
2168 				ImageDialog imageDialog = new ImageDialog(SwingUtilities.windowForComponent(htmlTextPane), true);
2169 				String html = imageDialog.showDialog();
2170 				if (html != null) {
2171 					if (imageDialog.isLink()) {
2172 						editorKit.insertHTML(htmlDocument, htmlTextPane.getCaretPosition(), html, 0, 0, Tag.A);
2173 					}
2174 					else {
2175 						editorKit.insertHTML(htmlDocument, htmlTextPane.getCaretPosition(), html, 0, 0, Tag.IMG);
2176 					}
2177 					refreshAfterAction();
2178 				}
2179 			} catch (BadLocationException e) {
2180 				throw new MetaphaseEditorException(e.getMessage(), e);
2181 			} catch (IOException e) {
2182 				throw new MetaphaseEditorException(e.getMessage(), e);
2183 			}
2184 		}
2185 	}
2186 }