diff --git a/oxygen/js-options/README.md b/oxygen/js-options/README.md new file mode 100644 index 0000000..509a306 --- /dev/null +++ b/oxygen/js-options/README.md @@ -0,0 +1,59 @@ +# Alltogether: opening a JavaScript debugger within Oxygen [JS Operations](https://www.oxygenxml.com/doc/versions/23.1/ug-editor/topics/dg-default-author-operations.html#dg-default-author-operations__jsoperation) through inclusion + +This rambling shows how a [debugger](../js-debugger) can be launched using an [inclusion](../js-include). + +## Installation + +To do so: +### 1) add the include() function to your commons.js file: + +```javascript +include = function (filepath, isAbsolute) { + if (isAbsolute === undefined || isAbsolute == false) { + filepath = Packages.ro.sync.ecss.extensions.commons.operations.CommonsOperationsUtil.expandAndResolvePath(authorAccess, filepath); + } + var text = new java.lang.String(java.nio.file.Files.readAllBytes(java.nio.file.Paths. get (java.net.URI(filepath)))); + text = String(text); + eval(text); +} +``` +### 2) create a debugger.js file: +```javascript +startDebugger = function () { + + var runnable = { + run: function () { + main.dispose(); + } + } + + + var context = Packages.org.mozilla.javascript.Context.getCurrentContext(); + // Within the current context... + var contextFactory = context.getFactory(); + var scope = Packages.org.mozilla.javascript.tools.shell.Environment(runnable.__parent__); + // and the scope of the runnable variable's parent ... + var main = Packages.org.mozilla.javascript.tools. debugger.Main.mainEmbedded(contextFactory, scope, 'Debugger'); + // start a debugging session ... + main.setExitAction(java.lang.Runnable(runnable)); + // , clean the resources at exit time... + main.setVisible(true); + // and make it visible/ +} +``` +### 3) Use these functions in your doOperation() method: +```javascript +doOperation = function () { + include('debugger.js'); + startDebugger(); +} +``` +## See also +More details on these hacks: +* [debugger](../js-debugger) +* [inclusion](../js-include) +# Framework + +This directory includes the Oxygen sample framework on which ou can test these techniques but they should work on any framework. + + diff --git a/oxygen/js-options/catalog.xml b/oxygen/js-options/catalog.xml new file mode 100644 index 0000000..329f379 --- /dev/null +++ b/oxygen/js-options/catalog.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/oxygen/js-options/commons.js b/oxygen/js-options/commons.js new file mode 100644 index 0000000..912d67e --- /dev/null +++ b/oxygen/js-options/commons.js @@ -0,0 +1,8 @@ +include = function (filepath, isAbsolute) { + if (isAbsolute === undefined || isAbsolute == false) { + filepath = Packages.ro.sync.ecss.extensions.commons.operations.CommonsOperationsUtil.expandAndResolvePath(authorAccess, filepath); + } + var text = new java.lang.String(java.nio.file.Files.readAllBytes(java.nio.file.Paths. get (java.net.URI(filepath)))); + text = String(text); + eval(text); +} diff --git a/oxygen/js-options/css/sdf.css b/oxygen/js-options/css/sdf.css new file mode 100644 index 0000000..c1b1541 --- /dev/null +++ b/oxygen/js-options/css/sdf.css @@ -0,0 +1,145 @@ +/* Element from another namespace */ +@namespace abs "http://www.oxygenxml.com/sample/documentation/abstracts"; + +abs|def{ + font-family:monospace; + font-size:smaller; +} +abs|def:before{ + content:"Definition:"; + color:gray; +} + +/* Vertical flow */ +book, +section, +para, +title, +image, +ref { + display:block; +} + +/* Horizontal flow */ +b,i { + display:inline; +} + +section{ + margin-left:1em; + margin-top:1em; +} + +section{ + -oxy-foldable:true; + -oxy-not-foldable-child: title; +} + +link[href]:before{ + display:inline; + link:attr(href); + content: "Click to open: " attr(href); +} + +link:before { + content:oxy_link-text(); +} + +/* Title rendering*/ +title{ + font-size: 2.4em; + font-weight:bold; +} + +* * title{ + font-size: 2.0em; +} +* * * title{ + font-size: 1.6em; +} +* * * * title{ + font-size: 1.2em; +} + +book, +article{ + counter-reset:sect; +} +book > section, +article > section{ + counter-increment:sect; +} +book > section > title:before, +article > section > title:before{ + content: "Section: " counter(sect) " "; +} + +/* Inlines rendering*/ +b { + font-weight:bold; +} + +i { + font-style:italic; +} + +/*Table rendering */ +table{ + display:table; + border:1px solid navy; + margin:1em; + max-width:1000px; + min-width:150px; +} + +table[width]{ + width:attr(width, length); +} + +table[bgcolor], +tr[bgcolor], +td[bgcolor]{ + background-color:attr(bgcolor); + color:inherit; +} + +tr, header{ + display:table-row; +} + +customcol { + text-align:left; + display:block; + color:#444444; + background-color:inherit; + margin:0.2em; + padding:2px; + font-family:monospace; + font-size:small; +} + +header{ + background-color: silver; + color:inherit +} + +td{ + display:table-cell; + border:1px solid navy; + padding:1em; +} + +image{ + display:block; + content: attr(href, url); + margin-left:2em; +} + +author, name, country { + display:block; + margin-left:20px; +} + +name:before { + content: oxy_label(text, "Author: ", width, 100px, color, gray); +} diff --git a/oxygen/js-options/debugger.js b/oxygen/js-options/debugger.js new file mode 100644 index 0000000..e011f65 --- /dev/null +++ b/oxygen/js-options/debugger.js @@ -0,0 +1,21 @@ +startDebugger = function () { + + var runnable = { + run: function () { + main.dispose(); + } + } + + + var context = Packages.org.mozilla.javascript.Context.getCurrentContext(); + // Within the current context... + var contextFactory = context.getFactory(); + var scope = Packages.org.mozilla.javascript.tools.shell.Environment(runnable.__parent__); + // and the scope of the runnable variable's parent ... + var main = Packages.org.mozilla.javascript.tools. debugger.Main.mainEmbedded(contextFactory, scope, 'Debugger'); + // start a debugging session ... + main.setExitAction(java.lang.Runnable(runnable)); + // , clean the resources at exit time... + main.setVisible(true); + // and make it visible/ +} diff --git a/oxygen/js-options/hello.js b/oxygen/js-options/hello.js new file mode 100644 index 0000000..e083f67 --- /dev/null +++ b/oxygen/js-options/hello.js @@ -0,0 +1,3 @@ +hello = function (){ + Packages.java.lang.System.out.println("Hello world."); +} \ No newline at end of file diff --git a/oxygen/js-options/icons/insertImage16.JPG b/oxygen/js-options/icons/insertImage16.JPG new file mode 100644 index 0000000..8055e8f Binary files /dev/null and b/oxygen/js-options/icons/insertImage16.JPG differ diff --git a/oxygen/js-options/icons/insertImage20.JPG b/oxygen/js-options/icons/insertImage20.JPG new file mode 100644 index 0000000..e3b1131 Binary files /dev/null and b/oxygen/js-options/icons/insertImage20.JPG differ diff --git a/oxygen/js-options/icons/insertSection16.JPG b/oxygen/js-options/icons/insertSection16.JPG new file mode 100644 index 0000000..6f8c020 Binary files /dev/null and b/oxygen/js-options/icons/insertSection16.JPG differ diff --git a/oxygen/js-options/icons/insertSection20.JPG b/oxygen/js-options/icons/insertSection20.JPG new file mode 100644 index 0000000..5dd7bb5 Binary files /dev/null and b/oxygen/js-options/icons/insertSection20.JPG differ diff --git a/oxygen/js-options/icons/insertTable16.JPG b/oxygen/js-options/icons/insertTable16.JPG new file mode 100644 index 0000000..c08f3c1 Binary files /dev/null and b/oxygen/js-options/icons/insertTable16.JPG differ diff --git a/oxygen/js-options/icons/insertTable20.JPG b/oxygen/js-options/icons/insertTable20.JPG new file mode 100644 index 0000000..56a0e95 Binary files /dev/null and b/oxygen/js-options/icons/insertTable20.JPG differ diff --git a/oxygen/js-options/icons/refreshCSS16.JPG b/oxygen/js-options/icons/refreshCSS16.JPG new file mode 100644 index 0000000..0cade03 Binary files /dev/null and b/oxygen/js-options/icons/refreshCSS16.JPG differ diff --git a/oxygen/js-options/icons/refreshCSS20.JPG b/oxygen/js-options/icons/refreshCSS20.JPG new file mode 100644 index 0000000..897169d Binary files /dev/null and b/oxygen/js-options/icons/refreshCSS20.JPG differ diff --git a/oxygen/js-options/icons/strictMode16.JPG b/oxygen/js-options/icons/strictMode16.JPG new file mode 100644 index 0000000..ed78f23 Binary files /dev/null and b/oxygen/js-options/icons/strictMode16.JPG differ diff --git a/oxygen/js-options/icons/strictMode20.JPG b/oxygen/js-options/icons/strictMode20.JPG new file mode 100644 index 0000000..6b7cc2d Binary files /dev/null and b/oxygen/js-options/icons/strictMode20.JPG differ diff --git a/oxygen/js-options/js-debugger-include.xpr b/oxygen/js-options/js-debugger-include.xpr new file mode 100644 index 0000000..de19426 --- /dev/null +++ b/oxygen/js-options/js-debugger-include.xpr @@ -0,0 +1,26 @@ + + + + + + + + + additional.frameworks.directories + + /home/vdv/projects/pro/Remix/oxygen/frameworks/http___oxygen.remix.rece.hachette_livre.fr_oxygenUpdate.xml + /home/vdv/projects/tea/dyomedea/ramblings/oxygen/js-debugger-include + + + + key.editor.document.type.custom.locations.option.pane + true + + + + + + + + + \ No newline at end of file diff --git a/oxygen/js-options/resources/countries.txt b/oxygen/js-options/resources/countries.txt new file mode 100644 index 0000000..228c8a6 --- /dev/null +++ b/oxygen/js-options/resources/countries.txt @@ -0,0 +1 @@ +France, Spain, Great Britain \ No newline at end of file diff --git a/oxygen/js-options/schema/abs.xsd b/oxygen/js-options/schema/abs.xsd new file mode 100644 index 0000000..32edb55 --- /dev/null +++ b/oxygen/js-options/schema/abs.xsd @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/oxygen/js-options/schema/sdf.xsd b/oxygen/js-options/schema/sdf.xsd new file mode 100644 index 0000000..2092837 --- /dev/null +++ b/oxygen/js-options/schema/sdf.xsd @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/oxygen/js-options/sdf.framework b/oxygen/js-options/sdf.framework new file mode 100644 index 0000000..d02458e --- /dev/null +++ b/oxygen/js-options/sdf.framework @@ -0,0 +1,1391 @@ + + + + + document.types + + + + + + + SDF + + + + + 2 + + + ${framework}/schema/sdf.xsd + + + + + + ${framework}/sdf.jar + ${frameworks}/docbook/docbook.jar + + + + + + + + + + + + ${framework}/css/sdf.css + + + sdf + + + false + + + + + + true + + + false + + + + + + insert_section + + + Insert section + + + Insert a section to the caret pasition. + + + ${framework}/icons/insertSection20.jpg + + + ${framework}/icons/insertSection16.jpg + + + i + + + + + + + + + local-name()='section' or local-name()='book' or local-name()='article' + + + + + fragment + <section xmlns="http://www.oxygenxml.com/sample/documentation"><title/><para/></section> + + + + + ro.sync.ecss.extensions.commons.operations.InsertFragmentOperation + + + + + + false + + + + + insert_table + + + Insert table. + + + Insert table. + + + ${framework}/icons/insertTable20.jpg + + + ${framework}/icons/insertTable16.jpg + + + t + + + + + + + + + + + + + + + simple.documentation.framework.operations.table.InsertTableOperation + + + + + + false + + + + + insert_image + + + Insert image + + + Inserts an image + + + ${framework}/icons/insertImage20.jpg + + + ${framework}/icons/insertImage16.jpg + + + i + + + + + + + + + + + + + + + simple.documentation.framework.operations.InsertImageOperation + + + + + + false + + + + + clients_report + + + Clients Report + + + Connects to the database and collects the list of clients. + + + + + + + + + r + + + + + + + + + local-name()='section' + + + + + + simple.documentation.framework.operations.QueryDatabaseOperation + + + + + + false + + + + + inser_ref + + + Insert Ref + + + Insert Ref + + + + + + + + + + + + + + + + + + + + + + + fragment + <ref location=""/> + + + + + ro.sync.ecss.extensions.commons.operations.SurroundWithFragmentOperation + + + + + + false + + + + + refresh + + + Refresh css + + + Refresh CSS + + + ${framework}/icons/refreshCSS20.jpg + + + ${framework}/icons/refreshCSS16.jpg + + + + + + + + + + + + true() + + + + + + simple.documentation.framework.operations.SDFRefreshCSSOperation + + + + + + false + + + + + show_file_status + + + Show File Status + + + Show File Status + + + + + + + + + + + + + + + + + + true() + + + + + + simple.documentation.framework.operations.SDFShowFileStatusOperation + + + + + + false + + + + + strict_mode + + + Strict Mode + + + Strict Mode On/Off + + + ${framework}/icons/strictMode20.jpg + + + ${framework}/icons/strictMode16.jpg + + + + + + + + + + + + true() + + + + + + simple.documentation.framework.operations.SDFStrictModeOperation + + + + + + false + + + + + transform + + + Transform + + + Custom Transformation using a specified xsl. + + + + + + + + + + + + + + + + + + true() + + + + + + simple.documentation.framework.operations.TrasformOperation + + + + + + false + + + + + select_children + + + Select Children + + + Select Children in Outline + + + + + + + + + + + + + + + + + + + + + + + + simple.documentation.framework.operations.SelectNodesInOutlinerOperation + + + + + + false + + + + + insert_element + + + Insert Element + + + Show a popup menu that contains the name of all elements that can be inserted at the caret offset. + + + + + + + + + + + + + + + + + + + + + + + + simple.documentation.framework.operations.InsertElementOperation + + + + + + false + + + + + extract_node + + + Extract Node + + + Save the Author node at caret in a separate document and refresh the new file path in the project + + + + + + + + + + + + + + + + + + + + + + + + simple.documentation.framework.operations.ExtractNodeToFileOperation + + + + + + false + + + + + add_highlight + + + Add highlight + + + Highlight element at the caret position + + + + + + + + + + + + + + + + + + + + + + + + simple.documentation.framework.operations.highlight.AddHighlightOperation + + + + + + false + + + + + edit_highlights + + + Edit Highlights + + + Edit all highlights from caret position + + + + + + + + + + + + + + + + + + + + + + + + simple.documentation.framework.operations.highlight.EditHighlightsOperation + + + + + + false + + + + + remove_highlights + + + Remove Highlights + + + Remove highlights from the caret position + + + + + + + + + + + + + + + + + + + + + + + + simple.documentation.framework.operations.highlight.RemoveAllHighlightsOperation + + + + + + false + + + + + change_review_author + + + Change Review Author + + + Change the Review Author by selecting from a list of possible author names + + + + + + + + + + + + + + + + + + + + + + + + simple.documentation.framework.operations.highlight.ChangeReviewAuthorOperation + + + + + + false + + + + + open_new_editor + + + Open in new editor + + + Open a new XML editor containing the selected content + + + + + + + + + + + + + + + + + + true() + + + + + + simple.documentation.framework.operations.OpenInNewEditor + + + + + + false + + + + + debugger + + + debugger + + + Open a JS debugger + + + + + + + + + + + + M1 M2 D + + + + + + + + + + + script + doOperation = function () { + include('debugger.js'); + startDebugger(); +} + + + + + + ro.sync.ecss.extensions.commons.operations.JSOperation + + + + + + false + + + + + hello + + + hello + + + Hello world + + + + + + + + + + + + ctrl shift H + + + + + + + + + + + script + doOperation = function () { + include('hello.js'); + hello(); +} + + + + + ro.sync.ecss.extensions.commons.operations.JSOperation + + + + + + false + + + + + + + + SD Framework + + + + + + + + + + + + clients_report + + + + + insert_image + + + + + insert_section + + + + + Table + + + + + + + + + + + + insert_table + + + + + + + + + + + + + + + + + + + Contextual menu + + + + + + + + + + + + add_highlight + + + + + edit_highlights + + + + + remove_highlights + + + + + + transform + + + + + select_children + + + + + transform + + + + + show_file_status + + + + + refresh + + + + + clients_report + + + + + strict_mode + + + + + insert_image + + + + + insert_element + + + + + extract_node + + + + + open_new_editor + + + + + Table + + + + + + + + + + + + insert_table + + + + + + + + + + + insert_section + + + + + + + + + + + + + Toolbar + + + 2 + + + + + + + + + + + + change_review_author + + + + + + add_highlight + + + + + edit_highlights + + + + + remove_highlights + + + + + + transform + + + + + refresh + + + + + show_file_status + + + + + strict_mode + + + + + clients_report + + + + + inser_ref + + + + + insert_image + + + + + insert_table + + + + + insert_section + + + + + insert_element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${frameworkDir}/templates + + + + + ${framework}/catalog.xml + + + + A Simple Documentation Framework . + + + + + + http://www.oxygenxml.com/sample/documentation + + + * + + + * + + + * + + + simple.documentation.framework.CustomRule + + + * + + + * + + + * + + + + + * + + + * + + + *.sdf + + + * + + + + + + * + + + * + + + * + + + + + + + + + + + + SDF to HTML + + + + + + + + + pdf + + + Apache FOP + + + + + + ${framework}/xsl/sdf.xsl + + + ${currentFileURL} + + + false + + + false + + + XSL + + + true + + + false + + + + + + false + + + + + + false + + + true + + + true + + + false + + + false + + + true + + + + + + + + + Saxon-PE + + + + + + + + + + + + + + + + + + simple.documentation.framework.SDFExtensionsBundle + + + false + + + Text + + + + + + + + + + + + + + + + + + + + + + + + 3 + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/oxygen/js-options/sdf.jar b/oxygen/js-options/sdf.jar new file mode 100644 index 0000000..d7eac35 Binary files /dev/null and b/oxygen/js-options/sdf.jar differ diff --git a/oxygen/js-options/sdf_sample.xml b/oxygen/js-options/sdf_sample.xml new file mode 100644 index 0000000..88629c4 --- /dev/null +++ b/oxygen/js-options/sdf_sample.xml @@ -0,0 +1,91 @@ + + + My Technical Book + + Jean + France + +
+ XML + Extensible Markup Language + In this section of the book I will explain different XML applications. +
+
+ Accessing XML data. +
+ XSLT + Extensible stylesheet language transformation (XSLT) is a language for + transforming XML documents into other XML documents. + A list of XSL elements and what they do.. + +
+
+ + + + + + + + + + + + + + + + + +
XSLT ElementsDescription
+ xsl:stylesheet + The xsl:stylesheet element is always the top-level element of an XSL + stylesheet. The name xsl:transform may be used as a synonym.
+ xsl:template + The xsl:template element has an optional mode attribute. If this is + present, the template will only be matched when the same mode is used in the + invoking xsl:apply-templates element.
+ for-each + The xsl:for-each element causes iteration over the nodes selected by a + node-set expression.
End of the list
+
+
+ XPath + XPath (XML Path Language) is a terse (non-XML) syntax for addressing portions + of an XML document. + Some of the XPath functions. + +
+
+ + + + + + + + + + + + + + +
FunctionDescription
format-numberThe format-number function converts its first argument to a string + using the format pattern string specified by the second argument and the + decimal-format named by the third argument, or the default decimal-format, + if there is no third argument
currentThe current function returns a node-set that has the current node as + its only member.
generate-idThe generate-id function returns a string that uniquely identifies + the node in the argument node-set that is first in document order.
+
+
+
+ Documentation frameworks + One of the most important documentation frameworks is Docbook. + + The other is the topic oriented DITA, promoted by OASIS. + +
+
diff --git a/oxygen/js-options/src-java/simple/documentation/framework/AuthorAccessProvider.java b/oxygen/js-options/src-java/simple/documentation/framework/AuthorAccessProvider.java new file mode 100644 index 0000000..0b2d152 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/AuthorAccessProvider.java @@ -0,0 +1,16 @@ +package simple.documentation.framework; + +import ro.sync.ecss.extensions.api.AuthorAccess; + +/** + * Provides access to Author functions, to specific components corresponding to + * editor, document, workspace, tables, change tracking and utility informations and actions. + */ +public interface AuthorAccessProvider { + /** + * Gets access to Author functions and components. + * + * @return The Author access. + */ + AuthorAccess getAuthorAccess(); +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/CustomRule.java b/oxygen/js-options/src-java/simple/documentation/framework/CustomRule.java new file mode 100644 index 0000000..1fc57d6 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/CustomRule.java @@ -0,0 +1,44 @@ +package simple.documentation.framework; + +import org.xml.sax.Attributes; + +import ro.sync.ecss.extensions.api.DocumentTypeCustomRuleMatcher; + +/** + * Matching rule to the SDF document type. + * + */ +public class CustomRule implements DocumentTypeCustomRuleMatcher { + + /** + * Check if the SDF document type should be used for the given document properties. + */ + public boolean matches( + String systemID, + String rootNamespace, + String rootLocalName, + String doctypePublicID, + Attributes rootAttributes) { + boolean matches = true; + int attributesCount = rootAttributes.getLength(); + for (int i = 0; i < attributesCount; i++) { + String localName = rootAttributes.getLocalName(i); + if ("version".equals(localName)) { + if ("2.0".equals(rootAttributes.getValue(i))) { + // Do not match the documents with "2.0" version + matches = false; + } + } + } + + return matches; + } + + /** + * Description. + */ + public String getDescription() { + return "Checks if the current Document Type Association" + + " is matching the document."; + } +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/SDFElement.java b/oxygen/js-options/src-java/simple/documentation/framework/SDFElement.java new file mode 100644 index 0000000..a26f0e3 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/SDFElement.java @@ -0,0 +1,552 @@ +package simple.documentation.framework; + +import java.util.ArrayList; +import java.util.List; + +import ro.sync.contentcompletion.xml.CIAttribute; +import ro.sync.contentcompletion.xml.CIElement; +import ro.sync.contentcompletion.xml.CIElementAdapter; + +/** + * Simple Documentation Framework element. + */ +public class SDFElement extends CIElementAdapter { + /** + * The namespace. + */ + protected String namespace = null; + + /** + * The element name. + */ + protected String name = null; + + /** + * The proxy. + */ + protected String proxy = null; + + /** + * The type description. + */ + private String typeDescription = null; + + /** + * List of attributes. + */ + protected List attributes = null; + + /** + * The possible values as String list. + */ + private List possiblesValuesList; + + /** + * The element model description. + */ + private String modelDescription; + + /** + * The facets. One facet can be null if it is not defined. + */ + private String lengthFacetValue; + + /** + * The content type of the element. + */ + private int contentType = CONTENT_TYPE_NOT_DETERMINED; + + /** + * True if content is nillable. Used only for XML Schema. + */ + private boolean nillable; + + /** + * Facet values. + */ + private String minLengthFacetValue; + private String maxLengthFacetValue; + private String whitespaceFacetValue; + private String minInclusiveFacetValue; + private String minExclusiveFacetValue; + private String maxInclusiveFacetValue; + private String maxExclusiveFacetValue; + private String totalDigitsFacetValue; + private String fractionDigitsFacetValue; + private String facetPatternValue; + + /** + * Guess some following elements if possible + */ + protected List guessElements = null; + + /** + * @see ro.sync.contentcompletion.xml.CIElement#getGuessElements() + */ + @Override + public List getGuessElements() { + if (guessElements != null && guessElements.isEmpty()) { + // Return null is the list is empty. + return null; + } + return guessElements; + } + + /** + * @see ro.sync.contentcompletion.xml.CIElement#addGuessElement(ro.sync.contentcompletion.xml.CIElement) + */ + @Override + public void addGuessElement(CIElement e) { + if (guessElements == null) { + guessElements = new ArrayList(); + } + guessElements.add(e); + } + + /** + * @return The string representing the name or null. + */ + @Override + public String getName(){ + return name; + } + + /** + * @see ro.sync.contentcompletion.xml.CIElement#getNamespace() + */ + @Override + public String getNamespace(){ + return namespace; + } + + /** + * True if the element has a namespace. + */ + private boolean xmlns; + + /** + * @see ro.sync.contentcompletion.xml.CIElement#setDeclareXmlns(boolean) + */ + @Override + public void setDeclareXmlns(boolean xmlns) { + this.xmlns = xmlns; + } + + /** + * @see ro.sync.contentcompletion.xml.CIElement#setContentType(int) + */ + @Override + public void setContentType(int contentType) { + this.contentType = contentType; + } + + /** + * @see ro.sync.contentcompletion.xml.CIElement#isEmpty() + */ + @Override + public boolean isEmpty() { + return getContentType() == CONTENT_TYPE_EMPTY; + } + + /** + * @see ro.sync.contentcompletion.xml.CIElement#getContentType() + */ + @Override + public int getContentType() { + return contentType; + } + + /** + * @see ro.sync.contentcompletion.xml.CIElement#isDeclareXmlns() + */ + @Override + public boolean isDeclareXmlns() { + return xmlns; + } + + /** + * @see ro.sync.contentcompletion.xml.CIElement#setName(java.lang.String) + */ + @Override + public void setName(String name) { + this.name = name; + } + /** + * @see ro.sync.contentcompletion.xml.CIElement#setPrefix(java.lang.String) + */ + @Override + public void setPrefix(String proxy) { + this.proxy = proxy; + } + + /** + * @see ro.sync.contentcompletion.xml.CIElement#setNamespace(java.lang.String) + */ + @Override + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + + /** + * @see ro.sync.contentcompletion.xml.CIElement#getQName() + */ + @Override + public String getQName() { + if (getPrefix() != null && !"".equals(getPrefix())) { + return getPrefix() + ":" + getName(); + } else { + return getName(); + } + } + + /*** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object o) { + boolean result = false; + if (o instanceof SDFElement) { + CIElement cie = (CIElement) o; + result = compareTo(cie) == 0; + } + return result; + } + + /** + * @see ro.sync.contentcompletion.xml.CIElement#getAttributes() + */ + @Override + public List getAttributes() { + return attributes; + } + + /** + * @see ro.sync.contentcompletion.xml.CIElement#setAttributes(java.util.List) + */ + @Override + public void setAttributes(List attributes) { + this.attributes = attributes; + } + + /** + * Concatenates the name and the namespace and compares it with the other name and namespace, + * as strings. + * @param other The object to compare to. + * @return The value 0 if the argument object is equal to this object. + */ + @Override + public int compareTo(CIElement other){ + String n1 = getName() == null ? "": getName(); + String nm1 = getNamespace() == null ? "": getNamespace(); + + String n2 = other.getName() == null ? "": other.getName(); + String nm2 = other.getNamespace() == null ? "": other.getNamespace(); + + int result = n1.compareTo(n2); + if(result == 0) { + result = nm1.compareTo(nm2); + } + return result; + } + + /** + * Return the name. + * + * @return The name. + */ + @Override + public String toString(){ + String toRet = String.valueOf(getName()); + return toRet; + } + + /** + * @see ro.sync.contentcompletion.xml.CIElement#hasPrefix() + */ + @Override + public boolean hasPrefix() { + return getPrefix() != null && !"".equals(getPrefix()); + } + + /** + * @see ro.sync.contentcompletion.xml.CIElement#getPrefix() + */ + @Override + public String getPrefix() { + return proxy; + } + + /** + * @param modelDescription The modelDescription to set. + */ + @Override + public void setModelDescription(String modelDescription) { + this.modelDescription = modelDescription; + } + + /** + * @param fractionDigitsFacetValue The fractionDigitsFacetValue to set. + */ + @Override + public void setFacetFractionDigitsValue(String fractionDigitsFacetValue) { + this.fractionDigitsFacetValue = fractionDigitsFacetValue; + } + /** + * @param maxExclusiveFacetValue The maxExclusiveFacetValue to set. + */ + @Override + public void setFacetMaxExclusiveValue(String maxExclusiveFacetValue) { + this.maxExclusiveFacetValue = maxExclusiveFacetValue; + } + /** + * @param maxInclusiveFacetValue The maxInclusiveFacetValue to set. + */ + @Override + public void setFacetMaxInclusiveValue(String maxInclusiveFacetValue) { + this.maxInclusiveFacetValue = maxInclusiveFacetValue; + } + /** + * @param maxLengthFacetValue The maxLengthFacetValue to set. + */ + @Override + public void setFacetMaxLengthValue(String maxLengthFacetValue) { + this.maxLengthFacetValue = maxLengthFacetValue; + } + /** + * @param minInclusiveFacetValue The minInclusiveFacetValue to set. + */ + @Override + public void setFacetMinInclusiveValue(String minInclusiveFacetValue) { + this.minInclusiveFacetValue = minInclusiveFacetValue; + } + /** + * @param possiblesValuesList The possiblesValuesList to set. + */ + @Override + public void setPossiblesValues(List possiblesValuesList) { + this.possiblesValuesList = possiblesValuesList; + } + /** + * @param totalDigitsFacetValue The totalDigitsFacetValue to set. + */ + @Override + public void setFacetTotalDigitsValue(String totalDigitsFacetValue) { + this.totalDigitsFacetValue = totalDigitsFacetValue; + } + /** + * @param whitespaceFacetValue The whitespaceFacetValue to set. + */ + @Override + public void setFacetWhitespaceValue(String whitespaceFacetValue) { + this.whitespaceFacetValue = whitespaceFacetValue; + } + + /** + * @param lengthFacetValue The lengthFacetValue to set. + */ + @Override + public void setFacetLengthValue(String lengthFacetValue) { + this.lengthFacetValue = lengthFacetValue; + } + /** + * @param minLengthFacetValue The minLengthFacetValue to set. + */ + @Override + public void setFacetMinLengthValue(String minLengthFacetValue) { + this.minLengthFacetValue = minLengthFacetValue; + } + /** + * @param minExclusiveFacetValue The minExclusiveFacetValue to set. + */ + @Override + public void setFacetMinExclusiveValue(String minExclusiveFacetValue) { + this.minExclusiveFacetValue = minExclusiveFacetValue; + } + + /** + * @see ro.sync.contentcompletion.xml.NodeDescription#getFacetFractionDigitsValue() + */ + @Override + public String getFacetFractionDigitsValue() { + return fractionDigitsFacetValue; + } + + + /** + * @see ro.sync.contentcompletion.xml.NodeDescription#getFacetTotalDigitsValue() + */ + @Override + public String getFacetTotalDigitsValue() { + return totalDigitsFacetValue; + } + + + /** + * @see ro.sync.contentcompletion.xml.NodeDescription#getFacetMaxInclusiveValue() + */ + @Override + public String getFacetMaxInclusiveValue() { + return maxInclusiveFacetValue; + } + + /** + * @see ro.sync.contentcompletion.xml.NodeDescription#getFacetMaxExclusiveValue() + */ + @Override + public String getFacetMaxExclusiveValue() { + return maxExclusiveFacetValue; + } + + + /** + * Get the value of MIN_INCLUSIVE facet, can be null if it is not defined. + * + * @return The value of MIN_INCLUSIVE facet. + */ + @Override + public String getFacetMinInclusiveValue() { + return minInclusiveFacetValue; + } + + /** + * Get the value of MIN_EXCLUSIVE facet, can be null if it is not defined. + * + * @return The value of MIN_EXCLUSIVE facet. + */ + @Override + public String getFacetMinExclusiveValue() { + return minExclusiveFacetValue; + } + + /** + * Get the possible values as String list. + * + * @return The possible values. + */ + @Override + public List getPossibleValues() { + return possiblesValuesList; + } + + /** + * Get the model description. + * @return The model description. + */ + @Override + public String getModelDescription() { + return modelDescription; + } + /** + * Get the value of LENGTH facet, can be null if it is not defined. + * + * @return The value of length facet. + */ + @Override + public String getFacetLengthValue() { + return lengthFacetValue; + } + /** + * Get the value of MIN LENGTH facet, can be null if it is not defined. + * + * @return The value of MIN LENGTH facet. + */ + @Override + public String getFacetMinLengthValue() { + return minLengthFacetValue; + } + /** + * Get the value of MAX LENGTH facet, can be null if it is not defined. + * + * @return The value of MAX LENGTH facet. + */ + @Override + public String getFacetMaxLengthValue() { + return maxLengthFacetValue; + } + + /** + * Get the value of WHITESPACE facet, can be null if it is not defined. + * + * @return The value of WHITESPACE facet. + */ + @Override + public String getFacetWhitespaceValue() { + return whitespaceFacetValue; + } + + /** + * Get the list with pattern facets. + * + * @return The list with pattern facets, can be null if no FACET_PATTERN defined. + */ + @Override + public String getFacetPattern(){ + return facetPatternValue; + } + + /** + * Set the list with pattern facets. + * + * @param patternFacets The list with pattern facets. + */ + @Override + public void setFacetPattern(String patternFacets){ + this.facetPatternValue = patternFacets; + } + + /** + * The annotation string, null as default. + */ + protected String annotation; + + /** + * Get the annotation for the element. + * + * @return A text that explain how to use the attribute, or null. + */ + @Override + public String getAnnotation() { + return annotation; + } + + /** + * @see ro.sync.contentcompletion.xml.CIElement#setAnnotation(java.lang.String) + */ + @Override + public void setAnnotation(String annotation){ + this.annotation = annotation; + } + + /** + * @see ro.sync.contentcompletion.xml.CIElement#getTypeDescription() + */ + @Override + public String getTypeDescription() { + return typeDescription; + } + + /** + * @see ro.sync.contentcompletion.xml.CIElement#setTypeDescription(java.lang.String) + */ + @Override + public void setTypeDescription(String typeDescription) { + this.typeDescription = typeDescription; + } + + /** + * @see ro.sync.contentcompletion.xml.CIElement#setNillable(boolean) + */ + @Override + public void setNillable(boolean nillable) { + this.nillable = nillable; + } + + /** + * @see ro.sync.contentcompletion.xml.CIElement#isNillable() + */ + @Override + public boolean isNillable() { + return nillable; + } +} \ No newline at end of file diff --git a/oxygen/js-options/src-java/simple/documentation/framework/SDFExtensionsBundle.java b/oxygen/js-options/src-java/simple/documentation/framework/SDFExtensionsBundle.java new file mode 100644 index 0000000..cf69612 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/SDFExtensionsBundle.java @@ -0,0 +1,173 @@ +package simple.documentation.framework; + +import ro.sync.contentcompletion.xml.SchemaManagerFilter; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorExtensionStateListener; +import ro.sync.ecss.extensions.api.AuthorExternalObjectInsertionHandler; +import ro.sync.ecss.extensions.api.AuthorReferenceResolver; +import ro.sync.ecss.extensions.api.AuthorSchemaAwareEditingHandler; +import ro.sync.ecss.extensions.api.AuthorTableCellSpanProvider; +import ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider; +import ro.sync.ecss.extensions.api.CustomAttributeValueEditor; +import ro.sync.ecss.extensions.api.ExtensionsBundle; +import ro.sync.ecss.extensions.api.StylesFilter; +import ro.sync.ecss.extensions.api.link.ElementLocatorProvider; +import ro.sync.ecss.extensions.api.link.LinkTextResolver; +import ro.sync.ecss.extensions.api.structure.AuthorBreadCrumbCustomizer; +import ro.sync.ecss.extensions.api.structure.AuthorOutlineCustomizer; +import ro.sync.ecss.extensions.commons.DefaultElementLocatorProvider; +import simple.documentation.framework.extensions.SDFAttributesValueEditor; +import simple.documentation.framework.extensions.SDFAuthorBreadCrumbCustomizer; +import simple.documentation.framework.extensions.SDFAuthorExtensionStateListener; +import simple.documentation.framework.extensions.SDFAuthorOutlineCustomizer; +import simple.documentation.framework.extensions.SDFExternalObjectInsertionHandler; +import simple.documentation.framework.extensions.SDFReferencesResolver; +import simple.documentation.framework.extensions.SDFSchemaAwareEditingHandler; +import simple.documentation.framework.extensions.SDFSchemaManagerFilter; +import simple.documentation.framework.extensions.SDFStylesFilter; +import simple.documentation.framework.extensions.TableCellSpanProvider; +import simple.documentation.framework.extensions.TableColumnWidthProvider; + +/** + * Simple Document Framework extension bundle. + * + */ +public class SDFExtensionsBundle extends ExtensionsBundle { + /** + * Simple documentation framework state listener. + */ + private SDFAuthorExtensionStateListener sdfAuthorExtensionStateListener; + + /** + * Editor for attributes values. + */ + @Override + public CustomAttributeValueEditor createCustomAttributeValueEditor(boolean forEclipsePlugin) { + return new SDFAttributesValueEditor(new AuthorAccessProvider() { + + public AuthorAccess getAuthorAccess() { + AuthorAccess access = null; + if (sdfAuthorExtensionStateListener != null) { + // Get the Author access. + access = sdfAuthorExtensionStateListener.getAuthorAccess(); + } + return access; + } + }); + } + + /** + * Simple documentation framework state listener. + */ + @Override + public AuthorExtensionStateListener createAuthorExtensionStateListener() { + sdfAuthorExtensionStateListener = new SDFAuthorExtensionStateListener(); + return sdfAuthorExtensionStateListener; + } + + /** + * Filter for content completion proposals from the schema manager. + */ + @Override + public SchemaManagerFilter createSchemaManagerFilter() { + return new SDFSchemaManagerFilter(); + } + + /** + * Default element locator. + */ + @Override + public ElementLocatorProvider createElementLocatorProvider() { + return new DefaultElementLocatorProvider(); + } + + /** + * Expand content references. + */ + @Override + public AuthorReferenceResolver createAuthorReferenceResolver() { + return new SDFReferencesResolver(); + } + + /** + * CSS styles filtering. + */ + @Override + public StylesFilter createAuthorStylesFilter() { + return new SDFStylesFilter(); + } + + /** + * Provider for table cell span informations. + */ + @Override + public AuthorTableCellSpanProvider createAuthorTableCellSpanProvider() { + return new TableCellSpanProvider(); + } + + /** + * Table column width provider responsible of handling modifications regarding + * table width and column widths. + */ + @Override + public AuthorTableColumnWidthProvider createAuthorTableColumnWidthProvider() { + return new TableColumnWidthProvider(); + } + + /** + * Editing support for SDF documents responsible of handling typing and paste events inside section and tables. + */ + @Override + public AuthorSchemaAwareEditingHandler getAuthorSchemaAwareEditingHandler() { + return new SDFSchemaAwareEditingHandler(); + } + + /** + * Author Outline customizer used for custom filtering and nodes rendering in the Outline. + */ + @Override + public AuthorOutlineCustomizer createAuthorOutlineCustomizer() { + return new SDFAuthorOutlineCustomizer(); + } + + /** + * Simple Document Framework Author customizer used for custom nodes rendering + * in the Breadcrumb. + */ + @Override + public AuthorBreadCrumbCustomizer createAuthorBreadCrumbCustomizer() { + return new SDFAuthorBreadCrumbCustomizer(); + } + + /** + * @see ro.sync.ecss.extensions.api.ExtensionsBundle#createExternalObjectInsertionHandler() + */ + @Override + public AuthorExternalObjectInsertionHandler createExternalObjectInsertionHandler() { + return new SDFExternalObjectInsertionHandler(); + } + + /** + * The unique identifier of the Document Type. + * This identifier will be used to store custom SDF options. + */ + @Override + public String getDocumentTypeID() { + return "Simple.Document.Framework.document.type"; + } + + /** + * Bundle description. + */ + public String getDescription() { + return "A custom extensions bundle used for the Simple Document Framework"; + } + + /** + * @see ro.sync.ecss.extensions.api.ExtensionsBundle#createLinkTextResolver() + */ + @Override + public LinkTextResolver createLinkTextResolver() { + return new SDFLinkTextResolver(); + } +} \ No newline at end of file diff --git a/oxygen/js-options/src-java/simple/documentation/framework/SDFLinkTextResolver.java b/oxygen/js-options/src-java/simple/documentation/framework/SDFLinkTextResolver.java new file mode 100644 index 0000000..8fd2adc --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/SDFLinkTextResolver.java @@ -0,0 +1,72 @@ +package simple.documentation.framework; + +import javax.xml.transform.Source; +import javax.xml.transform.TransformerException; +import javax.xml.transform.URIResolver; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; + +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.link.InvalidLinkException; +import ro.sync.ecss.extensions.api.link.LinkTextResolver; +import ro.sync.ecss.extensions.api.node.AttrValue; +import ro.sync.ecss.extensions.api.node.AuthorElement; +import ro.sync.ecss.extensions.api.node.AuthorNode; + +/** + * Resolve a link and obtains a text representation. The content of the link represent the name and + * the absolute location of the referred file. + */ +public class SDFLinkTextResolver extends LinkTextResolver { + + /** + * The author access. + */ + private AuthorAccess authorAccess; + + /** + * Logger for logging. + */ + private static final Logger logger = LogManager.getLogger(SDFLinkTextResolver.class.getName()); + + + /** + * @see ro.sync.ecss.extensions.api.link.LinkTextResolver#resolveReference(ro.sync.ecss.extensions.api.node.AuthorNode) + */ + @Override + public String resolveReference(AuthorNode node) throws InvalidLinkException { + String linkText = null; + if (node.getType() == AuthorNode.NODE_TYPE_ELEMENT + && "link".equals(ro.sync.basic.xml.BasicXmlUtil.getLocalName(node.getName()))) { + AuthorElement element = (AuthorElement) node; + AuthorElement[] authorElements = element.getElementsByLocalName("ref"); + + if (authorElements != null && authorElements.length > 0) { + // Get the first 'ref' element from link. + AuthorElement refElem = authorElements[0]; + AttrValue locationAttribute = refElem.getAttribute("location"); + String locationVal = locationAttribute.getValue(); + URIResolver uriResolver = authorAccess.getXMLUtilAccess().getURIResolver(); + try { + Source resolve = uriResolver.resolve(locationVal, authorAccess.getEditorAccess().getEditorLocation().toString()); + String systemId = resolve.getSystemId(); + linkText = "[" + locationVal + "] - " + systemId; + } catch (TransformerException e) { + logger.warn(e, e); + } + } + } + + return linkText; + } + + + /** + * @see ro.sync.ecss.extensions.api.link.LinkTextResolver#activated(ro.sync.ecss.extensions.api.AuthorAccess) + */ + @Override + public void activated(AuthorAccess authorAccess) { + this.authorAccess = authorAccess; + } +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/callouts/CalloutsRenderingProvider.java b/oxygen/js-options/src-java/simple/documentation/framework/callouts/CalloutsRenderingProvider.java new file mode 100644 index 0000000..ff1e884 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/callouts/CalloutsRenderingProvider.java @@ -0,0 +1,95 @@ +package simple.documentation.framework.callouts; + +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.swing.text.BadLocationException; + +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorDocumentController; +import ro.sync.ecss.extensions.api.callouts.AuthorCalloutRenderingInformation; +import ro.sync.ecss.extensions.api.callouts.CalloutsRenderingInformationProvider; +import ro.sync.ecss.extensions.api.highlights.AuthorPersistentHighlight; +import ro.sync.ecss.extensions.api.node.AuthorNode; +import ro.sync.exml.view.graphics.Color; + + +/** + * Callouts rendering provider. + * + * @author marius + */ +public class CalloutsRenderingProvider extends CalloutsRenderingInformationProvider { + /** + * The author access. + */ + private final AuthorAccess authorAccess; + + /** + * Constructor. + * + * @param authorAccess The author access. + */ + public CalloutsRenderingProvider(AuthorAccess authorAccess) { + this.authorAccess = authorAccess; + } + + @Override + public boolean shouldRenderAsCallout(AuthorPersistentHighlight persistentHighlight) { + return true; + } + + @Override + public AuthorCalloutRenderingInformation getCalloutRenderingInformation( + final AuthorPersistentHighlight hl) { + return new AuthorCalloutRenderingInformation() { + + @Override + public long getTimestamp() { + return -1; + } + + @Override + public String getContentFromTarget(int arg0) { + try { + AuthorDocumentController documentController = authorAccess.getDocumentController(); + AuthorNode nodeAtOffset = documentController.getNodeAtOffset(hl.getEndOffset()); + int startOffset = hl.getStartOffset() - nodeAtOffset.getStartOffset(); + int endOffset = hl.getEndOffset() - nodeAtOffset.getEndOffset(); + String textContent = nodeAtOffset.getTextContent(); + return textContent.substring(startOffset, endOffset); + } catch (BadLocationException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public String getComment(int arg0) { + return hl.getClonedProperties().get(AuthorPersistentHighlight.COMMENT_ATTRIBUTE); + } + + @Override + public Color getColor() { + return Color.COLOR_DARK_YELLOW; + } + + @Override + public String getCalloutType() { + return "Paragraph review"; + } + + @Override + public String getAuthor() { + return "para_reviewer"; + } + + @Override + public Map getAdditionalData() { + LinkedHashMap clonedProperties = hl.getClonedProperties(); + clonedProperties.put("Level", "Superior"); + return clonedProperties; + } + }; + } +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/callouts/SDFAuthorPersistentHighlightActionsProvider.java b/oxygen/js-options/src-java/simple/documentation/framework/callouts/SDFAuthorPersistentHighlightActionsProvider.java new file mode 100644 index 0000000..87a9d5d --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/callouts/SDFAuthorPersistentHighlightActionsProvider.java @@ -0,0 +1,63 @@ +package simple.documentation.framework.callouts; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.AbstractAction; + +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.highlights.AuthorPersistentHighlight; +import ro.sync.ecss.extensions.api.highlights.AuthorPersistentHighlightActionsProvider; +import ro.sync.exml.workspace.api.editor.page.author.actions.AuthorActionsProvider; + +/** + * Provides actions that are shown on the contextual menu of the callout + */ +public class SDFAuthorPersistentHighlightActionsProvider implements + AuthorPersistentHighlightActionsProvider { + /** + * The list with contextual actions for a custom callout. + */ + private List actions = new ArrayList(); + /** + * The default action. + */ + private AbstractAction defaultAction; + /** + * Constructor. + * + * @param authorAccess The author access. + */ + public SDFAuthorPersistentHighlightActionsProvider(AuthorAccess authorAccess) { + AuthorActionsProvider actionsProvider = authorAccess.getEditorAccess().getActionsProvider(); + Map authorCommonActions = actionsProvider.getAuthorCommonActions(); + Set keySet = authorCommonActions.keySet(); + for (String key : keySet) { + if (key != null) { + if ("Edit/Add_Comment".equals(key) || + "Edit/Edit_Comment".equals(key) || + "Edit/Remove_Comments".equals(key)) { + actions.add((AbstractAction) authorCommonActions.get(key)); + if ("Edit/Edit_Comment".equals(key)) { + defaultAction = (AbstractAction) authorCommonActions.get(key); + } + } + } + } + } + /** + * @see ro.sync.ecss.extensions.api.highlights.AuthorPersistentHighlightActionsProvider#getDefaultAction(ro.sync.ecss.extensions.api.highlights.AuthorPersistentHighlight) + */ + public AbstractAction getDefaultAction(AuthorPersistentHighlight hl) { + return defaultAction; + } + + /** + * @see ro.sync.ecss.extensions.api.highlights.AuthorPersistentHighlightActionsProvider#getActions(ro.sync.ecss.extensions.api.highlights.AuthorPersistentHighlight) + */ + public List getActions(AuthorPersistentHighlight hl) { + return actions; + } +} \ No newline at end of file diff --git a/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFAttributesValueEditor.java b/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFAttributesValueEditor.java new file mode 100644 index 0000000..bb60a70 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFAttributesValueEditor.java @@ -0,0 +1,69 @@ +package simple.documentation.framework.extensions; + +import java.io.File; + +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.CustomAttributeValueEditor; +import ro.sync.ecss.extensions.api.EditedAttribute; +import simple.documentation.framework.AuthorAccessProvider; + +/** + * Simple Document Framework attributes value editor. + */ +public class SDFAttributesValueEditor extends CustomAttributeValueEditor { + /** + * Provides access to Author functions and components. + */ + private final AuthorAccessProvider authorAccessProvider; + + /** + * Constructor. + * + * @param authorAccessProvider Provides access to Author functions and components. + */ + public SDFAttributesValueEditor(AuthorAccessProvider authorAccessProvider) { + this.authorAccessProvider = authorAccessProvider; + } + + /** + * Get the value for the current attribute. + * + * @param attr The Edited attribute information. + * @param parentComponent The parent component/composite. + */ + public String getAttributeValue(EditedAttribute attr, Object parentComponent) { + String refValue = null; + AuthorAccess authorAccess = authorAccessProvider.getAuthorAccess(); + if (authorAccess != null) { + File refFile = authorAccess.getWorkspaceAccess().chooseFile( + // Title + "Choose reference file", + // Extensions + null, + // Filter description + null, + // Open for save + false); + if (refFile != null) { + refValue = refFile.getAbsolutePath(); + } + } + return refValue; + } + + /** + * Description of the attribute value editor. + */ + @Override + public String getDescription() { + return "Reference attribute value editor"; + } + + /** + * Filters the attributes that it handles. + */ + @Override + public boolean shouldHandleAttribute(EditedAttribute attribute) { + return attribute.getAttributeQName().endsWith("ref"); + } +} \ No newline at end of file diff --git a/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFAuthorBreadCrumbCustomizer.java b/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFAuthorBreadCrumbCustomizer.java new file mode 100644 index 0000000..b376aee --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFAuthorBreadCrumbCustomizer.java @@ -0,0 +1,60 @@ +package simple.documentation.framework.extensions; + +import javax.swing.JPopupMenu; + +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.node.AuthorNode; +import ro.sync.ecss.extensions.api.structure.AuthorBreadCrumbCustomizer; +import ro.sync.ecss.extensions.api.structure.RenderingInformation; + +/** + * Simple Document Framework Author customizer used for custom nodes rendering + * in the Breadcrumb. + */ +public class SDFAuthorBreadCrumbCustomizer extends AuthorBreadCrumbCustomizer { + + /** + * For every node render its name in the Outline (with the first letter capitalized). + * Set 'Paragraph' as render for 'para' nodes + */ + @Override + public void customizeRenderingInformation(RenderingInformation renderInfo) { + // Get the node to be rendered + AuthorNode node = renderInfo.getNode(); + + String name = node.getName(); + if (name.length() > 0) { + if ("para".equals(name)) { + name = "Paragraph"; + } else { + if(renderInfo.getNode().getName().equals("customcol")) { + // Do not display the customcol elements in the BreadCrumb + name = null; + } else { + // Capitalize the first letter + name = name.substring(0, 1).toUpperCase() + name.substring(1, name.length()); + } + } + } + + if (name == null) { + renderInfo.setIgnoreNodeFromDisplay(true); + } else { + // Set the render text + renderInfo.setRenderedText("[" + name + "]"); + } + } + + /** + * Don't show the popUp menu for the bread crumb. + */ + @Override + public void customizePopUpMenu(Object popUp, AuthorAccess authorAccess) { + if (popUp instanceof JPopupMenu) { + JPopupMenu popupMenu = (JPopupMenu)popUp; + // Remove all the components from the popUp menu . + popupMenu.removeAll(); + } + } + +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFAuthorExtensionStateListener.java b/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFAuthorExtensionStateListener.java new file mode 100644 index 0000000..95115bd --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFAuthorExtensionStateListener.java @@ -0,0 +1,374 @@ +package simple.documentation.framework.extensions; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; + +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorCaretListener; +import ro.sync.ecss.extensions.api.AuthorExtensionStateListener; +import ro.sync.ecss.extensions.api.AuthorListener; +import ro.sync.ecss.extensions.api.AuthorMouseListener; +import ro.sync.ecss.extensions.api.OptionChangedEvent; +import ro.sync.ecss.extensions.api.OptionListener; +import ro.sync.ecss.extensions.api.callouts.AuthorCalloutsController; +import ro.sync.ecss.extensions.api.highlights.AuthorPersistentHighlight; +import ro.sync.ecss.extensions.api.highlights.AuthorPersistentHighlight.PersistentHighlightType; +import ro.sync.ecss.extensions.api.highlights.AuthorPersistentHighlighter; +import ro.sync.ecss.extensions.api.highlights.ColorHighlightPainter; +import ro.sync.ecss.extensions.api.highlights.HighlightPainter; +import ro.sync.ecss.extensions.api.highlights.PersistentHighlightRenderer; +import ro.sync.ecss.extensions.api.structure.AuthorPopupMenuCustomizer; +import ro.sync.exml.view.graphics.Color; +import ro.sync.exml.workspace.api.Platform; +import ro.sync.exml.workspace.api.editor.page.author.actions.AuthorActionsProvider; +import ro.sync.basic.util.URLUtil; +import simple.documentation.framework.callouts.CalloutsRenderingProvider; +import simple.documentation.framework.callouts.SDFAuthorPersistentHighlightActionsProvider; +import simple.documentation.framework.filters.SDFDocumentFilter; +import simple.documentation.framework.listeners.SDFAuthorCaretListener; +import simple.documentation.framework.listeners.SDFAuthorListener; +import simple.documentation.framework.listeners.SDFAuthorMouseListener; +import simple.documentation.framework.operations.highlight.HighlightProperties; + +/** + * Simple Document Framework state listener used to register custom listeners(caret listener, mouse + * listener, document listener and option listener) when the framework is activated. + * + */ +public class SDFAuthorExtensionStateListener implements AuthorExtensionStateListener { + /** + * The key used to store a custom option. + */ + private String customKey = "sdf.custom.option.key"; + + /** + * Custom caret listener to be added on activate and removed on deactivate + */ + private AuthorCaretListener sdfCaretListener; + + /** + * Custom mouse listener + */ + private AuthorMouseListener sdfMouseListener; + + /** + * Option listener to be added in the option storage. + */ + private OptionListener sdfOptionListener; + + /** + * Custom author listener + */ + private AuthorListener sdfAuthorDocumentListener; + + /** + * The access to the author functions. + */ + private AuthorAccess authorAccess; + + /** + * Map between author name and the corresponding highlight/colors association + */ + public static Map> authorHighlightColors = null; + + /** + * Defines the color for a highlight + */ + public static class HighlightColors { + private Color bgColor; + private Color decorationColor; + + /** + * @param bgColor The background color. + * @param decorationColor The color used for decoration + */ + public HighlightColors(Color bgColor, Color decorationColor) { + super(); + this.bgColor = bgColor; + this.decorationColor = decorationColor; + } + + /** + * @return Returns the bgColor. + */ + public Color getBgColor() { + return bgColor; + } + + /** + * @return Returns the decorationColor. + */ + public Color getDecorationColor() { + return decorationColor; + } + } + + static { + authorHighlightColors = new HashMap>(); + + // Set colors for Author_1 + Map colorsMap = new HashMap(); + // Set colors for Insert Change highlight + colorsMap.put(PersistentHighlightType.CHANGE_INSERT, + new HighlightColors(new Color(230, 255, 230), new Color(130, 255, 130))); + // Set colors for Delete Change highlight + colorsMap.put(PersistentHighlightType.CHANGE_DELETE, + new HighlightColors(new Color(255, 255, 230), new Color(255, 255, 130))); + authorHighlightColors.put("Author_1", colorsMap); + + // Set colors for Author_2 + colorsMap = new HashMap(); + // Set colors for Insert Change highlight + colorsMap.put(PersistentHighlightType.CHANGE_INSERT, + new HighlightColors(new Color(255, 255, 230), new Color(255, 255, 130))); + // Set colors for Delete Change highlight + colorsMap.put(PersistentHighlightType.CHANGE_DELETE, + new HighlightColors(new Color(240, 240, 240), new Color(64, 64, 64))); + authorHighlightColors.put("Author_2", colorsMap); + } + + /** + * The SDF Author extension is activated. + */ + public void activated(final AuthorAccess authorAccess) { + this.authorAccess = authorAccess; + sdfOptionListener = new OptionListener(customKey) { + @Override + public void optionValueChanged(OptionChangedEvent newValue) { + // The custom option changed. + } + }; + + // Add document filter. + authorAccess.getDocumentController().setDocumentFilter(new SDFDocumentFilter(authorAccess)); + + // Add an option listener. + authorAccess.getOptionsStorage().addOptionListener(sdfOptionListener); + + // Add author document listeners. + sdfAuthorDocumentListener = new SDFAuthorListener(authorAccess); + authorAccess.getDocumentController().addAuthorListener(sdfAuthorDocumentListener); + + if (authorAccess.getWorkspaceAccess().getPlatform() != Platform.WEBAPP) { + // Add mouse listener. + sdfMouseListener = new SDFAuthorMouseListener(authorAccess); + authorAccess.getEditorAccess().addAuthorMouseListener(sdfMouseListener); + + // Add custom tooltip + String tooltip = "[SDF] " + URLUtil.getDescription(authorAccess.getEditorAccess().getEditorLocation()); + authorAccess.getEditorAccess().setEditorTabTooltipText(tooltip); + + // Add caret listener. + sdfCaretListener = new SDFAuthorCaretListener(authorAccess); + //authorAccess.getEditorAccess().addAuthorCaretListener(sdfCaretListener); + + // Use the actions provider to switch to "No Tags" mode. + AuthorActionsProvider actionsProvider = authorAccess.getEditorAccess().getActionsProvider(); + Map authorCommonActions = actionsProvider.getAuthorCommonActions(); + // Switch to no tags Author/No_tags + actionsProvider.invokeAction(authorCommonActions.get("Author/No_tags")); + + // Set highlights tool tip and painter + authorAccess.getEditorAccess().getPersistentHighlighter().setHighlightRenderer(new PersistentHighlightRenderer() { + public String getTooltip(AuthorPersistentHighlight highlight) { + // Get highlight properties + Map properties = highlight.getClonedProperties(); + String highlightID = properties.get(HighlightProperties.ID); + String highlightAuthor = properties.get(HighlightProperties.AUTHOR); + String highlightComment = properties.get(HighlightProperties.COMMENT); + + StringBuilder tooltip = new StringBuilder(); + // Add create date + if (highlightID != null) { + try { + tooltip.append("Id: ").append(highlightID).append("\n"); + } catch (NumberFormatException e) { + // Wrong date + } + } + // Add author + if (highlightAuthor != null) { + tooltip.append("Author: ").append(highlightAuthor).append("\n");; + } + // Add comment + if (highlightComment != null) { + tooltip.append("Comment: ").append(highlightComment); + } + return tooltip.toString(); + } + + public HighlightPainter getHighlightPainter(AuthorPersistentHighlight highlight) { + ColorHighlightPainter painter = new ColorHighlightPainter(); + painter.setBgColor(new Color(0, 0, 255, 20)); + return painter; + } + }); + + // Set reviews tool tip and painters + authorAccess.getReviewController().setReviewRenderer(new PersistentHighlightRenderer() { + /** + * @see ro.sync.ecss.extensions.api.highlights.PersistentHighlightRenderer#getTooltip(ro.sync.ecss.extensions.api.highlights.AuthorPersistentHighlight) + */ + public String getTooltip(AuthorPersistentHighlight highlight) { + String tooltip = "Review Tooltip"; + + PersistentHighlightType type = highlight.getType(); + if (type == PersistentHighlightType.CHANGE_DELETE) { + // Delete highlight + tooltip = "Deleted by " + highlight.getClonedProperties().get("author"); + } else if (type == PersistentHighlightType.CHANGE_INSERT) { + // Insert highlight + tooltip = "Inserted by " + highlight.getClonedProperties().get("author"); + } else if (type == PersistentHighlightType.COMMENT) { + // Comment highlight + // If a null value is returned, the default tooltip text will be used + tooltip = null; + } + return tooltip; + } + + /** + * @see ro.sync.ecss.extensions.api.highlights.PersistentHighlightRenderer#getHighlightPainter(ro.sync.ecss.extensions.api.highlights.AuthorPersistentHighlight) + */ + public HighlightPainter getHighlightPainter(AuthorPersistentHighlight highlight) { + HighlightPainter reviewPainter = null; + Map properties = highlight.getClonedProperties(); + String authorName = properties.get("author"); + if (authorName != null) { + // Get the highlight/colors association for this author name + Map highlightColorsMap = authorHighlightColors.get(authorName); + if (highlightColorsMap != null) { + // Get the associated colors for this type of highlight + HighlightColors highlightColors = highlightColorsMap.get(highlight.getType()); + if (highlightColors != null) { + reviewPainter = new ColorHighlightPainter(); + // Background color + ((ColorHighlightPainter) reviewPainter).setBgColor(highlightColors.getBgColor()); + // Decoration color + ((ColorHighlightPainter) reviewPainter).setColor(highlightColors.getDecorationColor()); + } + } + } + + // If a null value is returned the default highlight painter is used. + return reviewPainter; + } + }); + + // Get the list with all track changes highlights from the document + AuthorPersistentHighlight[] changeHighlights = authorAccess.getReviewController().getChangeHighlights(); + if(changeHighlights.length > 0 && !authorAccess.getReviewController().isTrackingChanges()) { + // If the document has track changes highlights, then set track changes mode to ON + authorAccess.getReviewController().toggleTrackChanges(); + } + + // Add a popup menu customizer. Group Cut, Copy, Paste and Paste as XML actions in Edit menu. + authorAccess.getEditorAccess().addPopUpMenuCustomizer(new AuthorPopupMenuCustomizer() { + // Customize popup menu + @SuppressWarnings("serial") + public void customizePopUpMenu(Object popUp, final AuthorAccess authorAccess) { + JPopupMenu popupMenu = (JPopupMenu) popUp; + Component[] components = popupMenu.getComponents(); + // The new 'Edit' menu + JMenu editMenu = new JMenu("Edit"); + boolean shouldAddEditMenu = false; + for (int i = 0; i < components.length; i++) { + if (components[i] instanceof JMenuItem) { + JMenuItem menuItem = (JMenuItem) components[i]; + if (menuItem.getAction() != null) { + Object name = menuItem.getAction().getValue(Action.NAME); + if ("Cut".equals(name) || "Copy".equals(name) || "Paste".equals(name)|| "Paste as XML".equals(name)) { + // Remove edit actions + popupMenu.remove(menuItem); + // Add edit actions to edit menu + editMenu.add(menuItem); + shouldAddEditMenu = true; + } + } + } + } + if (shouldAddEditMenu) { + popupMenu.add(editMenu, 0); + } + + final URL selectedUrl; + try { + final String selectedText = authorAccess.getEditorAccess().getSelectedText(); + if (selectedText != null) { + selectedUrl = new URL(selectedText); + // Open selected url in system application + popupMenu.add(new JMenuItem(new AbstractAction("Open in system application") { + public void actionPerformed(ActionEvent e) { + authorAccess.getWorkspaceAccess().openInExternalApplication(selectedUrl, true); + } + }), 0); + } + } catch (MalformedURLException e2) {} + } + }); + + // Add a provider for callouts rendering + CalloutsRenderingProvider calloutsRenderingProvider = new CalloutsRenderingProvider(authorAccess); + AuthorCalloutsController authorCalloutsController = + authorAccess.getReviewController().getAuthorCalloutsController(); + authorCalloutsController.setCalloutsRenderingInformationProvider(calloutsRenderingProvider); + + // Show insertions callouts + authorCalloutsController.setShowInsertionsCallouts(true); + + // Add an actions provider for actions that are shown on the contextual menu of the + // custom callout + AuthorPersistentHighlighter highlighter = authorAccess.getEditorAccess().getPersistentHighlighter(); + SDFAuthorPersistentHighlightActionsProvider highlightActionsProvider = + new SDFAuthorPersistentHighlightActionsProvider(authorAccess); + highlighter.setHighlightsActionsProvider(highlightActionsProvider); + } + + // Other custom initializations... + } + + /** + * The SDF Author extension is deactivated. + */ + public void deactivated(AuthorAccess authorAccess) { + + // Remove the option listener. + authorAccess.getOptionsStorage().removeOptionListener(sdfOptionListener); + + // Remove document listeners. + authorAccess.getDocumentController().removeAuthorListener(sdfAuthorDocumentListener); + + if (authorAccess.getWorkspaceAccess().getPlatform() != Platform.WEBAPP) { + // Remove mouse listener. + authorAccess.getEditorAccess().removeAuthorMouseListener(sdfMouseListener); + + // Remove caret listener. + authorAccess.getEditorAccess().removeAuthorCaretListener(sdfCaretListener); + } + + // Other actions... + } + + public String getDescription() { + return "Simple Document Framework state listener"; + } + + /** + * Returns the Author access. + * + * @return The access to Author functions and components. + */ + public AuthorAccess getAuthorAccess() { + return authorAccess; + } +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFAuthorOutlineCustomizer.java b/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFAuthorOutlineCustomizer.java new file mode 100644 index 0000000..172dc19 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFAuthorOutlineCustomizer.java @@ -0,0 +1,158 @@ +package simple.documentation.framework.extensions; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; + +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.text.BadLocationException; +import javax.swing.tree.TreePath; + +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.node.AuthorElement; +import ro.sync.ecss.extensions.api.node.AuthorNode; +import ro.sync.ecss.extensions.api.structure.AuthorOutlineCustomizer; +import ro.sync.ecss.extensions.api.structure.RenderingInformation; + +/** + * Simple Document Framework Author Outline customizer used for custom filtering and nodes rendering + * in the Outline. + */ +public class SDFAuthorOutlineCustomizer extends AuthorOutlineCustomizer { + + /** + * Ignore 'b' nodes. + */ + @Override + public boolean ignoreNode(AuthorNode node) { + if (node.getName().equals("b")) { + return true; + } + // Default behavior + return false; + } + + /** + * For every node render its name in the Outline (with the first letter capitalized). + * Set 'Paragraph' as render for 'para' nodes + * Render the title text for 'section' nodes and add 'Section' as additional info and tooltip. + */ + @Override + public void customizeRenderingInformation( + RenderingInformation renderInfo) { + // Get the node to be rendered + AuthorNode node = renderInfo.getNode(); + + String name = node.getName(); + if (name.length() > 0) { + if ("para".equals(name)) { + name = "Paragraph"; + } else { + // Capitalize the first letter + name = name.substring(0, 1).toUpperCase() + name.substring(1, name.length()); + } + } + + // Render additional attribute value + String additionalAttrValue = renderInfo.getAdditionalRenderedAttributeValue(); + if (additionalAttrValue != null && additionalAttrValue.length() > 0) { + renderInfo.setAdditionalRenderedAttributeValue("[" + additionalAttrValue + "]"); + } + + // Set the render text + renderInfo.setRenderedText("[" + name + "]"); + + // Render the title text in the 'section' additional info. + if (node.getName().equals("section")) { + if (node instanceof AuthorElement) { + // Get the 'section' content nodes + List contentNodes = ((AuthorElement) node).getContentNodes(); + if (contentNodes.size() > 0) { + AuthorNode authorNode = contentNodes.get(0); + if ("title".equals(authorNode.getName())) { + try { + // Render the title + String textContent = authorNode.getTextContent(); + if (textContent != null && textContent.trim().length() > 0) { + if (textContent.length() > 20) { + textContent = textContent.substring(0, 20); + } + renderInfo.setRenderedText(textContent); + // Set 'Section' as additional info + renderInfo.setAdditionalRenderedText("[Section]"); + // Set the tooltip text + renderInfo.setTooltipText(textContent); + } + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + } + } + } + } + + /** + * Customize the popUp menu. + * If the selected node is a 'title' from 'section' remove the 'Delete' + * action from the popUp menu and add the 'Remove content' action if the selection + * is single. + */ + @Override + public void customizePopUpMenu(Object popUp, final AuthorAccess authorAccess) { + if (authorAccess != null) { + if (popUp instanceof JPopupMenu) { + JPopupMenu popupMenu = (JPopupMenu)popUp; + // Find the selected paths from the Outliner + TreePath[] selectedPaths = authorAccess.getOutlineAccess().getSelectedPaths(true); + for (TreePath treePath : selectedPaths) { + Object[] authorNodesPath = treePath.getPath(); + if (authorNodesPath.length > 0) { + // Find the selected node (last node from the list of path nodes) + final AuthorNode selectedAuthorNode = (AuthorNode) authorNodesPath[authorNodesPath.length - 1]; + // Find if the current selected node is a title from section + if ("title".equals(selectedAuthorNode.getName()) && + "section".equals(selectedAuthorNode.getParent().getName())) { + // Find the popUp item corresponding to the 'Delete' action + Component[] components = popupMenu.getComponents(); + int deleteItemIndex = 0; + for (int i = 0; i < components.length; i++) { + if (components[i] instanceof JMenuItem) { + String itemText = ((JMenuItem) components[i]).getText(); + if ("Delete".equals(itemText)) { + // The 'Remove Content' item will be added at the same index + deleteItemIndex = i; + // Remove the 'Delete' action + popupMenu.remove(deleteItemIndex); + } + } + } + + if (selectedPaths.length == 1) { + // Add the 'Remove content' action + JMenuItem sdfMenuItem = new JMenuItem("Remove Content"); + sdfMenuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + // Remove title content + int startOffset = selectedAuthorNode.getStartOffset(); + int endOffset = selectedAuthorNode.getEndOffset(); + if (endOffset > startOffset + 1) { + authorAccess.getDocumentController().delete( + startOffset + 1, + endOffset); + } + } + }); + // Add the menu item in the menu + popupMenu.add(sdfMenuItem, deleteItemIndex); + } + break; + } + } + } + } + } + } +} \ No newline at end of file diff --git a/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFExternalObjectInsertionHandler.java b/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFExternalObjectInsertionHandler.java new file mode 100644 index 0000000..48cbb20 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFExternalObjectInsertionHandler.java @@ -0,0 +1,72 @@ +package simple.documentation.framework.extensions; + +import java.net.URL; +import java.util.List; + +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorExternalObjectInsertionHandler; +import ro.sync.ecss.extensions.api.AuthorOperationException; +import ro.sync.ecss.extensions.api.node.AuthorDocumentFragment; +import ro.sync.basic.util.URLUtil; + +/** + * Accepts insertion of images and XML files from external sources. + */ +public class SDFExternalObjectInsertionHandler extends AuthorExternalObjectInsertionHandler { + /** + * @see ro.sync.ecss.extensions.api.AuthorExternalObjectInsertionHandler#insertURLs(ro.sync.ecss.extensions.api.AuthorAccess, java.util.List, int) + */ + @Override + public void insertURLs(AuthorAccess authorAccess, List urls, int source) + throws AuthorOperationException { + for (URL url : urls) { + String xmlFragment = null; + if (authorAccess.getUtilAccess().isSupportedImageURL(url)) { + // Insert an image element + xmlFragment = ""; + } else { + // Insert + xmlFragment = ""; + } + if (xmlFragment != null) { + // Create am image fragment + AuthorDocumentFragment imageFragment = authorAccess.getDocumentController().createNewDocumentFragmentInContext( + xmlFragment, authorAccess.getEditorAccess().getCaretOffset()); + // Insert the fragment + authorAccess.getDocumentController().insertFragment(authorAccess.getEditorAccess().getCaretOffset(), imageFragment); + } + } + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorExternalObjectInsertionHandler#acceptSource(ro.sync.ecss.extensions.api.AuthorAccess, int) + */ + @Override + public boolean acceptSource(AuthorAccess authorAccess, int source) { + if (source == DND_EXTERNAL) { + // Accept only from external source + return true; + } + // For urls from other sources (Dita Maps Manager, Project) a new tab will be open. + return false; + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorExternalObjectInsertionHandler#acceptURLs(ro.sync.ecss.extensions.api.AuthorAccess, java.util.List, int) + */ + @Override + public boolean acceptURLs(AuthorAccess authorAccess, List urls, int source) { + boolean accept = acceptSource(authorAccess, source); + if (accept) { + for (int i = 0; i < urls.size(); i++) { + // Accept only XML and image files + if (!authorAccess.getUtilAccess().isSupportedImageURL(urls.get(i)) && + !"xml".equalsIgnoreCase(URLUtil.getExtension(urls.get(i).toString()))) { + accept = false; + break; + } + } + } + return accept; + } +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFReferencesResolver.java b/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFReferencesResolver.java new file mode 100644 index 0000000..0c1cbb4 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFReferencesResolver.java @@ -0,0 +1,199 @@ +package simple.documentation.framework.extensions; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.xml.transform.sax.SAXSource; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorReferenceResolver; +import ro.sync.ecss.extensions.api.node.AttrValue; +import ro.sync.ecss.extensions.api.node.AuthorElement; +import ro.sync.ecss.extensions.api.node.AuthorNode; + +/** + * References resolver for Simple Documentation Framework. + * + */ +public class SDFReferencesResolver implements AuthorReferenceResolver { + /** + * Logger for logging. + */ + private static Logger logger = LogManager.getLogger( + SDFReferencesResolver.class.getName()); + + /** + * Verifies if the handler considers the node to have references. + * + * @param node The node to be analyzed. + * @return true if it is has references. + */ + public boolean hasReferences(AuthorNode node) { + boolean hasReferences = false; + if (node.getType() == AuthorNode.NODE_TYPE_ELEMENT) { + AuthorElement element = (AuthorElement) node; + if ("ref".equals(element.getLocalName())) { + AttrValue attrValue = element.getAttribute("location"); + hasReferences = attrValue != null; + } + } + return hasReferences; + } + + /** + * Returns the name of the node that contains the expanded referred content. + * + * @param node The node that contains references. + * @return The display name of the node. + */ + public String getDisplayName(AuthorNode node) { + String displayName = "ref-fragment"; + if (node.getType() == AuthorNode.NODE_TYPE_ELEMENT) { + AuthorElement element = (AuthorElement) node; + if ("ref".equals(element.getLocalName())) { + AttrValue attrValue = element.getAttribute("location"); + if (attrValue != null) { + displayName = attrValue.getValue(); + } + } + } + return displayName; + } + + /** + * Resolve the references of the node. + * + * The returning SAXSource will be used for creating the referred content + * using the parser and source inside it. + * + * @param node The clone of the node. + * @param systemID The system ID of the node with references. + * @param authorAccess The author access implementation. + * @param entityResolver The entity resolver that can be used to resolve: + * + *
    + *
  • Resources that are already opened in editor. + * For this case the InputSource will contains the editor content.
  • + *
  • Resources resolved through XML catalog.
  • + *
+ * + * @return The SAX source including the parser and the parser's input source. + */ + public SAXSource resolveReference( + AuthorNode node, + String systemID, + AuthorAccess authorAccess, + EntityResolver entityResolver) { + SAXSource saxSource = null; + if (node.getType() == AuthorNode.NODE_TYPE_ELEMENT) { + AuthorElement element = (AuthorElement) node; + if ("ref".equals(element.getLocalName())) { + AttrValue attrValue = element.getAttribute("location"); + if (attrValue != null){ + String attrStringVal = attrValue.getValue(); + if (attrStringVal.length() > 0) { + try { + URL absoluteUrl = new URL(new URL(systemID), + authorAccess.getUtilAccess().correctURL(attrStringVal)); + InputSource inputSource = entityResolver.resolveEntity(null, + absoluteUrl.toString()); + if(inputSource == null) { + inputSource = new InputSource(absoluteUrl.toString()); + } + XMLReader xmlReader = authorAccess.getXMLUtilAccess().newNonValidatingXMLReader(); + xmlReader.setEntityResolver(entityResolver); + saxSource = new SAXSource(xmlReader, inputSource); + } catch (MalformedURLException e) { + logger.error(e, e); + } catch (SAXException e) { + logger.error(e, e); + } catch (IOException e) { + logger.error(e, e); + } + } + } + } + } + return saxSource; + } + + /** + * Get an unique identifier for the node reference. + * + * The unique identifier is used to avoid resolving the references + * recursively. + * + * @param node The node that has reference. + * @return An unique identifier for the reference node. + */ + public String getReferenceUniqueID(AuthorNode node) { + String id = null; + if (node.getType() == AuthorNode.NODE_TYPE_ELEMENT) { + AuthorElement element = (AuthorElement) node; + if ("ref".equals(element.getLocalName())) { + AttrValue attrValue = element.getAttribute("location"); + if (attrValue != null) { + id = attrValue.getValue(); + } + } + } + return id; + } + + /** + * Return the systemID of the referred content. + * + * @param node The reference node. + * @param authorAccess The author access. + * + * @return The systemID of the referred content. + */ + public String getReferenceSystemID(AuthorNode node, + AuthorAccess authorAccess) { + String systemID = null; + if (node.getType() == AuthorNode.NODE_TYPE_ELEMENT) { + AuthorElement element = (AuthorElement) node; + if ("ref".equals(element.getLocalName())) { + AttrValue attrValue = element.getAttribute("location"); + if (attrValue != null) { + String attrStringVal = attrValue.getValue(); + try { + URL absoluteUrl = new URL(node.getXMLBaseURL(), + authorAccess.getUtilAccess().correctURL(attrStringVal)); + systemID = absoluteUrl.toString(); + } catch (MalformedURLException e) { + logger.error(e, e); + } + } + } + } + return systemID; + } + + /** + * Verifies if the references of the given node must be refreshed + * when the attribute with the specified name has changed. + * + * @param node The node with the references. + * @param attributeName The name of the changed attribute. + * @return true if the references must be refreshed. + */ + public boolean isReferenceChanged(AuthorNode node, String attributeName) { + return "location".equals(attributeName); + } + + /** + * @return The description of the author extension. + */ + public String getDescription() { + return "Resolves the 'ref' references"; + } +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFSchemaAwareEditingHandler.java b/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFSchemaAwareEditingHandler.java new file mode 100644 index 0000000..24a1193 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFSchemaAwareEditingHandler.java @@ -0,0 +1,353 @@ +package simple.documentation.framework.extensions; + +import java.util.List; + +import javax.swing.text.BadLocationException; + +import ro.sync.contentcompletion.xml.ContextElement; +import ro.sync.contentcompletion.xml.WhatElementsCanGoHereContext; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorOperationException; +import ro.sync.ecss.extensions.api.AuthorSchemaAwareEditingHandlerAdapter; +import ro.sync.ecss.extensions.api.AuthorSchemaManager; +import ro.sync.ecss.extensions.api.InvalidEditException; +import ro.sync.ecss.extensions.api.node.AuthorDocumentFragment; +import ro.sync.ecss.extensions.api.node.AuthorElement; +import ro.sync.ecss.extensions.api.node.AuthorNode; + +/** + * Specific editing support for SDF documents. Handles typing and paste events inside section and tables. + */ +public class SDFSchemaAwareEditingHandler extends AuthorSchemaAwareEditingHandlerAdapter { + + private static final String SDF_NAMESPACE = "http://www.oxygenxml.com/sample/documentation"; + /** + * SDF table element name. + */ + private static final String SDF_TABLE = "table"; + /** + * SDF table row name. + */ + private static final String SDF_TABLE_ROW = "tr"; + /** + * SDF table cell name. + */ + private static final String SDF_TABLE_CELL = "td"; + /** + * SDF section element name. + */ + private static final String SECTION = "section"; + /** + * SDF para element name. + */ + protected static final String PARA = "para"; + /** + * SDF title element name. + */ + protected static final String TITLE = "title"; + + /** + * @see ro.sync.ecss.extensions.api.AuthorSchemaAwareEditingHandler#handleDelete(int, int, ro.sync.ecss.extensions.api.AuthorAccess, boolean) + */ + @Override + public boolean handleDelete(int offset, int deleteType, AuthorAccess authorAccess, boolean wordLevel) + throws InvalidEditException { + // Not handled. + return false; + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorSchemaAwareEditingHandler#handleDeleteElementTags(ro.sync.ecss.extensions.api.node.AuthorNode, ro.sync.ecss.extensions.api.AuthorAccess) + */ + @Override + public boolean handleDeleteElementTags(AuthorNode nodeToUnwrap, AuthorAccess authorAccess) + throws InvalidEditException { + // Not handled. + return false; + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorSchemaAwareEditingHandler#handleDeleteSelection(int, int, int, ro.sync.ecss.extensions.api.AuthorAccess) + */ + @Override + public boolean handleDeleteSelection(int selectionStart, int selectionEnd, int generatedByActionId, + AuthorAccess authorAccess) throws InvalidEditException { + // Not handled. + return false; + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorSchemaAwareEditingHandler#handleJoinElements(ro.sync.ecss.extensions.api.node.AuthorNode, java.util.List, ro.sync.ecss.extensions.api.AuthorAccess) + */ + @Override + public boolean handleJoinElements(AuthorNode targetNode, List nodesToJoin, AuthorAccess authorAccess) + throws InvalidEditException { + // Not handled. + return false; + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorSchemaAwareEditingHandler#handlePasteFragment(int, ro.sync.ecss.extensions.api.node.AuthorDocumentFragment[], int, ro.sync.ecss.extensions.api.AuthorAccess) + */ + @Override + public boolean handlePasteFragment(int offset, AuthorDocumentFragment[] fragmentsToInsert, int actionId, + AuthorAccess authorAccess) throws InvalidEditException { + boolean handleInsertionEvent = false; + AuthorSchemaManager authorSchemaManager = authorAccess.getDocumentController().getAuthorSchemaManager(); + if (!authorSchemaManager.isLearnSchema() && + !authorSchemaManager.hasLoadingErrors() && + authorSchemaManager.getAuthorSchemaAwareOptions().isEnableSmartPaste()) { + handleInsertionEvent = handleInsertionEvent(offset, fragmentsToInsert, authorAccess); + } + return handleInsertionEvent; + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorSchemaAwareEditingHandler#handleTyping(int, char, ro.sync.ecss.extensions.api.AuthorAccess) + */ + @Override + public boolean handleTyping(int offset, char ch, AuthorAccess authorAccess) + throws InvalidEditException { + boolean handleTyping = false; + AuthorSchemaManager authorSchemaManager = authorAccess.getDocumentController().getAuthorSchemaManager(); + if (!authorSchemaManager.isLearnSchema() && + !authorSchemaManager.hasLoadingErrors() && + authorSchemaManager.getAuthorSchemaAwareOptions().isEnableSmartTyping()) { + try { + AuthorDocumentFragment characterFragment = + authorAccess.getDocumentController().createNewDocumentTextFragment(String.valueOf(ch)); + handleTyping = handleInsertionEvent(offset, new AuthorDocumentFragment[] {characterFragment}, authorAccess); + } catch (AuthorOperationException e) { + throw new InvalidEditException(e.getMessage(), "Invalid typing event: " + e.getMessage(), e, false); + } + } + return handleTyping; + } + + /** + * Handle an insertion event (either typing or paste). + * + * @param offset Offset where the insertion event occurred. + * @param fragmentsToInsert Fragments that must be inserted at the given offset. + * @param authorAccess Author access. + * @return true if the event was handled, false otherwise. + * + * @throws InvalidEditException The event was rejected because it is invalid. + */ + private boolean handleInsertionEvent( + int offset, + AuthorDocumentFragment[] fragmentsToInsert, + AuthorAccess authorAccess) throws InvalidEditException { + AuthorSchemaManager authorSchemaManager = authorAccess.getDocumentController().getAuthorSchemaManager(); + boolean handleEvent = false; + try { + AuthorNode nodeAtInsertionOffset = authorAccess.getDocumentController().getNodeAtOffset(offset); + if (isElementWithNameAndNamespace(nodeAtInsertionOffset, SDF_TABLE)) { + // Check if the fragment is allowed as it is. + boolean canInsertFragments = authorSchemaManager.canInsertDocumentFragments( + fragmentsToInsert, + offset, + AuthorSchemaManager.VALIDATION_MODE_STRICT_FIRST_CHILD_LAX_OTHERS); + if (!canInsertFragments) { + handleEvent = handleInvalidInsertionEventInTable( + offset, + fragmentsToInsert, + authorAccess, + authorSchemaManager); + } + } else if(isElementWithNameAndNamespace(nodeAtInsertionOffset, SECTION)) { + // Check if the fragment is allowed as it is. + boolean canInsertFragments = authorSchemaManager.canInsertDocumentFragments( + fragmentsToInsert, + offset, + AuthorSchemaManager.VALIDATION_MODE_STRICT_FIRST_CHILD_LAX_OTHERS); + if (!canInsertFragments) { + // Insertion in 'section' element + handleEvent = handleInvalidInsertionEventInSect( + offset, + fragmentsToInsert, + authorAccess, + authorSchemaManager); + } + } + } catch (BadLocationException e) { + throw new InvalidEditException(e.getMessage(), "Invalid typing event: " + e.getMessage(), e, false); + } catch (AuthorOperationException e) { + throw new InvalidEditException(e.getMessage(), "Invalid typing event: " + e.getMessage(), e, false); + } + return handleEvent; + } + + /** + * @return true if the given node is an element with the given local name and from the SDF namespace. + */ + protected boolean isElementWithNameAndNamespace(AuthorNode node, String elementLocalName) { + boolean result = false; + if(node.getType() == AuthorNode.NODE_TYPE_ELEMENT) { + AuthorElement element = (AuthorElement) node; + result = elementLocalName.equals(element.getLocalName()) && element.getNamespace().equals(SDF_NAMESPACE); + } + return result; + } + + /** + * Try to handle invalid insertion events in a SDF 'table'. + * A row element will be inserted with a new cell in which the fragments will be inserted. + * + * @param offset Offset where the insertion event occurred. + * @param fragmentsToInsert Fragments that must be inserted at the given offset. + * @param authorAccess Author access. + * @return true if the event was handled, false otherwise. + */ + private boolean handleInvalidInsertionEventInTable( + int offset, + AuthorDocumentFragment[] fragmentsToInsert, + AuthorAccess authorAccess, + AuthorSchemaManager authorSchemaManager) throws BadLocationException, AuthorOperationException { + boolean handleEvent = false; + // Typing/paste inside a SDF table. We will try to wrap the fragment into a new cell and insert it inside a new row. + WhatElementsCanGoHereContext context = authorSchemaManager.createWhatElementsCanGoHereContext(offset); + StringBuilder xmlFragment = new StringBuilder("<"); + xmlFragment.append(SDF_TABLE_ROW); + if (SDF_NAMESPACE != null && SDF_NAMESPACE.length() != 0) { + xmlFragment.append(" xmlns=\"").append(SDF_NAMESPACE).append("\""); + } + xmlFragment.append("/>"); + + // Check if a row can be inserted at the current offset. + boolean canInsertRow = authorSchemaManager.canInsertDocumentFragments( + new AuthorDocumentFragment[] {authorAccess.getDocumentController().createNewDocumentFragmentInContext(xmlFragment.toString(), offset)}, + context, + AuthorSchemaManager.VALIDATION_MODE_STRICT_FIRST_CHILD_LAX_OTHERS); + + // Derive the context by adding a new row element with a cell. + if (canInsertRow) { + pushContextElement(context, SDF_TABLE_ROW); + pushContextElement(context, SDF_TABLE_CELL); + + // Test if fragments can be inserted in the new context. + if (authorSchemaManager.canInsertDocumentFragments( + fragmentsToInsert, + context, + AuthorSchemaManager.VALIDATION_MODE_STRICT_FIRST_CHILD_LAX_OTHERS)) { + + // Insert a new row with a cell. + xmlFragment = new StringBuilder("<"); + xmlFragment.append(SDF_TABLE_ROW); + + if (SDF_NAMESPACE != null && SDF_NAMESPACE.length() != 0) { + xmlFragment.append(" xmlns=\"").append(SDF_NAMESPACE).append("\""); + } + xmlFragment.append("><"); + xmlFragment.append(SDF_TABLE_CELL); + xmlFragment.append("/>"); + authorAccess.getDocumentController().insertXMLFragment(xmlFragment.toString(), offset); + + // Get the newly inserted cell. + AuthorNode newCell = authorAccess.getDocumentController().getNodeAtOffset(offset + 2); + for (int i = 0; i < fragmentsToInsert.length; i++) { + authorAccess.getDocumentController().insertFragment(newCell.getEndOffset(), fragmentsToInsert[i]); + } + + handleEvent = true; + } + } + return handleEvent; + } + + /** + * Derive the given context by adding the specified element. + */ + protected void pushContextElement(WhatElementsCanGoHereContext context, String elementName) { + ContextElement contextElement = new ContextElement(); + contextElement.setQName(elementName); + contextElement.setNamespace(SDF_NAMESPACE); + context.pushContextElement(contextElement, null); + } + + /** + * Try to handle invalid insertion events in 'section'. + * The solution is to insert the fragmentsToInsert into a 'title' element if the sect element is empty or + * into a 'para' element if the sect already contains a 'title'. + * + * @param offset Offset where the insertion event occurred. + * @param fragmentsToInsert Fragments that must be inserted at the given offset. + * @param authorAccess Author access. + * @return true if the event was handled, false otherwise. + */ + private boolean handleInvalidInsertionEventInSect(int offset, AuthorDocumentFragment[] fragmentsToInsert, AuthorAccess authorAccess, + AuthorSchemaManager authorSchemaManager) throws BadLocationException, AuthorOperationException { + boolean handleEvent = false; + // Typing/paste inside an section. + AuthorElement sectionElement = (AuthorElement) authorAccess.getDocumentController().getNodeAtOffset(offset); + + if (sectionElement.getStartOffset() + 1 == sectionElement.getEndOffset()) { + // Empty section element + WhatElementsCanGoHereContext context = authorSchemaManager.createWhatElementsCanGoHereContext(offset); + // Derive the context by adding a title. + pushContextElement(context, TITLE); + + // Test if fragments can be inserted in 'title' element + if (authorSchemaManager.canInsertDocumentFragments( + fragmentsToInsert, + context, + AuthorSchemaManager.VALIDATION_MODE_STRICT_FIRST_CHILD_LAX_OTHERS)) { + // Create a title structure and insert fragments inside + StringBuilder xmlFragment = new StringBuilder("<").append(TITLE); + if (SDF_NAMESPACE != null && SDF_NAMESPACE.length() != 0) { + xmlFragment.append(" xmlns=\"").append(SDF_NAMESPACE).append("\""); + } + xmlFragment.append(">").append(""); + // Insert title + authorAccess.getDocumentController().insertXMLFragment(xmlFragment.toString(), offset); + + // Insert fragments + AuthorNode newParaNode = authorAccess.getDocumentController().getNodeAtOffset(offset + 1); + for (int i = 0; i < fragmentsToInsert.length; i++) { + authorAccess.getDocumentController().insertFragment(newParaNode.getEndOffset(), fragmentsToInsert[i]); + } + handleEvent = true; + } + } else { + // Check if there is just a title. + List contentNodes = sectionElement.getContentNodes(); + if (contentNodes.size() == 1) { + AuthorNode child = contentNodes.get(0); + boolean isTitleChild = isElementWithNameAndNamespace(child, TITLE); + if (isTitleChild && child.getEndOffset() < offset) { + // We are after the title. + + // Empty sect element + WhatElementsCanGoHereContext context = authorSchemaManager.createWhatElementsCanGoHereContext(offset); + // Derive the context by adding a para + pushContextElement(context, PARA); + + // Test if fragments can be inserted in 'para' element + if (authorSchemaManager.canInsertDocumentFragments( + fragmentsToInsert, + context, + AuthorSchemaManager.VALIDATION_MODE_STRICT_FIRST_CHILD_LAX_OTHERS)) { + // Create a para structure and insert fragments inside + StringBuilder xmlFragment = new StringBuilder("<").append(PARA); + if (SDF_NAMESPACE != null && SDF_NAMESPACE.length() != 0) { + xmlFragment.append(" xmlns=\"").append(SDF_NAMESPACE).append("\""); + } + xmlFragment.append(">").append(""); + // Insert para + authorAccess.getDocumentController().insertXMLFragment(xmlFragment.toString(), offset); + + // Insert fragments + AuthorNode newParaNode = authorAccess.getDocumentController().getNodeAtOffset(offset + 1); + for (int i = 0; i < fragmentsToInsert.length; i++) { + authorAccess.getDocumentController().insertFragment(newParaNode.getEndOffset(), fragmentsToInsert[i]); + } + handleEvent = true; + } + } + } + } + return handleEvent; + } +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFSchemaManagerFilter.java b/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFSchemaManagerFilter.java new file mode 100644 index 0000000..645e4b8 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFSchemaManagerFilter.java @@ -0,0 +1,142 @@ +package simple.documentation.framework.extensions; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Stack; + +import ro.sync.contentcompletion.xml.CIAttribute; +import ro.sync.contentcompletion.xml.CIElement; +import ro.sync.contentcompletion.xml.CIValue; +import ro.sync.contentcompletion.xml.Context; +import ro.sync.contentcompletion.xml.ContextElement; +import ro.sync.contentcompletion.xml.SchemaManagerFilter; +import ro.sync.contentcompletion.xml.WhatAttributesCanGoHereContext; +import ro.sync.contentcompletion.xml.WhatElementsCanGoHereContext; +import ro.sync.contentcompletion.xml.WhatPossibleValuesHasAttributeContext; +import simple.documentation.framework.SDFElement; + +/** + * Schema manager filter for the Simple Documentation Framework. + * Filters custom elements, attributes, elements values and attributes values. + * + */ +public class SDFSchemaManagerFilter implements SchemaManagerFilter { + + /** + * Filter values of the "href" attribute of an image element. + */ + public List filterAttributeValues(List attributeValues, + WhatPossibleValuesHasAttributeContext context) { + // If the element from the current context is the "image" element and the current context + // attribute is "href" then add a custom URL value to the list of default content completion + // proposals. + if (context != null) { + ContextElement currentElement = context.getParentElement(); + String attributeName = context.getAttributeName(); + if("image".equals(currentElement.getQName()) && "href".equals(attributeName)) { + CIValue newValue = new CIValue("Custom_image_URL"); + if (attributeValues == null) { + attributeValues = new ArrayList(); + } + // Add the new value. + attributeValues.add(newValue); + } + } + return attributeValues; + } + + /** + * Filter attributes of the "table" element. + */ + public List filterAttributes(List attributes, + WhatAttributesCanGoHereContext context) { + // If the element from the current context is the 'table' element add the + // attribute named 'frame' to the list of default content completion proposals + if (context != null) { + ContextElement contextElement = context.getParentElement(); + if ("table".equals(contextElement.getQName())) { + CIAttribute frameAttribute = new CIAttribute(); + frameAttribute.setName("frame"); + frameAttribute.setRequired(false); + frameAttribute.setFixed(false); + frameAttribute.setDefaultValue("void"); + if (attributes == null) { + attributes = new ArrayList(); + } + attributes.add(frameAttribute); + } + } + return attributes; + } + + /** + * Filter the value of the "title" element from the "section". + */ + public List filterElementValues(List elementValues, + Context context) { + // If the element from the current context is the title of the section element then add the + // "Custom Section Title" value to the list of default content completion proposals + if (context != null) { + Stack elementStack = context.getElementStack(); + if (elementStack != null) { + ContextElement contextElement = elementStack.peek(); + if(contextElement != null) { + int size = elementStack.size(); + if ("title".equals(contextElement.getQName())) { + ContextElement parentElement = elementStack.elementAt(size - 2); + if (parentElement != null && "section".equals(parentElement.getQName())) { + elementValues = new ArrayList(); + CIValue val = new CIValue("Custom Section Title"); + elementValues.add(val); + } + } + } + } + } + return elementValues; + } + + /** + * Filter "header" elements. + */ + public List filterElements(List elements, + WhatElementsCanGoHereContext context) { + // If the element from the current context is the 'header' element remove the + // 'td' element from the list of content completion proposals and add the + // 'th' element. + if (context != null) { + Stack elementStack = context.getElementStack(); + if (elementStack != null) { + ContextElement contextElement = context.getElementStack().peek(); + if ("header".equals(contextElement.getQName())) { + if (elements != null) { + for (Iterator iterator = elements.iterator(); iterator.hasNext();) { + CIElement element = iterator.next(); + // Remove the 'td' element + if ("td".equals(element.getQName())) { + elements.remove(element); + break; + } + } + } else { + elements = new ArrayList(); + } + // Insert the 'th' element in the list of content completion proposals + CIElement thElement = new SDFElement(); + thElement.setName("th"); + elements.add(thElement); + } + } + } else { + // If the given context is null then the given list of content completion elements contains + // global elements. + } + return elements; + } + + public String getDescription() { + return "Implementation for the Simple Documentation Framework schema manager filter."; + } + +} \ No newline at end of file diff --git a/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFStylesFilter.java b/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFStylesFilter.java new file mode 100644 index 0000000..c34e964 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFStylesFilter.java @@ -0,0 +1,139 @@ +package simple.documentation.framework.extensions; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +import ro.sync.ecss.css.EditorContent; +import ro.sync.ecss.css.LabelContent; +import ro.sync.ecss.css.StaticContent; +import ro.sync.ecss.css.Styles; +import ro.sync.ecss.extensions.api.StylesFilter; +import ro.sync.ecss.extensions.api.editor.InplaceEditorArgumentKeys; +import ro.sync.ecss.extensions.api.node.AuthorNode; + +/** + * + * Style Filter for Simple Documentation Framework. + * + */ +public class SDFStylesFilter implements StylesFilter { + + /** + * Filter the style of "para", "b" and "i" elements. + */ + public Styles filter(Styles styles, AuthorNode authorNode) { + //Use fixed font size of 12 for "para". + if(authorNode.getName().equals("para")) { + ro.sync.exml.view.graphics.Font original = styles.getFont(); + ro.sync.exml.view.graphics.Font modified = new ro.sync.exml.view.graphics.Font( + original.getName(), original.getStyle(), 12); + styles.setProperty(Styles.KEY_FONT, modified); + } + + //Use foreground color red for "b" + if(authorNode.getName().equals("b")) { + ro.sync.exml.view.graphics.Color red = ro.sync.exml.view.graphics.Color.COLOR_RED; + styles.setProperty(Styles.KEY_FOREGROUND_COLOR, red); + } + + //Use left border width of 5 pixels for "i" + if(authorNode.getName().equals("i")) { + styles.setProperty(Styles.KEY_BORDER_LEFT_WIDTH, new Integer(5)); + } + + if(authorNode.getType() == AuthorNode.NODE_TYPE_PSEUDO_ELEMENT + && "before".equals(authorNode.getName())) { + authorNode = authorNode.getParent(); + if ("country".equals(authorNode.getName())) { + // This is the BEFORE pseudo element of the "country" element. + // Read the supported countries from the configuration file. + // This will be a comma separated enumeration: France, Spain, Great Britain + String countries = readCountriesFromFile(); + Map formControlArgs = new HashMap(); + formControlArgs.put(InplaceEditorArgumentKeys.PROPERTY_EDIT_QUALIFIED, "#text"); + formControlArgs.put(InplaceEditorArgumentKeys.PROPERTY_TYPE, InplaceEditorArgumentKeys.TYPE_COMBOBOX); + formControlArgs.put(InplaceEditorArgumentKeys.PROPERTY_VALUES, countries); + formControlArgs.put(InplaceEditorArgumentKeys.PROPERTY_EDITABLE, "false"); + + // We also add a label in form of the form control. + Map labelProps = new HashMap(); + labelProps.put("text", "Country: "); + labelProps.put("styles", "* {width: 100px; color: gray;}"); + StaticContent[] mixedContent = new StaticContent[] {new LabelContent(labelProps), new EditorContent(formControlArgs)}; + styles.setProperty(Styles.KEY_MIXED_CONTENT, mixedContent); + } + } + + // The previously added form control is the only way the element can be edited. + if ("country".equals(authorNode.getName())) { + styles.setProperty(Styles.KEY_VISIBITY, "-oxy-collapse-text"); + } + + return styles; + } + + /** + * The filter is invoked every time the styles are needed so we will make sure + * we load the countries only once. + */ + private String countries = null; + + /** + * Read the available countries from the configuration file. + * + * @return The supported countries, read from the configuration file. + * This will be a comma separated enumeration: France, Spain, Great Britain. + */ + private String readCountriesFromFile() { + if (countries == null) { + StringBuilder countriesBuilder = new StringBuilder(); + // Our countries file is located in the framework folder. To compute + // the framework location we will use the fact that the JAR this class is in + // is located inside the framework folder. + String classLocation = "/" + getClass().getCanonicalName().replace(".", "/") + ".class"; + URL resource = getClass().getResource(classLocation); + + if (resource != null) { + // The URL for the class looks like this: + // jar:file:/D:/projects/eXml/frameworks/sdf/sdf.jar!/simple/documentation/framework/extensions/SDFStylesFilter.class + String jarURL = resource.toString(); + // This is the URL of the JAR form where the class was loaded. + jarURL = jarURL.substring(jarURL.indexOf(":") + 1, jarURL.indexOf("!/")); + + try { + // We know the resources are next to the JAR. + URL resourceFile = new URL(new URL(jarURL), "resources/countries.txt"); + InputStream openStream = resourceFile.openStream(); + try { + InputStreamReader inputStreamReader = new InputStreamReader(openStream, "UTF8"); + char[] cbuf = new char[1024]; + int length = -1; + while ((length = inputStreamReader.read(cbuf)) != -1) { + countriesBuilder.append(cbuf, 0, length); + } + } finally { + openStream.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + countries = countriesBuilder.toString(); + } + + return countries; + } + + /** + * Description. + */ + public String getDescription() { + return "Implementation for the Simple Documentation Framework style filter."; + } +} + diff --git a/oxygen/js-options/src-java/simple/documentation/framework/extensions/TableCellSpanProvider.java b/oxygen/js-options/src-java/simple/documentation/framework/extensions/TableCellSpanProvider.java new file mode 100644 index 0000000..b5b4126 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/extensions/TableCellSpanProvider.java @@ -0,0 +1,74 @@ +package simple.documentation.framework.extensions; + +import ro.sync.ecss.extensions.api.AuthorTableCellSpanProvider; +import ro.sync.ecss.extensions.api.node.AttrValue; +import ro.sync.ecss.extensions.api.node.AuthorElement; + +/** + * Simple Documentation Framework table cell span provider. + * + */ +public class TableCellSpanProvider implements AuthorTableCellSpanProvider { + + /** + * Extracts the integer specifying what is the width (in columns) of the cell + * representing in the table layout the cell element. + */ + public Integer getColSpan(AuthorElement cell) { + Integer colSpan = null; + + AttrValue attrValue = cell.getAttribute("column_span"); + if(attrValue != null) { + // The attribute was found. + String cs = attrValue.getValue(); + if(cs != null) { + try { + colSpan = new Integer(cs); + } catch (NumberFormatException ex) { + // The attribute value was not a number. + } + } + } + return colSpan; + } + + /** + * Extracts the integer specifying what is the height (in rows) of the cell + * representing in the table layout the cell element. + */ + public Integer getRowSpan(AuthorElement cell) { + Integer rowSpan = null; + + AttrValue attrValue = cell.getAttribute("row_span"); + if(attrValue != null) { + // The attribute was found. + String rs = attrValue.getValue(); + if(rs != null) { + try { + rowSpan = new Integer(rs); + } catch (NumberFormatException ex) { + // The attribute value was not a number. + } + } + } + return rowSpan; + } + + /** + * @return true considering the column specifications always available. + */ + public boolean hasColumnSpecifications(AuthorElement tableElement) { + return true; + } + + /** + * Ignored. We do not extract data from the table element. + */ + public void init(AuthorElement table) { + // Nothing to do. + } + + public String getDescription() { + return "Implementation for the Simple Documentation Framework table layout."; + } +} \ No newline at end of file diff --git a/oxygen/js-options/src-java/simple/documentation/framework/extensions/TableColumnWidthProvider.java b/oxygen/js-options/src-java/simple/documentation/framework/extensions/TableColumnWidthProvider.java new file mode 100644 index 0000000..f0b5d1d --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/extensions/TableColumnWidthProvider.java @@ -0,0 +1,221 @@ +package simple.documentation.framework.extensions; +import java.util.ArrayList; +import java.util.List; + +import ro.sync.ecss.extensions.api.AuthorDocumentController; +import ro.sync.ecss.extensions.api.AuthorOperationException; +import ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider; +import ro.sync.ecss.extensions.api.WidthRepresentation; +import ro.sync.ecss.extensions.api.node.AttrValue; +import ro.sync.ecss.extensions.api.node.AuthorElement; + +/** + * Simple Documentation Framework table column width provider. + * + */ +public class TableColumnWidthProvider implements AuthorTableColumnWidthProvider { + + /** + * Cols start offset + */ + private int colsStartOffset; + + /** + * Cols end offset + */ + private int colsEndOffset; + + /** + * Column widths specifications + */ + private List colWidthSpecs = new ArrayList(); + + /** + * The table element + */ + private AuthorElement tableElement; + + /** + * @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#commitColumnWidthModifications(ro.sync.ecss.extensions.api.AuthorDocumentController, ro.sync.ecss.extensions.api.WidthRepresentation[], java.lang.String) + */ + public void commitColumnWidthModifications(AuthorDocumentController authorDocumentController, + WidthRepresentation[] colWidths, String tableCellsTagName) throws AuthorOperationException { + if ("td".equals(tableCellsTagName)) { + if (colWidths != null && tableElement != null) { + if (colsStartOffset >= 0 && colsEndOffset >= 0 && colsStartOffset < colsEndOffset) { + authorDocumentController.delete(colsStartOffset, + colsEndOffset); + } + String xmlFragment = createXMLFragment(colWidths); + int offset = -1; + AuthorElement[] header = tableElement.getElementsByLocalName("header"); + if (header != null && header.length > 0) { + // Insert the cols elements before the 'header' element + offset = header[0].getStartOffset(); + } else { + AuthorElement[] title = tableElement.getElementsByLocalName("title"); + if (title != null && title.length > 0) { + // Insert the cols elements after the 'title' element + offset = title[0].getStartOffset() + 1; + } else { + // Just insert after table start tag + offset = tableElement.getStartOffset() + 1; + } + } + if (offset == -1) { + throw new AuthorOperationException("No valid offset to insert the columns width specification."); + } + authorDocumentController.insertXMLFragment(xmlFragment, offset); + } + } + } + + /** + * Creates the XML fragment representing the column specifications. + * + * @param widthRepresentations + * @return The XML fragment as a string. + */ + private String createXMLFragment(WidthRepresentation[] widthRepresentations) { + StringBuffer fragment = new StringBuffer(); + String ns = tableElement.getNamespace(); + for (int i = 0; i < widthRepresentations.length; i++) { + WidthRepresentation width = widthRepresentations[i]; + fragment.append(" 0) { + fragment.append(" xmlns=\"" + ns + "\""); + } + fragment.append("/>"); + } + return fragment.toString(); + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#commitTableWidthModification(ro.sync.ecss.extensions.api.AuthorDocumentController, int, java.lang.String) + */ + public void commitTableWidthModification(AuthorDocumentController authorDocumentController, + int newTableWidth, String tableCellsTagName) throws AuthorOperationException { + if ("td".equals(tableCellsTagName)) { + if (newTableWidth > 0) { + if (tableElement != null) { + String newWidth = String.valueOf(newTableWidth); + + authorDocumentController.setAttribute( + "width", + new AttrValue(newWidth), + tableElement); + } else { + throw new AuthorOperationException("Cannot find the element representing the table."); + } + } + } + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#getCellWidth(ro.sync.ecss.extensions.api.node.AuthorElement, int, int) + */ + public List getCellWidth(AuthorElement cellElement, int colNumberStart, + int colSpan) { + List toReturn = null; + int size = colWidthSpecs.size(); + if (size >= colNumberStart && size >= colNumberStart + colSpan) { + toReturn = new ArrayList(colSpan); + for (int i = colNumberStart; i < colNumberStart + colSpan; i ++) { + // Add the column widths + toReturn.add(colWidthSpecs.get(i)); + } + } + return toReturn; + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#getTableWidth(java.lang.String) + */ + public WidthRepresentation getTableWidth(String tableCellsTagName) { + WidthRepresentation toReturn = null; + if (tableElement != null && "td".equals(tableCellsTagName)) { + AttrValue widthAttr = tableElement.getAttribute("width"); + if (widthAttr != null) { + String width = widthAttr.getValue(); + if (width != null) { + toReturn = new WidthRepresentation(width, true); + } + } + } + return toReturn; + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#init(ro.sync.ecss.extensions.api.node.AuthorElement) + */ + public void init(AuthorElement tableElement) { + this.tableElement = tableElement; + AuthorElement[] colChildren = tableElement.getElementsByLocalName("customcol"); + if (colChildren != null && colChildren.length > 0) { + for (int i = 0; i < colChildren.length; i++) { + AuthorElement colChild = colChildren[i]; + if (i == 0) { + colsStartOffset = colChild.getStartOffset(); + } + if (i == colChildren.length - 1) { + colsEndOffset = colChild.getEndOffset(); + } + // Determine the 'width' for this col. + AttrValue colWidthAttribute = colChild.getAttribute("width"); + String colWidth = null; + if (colWidthAttribute != null) { + colWidth = colWidthAttribute.getValue(); + // Add WidthRepresentation objects for the columns this 'customcol' specification + // spans over. + colWidthSpecs.add(new WidthRepresentation(colWidth, true)); + } + } + } + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#isAcceptingFixedColumnWidths(java.lang.String) + */ + public boolean isAcceptingFixedColumnWidths(String tableCellsTagName) { + return "td".equals(tableCellsTagName); + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#isAcceptingPercentageColumnWidths(java.lang.String) + */ + public boolean isAcceptingPercentageColumnWidths(String tableCellsTagName) { + return "td".equals(tableCellsTagName); + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#isAcceptingProportionalColumnWidths(java.lang.String) + */ + public boolean isAcceptingProportionalColumnWidths(String tableCellsTagName) { + return "td".equals(tableCellsTagName); + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#isTableAcceptingWidth(java.lang.String) + */ + public boolean isTableAcceptingWidth(String tableCellsTagName) { + return "td".equals(tableCellsTagName); + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#isTableAndColumnsResizable(java.lang.String) + */ + public boolean isTableAndColumnsResizable(String tableCellsTagName) { + return "td".equals(tableCellsTagName); + } + + /** + * @see ro.sync.ecss.extensions.api.Extension#getDescription() + */ + public String getDescription() { + return "Implementation for the Simple Documentation Framework table layout."; + } +} \ No newline at end of file diff --git a/oxygen/js-options/src-java/simple/documentation/framework/filters/SDFDocumentFilter.java b/oxygen/js-options/src-java/simple/documentation/framework/filters/SDFDocumentFilter.java new file mode 100644 index 0000000..f4c986a --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/filters/SDFDocumentFilter.java @@ -0,0 +1,104 @@ +package simple.documentation.framework.filters; + +import javax.swing.text.BadLocationException; + +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorDocumentFilter; +import ro.sync.ecss.extensions.api.AuthorDocumentFilterBypass; +import ro.sync.ecss.extensions.api.AuthorOperationException; +import ro.sync.ecss.extensions.api.node.AuthorElement; +import ro.sync.ecss.extensions.api.node.AuthorNode; + +/** + * Simple Documentation Framework document filter used to restrict insertion and deletion of + * different nodes from the document when the strict mode is activated. + * + */ +public class SDFDocumentFilter extends AuthorDocumentFilter { + + /** + * The author access. + */ + private final AuthorAccess authorAccess; + + /** + * Constructor. + * + * @param access The author access. + */ + public SDFDocumentFilter(AuthorAccess access) { + this.authorAccess = access; + } + + /** + * Check if the strict mode is activated + * @return True if the strict mode is activated. + */ + private boolean isStrictModeActivated() { + String strictMode = authorAccess.getOptionsStorage().getOption("strictMode", "false"); + return "true".equals(strictMode); + } + + /** + * Insert node filter. + */ + @Override + public boolean insertNode(AuthorDocumentFilterBypass filterBypass, + int caretOffset, AuthorNode element) { + // Restrict the insertion of the "title" element if the parent element already contains a + // title element. + if (isStrictModeActivated()) { + String restrict = "title"; + if(element instanceof AuthorElement) { + String elementName = ((AuthorElement) element).getLocalName(); + if (restrict.equals(elementName)) { + try { + AuthorNode nodeAtOffset = authorAccess.getDocumentController().getNodeAtOffset(caretOffset); + if (nodeAtOffset != null && nodeAtOffset instanceof AuthorElement) { + AuthorElement[] elements = ((AuthorElement) nodeAtOffset).getElementsByLocalName(restrict); + if (elements != null && elements.length > 0) { + AuthorElement titleChild = elements[0]; + if (titleChild != null) { + authorAccess.getWorkspaceAccess().showInformationMessage("Title already added."); + return false; + } + } + } + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + } + } + return super.insertNode(filterBypass, caretOffset, element); + } + + /** + * Insert text filter. + */ + @Override + public void insertText(AuthorDocumentFilterBypass filterBypass, int caretOffset, + String toInsert) { + super.insertText(filterBypass, caretOffset, toInsert); + // If the strict mode is activated and the element where the text is inserted is the "content" + // element then surround the inserted text into a "para" element. + if (isStrictModeActivated()) { + try { + AuthorNode nodeAtOffset = authorAccess.getDocumentController().getNodeAtOffset(caretOffset); + if (nodeAtOffset != null && nodeAtOffset instanceof AuthorElement) { + if ("content".equals(((AuthorElement)nodeAtOffset).getLocalName())) { + try { + filterBypass.surroundInFragment("", caretOffset, caretOffset + toInsert.length() - 1); + authorAccess.getEditorAccess().setCaretPosition(caretOffset + toInsert.length() + 1); + } catch (AuthorOperationException e) { + e.printStackTrace(); + } + } + } + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + + } +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/listeners/SDFAuthorCaretListener.java b/oxygen/js-options/src-java/simple/documentation/framework/listeners/SDFAuthorCaretListener.java new file mode 100644 index 0000000..91090e4 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/listeners/SDFAuthorCaretListener.java @@ -0,0 +1,76 @@ +package simple.documentation.framework.listeners; + +import javax.swing.text.BadLocationException; + +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorCaretEvent; +import ro.sync.ecss.extensions.api.AuthorCaretListener; +import ro.sync.ecss.extensions.api.node.AuthorDocumentFragment; +import ro.sync.ecss.extensions.api.node.AuthorElement; +import ro.sync.ecss.extensions.api.node.AuthorNode; +import ro.sync.exml.view.graphics.Rectangle; +import ro.sync.exml.workspace.api.Platform; +import simple.documentation.framework.ui.SDFPopupWindow; + +/** + * Author caret listener used to display the XML fragment corresponding to a para element + * found at the caret position. + * + */ +public class SDFAuthorCaretListener implements AuthorCaretListener { + + /** + * Access to the author specific functions. + */ + private AuthorAccess authorAcess; + + /** + * The popup used to display the XML fragment. + */ + private SDFPopupWindow popupWindow; + + /** + * Constructor. + * + * @param authorAccess Access to the author specific functions. + */ + public SDFAuthorCaretListener(AuthorAccess authorAccess) { + this.authorAcess = authorAccess; + // Use the information popup only if this is the standalone Oxygen version. + if (authorAccess.getWorkspaceAccess().getPlatform() == Platform.STANDALONE) { + // Create the information popup window. + popupWindow = new SDFPopupWindow(authorAccess, "XML Fragment:"); + } + } + + /** + * Caret moved. + * Display the XML fragment corresponding to a para element found at the caret + * position in a popup window. + */ + public void caretMoved(AuthorCaretEvent caretEvent) { + // Verify if the node corresponding to the new caret position corresponds to a "para" element. + if (authorAcess.getWorkspaceAccess().getPlatform() == Platform.STANDALONE) { + int caretOffset = authorAcess.getEditorAccess().getCaretOffset(); + try { + AuthorNode nodeAtOffset = authorAcess.getDocumentController().getNodeAtOffset(caretOffset); + if (nodeAtOffset != null && nodeAtOffset instanceof AuthorElement) { + AuthorElement element = (AuthorElement) nodeAtOffset; + if ("para".equals(element.getLocalName())) { + AuthorDocumentFragment paraFragment = authorAcess.getDocumentController().createDocumentFragment(element, true); + String serializeFragmentToXML = authorAcess.getDocumentController().serializeFragmentToXML(paraFragment); + // Find the x and y coordinates from the caret shape (the popup window location). + Rectangle modelToView = authorAcess.getEditorAccess().modelToViewRectangle(authorAcess.getEditorAccess().getCaretOffset()); + popupWindow.setTimeToDisplay(3); + popupWindow.display( + serializeFragmentToXML, + modelToView.x + modelToView.width, + modelToView.y + modelToView.height, + 10); + } + } + } catch (BadLocationException e) { + } + } + } +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/listeners/SDFAuthorListener.java b/oxygen/js-options/src-java/simple/documentation/framework/listeners/SDFAuthorListener.java new file mode 100644 index 0000000..e955b24 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/listeners/SDFAuthorListener.java @@ -0,0 +1,130 @@ +package simple.documentation.framework.listeners; + +import java.util.List; + +import ro.sync.ecss.extensions.api.AttributeChangedEvent; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorListenerAdapter; +import ro.sync.ecss.extensions.api.DocumentContentDeletedEvent; +import ro.sync.ecss.extensions.api.DocumentContentInsertedEvent; +import ro.sync.ecss.extensions.api.node.AuthorDocumentFragment; +import ro.sync.ecss.extensions.api.node.AuthorElement; +import ro.sync.ecss.extensions.api.node.AuthorNode; + +/** + * Simple Documentation Framework Author listener. + */ +public class SDFAuthorListener extends AuthorListenerAdapter { + + /** + * Access to the author specific functions. + */ + private AuthorAccess authorAccess; + + /** + * Constructor. + * + * @param authorAccess Access to the author specific functions + */ + public SDFAuthorListener(AuthorAccess authorAccess) { + this.authorAccess = authorAccess; + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorListenerAdapter#attributeChanged(ro.sync.ecss.extensions.api.AttributeChangedEvent) + */ + @Override + public void attributeChanged(AttributeChangedEvent e) { + String strictMode = authorAccess.getOptionsStorage().getOption("strictMode", "false"); + if ("true".equals(strictMode)) { + // If the changed attribute is the "column_span" or "row_span" attribute of the "td" + // element then verify if the new value is an integer value + AuthorNode ownerAuthorNode = e.getOwnerAuthorNode(); + if (ownerAuthorNode instanceof AuthorElement) { + AuthorElement ownerAuthorElement = (AuthorElement) ownerAuthorNode; + if ("td".equals(ownerAuthorElement.getLocalName())) { + String attributeName = e.getAttributeName(); + if ("column_span".equals(attributeName) || "row_span".equals(attributeName)) { + String spanValue = ownerAuthorElement.getAttribute(attributeName).getValue(); + try { + Integer.parseInt(spanValue); + } catch (NumberFormatException ex) { + authorAccess.getWorkspaceAccess().showInformationMessage("The value " + spanValue + " of attribute " + attributeName + + " is not valid."); + } + } + } + } + } + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorListenerAdapter#beforeContentDelete(ro.sync.ecss.extensions.api.DocumentContentDeletedEvent) + */ + @Override + public void beforeContentDelete(DocumentContentDeletedEvent deleteEvent) { + String strictMode = authorAccess.getOptionsStorage().getOption("strictMode", "false"); + if ("true".equals(strictMode)) { + // If the section title is deleted an error message will inform the user that the title + // is required. + if (deleteEvent.getType() != DocumentContentDeletedEvent.INSERT_TEXT_EVENT + && deleteEvent.getType() != DocumentContentDeletedEvent.DELETE_TEXT_EVENT) { + AuthorNode changedNode = deleteEvent.getParentNode(); + if (changedNode instanceof AuthorElement) { + AuthorElement changedElement = (AuthorElement) changedNode; + // Section element + if ("section".equals(changedElement.getLocalName())) { + AuthorElement[] titleElements = changedElement.getElementsByLocalName("title"); + // If the section has one "title" child element + if (titleElements.length == 1) { + // Find if the deleted element is the "title" one. + AuthorDocumentFragment deletedFragment = deleteEvent.getDeletedFragment(); + List contentNodes = deletedFragment.getContentNodes(); + for (AuthorNode authorNode : contentNodes) { + if (authorNode instanceof AuthorElement) { + if ("title".equals(((AuthorElement)authorNode).getLocalName())) { + String errorMessage = "The section must have a title."; + authorAccess.getWorkspaceAccess().showErrorMessage(errorMessage); + } + } + } + } + } + } + } + } + super.beforeContentDelete(deleteEvent); + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorListenerAdapter#contentInserted(ro.sync.ecss.extensions.api.DocumentContentInsertedEvent) + */ + @Override + public void contentInserted(DocumentContentInsertedEvent e) { + AuthorNode node = e.getParentNode(); + AuthorNode parentNode = node.getParent(); + // For 'section' nodes the title text is rendered in the Outline + // (see customizeRenderingInformation method from SDFAuthorOutlineCustomizer) + // so we need to refresh the section node from the Outline when the title + // text has changed. + if ("title".equals(node.getName()) && "section".equals(parentNode.getName())) { + authorAccess.getOutlineAccess().refreshNodes(new AuthorNode[] {parentNode}); + } + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorListenerAdapter#contentDeleted(ro.sync.ecss.extensions.api.DocumentContentDeletedEvent) + */ + @Override + public void contentDeleted(DocumentContentDeletedEvent e) { + AuthorNode node = e.getParentNode(); + AuthorNode parentNode = node.getParent(); + // For 'section' nodes the title text is rendered in the Outline + // (see customizeRenderingInformation method from SDFAuthorOutlineCustomizer) + // so we need to refresh the section node from the Outline when the title + // text has changed. + if ("title".equals(node.getName()) && "section".equals(parentNode.getName())) { + authorAccess.getOutlineAccess().refreshNodes(new AuthorNode[] {parentNode}); + } + } +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/listeners/SDFAuthorMouseListener.java b/oxygen/js-options/src-java/simple/documentation/framework/listeners/SDFAuthorMouseListener.java new file mode 100644 index 0000000..9db5685 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/listeners/SDFAuthorMouseListener.java @@ -0,0 +1,62 @@ +package simple.documentation.framework.listeners; + +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorMouseEvent; +import ro.sync.ecss.extensions.api.AuthorMouseListener; +import ro.sync.exml.workspace.api.Platform; +import simple.documentation.framework.ui.SDFPopupWindow; + +/** + * Custom author mouse listener used to display the mouse coordinates in a popup window + * on mouse clicked. + * + */ +public class SDFAuthorMouseListener implements AuthorMouseListener { + + /** + * Access to the author specific functions. + */ + private AuthorAccess authorAccess; + + /** + * The popup used to display the mouse coordinates. + */ + private SDFPopupWindow popupWindow; + + /** + * Constructor. + * + * @param authorAccess Access to the author specific functions + */ + public SDFAuthorMouseListener(AuthorAccess authorAccess) { + this.authorAccess = authorAccess; + // Use the information popup only if this is the standalone Oxygen version. + if (authorAccess.getWorkspaceAccess().getPlatform() == Platform.STANDALONE) { + popupWindow = new SDFPopupWindow(authorAccess, "Position"); + } + } + + + public void mouseClicked(AuthorMouseEvent e) { + // Display the mouse coordinates. + if (authorAccess.getWorkspaceAccess().getPlatform() == Platform.STANDALONE) { + if (e.clickCount == 2) { + String toDisplay = "X: " + e.X + " Y: " + e.Y; + popupWindow.setTimeToDisplay(2); + popupWindow.display(toDisplay, e.X, e.Y, 10); + } + } + } + + public void mousePressed(AuthorMouseEvent e) { + } + + public void mouseReleased(AuthorMouseEvent e) { + } + + public void mouseDragged(AuthorMouseEvent e) { + } + + public void mouseMoved(AuthorMouseEvent e) { + } +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/ExtractNodeToFileOperation.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/ExtractNodeToFileOperation.java new file mode 100644 index 0000000..e01f2a2 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/ExtractNodeToFileOperation.java @@ -0,0 +1,76 @@ +package simple.documentation.framework.operations; + +import java.awt.Component; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.net.URL; + +import javax.swing.JFileChooser; + +import ro.sync.ecss.extensions.api.ArgumentDescriptor; +import ro.sync.ecss.extensions.api.ArgumentsMap; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorOperation; +import ro.sync.ecss.extensions.api.AuthorOperationException; +import ro.sync.ecss.extensions.api.node.AuthorDocumentFragment; +import ro.sync.ecss.extensions.api.node.AuthorNode; +import ro.sync.basic.util.URLUtil; + +/** + * Operation to save the Author node at caret in a separate document and refresh the new file path in the project. + */ +public class ExtractNodeToFileOperation implements AuthorOperation { + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#doOperation(ro.sync.ecss.extensions.api.AuthorAccess, ro.sync.ecss.extensions.api.ArgumentsMap) + */ + public void doOperation(AuthorAccess authorAccess, ArgumentsMap args) throws IllegalArgumentException, + AuthorOperationException { + int caretOffset = authorAccess.getEditorAccess().getCaretOffset(); + try { + // Get node at caret + AuthorNode nodeAtCaret = authorAccess.getDocumentController().getNodeAtOffset(caretOffset); + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setMultiSelectionEnabled(false); + + // Show Save Dialog + if (fileChooser.showSaveDialog((Component) authorAccess.getWorkspaceAccess().getParentFrame()) + == JFileChooser.APPROVE_OPTION) { + File outputFile = fileChooser.getSelectedFile(); + FileOutputStream fos = new FileOutputStream(outputFile); + OutputStreamWriter writer = new OutputStreamWriter(fos, "UTF-8"); + + // Write the node fragment + AuthorDocumentFragment fragment = authorAccess.getDocumentController().createDocumentFragment(nodeAtCaret, true); + String xmlFragment = authorAccess.getDocumentController().serializeFragmentToXML(fragment); + writer.write(xmlFragment); + writer.close(); + + // Open file + URL outputFileUrl = URLUtil.correct(outputFile); + authorAccess.getWorkspaceAccess().open(outputFileUrl); + + // Refresh in project + authorAccess.getWorkspaceAccess().refreshInProject(outputFileUrl); + } + } catch (Exception e) { + authorAccess.getWorkspaceAccess().showErrorMessage(e.getMessage()); + } + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#getArguments() + */ + public ArgumentDescriptor[] getArguments() { + return null; + } + + /** + * @see ro.sync.ecss.extensions.api.Extension#getDescription() + */ + public String getDescription() { + return "Save the Author node at caret in a separate document and refresh the new file path in the project"; + } + +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/InsertElementOperation.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/InsertElementOperation.java new file mode 100644 index 0000000..760e69f --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/InsertElementOperation.java @@ -0,0 +1,127 @@ +package simple.documentation.framework.operations; + +import java.awt.Component; +import java.awt.Cursor; +import java.awt.event.ActionEvent; +import java.util.List; + +import javax.swing.AbstractAction; +import javax.swing.JFrame; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import javax.swing.text.BadLocationException; + +import ro.sync.contentcompletion.xml.CIElement; +import ro.sync.contentcompletion.xml.WhatElementsCanGoHereContext; +import ro.sync.ecss.extensions.api.ArgumentDescriptor; +import ro.sync.ecss.extensions.api.ArgumentsMap; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorOperation; +import ro.sync.ecss.extensions.api.AuthorOperationException; +import ro.sync.ecss.extensions.api.AuthorSchemaManager; +import ro.sync.ecss.extensions.api.node.AuthorDocumentFragment; +import ro.sync.exml.view.graphics.Point; +import ro.sync.exml.view.graphics.Rectangle; + +/** + * Show a popup menu that contains the name of all elements that can be inserted at the caret offset. + */ +public class InsertElementOperation implements AuthorOperation { + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#doOperation(ro.sync.ecss.extensions.api.AuthorAccess, ro.sync.ecss.extensions.api.ArgumentsMap) + */ + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#doOperation(ro.sync.ecss.extensions.api.AuthorAccess, ro.sync.ecss.extensions.api.ArgumentsMap) + */ + @SuppressWarnings("serial") + public void doOperation(final AuthorAccess authorAccess, ArgumentsMap args) + throws IllegalArgumentException, AuthorOperationException { + try { + //Get the caret offset + final int caretOffset = authorAccess.getEditorAccess().getCaretOffset(); + //The schema manager + final AuthorSchemaManager schemaManager = authorAccess.getDocumentController().getAuthorSchemaManager(); + //The context of elements. + WhatElementsCanGoHereContext ctxt = schemaManager.createWhatElementsCanGoHereContext(caretOffset); + //Get the list of elements which can be inserted here + final List childrenElements = schemaManager.getChildrenElements(ctxt); + JPopupMenu jpo = new JPopupMenu(); + for (int i = 0; i < childrenElements.size(); i++) { + final int index = i; + jpo.add(new JMenuItem(new AbstractAction(childrenElements.get(index).getName()) { + public void actionPerformed(ActionEvent e) { + CIElement toInsert = childrenElements.get(index); + try { + //The CIElement contains all data necessary to make a small XML fragment with + //the string to insert and then call + // authorAccess.getDocumentController().createNewDocumentFragmentInContext(xmlFragment, caretOffset); + //But Oxygen 11.2 will come with a easier method: + //Create a document fragment from the CIElement. + AuthorDocumentFragment frag = schemaManager.createAuthorDocumentFragment(toInsert); + //Now you can process the fragment and remove/add attributes. + authorAccess.getDocumentController().insertFragment(caretOffset, frag); + } catch (BadLocationException e1) { + e1.printStackTrace(); + } + } + })); + } + Rectangle mtv = authorAccess.getEditorAccess().modelToViewRectangle(caretOffset); + Point popupLocation = authorAccess.getEditorAccess().getLocationOnScreenAsPoint(mtv.x, mtv.y); + jpo.setLocation(popupLocation.x, popupLocation.y); + JFrame oxygenFrame = (JFrame)authorAccess.getWorkspaceAccess().getParentFrame(); + + // Get the author component + JPanel authorComponent = (JPanel)authorAccess.getEditorAccess().getAuthorComponent(); + // Get the glass pane + final Component glassPane = authorComponent.getRootPane().getGlassPane(); + if (glassPane != null) { + glassPane.setVisible(true); + // Set wait cursor + glassPane.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + } + + // Show popup menu + jpo.show(oxygenFrame, popupLocation.x - oxygenFrame.getLocation().x, popupLocation.y - oxygenFrame.getLocation().y); + + // Add a popup menu listener + jpo.addPopupMenuListener(new PopupMenuListener() { + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + } + + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + // Reset cursor to default + if (glassPane != null) { + glassPane.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + glassPane.setVisible(false); + } + } + + public void popupMenuCanceled(PopupMenuEvent e) { + } + }); + + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#getArguments() + */ + public ArgumentDescriptor[] getArguments() { + return null; + } + + /** + * @see ro.sync.ecss.extensions.api.Extension#getDescription() + */ + public String getDescription() { + return "Insert element at the caret position."; + } + +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/InsertImageOperation.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/InsertImageOperation.java new file mode 100644 index 0000000..81447f8 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/InsertImageOperation.java @@ -0,0 +1,213 @@ +package simple.documentation.framework.operations; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.net.MalformedURLException; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.filechooser.FileFilter; + +import ro.sync.ecss.extensions.api.ArgumentDescriptor; +import ro.sync.ecss.extensions.api.ArgumentsMap; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorOperation; +import ro.sync.ecss.extensions.api.AuthorOperationException; + + +/** + * Insert image operation. + */ +public class InsertImageOperation implements AuthorOperation { + + // + // Implementing the Author Operation Interface. + // + + /** + * Performs the operation. + */ + public void doOperation(AuthorAccess authorAccess, + ArgumentsMap arguments) + throws IllegalArgumentException, + AuthorOperationException { + + JFrame oxygenFrame = (JFrame) authorAccess.getWorkspaceAccess().getParentFrame(); + String href = displayURLDialog(oxygenFrame); + if (href.length() != 0) { + // Creates the image XML fragment. + String imageFragment = + ""; + + // Inserts this fragment at the caret position. + int caretPosition = authorAccess.getEditorAccess().getCaretOffset(); + authorAccess.getDocumentController().insertXMLFragment(imageFragment, caretPosition); + } + } + + /** + * Has no arguments. + * + * @return null. + */ + public ArgumentDescriptor[] getArguments() { + return null; + } + + /** + * @return A description of the operation. + */ + public String getDescription() { + return "Inserts an image element. Asks the" + + " user for a URL reference."; + } + + // + // End of interface implementation. + // + + // + // Auxiliary methods. + // + + /** + * Displays the URL dialog. + * + * @param parentFrame The parent frame for + * the dialog. + * @return The selected URL string value, + * or the empty string if the user canceled + * the URL selection. + */ + private String displayURLDialog(JFrame parentFrame) { + + final JDialog dlg = new JDialog(parentFrame, + "Enter the value for the href attribute", true); + JPanel mainContent = new JPanel(new GridBagLayout()); + + // The text field. + GridBagConstraints cstr = new GridBagConstraints(); + cstr.gridx = 0; + cstr.gridy = 0; + cstr.weightx = 0; + cstr.gridwidth = 1; + cstr.fill = GridBagConstraints.HORIZONTAL; + mainContent.add(new JLabel("Image URI:"), cstr); + + cstr.gridx = 1; + cstr.weightx = 1; + final JTextField urlField = new JTextField(); + urlField.setColumns(15); + mainContent.add(urlField, cstr); + + // Add the "Browse button." + cstr.gridx = 2; + cstr.weightx = 0; + JButton browseButton = new JButton("Browse"); + browseButton.addActionListener(new ActionListener() { + + /** + * Shows a file chooser dialog. + */ + public void actionPerformed(ActionEvent e) { + JFileChooser fileChooser = new JFileChooser(); + + fileChooser.setMultiSelectionEnabled(false); + // Accepts only the image files. + fileChooser.setFileFilter(new FileFilter() { + @Override + public String getDescription() { + return "Image files"; + } + + @Override + public boolean accept(File f) { + String fileName = f.getName(); + return f.isFile() && + ( fileName.endsWith(".jpeg") + || fileName.endsWith(".jpg") + || fileName.endsWith(".gif") + || fileName.endsWith(".png") + || fileName.endsWith(".svg")); + } + }); + if (fileChooser.showOpenDialog(dlg) + == JFileChooser.APPROVE_OPTION) { + File file = fileChooser.getSelectedFile(); + try { + // Set the file into the text field. + urlField.setText(file.toURI().toURL().toString()); + } catch (MalformedURLException ex) { + // This should not happen. + ex.printStackTrace(); + } + } + } + }); + mainContent.add(browseButton, cstr); + + // Add the "Ok" button to the layout. + cstr.gridx = 0; + cstr.gridy = 1; + cstr.weightx = 0; + JButton okButton = new JButton("Ok"); + okButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + dlg.setVisible(false); + } + }); + mainContent.add(okButton, cstr); + mainContent.setBorder( + BorderFactory.createEmptyBorder(10, 5, 10, 5)); + + // Add the "Cancel" button to the layout. + cstr.gridx = 2; + JButton cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + urlField.setText(""); + dlg.setVisible(false); + } + }); + mainContent.add(cancelButton, cstr); + + // When the user closes the dialog + // from the window decoration, + // assume "Cancel" action. + dlg.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + urlField.setText(""); + } + }); + + dlg.getContentPane().add(mainContent); + dlg.pack(); + dlg.setLocationRelativeTo(parentFrame); + dlg.setVisible(true); + return urlField.getText(); + } + + /** + * Test method. + * + * @param args The arguments are ignored. + */ + public static void main(String[] args) { + InsertImageOperation operation = + new InsertImageOperation(); + System.out.println("Choosen URL: " + + operation.displayURLDialog(new JFrame())); + } +} \ No newline at end of file diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/OpenInNewEditor.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/OpenInNewEditor.java new file mode 100644 index 0000000..8303849 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/OpenInNewEditor.java @@ -0,0 +1,39 @@ +package simple.documentation.framework.operations; + +import ro.sync.ecss.extensions.api.ArgumentDescriptor; +import ro.sync.ecss.extensions.api.ArgumentsMap; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorOperation; +import ro.sync.ecss.extensions.api.AuthorOperationException; + +/** + * Open the selected text in a new TXT editor. + */ +public class OpenInNewEditor implements AuthorOperation { + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#doOperation(ro.sync.ecss.extensions.api.AuthorAccess, ro.sync.ecss.extensions.api.ArgumentsMap) + */ + public void doOperation(AuthorAccess authorAccess, ArgumentsMap arg1) throws IllegalArgumentException, + AuthorOperationException { + String selectedText = authorAccess.getEditorAccess().getSelectedText(); + // Open in new editor + authorAccess.getWorkspaceAccess().createNewEditor("txt", null, selectedText); + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#getArguments() + */ + public ArgumentDescriptor[] getArguments() { + // No arguments + return null; + } + + /** + * @see ro.sync.ecss.extensions.api.Extension#getDescription() + */ + public String getDescription() { + return "Open new editor"; + } + +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/QueryDatabaseOperation.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/QueryDatabaseOperation.java new file mode 100644 index 0000000..6a5b4f9 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/QueryDatabaseOperation.java @@ -0,0 +1,196 @@ +package simple.documentation.framework.operations; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.Properties; + +import ro.sync.ecss.extensions.api.ArgumentDescriptor; +import ro.sync.ecss.extensions.api.ArgumentsMap; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorOperation; +import ro.sync.ecss.extensions.api.AuthorOperationException; + +/** + * Query DB operation. + */ +public class QueryDatabaseOperation implements AuthorOperation{ + private static String ARG_JDBC_DRIVER ="jdbc_driver"; + private static String ARG_USER ="user"; + private static String ARG_PASSWORD ="password"; + private static String ARG_SQL ="sql"; + private static String ARG_CONNECTION ="connection"; + + /** + * @return The array of arguments the developer must specify when + * configuring the action. + */ + public ArgumentDescriptor[] getArguments() { + ArgumentDescriptor args[] = new ArgumentDescriptor[] { + new ArgumentDescriptor( + ARG_JDBC_DRIVER, + ArgumentDescriptor.TYPE_STRING, + "The name of the Java class that is the JDBC driver."), + new ArgumentDescriptor( + ARG_CONNECTION, + ArgumentDescriptor.TYPE_STRING, + "The database URL connection string."), + new ArgumentDescriptor( + ARG_USER, + ArgumentDescriptor.TYPE_STRING, + "The name of the database user."), + new ArgumentDescriptor( + ARG_PASSWORD, + ArgumentDescriptor.TYPE_STRING, + "The database password."), + new ArgumentDescriptor( + ARG_SQL, + ArgumentDescriptor.TYPE_STRING, + "The SQL statement to be executed.") + }; + return args; + } + + /** + * @return The operation description. + */ + public String getDescription() { + return "Executes a database query and puts the result in a table."; + } + + public void doOperation(AuthorAccess authorAccess, ArgumentsMap map) + throws IllegalArgumentException, AuthorOperationException { + // Collects the arguments. + String jdbcDriver = + (String)map.getArgumentValue(ARG_JDBC_DRIVER); + String connection = + (String)map.getArgumentValue(ARG_CONNECTION); + String user = + (String)map.getArgumentValue(ARG_USER); + String password = + (String)map.getArgumentValue(ARG_PASSWORD); + String sql = + (String)map.getArgumentValue(ARG_SQL); + int caretPosition = authorAccess.getEditorAccess().getCaretOffset(); + try { + if (jdbcDriver == null) { + throw new AuthorOperationException("No jdbc driver provided."); + } + if (connection == null) { + throw new AuthorOperationException("No connection provided."); + } + if (user == null) { + throw new AuthorOperationException("No user provided."); + } + if (sql == null) { + throw new AuthorOperationException("No sql provided."); + } + authorAccess.getDocumentController().insertXMLFragment( + getFragment(jdbcDriver, connection, user, password, sql), + caretPosition); + } catch (SQLException e) { + throw new AuthorOperationException( + "The operation failed due to the following database error: " + + e.getMessage(), e); + } catch (ClassNotFoundException e) { + throw new AuthorOperationException( + "The JDBC database driver was not found. Tried to load ' " + + jdbcDriver + "'", e); + } + } + + /** + * Creates a connection to the database, executes + * the SQL statement and creates an XML fragment + * containing a table element that wraps the data + * from the result set. + * + * + * @param jdbcDriver The class name of the JDBC driver. + * @param connectionURL The connection URL. + * @param user The database user. + * @param password The password. + * @param sql The SQL statement. + * @return The string containing the XML fragment. + * + * @throws SQLException thrown when there is a + * problem accessing the database or there are + * erors in the SQL expression. + * @throws ClassNotFoundException when the JDBC + * driver class could not be loaded. + */ + private String getFragment( + String jdbcDriver, + String connectionURL, + String user, + String password, + String sql) throws + SQLException, + ClassNotFoundException { + Properties pr = new Properties(); + pr.put("characterEncoding", "UTF8"); + pr.put("useUnicode", "TRUE"); + pr.put("user", user); + pr.put("password", password); + // Loads the database driver. + Class.forName(jdbcDriver); + // Opens the connection + Connection connection = + DriverManager.getConnection(connectionURL, pr); + java.sql.Statement statement = + connection.createStatement(); + ResultSet resultSet = + statement.executeQuery(sql); + StringBuffer fragmentBuffer = new StringBuffer(); + fragmentBuffer.append( + ""); + // + // Creates the table header. + // + fragmentBuffer.append("
"); + ResultSetMetaData metaData = resultSet.getMetaData(); + int columnCount = metaData.getColumnCount(); + for (int i = 1; i <= columnCount; i++) { + fragmentBuffer.append("
"); + } + fragmentBuffer.append(""); + // + // Creates the table content. + // + while (resultSet.next()) { + fragmentBuffer.append(""); + for (int i = 1; i <= columnCount; i++) { + fragmentBuffer.append(""); + } + fragmentBuffer.append(""); + } + fragmentBuffer.append("
"); + fragmentBuffer.append( + xmlEscape(metaData.getColumnName(i))); + fragmentBuffer.append("
"); + fragmentBuffer.append( + xmlEscape(resultSet.getObject(i))); + fragmentBuffer.append("
"); + // Cleanup + resultSet.close(); + statement.close(); + connection.close(); + return fragmentBuffer.toString(); + } + + /** + * Some of the values from the database table + * may contain characters that must be escaped + * in XML, to ensure the fragment is well formed. + * + * @param object The object from the database. + * @return The escaped string representation. + */ + private String xmlEscape(Object object) { + String str = String.valueOf(object); + return str. + replaceAll("&", "&"). + replaceAll("<", "<"); + } +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/SDFRefreshCSSOperation.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/SDFRefreshCSSOperation.java new file mode 100644 index 0000000..93fc555 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/SDFRefreshCSSOperation.java @@ -0,0 +1,38 @@ +package simple.documentation.framework.operations; + +import ro.sync.ecss.extensions.api.ArgumentDescriptor; +import ro.sync.ecss.extensions.api.ArgumentsMap; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorOperation; +import ro.sync.ecss.extensions.api.AuthorOperationException; +import ro.sync.ecss.extensions.api.access.AuthorEditorAccess; + +/** + * Refresh CSS operation. + * + */ +public class SDFRefreshCSSOperation implements AuthorOperation { + + public void doOperation(AuthorAccess authorAccess, ArgumentsMap args) + throws IllegalArgumentException, AuthorOperationException { + AuthorEditorAccess access = authorAccess.getEditorAccess(); + // Reload the CSS files and perform a refresh on the whole document to recompute + // the layout + access.refresh(); + } + + /** + * Arguments. + */ + public ArgumentDescriptor[] getArguments() { + // No arguments + return null; + } + + /** + * Description. + */ + public String getDescription() { + return "Refresh CSS operation for Simple Documentation Framework"; + } +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/SDFShowFileStatusOperation.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/SDFShowFileStatusOperation.java new file mode 100644 index 0000000..585cfca --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/SDFShowFileStatusOperation.java @@ -0,0 +1,45 @@ +package simple.documentation.framework.operations; + +import ro.sync.ecss.extensions.api.ArgumentDescriptor; +import ro.sync.ecss.extensions.api.ArgumentsMap; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorOperation; +import ro.sync.ecss.extensions.api.AuthorOperationException; + +/** + * Show file status operation. + * + */ +public class SDFShowFileStatusOperation implements AuthorOperation { + + public void doOperation(AuthorAccess authorAccess, ArgumentsMap args) + throws IllegalArgumentException, AuthorOperationException { + // Build the file status message. + StringBuilder message = new StringBuilder(); + // Editor location + message.append("Location: " + authorAccess.getEditorAccess().getEditorLocation() + "\n"); + // Determine if the document from the editor contains unsaved modifications. + message.append("Modified: " + authorAccess.getEditorAccess().isModified() + "\n"); + // Determine if the document from the editor was ever saved. + message.append("Untitled: " + authorAccess.getEditorAccess().isNewDocument()); + + // Show the informations about the file status + authorAccess.getWorkspaceAccess().showInformationMessage(message.toString()); + } + + /** + * Arguments. + */ + public ArgumentDescriptor[] getArguments() { + // No arguments + return null; + } + + /** + * Description + */ + public String getDescription() { + return "Show the status of the current file"; + } + +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/SDFStrictModeOperation.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/SDFStrictModeOperation.java new file mode 100644 index 0000000..cb73acf --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/SDFStrictModeOperation.java @@ -0,0 +1,54 @@ +package simple.documentation.framework.operations; + +import ro.sync.ecss.extensions.api.ArgumentDescriptor; +import ro.sync.ecss.extensions.api.ArgumentsMap; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorOperation; +import ro.sync.ecss.extensions.api.AuthorOperationException; + +/** + * + * Strict Mode Operation used to change the permissions to change parts of the document content. + * + */ +public class SDFStrictModeOperation implements AuthorOperation { + + // The strict mode key used to store the strict mode option. + private String strictModeOptionKey = "strictMode"; + + /** + * The Strict mode has changed. + */ + public void doOperation(final AuthorAccess authorAccess, ArgumentsMap args) + throws IllegalArgumentException, AuthorOperationException { + + // Get the strict mode option value from the option storage. + String strictMode = authorAccess.getOptionsStorage().getOption(strictModeOptionKey, "false"); + boolean enabled = Boolean.parseBoolean(strictMode); + + // Change the strict mode option state + enabled = !enabled; + + // Save the new value of the strict mode option + authorAccess.getOptionsStorage().setOption(strictModeOptionKey, String.valueOf(enabled)); + + // Show the strict mode operation status. + String statusMessage = "Strict Mode: " + (enabled ? " ON " : "OFF"); + authorAccess.getWorkspaceAccess().showStatusMessage(statusMessage); + } + + /** + * Arguments. + */ + public ArgumentDescriptor[] getArguments() { + // No arguments + return null; + } + + /** + * Description + */ + public String getDescription() { + return "Strict mode operation"; + } +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/SelectNodesInOutlinerOperation.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/SelectNodesInOutlinerOperation.java new file mode 100644 index 0000000..defe368 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/SelectNodesInOutlinerOperation.java @@ -0,0 +1,103 @@ +package simple.documentation.framework.operations; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.text.BadLocationException; +import javax.swing.tree.TreePath; + +import ro.sync.ecss.extensions.api.ArgumentDescriptor; +import ro.sync.ecss.extensions.api.ArgumentsMap; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorOperation; +import ro.sync.ecss.extensions.api.AuthorOperationException; +import ro.sync.ecss.extensions.api.node.AuthorDocument; +import ro.sync.ecss.extensions.api.node.AuthorElement; +import ro.sync.ecss.extensions.api.node.AuthorNode; +import ro.sync.ecss.extensions.api.structure.AuthorOutlineCustomizer; +import simple.documentation.framework.extensions.SDFAuthorOutlineCustomizer; + +/** + * Select in the Outline the children nodes of the node at caret. + */ +public class SelectNodesInOutlinerOperation implements AuthorOperation { + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#doOperation(ro.sync.ecss.extensions.api.AuthorAccess, ro.sync.ecss.extensions.api.ArgumentsMap) + */ + public void doOperation(AuthorAccess authorAccess, ArgumentsMap args) + throws IllegalArgumentException, AuthorOperationException { + // Renderer customizer used to find if a given node is filtered in the Outline. + AuthorOutlineCustomizer rendererCustomizer = new SDFAuthorOutlineCustomizer(); + + int caretOffset = authorAccess.getEditorAccess().getCaretOffset(); + try { + // Node at caret position + AuthorNode currentNode = authorAccess.getDocumentController().getNodeAtOffset(caretOffset); + if (currentNode instanceof AuthorElement) { + AuthorElement currentElement = (AuthorElement)currentNode; + // Find the content nodes + List contentNodes = currentElement.getContentNodes(); + List selectTreePaths = new ArrayList(); + LinkedList reversedPath = findReversedPath(authorAccess, rendererCustomizer, + currentNode); + + for (AuthorNode authorNode : contentNodes) { + if (!rendererCustomizer.ignoreNode(authorNode)) { + LinkedList pathList = new LinkedList(reversedPath); + pathList.add(authorNode); + // Add the children tree path in the selected tree paths list + selectTreePaths.add(new TreePath(pathList.toArray(new AuthorNode[0]))); + } + } + + // Select the children tree paths in the Outline + authorAccess.getOutlineAccess().setSelectionPaths(selectTreePaths.toArray(new TreePath[0])); + } + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#getArguments() + */ + public ArgumentDescriptor[] getArguments() { + return null; + } + + /** + * @see ro.sync.ecss.extensions.api.Extension#getDescription() + */ + public String getDescription() { + return "Select nodes in the Outliner"; + } + + /** + * Builds the list of node parents up to and including the root node, where the + * original node is the last element in the returned array. + * + * @param authorAccess The author access. + * @param rendererCustomizer Renderer customizer used to find if a given node + * is filtered in the Outline. + * @param node The node to reverse path for. + * @return The path nodes list. + */ + private LinkedList findReversedPath(AuthorAccess authorAccess, + AuthorOutlineCustomizer rendererCustomizer, AuthorNode node) { + AuthorDocument document = authorAccess.getDocumentController().getAuthorDocumentNode(); + + // Builds the path. + LinkedList reversedPath = new LinkedList(); + while (node != null && !rendererCustomizer.ignoreNode(node)) { + reversedPath.addFirst(node); + if (node == document) { + // Just added root. + break; + } + node = node.getParent(); + } + return reversedPath; + } +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/TrasformOperation.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/TrasformOperation.java new file mode 100644 index 0000000..a4d46d6 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/TrasformOperation.java @@ -0,0 +1,82 @@ +package simple.documentation.framework.operations; + +import java.io.File; +import java.io.StringWriter; +import java.net.MalformedURLException; + +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stream.StreamResult; + +import org.xml.sax.InputSource; + +import ro.sync.ecss.extensions.api.ArgumentDescriptor; +import ro.sync.ecss.extensions.api.ArgumentsMap; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorOperation; +import ro.sync.ecss.extensions.api.AuthorOperationException; +import ro.sync.ecss.extensions.api.access.AuthorXMLUtilAccess; + +/** + * Custom transform operation using Saxon EE transformer. + */ +public class TrasformOperation implements AuthorOperation { + + public void doOperation(AuthorAccess authorAccess, ArgumentsMap arguments) + throws IllegalArgumentException, AuthorOperationException { + // Choose the XSLT used in the transform operation + File xsltFile = authorAccess.getWorkspaceAccess().chooseFile( + // Title + "Choose the XSLT", + // Extensions + new String[] {"xsl", "xslt"}, + // Filter description + "XSLT files", + // Open for save + false); + + if (xsltFile != null) { + try { + String xslSystemId = xsltFile.toURI().toURL().toString(); + // Create the XSLT source used in transform operation + Source xslSource = new SAXSource(new InputSource(xslSystemId)); + // Create a XSL transformer without Oxygen options + Transformer xslTransformer = authorAccess.getXMLUtilAccess().createXSLTTransformer(xslSource, null, AuthorXMLUtilAccess.TRANSFORMER_SAXON_ENTERPRISE_EDITION, false); + Source editorSource = new SAXSource(new InputSource(authorAccess.getEditorAccess().createContentReader())); + StringWriter result = new StringWriter(); + // Transform the current document using the specified XSLT + xslTransformer.transform(editorSource, new StreamResult(result)); + StringBuffer resultBuffer = result.getBuffer(); + // Display the result + authorAccess.getWorkspaceAccess().showInformationMessage("Transformation result: " + resultBuffer.toString()); + authorAccess.getWorkspaceAccess().showStatusMessage("Transformation finished"); + } catch (TransformerConfigurationException e) { + // Display the error message + authorAccess.getWorkspaceAccess().showErrorMessage(e.getMessage()); + } catch (TransformerException e) { + // Display the error message + authorAccess.getWorkspaceAccess().showErrorMessage(e.getMessage()); + } catch (MalformedURLException e) { + // Display the error message + authorAccess.getWorkspaceAccess().showErrorMessage(e.getMessage()); + } + } + } + + /** + * Arguments + */ + public ArgumentDescriptor[] getArguments() { + return null; + } + + /** + * Description + */ + public String getDescription() { + return "Custom transform operation using Saxon EE transformer."; + } +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/AddHighlightOperation.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/AddHighlightOperation.java new file mode 100644 index 0000000..f90b68e --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/AddHighlightOperation.java @@ -0,0 +1,71 @@ +package simple.documentation.framework.operations.highlight; + +import java.util.LinkedHashMap; + +import javax.swing.JFrame; +import javax.swing.text.BadLocationException; + +import ro.sync.ecss.extensions.api.ArgumentDescriptor; +import ro.sync.ecss.extensions.api.ArgumentsMap; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorOperation; +import ro.sync.ecss.extensions.api.AuthorOperationException; +import ro.sync.ecss.extensions.api.highlights.AuthorPersistentHighlighter; +import ro.sync.ecss.extensions.api.node.AuthorNode; +import ro.sync.ecss.extensions.commons.ui.OKCancelDialog; + +/** + * Operation used to highlight element from the caret position. + */ +public class AddHighlightOperation implements AuthorOperation { + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#doOperation(ro.sync.ecss.extensions.api.AuthorAccess, ro.sync.ecss.extensions.api.ArgumentsMap) + */ + public void doOperation(AuthorAccess authorAccess, ArgumentsMap map) throws IllegalArgumentException, + AuthorOperationException { + // Show dialog for adding highlight comment + AuthorPersistentHighlighter highlighter = authorAccess.getEditorAccess().getPersistentHighlighter(); + EditHighlightsDialog commentDlg = new EditHighlightsDialog( + (JFrame) authorAccess.getWorkspaceAccess().getParentFrame(), + "Add highlight comment", + true, + null, + authorAccess); + commentDlg.showDialog(); + if (commentDlg.getResult() == OKCancelDialog.RESULT_OK) { + int caretOffset = authorAccess.getEditorAccess().getCaretOffset(); + try { + // Highlight the node at caret + AuthorNode nodeAtCaret = authorAccess.getDocumentController().getNodeAtOffset(caretOffset); + String authorName = authorAccess.getReviewController().getReviewerAuthorName(); + String timestamp = authorAccess.getReviewController().getCurrentTimestamp(); + + // Compose highlight properties + LinkedHashMap properties = new LinkedHashMap(); + properties.put(HighlightProperties.ID, timestamp); + properties.put(HighlightProperties.AUTHOR, authorName); + properties.put(HighlightProperties.COMMENT, commentDlg.getComment()); + // Add highlight + highlighter.addHighlight(nodeAtCaret.getStartOffset(), nodeAtCaret.getEndOffset(), properties); + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + } + + /** + * @see ro.sync.ecss.extensions.api.Extension#getDescription() + */ + public String getDescription() { + return "Highlight element from the caret position."; + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#getArguments() + */ + public ArgumentDescriptor[] getArguments() { + return null; + } + +} \ No newline at end of file diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/ChangeReviewAuthorDialog.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/ChangeReviewAuthorDialog.java new file mode 100644 index 0000000..0fd043e --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/ChangeReviewAuthorDialog.java @@ -0,0 +1,55 @@ +package simple.documentation.framework.operations.highlight; + +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; + +import ro.sync.ecss.extensions.commons.ui.OKCancelDialog; + +/** + * Dialog used for changing the current review author + */ +@SuppressWarnings("serial") +public class ChangeReviewAuthorDialog extends OKCancelDialog { + /** + * Combo box containing all possible author names + */ + private JComboBox authorNamesComboBox; + + /** + * Constructor. + * + * @param parentFrame The parent frame. + * @param title The dialog title. + * @param authorNames All the possible author names. + */ + public ChangeReviewAuthorDialog( + JFrame parentFrame, + String title, + String[] authorNames) { + super(parentFrame, title, true); + // Add label + add(new JLabel("Choose Review Author: ")); + // Add the combobox containing possible author names + authorNamesComboBox = new JComboBox(authorNames); + add(authorNamesComboBox); + } + + /** + * Show the dialog. + */ + public void showDialog() { + setLocationRelativeTo(null); + pack(); + setVisible(true); + } + + /** + * Get the selected author name. + * + * @return The selected author name. + */ + public String getSelectedAuthorName() { + return (String) authorNamesComboBox.getSelectedItem(); + } +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/ChangeReviewAuthorOperation.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/ChangeReviewAuthorOperation.java new file mode 100644 index 0000000..47f5dff --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/ChangeReviewAuthorOperation.java @@ -0,0 +1,51 @@ +package simple.documentation.framework.operations.highlight; + +import javax.swing.JFrame; + +import ro.sync.ecss.extensions.api.ArgumentDescriptor; +import ro.sync.ecss.extensions.api.ArgumentsMap; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorOperation; +import ro.sync.ecss.extensions.api.AuthorOperationException; +import ro.sync.ecss.extensions.commons.ui.OKCancelDialog; + +/** + * Operation that allow changing the author. + */ +public class ChangeReviewAuthorOperation implements AuthorOperation { + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#doOperation(ro.sync.ecss.extensions.api.AuthorAccess, ro.sync.ecss.extensions.api.ArgumentsMap) + */ + public void doOperation(AuthorAccess authorAccess, ArgumentsMap args) + throws IllegalArgumentException, AuthorOperationException { + ChangeReviewAuthorDialog commentDlg = new ChangeReviewAuthorDialog( + (JFrame) authorAccess.getWorkspaceAccess().getParentFrame(), + "Change Review Author", + new String[] {"Author_1", "Author_2", "Author_3", "Default"}); + // Show the dialog + commentDlg.showDialog(); + if (commentDlg.getResult() == OKCancelDialog.RESULT_OK) { + String reviewerAuthorName = commentDlg.getSelectedAuthorName(); + // If the the reviewer author name is set to null, the default author name is used. + reviewerAuthorName = "Default".equals(reviewerAuthorName) ? null : reviewerAuthorName; + // Set the reviewer author name + authorAccess.getReviewController().setReviewerAuthorName(reviewerAuthorName); + } + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#getArguments() + */ + public ArgumentDescriptor[] getArguments() { + return null; + } + + /** + * @see ro.sync.ecss.extensions.api.Extension#getDescription() + */ + public String getDescription() { + return "Change review author name"; + } + +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/EditHighlightsDialog.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/EditHighlightsDialog.java new file mode 100644 index 0000000..1d23766 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/EditHighlightsDialog.java @@ -0,0 +1,190 @@ +package simple.documentation.framework.operations.highlight; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.border.BevelBorder; + +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.highlights.AuthorPersistentHighlight; +import ro.sync.ecss.extensions.commons.ui.OKCancelDialog; + +/** + * Dialog used for adding or editing highlights. + */ +@SuppressWarnings("serial") +public class EditHighlightsDialog extends OKCancelDialog { + /** + * Information label (presents highlight ID and author). + */ + private JLabel infoLabel; + /** + * Comment area. + */ + private JTextArea commentArea; + /** + * Map between highlight and edited properties. + */ + private Map> mapHighlightsToProps; + /** + * Current edited highlight index. + */ + private final int[] currentIndex = new int[1]; + /** + * Current edited highlight. + */ + private AuthorPersistentHighlight currentHighlight; + /** + * The Author access. + */ + private final AuthorAccess authorAccess; + + /** + * Constructor. + * + * @param parentFrame The parent frame. + * @param title The dialog title. + * @param modal true if modal. + * @param highlights List of highlights to be edited. + * @param auhorAccess The Author access + */ + public EditHighlightsDialog( + JFrame parentFrame, + String title, + boolean modal, + final List highlights, + final AuthorAccess auhorAccess) { + super(parentFrame, title, modal); + this.authorAccess = auhorAccess; + + boolean editHighlights = highlights != null && highlights.size() > 0; + + commentArea = new JTextArea(); + commentArea.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); + + // Add scroll pane + JScrollPane commentAreaPane = new JScrollPane(commentArea); + commentAreaPane.setPreferredSize(new Dimension(400, 200)); + commentArea.setMargin(new Insets(10, 10, 10 , 10)); + // Add comment area + add(commentAreaPane, BorderLayout.CENTER); + + if (editHighlights) { + mapHighlightsToProps = new LinkedHashMap>(); + currentHighlight = highlights.get(0); + JPanel northPanel = new JPanel(); + infoLabel = new JLabel(); + northPanel.add(infoLabel); + // Add next button if necessary + if (highlights.size() > 1) { + final JButton nextButton = new JButton("Next"); + nextButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + saveCurrentHighlightProps(currentHighlight); + + currentIndex[0] = currentIndex[0] + 1; + currentHighlight = highlights.get(currentIndex[0]); + + // Display next highlight + displayHighlightProps(currentHighlight); + + // Disable next button if necessary + if (highlights.size() == currentIndex[0] + 1) { + nextButton.setEnabled(false); + } + } + }); + // Add next button + northPanel.add(nextButton); + } + add(northPanel, BorderLayout.NORTH); + + // Display properties for the current highlight + displayHighlightProps(currentHighlight); + } + } + + private void saveCurrentHighlightProps(AuthorPersistentHighlight highlight) { + // Save edited properties for the current map + LinkedHashMap props = highlight.getClonedProperties(); + props.put(HighlightProperties.COMMENT, commentArea.getText()); + mapHighlightsToProps.put(highlight, props); + } + + /** + * @see ro.sync.ecss.extensions.commons.ui.OKCancelDialog#doOK() + */ + @Override + protected void doOK() { + if (currentHighlight != null) { + saveCurrentHighlightProps(currentHighlight); + if (mapHighlightsToProps != null) { + Set highlights = mapHighlightsToProps.keySet(); + for (AuthorPersistentHighlight highlight : highlights) { + // Update the timestamp + mapHighlightsToProps.get(highlight).put(HighlightProperties.ID, authorAccess.getReviewController().getCurrentTimestamp()); + } + } + } + super.doOK(); + } + + /** + * Display highlight properties. + * + * @param highlight The current highlight. + */ + private void displayHighlightProps(AuthorPersistentHighlight highlight) { + LinkedHashMap cloneProperties = highlight.getClonedProperties(); + + // Highlight properties + String id = cloneProperties.get(HighlightProperties.ID); + String author = cloneProperties.get(HighlightProperties.AUTHOR); + String comment = cloneProperties.get(HighlightProperties.COMMENT); + + // Display highlight comment + commentArea.setText(comment); + + // Display highlight ID and author + infoLabel.setText("Id: " + id + " Author: " + author); + } + + /** + * Show the dialog. + */ + public void showDialog() { + setLocationRelativeTo(null); + pack(); + setVisible(true); + } + + /** + * Get the last edited comment. + * + * @return The inserted comment. + */ + public String getComment() { + return commentArea.getText(); + } + + /** + * @return Returns the highlights to properties map. + */ + public Map> getMapHighlightsToProps() { + return mapHighlightsToProps; + } +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/EditHighlightsOperation.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/EditHighlightsOperation.java new file mode 100644 index 0000000..d144e97 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/EditHighlightsOperation.java @@ -0,0 +1,79 @@ +package simple.documentation.framework.operations.highlight; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.JFrame; + +import ro.sync.ecss.extensions.api.ArgumentDescriptor; +import ro.sync.ecss.extensions.api.ArgumentsMap; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorOperation; +import ro.sync.ecss.extensions.api.AuthorOperationException; +import ro.sync.ecss.extensions.api.highlights.AuthorPersistentHighlight; +import ro.sync.ecss.extensions.api.highlights.AuthorPersistentHighlighter; +import ro.sync.ecss.extensions.commons.ui.OKCancelDialog; + +/** + * Operation used to edit the highlights from the caret position. + */ +public class EditHighlightsOperation implements AuthorOperation { + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#doOperation(ro.sync.ecss.extensions.api.AuthorAccess, ro.sync.ecss.extensions.api.ArgumentsMap) + */ + public void doOperation(AuthorAccess authorAccess, ArgumentsMap args) + throws IllegalArgumentException, AuthorOperationException { + AuthorPersistentHighlighter highlighter = authorAccess.getEditorAccess().getPersistentHighlighter(); + AuthorPersistentHighlight[] highlights = highlighter.getHighlights(); + if (highlights.length > 0) { + int caretOffset = authorAccess.getEditorAccess().getCaretOffset(); + List caretHighlights = new ArrayList(); + // Remove highlights from the caret position + for (AuthorPersistentHighlight highlight : highlights) { + // Get the highlights from the caret position + if (highlight.getStartOffset() <= caretOffset && highlight.getEndOffset() >= caretOffset) { + caretHighlights.add(highlight); + } + } + + if (caretHighlights.size() > 0) { + // Show edit highlights dialog + EditHighlightsDialog commentDlg = new EditHighlightsDialog( + (JFrame) authorAccess.getWorkspaceAccess().getParentFrame(), + "Add highlight comment", + true, + caretHighlights, + authorAccess); + commentDlg.showDialog(); + // Save edited highlights if dialog result is OK + if (commentDlg.getResult() == OKCancelDialog.RESULT_OK) { + Map> mapHighlightsToProps = commentDlg.getMapHighlightsToProps(); + Set highlightsSet = mapHighlightsToProps.keySet(); + for (AuthorPersistentHighlight h : highlightsSet) { + // Save edited properties + highlighter.setProperties(h, mapHighlightsToProps.get(h)); + } + } + } + } + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#getArguments() + */ + public ArgumentDescriptor[] getArguments() { + return null; + } + + /** + * @see ro.sync.ecss.extensions.api.Extension#getDescription() + */ + public String getDescription() { + return "Edit Highlights from the caret position"; + } + +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/HighlightProperties.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/HighlightProperties.java new file mode 100644 index 0000000..2f0c1be --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/HighlightProperties.java @@ -0,0 +1,19 @@ +package simple.documentation.framework.operations.highlight; + +/** + * Hightlight properties names + */ +public interface HighlightProperties { + /** + * Highlight author. + */ + static final String AUTHOR = "author"; + /** + * Highlight ID. + */ + static final String ID = "id"; + /** + * Highlight comment. + */ + static final String COMMENT = "comment"; +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/RemoveAllHighlightsOperation.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/RemoveAllHighlightsOperation.java new file mode 100644 index 0000000..f61b2dd --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/RemoveAllHighlightsOperation.java @@ -0,0 +1,41 @@ +package simple.documentation.framework.operations.highlight; + +import ro.sync.ecss.extensions.api.ArgumentDescriptor; +import ro.sync.ecss.extensions.api.ArgumentsMap; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorOperation; +import ro.sync.ecss.extensions.api.AuthorOperationException; +import ro.sync.ecss.extensions.api.highlights.AuthorPersistentHighlighter; + +/** + * Operation used to remove all persistent highlights. + */ +public class RemoveAllHighlightsOperation implements AuthorOperation { + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#doOperation(ro.sync.ecss.extensions.api.AuthorAccess, ro.sync.ecss.extensions.api.ArgumentsMap) + */ + public void doOperation(AuthorAccess authorAccess, ArgumentsMap args) + throws IllegalArgumentException, AuthorOperationException { + AuthorPersistentHighlighter highlighter = authorAccess.getEditorAccess().getPersistentHighlighter(); + int highlights = highlighter.getHighlights().length; + // Remove all highlights + highlighter.removeAllHighlights(); + authorAccess.getWorkspaceAccess().showInformationMessage(highlights == 1 ? "1 highlight removed" : highlights + " highlights removed"); + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#getArguments() + */ + public ArgumentDescriptor[] getArguments() { + return null; + } + + /** + * @see ro.sync.ecss.extensions.api.Extension#getDescription() + */ + public String getDescription() { + return "Remove all persistent highlights"; + } + +} diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/ReviewParaOperation.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/ReviewParaOperation.java new file mode 100644 index 0000000..a4fca42 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/ReviewParaOperation.java @@ -0,0 +1,108 @@ +package simple.documentation.framework.operations.highlight; + +import java.awt.Component; +import java.util.LinkedHashMap; + +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.text.BadLocationException; + +import ro.sync.ecss.extensions.api.ArgumentDescriptor; +import ro.sync.ecss.extensions.api.ArgumentsMap; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorOperation; +import ro.sync.ecss.extensions.api.AuthorOperationException; +import ro.sync.ecss.extensions.api.highlights.AuthorHighlighter; +import ro.sync.ecss.extensions.api.highlights.AuthorPersistentHighlighter; +import ro.sync.ecss.extensions.api.highlights.ColorHighlightPainter; +import ro.sync.ecss.extensions.api.node.AuthorNode; +import ro.sync.ecss.extensions.commons.ui.OKCancelDialog; +import ro.sync.exml.view.graphics.Color; + + +/** + * Operation used to highlight a paragraph as reviewed. + */ +public class ReviewParaOperation implements AuthorOperation { + /** + * Highlight author. + */ + static final String AUTHOR = "author"; + /** + * Highlight ID. + */ + static final String ID = "id"; + /** + * Highlight comment. + */ + static final String COMMENT = "comment"; + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#doOperation(ro.sync.ecss.extensions.api.AuthorAccess, ro.sync.ecss.extensions.api.ArgumentsMap) + */ + public void doOperation(AuthorAccess authorAccess, ArgumentsMap map) throws IllegalArgumentException, + AuthorOperationException { + // Highlight the selected paragraph if any. + AuthorNode selectedNode = authorAccess.getEditorAccess().getFullySelectedNode(); + if (selectedNode != null) { + // Show dialog for adding highlight comment + AuthorPersistentHighlighter persistentHighlighter = authorAccess.getEditorAccess().getPersistentHighlighter(); + AuthorHighlighter highlighter = authorAccess.getEditorAccess().getHighlighter(); + EditHighlightsDialog commentDlg = new EditHighlightsDialog( + (JFrame) authorAccess.getWorkspaceAccess().getParentFrame(), + "Review Paragraph", + true, + null, + authorAccess); + commentDlg.showDialog(); + if (commentDlg.getResult() == OKCancelDialog.RESULT_OK) { + + // Get author name and timestamp. + String authorName = authorAccess.getReviewController().getReviewerAuthorName(); + String timestamp = authorAccess.getReviewController().getCurrentTimestamp(); + + // Compose highlight properties + LinkedHashMap properties = new LinkedHashMap(); + properties.put(ID, timestamp); + properties.put(AUTHOR, authorName); + String comment = commentDlg.getComment(); + properties.put(COMMENT, comment); + int startOffset = selectedNode.getStartOffset(); + int endOffset = selectedNode.getEndOffset(); + if (comment != null && comment.trim().length() > 0) { + // Add a persistent highlight + persistentHighlighter.addHighlight(startOffset, endOffset, properties); + } else { + // Add non-persistent highlight + + ColorHighlightPainter painter = new ColorHighlightPainter(); + painter.setTextForegroundColor(Color.COLOR_RED_DARKER); + + try { + highlighter.addHighlight(startOffset, endOffset, painter, null); + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + } + } else { + JOptionPane.showMessageDialog( + (Component) authorAccess.getWorkspaceAccess().getParentFrame(), + "Select the whole paragraph!!!"); + } + } + + /** + * @see ro.sync.ecss.extensions.api.Extension#getDescription() + */ + public String getDescription() { + return "Review selected paragraph."; + } + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#getArguments() + */ + public ArgumentDescriptor[] getArguments() { + return null; + } +} \ No newline at end of file diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/table/InsertTableOperation.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/table/InsertTableOperation.java new file mode 100644 index 0000000..11c7db6 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/table/InsertTableOperation.java @@ -0,0 +1,89 @@ +package simple.documentation.framework.operations.table; + +import java.awt.Component; + +import javax.swing.JFrame; + +import ro.sync.ecss.extensions.api.ArgumentDescriptor; +import ro.sync.ecss.extensions.api.ArgumentsMap; +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.ecss.extensions.api.AuthorOperation; +import ro.sync.ecss.extensions.api.AuthorOperationException; +import ro.sync.exml.workspace.api.Platform; +import simple.documentation.framework.operations.table.TableCustomizerDialog.TableInfo; + +/** + * Operation used to insert a SDF table. + */ +public class InsertTableOperation implements AuthorOperation { + + /** + * @see ro.sync.ecss.extensions.api.AuthorOperation#doOperation(ro.sync.ecss.extensions.api.AuthorAccess, ro.sync.ecss.extensions.api.ArgumentsMap) + */ + public void doOperation(AuthorAccess authorAccess, ArgumentsMap args) + throws IllegalArgumentException, AuthorOperationException { + // Show the 'Insert table' dialog + TableInfo tableInfo = null; + if(authorAccess.getWorkspaceAccess().getPlatform() == Platform.STANDALONE) { + TableCustomizerDialog tableCustomizerDialog = new TableCustomizerDialog( + (JFrame) authorAccess.getWorkspaceAccess().getParentFrame()); + tableCustomizerDialog.setLocationRelativeTo( + (Component) authorAccess.getWorkspaceAccess().getParentFrame()); + tableInfo = tableCustomizerDialog.showDialog(); + } + + if (tableInfo != null) { + // Create the table XML fragment + StringBuffer tableXMLFragment = new StringBuffer(); + tableXMLFragment.append(""); + if(tableInfo.getTitle() != null && tableInfo.getTitle().trim().length() > 0) { + tableXMLFragment.append("" + tableInfo.getTitle().trim() + ""); + } + + // Add table body + int columns = tableInfo.getColumnsNumber(); + int rows = tableInfo.getRowsNumber(); + for (int i = 0; i < rows; i++) { + tableXMLFragment.append(""); + for (int j = 0; j < columns; j++) { + tableXMLFragment.append(""); + } + tableXMLFragment.append(""); + } + + tableXMLFragment.append("
"); + + + // Insert the table + authorAccess.getDocumentController().insertXMLFragmentSchemaAware( + tableXMLFragment.toString(), + authorAccess.getEditorAccess().getCaretOffset()); + } else { + // User canceled the operation + } + } + + /** + * No arguments. The operation will display a dialog for choosing the table attributes. + * + * @see ro.sync.ecss.extensions.api.AuthorOperation#getArguments() + */ + public ArgumentDescriptor[] getArguments() { + return null; + } + + /** + * @see ro.sync.ecss.extensions.api.Extension#getDescription() + */ + public String getDescription() { + return "Insert a SDF table"; + } +} \ No newline at end of file diff --git a/oxygen/js-options/src-java/simple/documentation/framework/operations/table/TableCustomizerDialog.java b/oxygen/js-options/src-java/simple/documentation/framework/operations/table/TableCustomizerDialog.java new file mode 100644 index 0000000..f6acad7 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/operations/table/TableCustomizerDialog.java @@ -0,0 +1,339 @@ +package simple.documentation.framework.operations.table; + + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JColorChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.JTextField; +import javax.swing.SpinnerNumberModel; + +import ro.sync.ecss.extensions.commons.table.operations.TableCustomizerConstants; +import ro.sync.ecss.extensions.commons.ui.OKCancelDialog; + +/** + * Dialog used to customize the insertion of a table (number of rows, columns, table caption). + * It is used on Standalone implementation. + */ +@SuppressWarnings("serial") +public class TableCustomizerDialog extends OKCancelDialog implements TableCustomizerConstants { + + /** + * If selected the user can specify the table title. + */ + private JCheckBox titleCheckbox; + + /** + * Text field for specify the table title. + */ + private JTextField titleTextField; + + /** + * Used to specify the number of rows. + */ + private JSpinner rowsSpinner; + + /** + * Used to specify the number of columns. + */ + private JSpinner columnsSpinner; + + /** + * If selected the user can specify the table background color. + */ + private JCheckBox tableBgColorCheckbox; + + /** + * Button used to choose table background color. + */ + private JButton tableBgColorButton; + + /** + * Constructor. + * + * @param parentFrame The parent {@link JFrame} of the dialog. + */ + public TableCustomizerDialog( + JFrame parentFrame) { + super(parentFrame, "Insert Table", true); + + // The main panel + JPanel mainPanel = new JPanel(new GridBagLayout()); + mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + GridBagConstraints gridBagConstr = new GridBagConstraints(); + gridBagConstr.anchor = GridBagConstraints.WEST; + gridBagConstr.gridy = 0; + gridBagConstr.gridx = 0; + gridBagConstr.weightx = 0; + gridBagConstr.gridwidth = 1; + gridBagConstr.insets = new Insets(5, 0, 5, 5); + gridBagConstr.fill = GridBagConstraints.NONE; + + // Title check box + titleCheckbox = new JCheckBox("Title"); + titleCheckbox.setName("Title checkbox"); + titleCheckbox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + titleTextField.setEditable(titleCheckbox.isSelected()); + } + }); + titleCheckbox.setBorder(BorderFactory.createEmptyBorder()); + mainPanel.add(titleCheckbox, gridBagConstr); + + // Title text field + titleTextField = new JTextField(); + titleTextField.setName("Title text field"); + gridBagConstr.gridx ++; + gridBagConstr.weightx = 1; + gridBagConstr.fill = GridBagConstraints.HORIZONTAL; + gridBagConstr.insets = new Insets(5, 0, 5, 0); + mainPanel.add(titleTextField, gridBagConstr); + + gridBagConstr.gridy ++; + gridBagConstr.gridx = 0; + gridBagConstr.weightx = 0; + gridBagConstr.gridwidth = 1; + gridBagConstr.insets = new Insets(5, 0, 5, 5); + gridBagConstr.fill = GridBagConstraints.BOTH; + + // Table bgcolor box + tableBgColorCheckbox = new JCheckBox("Table Background"); + tableBgColorButton = new JButton(); + tableBgColorCheckbox.setName("Table Background"); + tableBgColorCheckbox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + tableBgColorButton.setEnabled(tableBgColorCheckbox.isSelected()); + } + }); + tableBgColorCheckbox.setBorder(BorderFactory.createEmptyBorder()); + mainPanel.add(tableBgColorCheckbox, gridBagConstr); + + // Table bg color + tableBgColorButton.setIcon(new Icon() { + public void paintIcon(Component parent, Graphics g, int x, int y) { + Color color = tableBgColorButton.getBackground(); + if (color == null) { + return; + } + Color used4Draw = color; + if (parent != null && !parent.isEnabled()) { + used4Draw = parent.getBackground(); + } + g.setColor(used4Draw); + g.fillRect(x, y, getIconWidth(), getIconHeight()); + g.setColor(used4Draw.darker()); + g.drawRect(x, y, getIconWidth() - 1, getIconHeight() - 1); + } + + public int getIconWidth() { + return tableBgColorButton.getWidth(); + } + + public int getIconHeight() { + return tableBgColorButton.getHeight(); + } + }); + + tableBgColorButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + Color initialBackground = tableBgColorButton.getBackground(); + Color background = JColorChooser.showDialog(null, + "Choose Color", initialBackground); + if (background != null) { + tableBgColorButton.setBackground(background); + + tableBgColorButton.setContentAreaFilled(true); + } + } + }); + tableBgColorButton.setPreferredSize(new Dimension(100, 15)); + gridBagConstr.gridx ++; + gridBagConstr.weightx = 1; + gridBagConstr.fill = GridBagConstraints.HORIZONTAL; + gridBagConstr.insets = new Insets(2, 2, 2, 2); + mainPanel.add(tableBgColorButton, gridBagConstr); + + // Table size panel + JPanel sizePanel = new JPanel(new GridBagLayout()); + sizePanel.setBorder(BorderFactory.createTitledBorder("Table Size")); + + gridBagConstr.gridy ++; + gridBagConstr.gridx = 0; + gridBagConstr.weightx = 1; + gridBagConstr.fill = GridBagConstraints.HORIZONTAL; + gridBagConstr.gridwidth = 2; + gridBagConstr.insets = new Insets(5, 0, 5, 0); + mainPanel.add(sizePanel, gridBagConstr); + + // 'Rows' label + JLabel rowsLabel = new JLabel("Rows"); + GridBagConstraints c = new GridBagConstraints(); + c.gridx = 0; + c.gridy = 0; + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 0; + c.insets = new Insets(0, 5, 5, 5); + sizePanel.add(rowsLabel, c); + + // Number of rows text field + rowsSpinner = new JSpinner(); + rowsSpinner.setName("Rows spinner"); + rowsSpinner.setModel(new SpinnerNumberModel(2, 0, 100, 1)); + c.gridx++; + c.weightx = 1; + sizePanel.add(rowsSpinner, c); + + // 'Columns' label + JLabel columnsLabel = new JLabel("Columns"); + c.gridx++; + c.weightx = 0; + sizePanel.add(columnsLabel, c); + + // Number of rows text field + columnsSpinner = new JSpinner(); + columnsSpinner.setName("Columns spinner"); + columnsSpinner.setModel(new SpinnerNumberModel(2, 0, 100, 1)); + c.gridx++; + c.weightx = 1; + sizePanel.add(columnsSpinner, c); + + //Add the main panel + getContentPane().add(mainPanel, BorderLayout.CENTER); + + pack(); + setResizable(false); + } + + /** + * Contains informations about the table element. + */ + class TableInfo { + /** + * Table title. + */ + private final String title; + /** + * Table rows number. + */ + private final int rowsNumber; + /** + * Table columns number. + */ + private final int columnsNumber; + /** + * Table background color. + */ + private final Color tableBgColor; + + /** + * @param title Table title. + * @param rowsNumber Table rows number. + * @param columnsNumber Table columns number. + * @param tableBgColor Table background color. + */ + public TableInfo(String title, int rowsNumber, int columnsNumber, Color tableBgColor) { + this.title = title; + this.rowsNumber = rowsNumber; + this.columnsNumber = columnsNumber; + this.tableBgColor = tableBgColor; + } + + /** + * @return Returns the title. + */ + public String getTitle() { + return title; + } + + /** + * @return Returns the rows number. + */ + public int getRowsNumber() { + return rowsNumber; + } + + /** + * @return Returns the columns number. + */ + public int getColumnsNumber() { + return columnsNumber; + } + + /** + * @return Returns the table background color. + */ + public Color getTableBackgroundColor() { + return tableBgColor; + } + } + + /** + * Show the dialog to customize the table attributes. + * + * @return The object containing informations about the table to be inserted. + * If null then the user canceled the table insertion. + */ + public TableInfo showDialog() { + // Reset components to default values + titleTextField.setEditable(true); + titleTextField.setText(""); + titleCheckbox.setSelected(true); + + tableBgColorButton.setEnabled(false); + tableBgColorButton.setBackground(tableBgColorCheckbox.getBackground()); + tableBgColorCheckbox.setSelected(false); + + // Set the default number of rows and columns + rowsSpinner.setValue(new Integer(3)); + columnsSpinner.setValue(new Integer(2)); + + // Request focus in title field + titleTextField.requestFocus(); + + super.setVisible(true); + + TableInfo tableInfo = null; + if(getResult() == RESULT_OK) { + // Title + String title = null; + if(titleCheckbox.isSelected()) { + title = titleTextField.getText(); + title = ro.sync.basic.xml.BasicXmlUtil.escape(title); + } + // Table background color + Color tableBgColor = null; + if(tableBgColorCheckbox.isSelected()) { + tableBgColor = tableBgColorButton.getBackground(); + } + int rowsNumber = ((Integer)rowsSpinner.getValue()).intValue(); + int columnsNumber = ((Integer)columnsSpinner.getValue()).intValue(); + + tableInfo = + new TableInfo( + title, + rowsNumber, + columnsNumber, + tableBgColor); + } else { + // Cancel was pressed + } + return tableInfo; + } +} \ No newline at end of file diff --git a/oxygen/js-options/src-java/simple/documentation/framework/ui/SDFPopupWindow.java b/oxygen/js-options/src-java/simple/documentation/framework/ui/SDFPopupWindow.java new file mode 100644 index 0000000..51bc127 --- /dev/null +++ b/oxygen/js-options/src-java/simple/documentation/framework/ui/SDFPopupWindow.java @@ -0,0 +1,110 @@ +package simple.documentation.framework.ui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.IllegalComponentStateException; +import java.util.Timer; +import java.util.TimerTask; + +import javax.swing.BorderFactory; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextArea; +import javax.swing.JWindow; + +import ro.sync.ecss.extensions.api.AuthorAccess; +import ro.sync.exml.view.graphics.Point; + +/** + * Popup window used to display Simple Documentation Framework specific information. + * + */ +@SuppressWarnings("serial") +public class SDFPopupWindow extends JWindow { + + /** + * Text area used to display useful informations. + */ + private JTextArea infoTextArea; + + /** + * Access to the author specific functions. + */ + AuthorAccess authorAccess; + + /** + * The display time of the popup window (seconds). + */ + private int timeToDisplay; + + /** + * + * @param access Author access. + * @param infoDescription Description. + */ + public SDFPopupWindow(AuthorAccess access, String infoDescription) { + super((JFrame) access.getWorkspaceAccess().getParentFrame()); + this.authorAccess = access; + + // Create information text area. + infoTextArea = new JTextArea(); + infoTextArea.setLineWrap(true); + infoTextArea.setWrapStyleWord(true); + infoTextArea.setEditable(false); + infoTextArea.setFocusable(false); + + JPanel mainContent = new JPanel(new BorderLayout()); + if (infoDescription != null) { + mainContent.add(new JLabel(infoDescription), BorderLayout.NORTH); + } + mainContent.setFocusable(false); + mainContent.add(infoTextArea, BorderLayout.SOUTH); + mainContent.setBorder(BorderFactory.createLineBorder(Color.black)); + getContentPane().add(mainContent); + setVisible(false); + } + + /** + * Set the time to display this popup window. + * + * @param timeToDisplay The display time in seconds. + */ + public void setTimeToDisplay(int timeToDisplay) { + this.timeToDisplay = timeToDisplay; + } + + /** + * Display the specified text information. + * + * @param text The text to be displayed in the popup window. + * @param relX The "x" coordinate relative to the viewport. + * @param relY The "y" coordinate relative to the viewport. + * @param delta The translation point where the popup should be displayed from the given (x, y) point. + */ + public void display(String text, int relX, int relY, int delta) { + // Transform the given relative coordinates into absolute coordinates. + try { + Point translatedPoint = authorAccess.getEditorAccess().getLocationOnScreenAsPoint(relX, relY); + setVisible(false); + infoTextArea.setText(text); + setLocation(translatedPoint.x + delta, translatedPoint.y + delta); + pack(); + // Show the information popup window + setVisible(true); + + // Hide the window when the given display time is finished. + if (timeToDisplay > 0) { + Timer timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + setVisible(false); + } + }, timeToDisplay * 1000); + } + } catch (IllegalComponentStateException e) { + // Do nothing + } + } +} diff --git a/oxygen/js-options/templates/article.xml b/oxygen/js-options/templates/article.xml new file mode 100644 index 0000000..a40fa2a --- /dev/null +++ b/oxygen/js-options/templates/article.xml @@ -0,0 +1,11 @@ + +
+ +
+ + + +
+
\ No newline at end of file diff --git a/oxygen/js-options/templates/book.xml b/oxygen/js-options/templates/book.xml new file mode 100644 index 0000000..7586442 --- /dev/null +++ b/oxygen/js-options/templates/book.xml @@ -0,0 +1,22 @@ + + + Book Template Title +
+ Section Title + + This content is copyrighted: + + Table Title +
+
+ + + + +
CompanyDate
+ +
+
+
diff --git a/oxygen/js-options/xsl/sdf.xsl b/oxygen/js-options/xsl/sdf.xsl new file mode 100644 index 0000000..3038bd9 --- /dev/null +++ b/oxygen/js-options/xsl/sdf.xsl @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + +

+ +

+
+ +

+ + + +

+
+ +

+ +

+
+ + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +