From 25ac1d1a9676e9b84945e616b250d371050011f2 Mon Sep 17 00:00:00 2001 From: Eric van der Vlist Date: Wed, 28 Apr 2021 15:12:30 +0200 Subject: [PATCH] Copying --- oxygen/js-options/README.md | 59 + oxygen/js-options/catalog.xml | 9 + oxygen/js-options/commons.js | 8 + oxygen/js-options/css/sdf.css | 145 ++ oxygen/js-options/debugger.js | 21 + oxygen/js-options/hello.js | 3 + oxygen/js-options/icons/insertImage16.JPG | Bin 0 -> 777 bytes oxygen/js-options/icons/insertImage20.JPG | Bin 0 -> 927 bytes oxygen/js-options/icons/insertSection16.JPG | Bin 0 -> 762 bytes oxygen/js-options/icons/insertSection20.JPG | Bin 0 -> 826 bytes oxygen/js-options/icons/insertTable16.JPG | Bin 0 -> 811 bytes oxygen/js-options/icons/insertTable20.JPG | Bin 0 -> 928 bytes oxygen/js-options/icons/refreshCSS16.JPG | Bin 0 -> 800 bytes oxygen/js-options/icons/refreshCSS20.JPG | Bin 0 -> 946 bytes oxygen/js-options/icons/strictMode16.JPG | Bin 0 -> 766 bytes oxygen/js-options/icons/strictMode20.JPG | Bin 0 -> 870 bytes oxygen/js-options/js-debugger-include.xpr | 26 + oxygen/js-options/resources/countries.txt | 1 + oxygen/js-options/schema/abs.xsd | 6 + oxygen/js-options/schema/sdf.xsd | 112 ++ oxygen/js-options/sdf.framework | 1391 +++++++++++++++++ oxygen/js-options/sdf.jar | Bin 0 -> 110627 bytes oxygen/js-options/sdf_sample.xml | 91 ++ .../framework/AuthorAccessProvider.java | 16 + .../documentation/framework/CustomRule.java | 44 + .../documentation/framework/SDFElement.java | 552 +++++++ .../framework/SDFExtensionsBundle.java | 173 ++ .../framework/SDFLinkTextResolver.java | 72 + .../callouts/CalloutsRenderingProvider.java | 95 ++ ...horPersistentHighlightActionsProvider.java | 63 + .../extensions/SDFAttributesValueEditor.java | 69 + .../SDFAuthorBreadCrumbCustomizer.java | 60 + .../SDFAuthorExtensionStateListener.java | 374 +++++ .../SDFAuthorOutlineCustomizer.java | 158 ++ .../SDFExternalObjectInsertionHandler.java | 72 + .../extensions/SDFReferencesResolver.java | 199 +++ .../SDFSchemaAwareEditingHandler.java | 353 +++++ .../extensions/SDFSchemaManagerFilter.java | 142 ++ .../framework/extensions/SDFStylesFilter.java | 139 ++ .../extensions/TableCellSpanProvider.java | 74 + .../extensions/TableColumnWidthProvider.java | 221 +++ .../framework/filters/SDFDocumentFilter.java | 104 ++ .../listeners/SDFAuthorCaretListener.java | 76 + .../listeners/SDFAuthorListener.java | 130 ++ .../listeners/SDFAuthorMouseListener.java | 62 + .../ExtractNodeToFileOperation.java | 76 + .../operations/InsertElementOperation.java | 127 ++ .../operations/InsertImageOperation.java | 213 +++ .../framework/operations/OpenInNewEditor.java | 39 + .../operations/QueryDatabaseOperation.java | 196 +++ .../operations/SDFRefreshCSSOperation.java | 38 + .../SDFShowFileStatusOperation.java | 45 + .../operations/SDFStrictModeOperation.java | 54 + .../SelectNodesInOutlinerOperation.java | 103 ++ .../operations/TrasformOperation.java | 82 + .../highlight/AddHighlightOperation.java | 71 + .../highlight/ChangeReviewAuthorDialog.java | 55 + .../ChangeReviewAuthorOperation.java | 51 + .../highlight/EditHighlightsDialog.java | 190 +++ .../highlight/EditHighlightsOperation.java | 79 + .../highlight/HighlightProperties.java | 19 + .../RemoveAllHighlightsOperation.java | 41 + .../highlight/ReviewParaOperation.java | 108 ++ .../table/InsertTableOperation.java | 89 ++ .../table/TableCustomizerDialog.java | 339 ++++ .../framework/ui/SDFPopupWindow.java | 110 ++ oxygen/js-options/templates/article.xml | 11 + oxygen/js-options/templates/book.xml | 22 + oxygen/js-options/xsl/sdf.xsl | 68 + 69 files changed, 7346 insertions(+) create mode 100644 oxygen/js-options/README.md create mode 100644 oxygen/js-options/catalog.xml create mode 100644 oxygen/js-options/commons.js create mode 100644 oxygen/js-options/css/sdf.css create mode 100644 oxygen/js-options/debugger.js create mode 100644 oxygen/js-options/hello.js create mode 100644 oxygen/js-options/icons/insertImage16.JPG create mode 100644 oxygen/js-options/icons/insertImage20.JPG create mode 100644 oxygen/js-options/icons/insertSection16.JPG create mode 100644 oxygen/js-options/icons/insertSection20.JPG create mode 100644 oxygen/js-options/icons/insertTable16.JPG create mode 100644 oxygen/js-options/icons/insertTable20.JPG create mode 100644 oxygen/js-options/icons/refreshCSS16.JPG create mode 100644 oxygen/js-options/icons/refreshCSS20.JPG create mode 100644 oxygen/js-options/icons/strictMode16.JPG create mode 100644 oxygen/js-options/icons/strictMode20.JPG create mode 100644 oxygen/js-options/js-debugger-include.xpr create mode 100644 oxygen/js-options/resources/countries.txt create mode 100644 oxygen/js-options/schema/abs.xsd create mode 100644 oxygen/js-options/schema/sdf.xsd create mode 100644 oxygen/js-options/sdf.framework create mode 100644 oxygen/js-options/sdf.jar create mode 100644 oxygen/js-options/sdf_sample.xml create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/AuthorAccessProvider.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/CustomRule.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/SDFElement.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/SDFExtensionsBundle.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/SDFLinkTextResolver.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/callouts/CalloutsRenderingProvider.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/callouts/SDFAuthorPersistentHighlightActionsProvider.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFAttributesValueEditor.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFAuthorBreadCrumbCustomizer.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFAuthorExtensionStateListener.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFAuthorOutlineCustomizer.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFExternalObjectInsertionHandler.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFReferencesResolver.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFSchemaAwareEditingHandler.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFSchemaManagerFilter.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/extensions/SDFStylesFilter.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/extensions/TableCellSpanProvider.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/extensions/TableColumnWidthProvider.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/filters/SDFDocumentFilter.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/listeners/SDFAuthorCaretListener.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/listeners/SDFAuthorListener.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/listeners/SDFAuthorMouseListener.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/ExtractNodeToFileOperation.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/InsertElementOperation.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/InsertImageOperation.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/OpenInNewEditor.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/QueryDatabaseOperation.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/SDFRefreshCSSOperation.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/SDFShowFileStatusOperation.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/SDFStrictModeOperation.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/SelectNodesInOutlinerOperation.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/TrasformOperation.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/AddHighlightOperation.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/ChangeReviewAuthorDialog.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/ChangeReviewAuthorOperation.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/EditHighlightsDialog.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/EditHighlightsOperation.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/HighlightProperties.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/RemoveAllHighlightsOperation.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/highlight/ReviewParaOperation.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/table/InsertTableOperation.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/operations/table/TableCustomizerDialog.java create mode 100644 oxygen/js-options/src-java/simple/documentation/framework/ui/SDFPopupWindow.java create mode 100644 oxygen/js-options/templates/article.xml create mode 100644 oxygen/js-options/templates/book.xml create mode 100644 oxygen/js-options/xsl/sdf.xsl 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 0000000000000000000000000000000000000000..8055e8f81ed1059a10917b3f0fa865d375273bd5 GIT binary patch literal 777 zcmex=Hq-)3-T;9z58XJh4H zXJ_Z+|EI`$@KzRlhK~^C}Lq|5@z(jVX zLJ_0Ji3>TDoi-j64Z8S2#W<;`iIYoATtZSxRZU$(Q_IBE%-q7#%Gt%$&E3P(D>x)H zEIcAIDmf)JEj=SMtGJ}Jth}PKs=1}Lt-YhOYtrN?Q>RUzF>}_U#Y>hhTfSoDs!f}> zY~8kf$Ie}c4j(ys?D&b3r!HN-a`oEv8#iw~eDwIq(`V0LynOZX)8{W=zkUDl^B2fp zj10^WZ^3TY#v@lcIY~ c8v>`KR?mJ^C^$!1Vs+HreYN^O=KsG50M?feod5s; literal 0 HcmV?d00001 diff --git a/oxygen/js-options/icons/insertImage20.JPG b/oxygen/js-options/icons/insertImage20.JPG new file mode 100644 index 0000000000000000000000000000000000000000..e3b1131662648b5ae3003adb642c265590cc2ec8 GIT binary patch literal 927 zcmex=Hq-)3-T;9z58XJh4H zXJ_Z+|EI`$@KzRlhK~^C}Lq|5@z(jVX zLJ_0Ji3>TDoi-j64Z8S2#W<;`iIYoATtZSxRZU$(Q_IBE%-q7#%Gt%$&E3P(D>x)H zEIcAIDmf)JEj=SMtGJ}Jth}PKs=1}Lt-YhOYtrN?Q>RUzF>}_U#Y>hhTfSoDs!f}> zY~8kf$Ie}c4j(ys?D&b3r!HN-a`oEv8#iw~eDwIq(`V0LynOZX)8{W=zkUDl^B2fp zj10^WZ^3| zTrpW|9P&vw>61%g?L4)E858?;mV8xTxvu$Pe!ppL{O{<0LfX5#?`xm@WOp|CSlwU!pEXAHAzqd7->3 zwR^3du9t<+nb%uVa+LSk7QYN_Z!2m4&(ObGL*MXw-Blsa&DSMm(tmMjEL`#>Iny*P f%T=VnX~JPCcabbnK{io`2Mp|vt|Gyb|8D{SwfJBF literal 0 HcmV?d00001 diff --git a/oxygen/js-options/icons/insertSection16.JPG b/oxygen/js-options/icons/insertSection16.JPG new file mode 100644 index 0000000000000000000000000000000000000000..6f8c020c7016c83ffa81f0aa4fcd869f2fbc7768 GIT binary patch literal 762 zcmex=Hq-)3-T;9z58XJh4H zXJ_Z+|EI`$@KzRlhK~^C}Lq|5@z(jVX zLJ_0Ji3>TDoi-j64Z8S2#W<;`iIYoATtZSxRZU$(Q_IBE%-q7#%Gt%$&E3P(D>x)H zEIcAIDmf)JEj=SMtGJ}Jth}PKs=1}Lt-YhOYtrN?Q>RUzF>}_U#Y>hhTfSoDs!f}> zY~8kf$Ie}c4j(ys?D&b3r!HN-a`oEv8#iw~eDwIq(`V0LynOZX)8{W=zkUDl^B2fp zj10^WZ^3mWRhlc#=+1-8*ceUZ`Zq1Zqxq9xWC8#h3dlp4EKNT L|8bR}{{Kw?=}-%0 literal 0 HcmV?d00001 diff --git a/oxygen/js-options/icons/insertSection20.JPG b/oxygen/js-options/icons/insertSection20.JPG new file mode 100644 index 0000000000000000000000000000000000000000..5dd7bb5c90079cd5bb288918732ec83f46355877 GIT binary patch literal 826 zcmex=Hq-)3-T;9z58XJh4H zXJ_Z+|EI`$@KzRlhK~^C}Lq|5@z(jVX zLJ_0Ji3>TDoi-j64Z8S2#W<;`iIYoATtZSxRZU$(Q_IBE%-q7#%Gt%$&E3P(D>x)H zEIcAIDmf)JEj=SMtGJ}Jth}PKs=1}Lt-YhOYtrN?Q>RUzF>}_U#Y>hhTfSoDs!f}> zY~8kf$Ie}c4j(ys?D&b3r!HN-a`oEv8#iw~eDwIq(`V0LynOZX)8{W=zkUDl^B2fp zj10^WZ^3@h+8y8apFw=VkJdGJU*;~_+w$S|-=)%TnNmx6M4mql;?UXg;Q726&(Hl+t5|jZ z;6CHoZX15oK6K0$G2G=_xs=WLUCgwYTxrKs-=Ff^wz19pcs$~w&HXp?c`AYqpF2MD zn>TxRopI0G3F(rywZ06sVJ|pexnDf-FC_d-`nTSXmH!!LHq-)3-T;9z58XJh4H zXJ_Z+|EI`$@KzRlhK~^C}Lq|5@z(jVX zLJ_0Ji3>TDoi-j64Z8S2#W<;`iIYoATtZSxRZU$(Q_IBE%-q7#%Gt%$&E3P(D>x)H zEIcAIDmf)JEj=SMtGJ}Jth}PKs=1}Lt-YhOYtrN?Q>RUzF>}_U#Y>hhTfSoDs!f}> zY~8kf$Ie}c4j(ys?D&b3r!HN-a`oEv8#iw~eDwIq(`V0LynOZX)8{W=zkUDl^B2fp zj10^WZ^3*=T_(52a_jpCO(|&s;j&9Kf^=Wk22xR(cb6H9!Ey%UXtI=x#{EImCXkI>rWin MZ>j!l0ssG-03+ZdwEzGB literal 0 HcmV?d00001 diff --git a/oxygen/js-options/icons/insertTable20.JPG b/oxygen/js-options/icons/insertTable20.JPG new file mode 100644 index 0000000000000000000000000000000000000000..56a0e95edf42bb25e700c88d02c998e86e0ecf3d GIT binary patch literal 928 zcmex=Hq-)3-T;9z58XJh4H zXJ_Z+|EI`$@KzRlhK~^C}Lq|5@z(jVX zLJ_0Ji3>TDoi-j64Z8S2#W<;`iIYoATtZSxRZU$(Q_IBE%-q7#%Gt%$&E3P(D>x)H zEIcAIDmf)JEj=SMtGJ}Jth}PKs=1}Lt-YhOYtrN?Q>RUzF>}_U#Y>hhTfSoDs!f}> zY~8kf$Ie}c4j(ys?D&b3r!HN-a`oEv8#iw~eDwIq(`V0LynOZX)8{W=zkUDl^B2fp zj10^WZ^3Bg~j;AiyJ(6o^E@^Do`c41cW0_4odCymhKD(|be8eow`%%AS?X+mm zrxUifH%zXwQ7l`3V^cWaPYVm{N=eDd#|vNi7d{T#w0lb%>xVsVPt~?%M!BAS&vD3= zqi~)_-r_#X3%j;{4F97Z<+W||V##wes{)Tr)(F^_yY4~wTT7L>J1iv*K6u{1H~CQc zb*cHyCDy-B+MHg!*6v>B{0!;2i-V3cH%{{R(%Ro>|72c8_P>K+`+sR>Ea09sy={wZ z+QU3kUt!;Ua|4u4mS#=b5Hq9e?^wWzUyL56`{Vo{d{^Z%OwH~v_p literal 0 HcmV?d00001 diff --git a/oxygen/js-options/icons/refreshCSS16.JPG b/oxygen/js-options/icons/refreshCSS16.JPG new file mode 100644 index 0000000000000000000000000000000000000000..0cade03d0b2f8bf5b7813cbf4e55d1f9e0c4b026 GIT binary patch literal 800 zcmex=Hq-)3-T;9z58XJh4H zXJ_Z+|EI`$@KzRlhK~^C}Lq|5@z(jVX zLJ_0Ji3>TDoi-j64Z8S2#W<;`iIYoATtZSxRZU$(Q_IBE%-q7#%Gt%$&E3P(D>x)H zEIcAIDmf)JEj=SMtGJ}Jth}PKs=1}Lt-YhOYtrN?Q>RUzF>}_U#Y>hhTfSoDs!f}> zY~8kf$Ie}c4j(ys?D&b3r!HN-a`oEv8#iw~eDwIq(`V0LynOZX)8{W=zkUDl^B2fp zj10^WZ^3Hq-)3-T;9z58XJh4H zXJ_Z+|EI`$@KzRlhK~^C}Lq|5@z(jVX zLJ_0Ji3>TDoi-j64Z8S2#W<;`iIYoATtZSxRZU$(Q_IBE%-q7#%Gt%$&E3P(D>x)H zEIcAIDmf)JEj=SMtGJ}Jth}PKs=1}Lt-YhOYtrN?Q>RUzF>}_U#Y>hhTfSoDs!f}> zY~8kf$Ie}c4j(ys?D&b3r!HN-a`oEv8#iw~eDwIq(`V0LynOZX)8{W=zkUDl^B2fp zj10^WZ^3wGpjC4iGj?44hw1@jK}N$fS(rT;T zU$~;~{biBpX zW!pRte0f)OF?PbUr!lwZe)+jm-0rXU^{{Q?^(PF6}rMnOeST|r4lSw=>~TvNxu(8R<ECr+Na zbot8FYu9hwy!G(W<0ns_J%91?)yGetzkL1n{m0K=Ab&A3Fhjfr_ZgbM1cClyVqsxs zVF&q(k*OSrnFU!`6%E;h90S=C3x$=88aYIqCNA7~kW<+>=!0ld(M2vX6_bamA3uRl~&9pmntuye=GzH178SMNP%*q!h; z@TB^$tG!0A19nEbEnL2INyh>KN`QWte!r@Y*ID;e~N8vR54Y&ydmVJt@RY%-onWWm`alfsSMwcL~EB X#_O|Y?@KO~u=4%-Z_j@Q=l?eW?UoO# literal 0 HcmV?d00001 diff --git a/oxygen/js-options/icons/strictMode20.JPG b/oxygen/js-options/icons/strictMode20.JPG new file mode 100644 index 0000000000000000000000000000000000000000..6b7cc2d2592754b5bb5adeebaa8b514ba53296bc GIT binary patch literal 870 zcmex=Hq-)3-T;9z58XJh4H zXJ_Z+|EI`$@KzRlhK~^C}Lq|5@z(jVX zLJ_0Ji3>TDoi-j64Z8S2#W<;`iIYoATtZSxRZU$(Q_IBE%-q7#%Gt%$&E3P(D>x)H zEIcAIDmf)JEj=SMtGJ}Jth}PKs=1}Lt-YhOYtrN?Q>RUzF>}_U#Y>hhTfSoDs!f}> zY~8kf$Ie}c4j(ys?D&b3r!HN-a`oEv8#iw~eDwIq(`V0LynOZX)8{W=zkUDl^B2fp zj10^WZ^3hjvq3c z_9^DD>DjYa=7nxu!))Yrr$lZ}|Cy?5^6mRn=XyssyII69O4IUvTc+kSY`Kyv>VGrk!P9QRPao%5{;XX+d;9gLn?Fu(|MJ&6 z?BbqO(KAOAcC_6yn|E^4j)HIt$%9`HO}-IkG&$(bmy8R`t<0b9zP{h@u5DhmtVm$A zTw!>?<(#eA=9{nRChgo_lh+r@vs~8dnqT{adftkn2X``~d<^{0sitMhq_2}asr+G4 XS6KMYtR+`sf-lZx6$$id{(lnyq+LWw literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d7eac35c7b18a48ebf1beab3b1daa7ca6d03acf2 GIT binary patch literal 110627 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

Cs6WvKCcdZTm4nf{O@H1VM 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 + + 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 @@ + + + + + + + + + + + + + + +

+ +

+
+ +

+ + + +

+
+ +

+ +

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